Merge "Cleanup: remove fs-verity availability check" into main
diff --git a/apct-tests/perftests/core/src/android/os/OWNERS b/apct-tests/perftests/core/src/android/os/OWNERS
index a1719c9..76ab303 100644
--- a/apct-tests/perftests/core/src/android/os/OWNERS
+++ b/apct-tests/perftests/core/src/android/os/OWNERS
@@ -1 +1,4 @@
-per-file PackageParsingPerfTest.kt = file:/services/core/java/com/android/server/pm/OWNERS
\ No newline at end of file
+per-file PackageParsingPerfTest.kt = file:/services/core/java/com/android/server/pm/OWNERS
+
+# Bug component: 345036
+per-file VibratorPerfTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
\ No newline at end of file
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index 47e1dad..1ce8f9f 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -1,87 +1,96 @@
 # Usage
-##  Two options to use the uinput command:
-### 1. Interactive through stdin:
-type `uinput -` into the terminal, then type/paste commands to send to the binary.
-Use Ctrl+D to signal end of stream to the binary (EOF).
 
-This mode can be also used from an app to send uinput events.
-For an example, see the cts test case at: [InputTestCase.java][2]
+There are two ways to use the `uinput` command:
 
-When using another program to control uinput in interactive mode, registering a
-new input device (for example, a bluetooth joystick) should be the first step.
-After the device is added, you need to wait for the _onInputDeviceAdded_
-(see [InputDeviceListener][1]) notification before issuing commands
-to the device.
-Failure to do so will cause missed events and inconsistent behavior.
+* **Recommended:** `uinput -` reads commands from standard input until End-of-File (Ctrl+D) is sent.
+  This mode can be used interactively from a terminal or used to control uinput from another program
+  or app (such as the CTS tests via [`UinputDevice`][UinputDevice]).
+* `uinput <filename>` reads commands from a file instead of standard input.
 
-### 2. Using a file as an input:
-type `uinput <filename>`, and the file will be used an an input to the binary.
-You must add a sufficient delay after a "register" command to ensure device
-is ready. The interactive mode is the recommended method of communicating
-with the uinput binary.
+[UinputDevice]: https://cs.android.com/android/platform/superproject/main/+/main:cts/libs/input/src/com/android/cts/input/UinputDevice.java
 
-All of the input commands should be in pseudo-JSON format as documented below.
-See examples [here][3].
+## Command format
 
-The file can have multiple commands one after the other (which is not strictly
-legal JSON format, as this would imply multiple root elements).
+Input commands should be in JSON format, though the parser is in [lenient mode] to allow comments,
+and integers can be specified in hexadecimal (e.g. `0xABCD`). The input file (or standard input) can
+contain multiple commands, which will be executed in sequence. Simply add multiple JSON objects to
+the file, one after the other without separators:
 
-## Command description
+```json5
+{
+  "id": 1,
+  "command": "register",
+  // ...
+}
+{
+  "id": 1,
+  "command": "delay",
+  // ...
+}
+```
 
-1. `register`
+Many examples of command files can be found [in the CTS tests][cts-example-jsons].
+
+[lenient mode]: https://developer.android.com/reference/android/util/JsonReader#setLenient(boolean)
+[cts-example-jsons]: https://cs.android.com/android/platform/superproject/main/+/main:cts/tests/tests/hardware/res/raw/
+
+## Command reference
+
+### `register`
+
 Register a new uinput device
 
-| Field         | Type          | Description                |
+| Field            | Type           | Description                |
+|:----------------:|:--------------:|:-------------------------- |
+| `id`             | integer        | Device ID                  |
+| `command`        | string         | Must be set to "register"  |
+| `name`           | string         | Device name                |
+| `vid`            | 16-bit integer | Vendor ID                  |
+| `pid`            | 16-bit integer | Product ID                 |
+| `bus`            | string         | Bus that device should use |
+| `configuration`  | object array   | uinput device configuration|
+| `ff_effects_max` | integer        | `ff_effects_max` value     |
+| `abs_info`       | array          | Absolute axes information  |
+
+`id` is used for matching the subsequent commands to a specific device to avoid ambiguity when
+multiple devices are registered.
+
+`bus` is used to determine how the uinput device is connected to the host. The options are `"usb"`
+and `"bluetooth"`.
+
+Device configuration is used to configure the uinput device. The `type` field provides a `UI_SET_*`
+control code, and data is a vector of control values to be sent to the uinput device, which depends
+on the control code.
+
+| Field         |     Type      | Description                |
 |:-------------:|:-------------:|:-------------------------- |
-| id            | integer       | Device id                  |
-| command       | string        | Must be set to "register"  |
-| name          | string        | Device name                |
-| vid           | 16-bit integer| Vendor id                  |
-| pid           | 16-bit integer| Product id                 |
-| bus           | string        | Bus that device should use |
-| configuration | int array     | uinput device configuration|
-| ff_effects_max| integer       | ff_effects_max value       |
-| abs_info      | array         | ABS axes information       |
+| `type`        | integer       | `UI_SET_` control type     |
+| `data`        | integer array | control values             |
 
-Device ID is used for matching the subsequent commands to a specific device
-to avoid ambiguity when multiple devices are registered.
+`ff_effects_max` must be provided if `UI_SET_FFBIT` is used in `configuration`.
 
-Device bus is used to determine how the uinput device is connected to the host.
-The options are "usb" and "bluetooth".
-
-Device configuration is used to configure uinput device.  "type" field provides the UI_SET_*
-control code, and data is a vector of control values to be sent to uinput device, depends on
-the control code.
+`abs_info` fields are provided to set the device axes information. It is an array of below objects:
 
 | Field         | Type          | Description                |
 |:-------------:|:-------------:|:-------------------------- |
-| type          | integer       | UI_SET_ control type       |
-| data          | int array     | control values             |
+| `code`        | integer       | Axis code                  |
+| `info`        | object        | Axis information object    |
 
-Device ff_effects_max must be provided if FFBIT is set.
+The axis information object is defined as below, with the fields having the same meaning as those
+Linux's [`struct input_absinfo`][struct input_absinfo]:
 
-Device abs_info fields are provided to set the device axes information. It is an array of below
-objects:
 | Field         | Type          | Description                |
 |:-------------:|:-------------:|:-------------------------- |
-| code          | integer       | Axis code                  |
-| info          | object        | ABS information object     |
-
-ABS information object is defined as below:
-| Field         | Type          | Description                |
-|:-------------:|:-------------:|:-------------------------- |
-| value         | integer       | Latest reported value      |
-| minimum       | integer       | Minimum value for the axis |
-| maximum       | integer       | Maximum value for the axis |
-| fuzz          | integer       | fuzz value for noise filter|
-| flat          | integer       | values to be discarded     |
-| resolution    | integer       | resolution of axis         |
-
-See [struct input_absinfo][4]) definitions.
+| `value`       | integer       | Latest reported value      |
+| `minimum`     | integer       | Minimum value for the axis |
+| `maximum`     | integer       | Maximum value for the axis |
+| `fuzz`        | integer       | fuzz value for noise filter|
+| `flat`        | integer       | values to be discarded     |
+| `resolution`  | integer       | resolution of axis         |
 
 Example:
-```json
 
+```json5
 {
   "id": 1,
   "command": "register",
@@ -90,9 +99,9 @@
   "pid": 0x2c42,
   "bus": "usb",
   "configuration":[
-        {"type":100, "data":[1, 21]},  // UI_SET_EVBIT : EV_KEY and EV_FF
+        {"type":100, "data":[1, 21]},         // UI_SET_EVBIT : EV_KEY and EV_FF
         {"type":101, "data":[11, 2, 3, 4]},   // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3
-        {"type":107, "data":[80]}    //  UI_SET_FFBIT : FF_RUMBLE
+        {"type":107, "data":[80]}             // UI_SET_FFBIT : FF_RUMBLE
   ],
   "ff_effects_max" : 1,
   "abs_info": [
@@ -104,19 +113,39 @@
         }
   ]
 }
-
 ```
-2. `delay`
+
+[struct input_absinfo]: https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/kernel/uapi/linux/input.h?q=%22struct%20input_absinfo%22
+
+#### Waiting for registration
+
+After the command is sent, there will be a delay before the device is set up by the Android input
+stack, and `uinput` does not wait for that process to finish. Any commands sent to the device during
+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.
+
+[onInputDeviceAdded]: https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html
+
+#### Unregistering the device
+
+As soon as EOF is reached (either in interactive mode, or in file mode), the device that was created
+will be unregistered. There is no explicit command for unregistering a device.
+
+### `delay`
+
 Add a delay to command processing
 
 | Field         | Type          | Description                |
 |:-------------:|:-------------:|:-------------------------- |
-| id            | integer       | Device id                  |
-| command       | string        | Must be set to "delay"     |
-| duration      | integer       | Delay in milliseconds      |
+| `id`          | integer       | Device ID                  |
+| `command`     | string        | Must be set to "delay"     |
+| `duration`    | integer       | Delay in milliseconds      |
 
 Example:
-```json
+
+```json5
 {
   "id": 1,
   "command": "delay",
@@ -124,20 +153,21 @@
 }
 ```
 
-3. `inject`
-Send an array of uinput event packets [type, code, value] to the uinput device
+### `inject`
+
+Send an array of uinput event packets to the uinput device
 
 | Field         | Type          | Description                |
 |:-------------:|:-------------:|:-------------------------- |
-| id            | integer       | Device id                  |
-| command       | string        | Must be set to "inject"    |
-| events        | integer array | events to inject           |
+| `id`          | integer       | Device ID                  |
+| `command`     | string        | Must be set to "inject"    |
+| `events`      | integer array | events to inject           |
 
-The "events" parameter is an array of integers, encapsulates evdev input_event type, code and value,
-see the example below.
+The `events` parameter is an array of integers in sets of three: a type, an axis code, and an axis
+value, like you'd find in Linux's `struct input_event`. For example, sending presses of the 0 and 1
+keys would look like this:
 
-Example:
-```json
+```json5
 {
   "id": 1,
   "command": "inject",
@@ -153,14 +183,6 @@
 }
 ```
 
-### Notes
-1. As soon as EOF is reached (either in interactive mode, or in file mode),
-the device that was created will be unregistered. There is no
-explicit command for unregistering a device.
-2. The `getevent` utility can used to print out the key events
-for debugging purposes.
+## Notes
 
-[1]: https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html
-[2]: ../../../../cts/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
-[3]: ../../../../cts/tests/tests/hardware/res/raw/
-[4]: ../../../../bionic/libc/kernel/uapi/linux/input.h
+The `getevent` utility can used to print out the key events for debugging purposes.
diff --git a/core/api/current.txt b/core/api/current.txt
index eaefe84..0d73695 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26391,6 +26391,17 @@
     method public abstract void onDisconnect(android.media.midi.MidiReceiver);
   }
 
+  public abstract class MidiUmpDeviceService extends android.app.Service {
+    ctor public MidiUmpDeviceService();
+    method @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo();
+    method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers();
+    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public void onClose();
+    method public void onDeviceStatusChanged(@Nullable android.media.midi.MidiDeviceStatus);
+    method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers();
+    field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
+  }
+
 }
 
 package android.media.projection {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 853528f..12ffdb3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -65,6 +65,7 @@
 import android.app.servertransaction.ResumeActivityItem;
 import android.app.servertransaction.TransactionExecutor;
 import android.app.servertransaction.TransactionExecutorHelper;
+import android.app.servertransaction.WindowTokenClientController;
 import android.bluetooth.BluetoothFrameworkInitializer;
 import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -6221,6 +6222,18 @@
                 false /* clearPending */);
     }
 
+    @Override
+    public void handleWindowContextConfigurationChanged(@NonNull IBinder clientToken,
+            @NonNull Configuration configuration, int displayId) {
+        WindowTokenClientController.getInstance().onWindowContextConfigurationChanged(clientToken,
+                configuration, displayId);
+    }
+
+    @Override
+    public void handleWindowContextWindowRemoval(@NonNull IBinder clientToken) {
+        WindowTokenClientController.getInstance().onWindowContextWindowRemoved(clientToken);
+    }
+
     /**
      * Sends windowing mode change callbacks to {@link Activity} if applicable.
      *
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 49fb794..f7a43f4 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -163,6 +163,13 @@
     public abstract void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
             Configuration overrideConfig, int displayId);
 
+    /** Deliver {@link android.window.WindowContext} configuration change. */
+    public abstract void handleWindowContextConfigurationChanged(@NonNull IBinder clientToken,
+            @NonNull Configuration configuration, int displayId);
+
+    /** Deliver {@link android.window.WindowContext} window removal event. */
+    public abstract void handleWindowContextWindowRemoval(@NonNull IBinder clientToken);
+
     /** Deliver result from another activity. */
     public abstract void handleSendResult(
             @NonNull ActivityClientRecord r, List<ResultInfo> results, String reason);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 59b0dac..1f95497 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -27,6 +27,7 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.UiContext;
+import android.app.servertransaction.WindowTokenClientController;
 import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
@@ -3276,7 +3277,8 @@
         // if this Context is not a WindowContext. WindowContext finalization is handled in
         // WindowContext class.
         if (mToken instanceof WindowTokenClient && mOwnsToken) {
-            ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded();
+            WindowTokenClientController.getInstance().detachIfNeeded(
+                    (WindowTokenClient) mToken);
         }
         super.finalize();
     }
@@ -3304,7 +3306,7 @@
         final WindowTokenClient token = new WindowTokenClient();
         final ContextImpl context = systemContext.createWindowContextBase(token, displayId);
         token.attachContext(context);
-        token.attachToDisplayContent(displayId);
+        WindowTokenClientController.getInstance().attachToDisplayContent(token, displayId);
         context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI;
         context.mOwnsToken = true;
 
diff --git a/core/java/android/app/HomeVisibilityListener.java b/core/java/android/app/HomeVisibilityListener.java
index ca20648..0b5a5ed1 100644
--- a/core/java/android/app/HomeVisibilityListener.java
+++ b/core/java/android/app/HomeVisibilityListener.java
@@ -25,6 +25,7 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.os.Binder;
+import android.util.Log;
 
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -40,6 +41,8 @@
 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 @TestApi
 public abstract class HomeVisibilityListener {
+    private static final String TAG = HomeVisibilityListener.class.getSimpleName();
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     private ActivityTaskManager mActivityTaskManager;
     private Executor mExecutor;
     private int mMaxScanTasksForHomeVisibility;
@@ -102,6 +105,11 @@
 
         for (int i = 0, taskSize = tasksTopToBottom.size(); i < taskSize; ++i) {
             ActivityManager.RunningTaskInfo task = tasksTopToBottom.get(i);
+            if (DBG) {
+                Log.d(TAG, "Task#" + i + ": activity=" + task.topActivity
+                        + ", visible=" + task.isVisible()
+                        + ", flg=" + Integer.toHexString(task.baseIntent.getFlags()));
+            }
             if (!task.isVisible()
                     || (task.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) {
                 continue;
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 729e555..0857c96 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -40,7 +40,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -195,7 +195,8 @@
         XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
         int outerDepth = parser.getDepth();
         AttributeSet attrs = Xml.asAttributeSet(parser);
-        Set<String> localeNames = new HashSet<String>();
+        // LinkedHashSet to preserve insertion order
+        Set<String> localeNames = new LinkedHashSet<>();
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
             if (TAG_LOCALE.equals(parser.getName())) {
                 final TypedArray attributes = res.obtainAttributes(
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index bc5f7f4..8da8442 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -16,7 +16,12 @@
         },
         {
             "file_patterns": ["(/|^)AppOpsManager.java"],
-            "name": "CtsAppOpsTestCases"
+            "name": "CtsAppOpsTestCases",
+            "options": [
+                {
+                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                }
+            ]
         },
         {
             "file_patterns": ["(/|^)AppOpsManager.java"],
@@ -57,7 +62,7 @@
             "name": "CtsWindowManagerDeviceTestCases",
             "options": [
                 {
-                    "include-filter": "android.server.wm.ToastWindowTest"
+                    "include-filter": "android.server.wm.window.ToastWindowTest"
                 }
             ],
             "file_patterns": ["INotificationManager\\.aidl"]
@@ -238,11 +243,9 @@
             "file_patterns": [
                 "(/|^)DeviceFeature[^/]*", "(/|^)Hdmi[^/]*"
             ]
-        }
-    ],
-    "presubmit-large": [
+        },
         {
-            "name": "CtsContentTestCases",
+            "name": "CtsWindowManagerTestCases",
             "options": [
                 {
                     "exclude-annotation": "androidx.test.filters.FlakyTest"
@@ -265,6 +268,10 @@
         {
             "file_patterns": ["(/|^)ActivityThreadTest.java"],
             "name": "FrameworksCoreTests"
+        },
+        {
+            "file_patterns": ["(/|^)AppOpsManager.java"],
+            "name": "CtsAppOpsTestCases"
         }
     ]
 }
diff --git a/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java b/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java
new file mode 100644
index 0000000..3ac642f
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java
@@ -0,0 +1,135 @@
+/*
+ * 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 android.app.servertransaction;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ClientTransactionHandler;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+import java.util.Objects;
+
+/**
+ * {@link android.window.WindowContext} configuration change message.
+ * @hide
+ */
+public class WindowContextConfigurationChangeItem extends ClientTransactionItem {
+
+    @Nullable
+    private IBinder mClientToken;
+    @Nullable
+    private Configuration mConfiguration;
+    private int mDisplayId;
+
+    @Override
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
+        client.handleWindowContextConfigurationChanged(mClientToken, mConfiguration, mDisplayId);
+    }
+
+    // ObjectPoolItem implementation
+
+    private WindowContextConfigurationChangeItem() {}
+
+    /** Obtains an instance initialized with provided params. */
+    public static WindowContextConfigurationChangeItem obtain(
+            @NonNull IBinder clientToken, @NonNull Configuration config, int displayId) {
+        WindowContextConfigurationChangeItem instance =
+                ObjectPool.obtain(WindowContextConfigurationChangeItem.class);
+        if (instance == null) {
+            instance = new WindowContextConfigurationChangeItem();
+        }
+        instance.mClientToken = requireNonNull(clientToken);
+        instance.mConfiguration = requireNonNull(config);
+        instance.mDisplayId = displayId;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mClientToken = null;
+        mConfiguration = null;
+        mDisplayId = INVALID_DISPLAY;
+        ObjectPool.recycle(this);
+    }
+
+    // Parcelable implementation
+
+    /** Writes to Parcel. */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mClientToken);
+        dest.writeTypedObject(mConfiguration, flags);
+        dest.writeInt(mDisplayId);
+    }
+
+    /** Reads from Parcel. */
+    private WindowContextConfigurationChangeItem(@NonNull Parcel in) {
+        mClientToken = in.readStrongBinder();
+        mConfiguration = in.readTypedObject(Configuration.CREATOR);
+        mDisplayId = in.readInt();
+    }
+
+    public static final @NonNull Creator<WindowContextConfigurationChangeItem> CREATOR =
+            new Creator<>() {
+                public WindowContextConfigurationChangeItem createFromParcel(Parcel in) {
+                    return new WindowContextConfigurationChangeItem(in);
+                }
+
+                public WindowContextConfigurationChangeItem[] newArray(int size) {
+                    return new WindowContextConfigurationChangeItem[size];
+                }
+    };
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final WindowContextConfigurationChangeItem other = (WindowContextConfigurationChangeItem) o;
+        return Objects.equals(mClientToken, other.mClientToken)
+                && Objects.equals(mConfiguration, other.mConfiguration)
+                && mDisplayId == other.mDisplayId;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + Objects.hashCode(mClientToken);
+        result = 31 * result + Objects.hashCode(mConfiguration);
+        result = 31 * result + mDisplayId;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "WindowContextConfigurationChangeItem{clientToken=" + mClientToken
+                + ", config=" + mConfiguration
+                + ", displayId=" + mDisplayId
+                + "}";
+    }
+}
diff --git a/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java
new file mode 100644
index 0000000..ed52a64
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java
@@ -0,0 +1,112 @@
+/*
+ * 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 android.app.servertransaction;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+
+import java.util.Objects;
+
+/**
+ * {@link android.window.WindowContext} window removal message.
+ * @hide
+ */
+public class WindowContextWindowRemovalItem extends ClientTransactionItem {
+
+    @Nullable
+    private IBinder mClientToken;
+
+    @Override
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
+        client.handleWindowContextWindowRemoval(mClientToken);
+    }
+
+    // ObjectPoolItem implementation
+
+    private WindowContextWindowRemovalItem() {}
+
+    /** Obtains an instance initialized with provided params. */
+    public static WindowContextWindowRemovalItem obtain(@NonNull IBinder clientToken) {
+        WindowContextWindowRemovalItem instance =
+                ObjectPool.obtain(WindowContextWindowRemovalItem.class);
+        if (instance == null) {
+            instance = new WindowContextWindowRemovalItem();
+        }
+        instance.mClientToken = requireNonNull(clientToken);
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mClientToken = null;
+        ObjectPool.recycle(this);
+    }
+
+    // Parcelable implementation
+
+    /** Writes to Parcel. */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mClientToken);
+    }
+
+    /** Reads from Parcel. */
+    private WindowContextWindowRemovalItem(@NonNull Parcel in) {
+        mClientToken = in.readStrongBinder();
+    }
+
+    public static final @NonNull Creator<WindowContextWindowRemovalItem> CREATOR = new Creator<>() {
+        public WindowContextWindowRemovalItem createFromParcel(Parcel in) {
+            return new WindowContextWindowRemovalItem(in);
+        }
+
+        public WindowContextWindowRemovalItem[] newArray(int size) {
+            return new WindowContextWindowRemovalItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final WindowContextWindowRemovalItem other = (WindowContextWindowRemovalItem) o;
+        return Objects.equals(mClientToken, other.mClientToken);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + Objects.hashCode(mClientToken);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "WindowContextWindowRemovalItem{clientToken=" + mClientToken + "}";
+    }
+}
diff --git a/core/java/android/app/servertransaction/WindowTokenClientController.java b/core/java/android/app/servertransaction/WindowTokenClientController.java
new file mode 100644
index 0000000..3060ab8
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowTokenClientController.java
@@ -0,0 +1,200 @@
+/*
+ * 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 android.app.servertransaction;
+
+import static android.view.WindowManager.LayoutParams.WindowType;
+import static android.view.WindowManagerGlobal.getWindowManagerService;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.app.IApplicationThread;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.IWindowManager;
+import android.window.WindowContext;
+import android.window.WindowTokenClient;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Singleton controller to manage the attached {@link WindowTokenClient}s, and to dispatch
+ * corresponding window configuration change from server side.
+ * @hide
+ */
+public class WindowTokenClientController {
+
+    private static final String TAG = WindowTokenClientController.class.getSimpleName();
+    private static WindowTokenClientController sController;
+
+    private final Object mLock = new Object();
+    private final IApplicationThread mAppThread = ActivityThread.currentActivityThread()
+            .getApplicationThread();
+
+    /** Mapping from a client defined token to the {@link WindowTokenClient} it represents. */
+    @GuardedBy("mLock")
+    private final ArrayMap<IBinder, WindowTokenClient> mWindowTokenClientMap = new ArrayMap<>();
+
+    /** Gets the singleton controller. */
+    public static WindowTokenClientController getInstance() {
+        synchronized (WindowTokenClientController.class) {
+            if (sController == null) {
+                sController = new WindowTokenClientController();
+            }
+            return sController;
+        }
+    }
+
+    /** Overrides the {@link #getInstance()} for test only. */
+    @VisibleForTesting
+    public static void overrideForTesting(@NonNull WindowTokenClientController controller) {
+        synchronized (WindowTokenClientController.class) {
+            sController = controller;
+        }
+    }
+
+    private WindowTokenClientController() {}
+
+    /**
+     * Attaches a {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}.
+     *
+     * @param client The {@link WindowTokenClient} to attach.
+     * @param type The window type of the {@link WindowContext}
+     * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
+     * @param options The window context launched option
+     * @return {@code true} if attaching successfully.
+     */
+    public boolean attachToDisplayArea(@NonNull WindowTokenClient client,
+            @WindowType int type, int displayId, @Nullable Bundle options) {
+        final Configuration configuration;
+        try {
+            configuration = getWindowManagerService().attachWindowContextToDisplayArea(
+                    mAppThread, client, type, displayId, options);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        if (configuration == null) {
+            return false;
+        }
+        onWindowContextTokenAttached(client, displayId, configuration);
+        return true;
+    }
+
+    /**
+     * Attaches a {@link WindowTokenClient} to a {@code DisplayContent}.
+     *
+     * @param client The {@link WindowTokenClient} to attach.
+     * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
+     * @return {@code true} if attaching successfully.
+     */
+    public boolean attachToDisplayContent(@NonNull WindowTokenClient client, int displayId) {
+        final IWindowManager wms = getWindowManagerService();
+        // #createSystemUiContext may call this method before WindowManagerService is initialized.
+        if (wms == null) {
+            return false;
+        }
+        final Configuration configuration;
+        try {
+            configuration = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        if (configuration == null) {
+            return false;
+        }
+        onWindowContextTokenAttached(client, displayId, configuration);
+        return true;
+    }
+
+    /**
+     * Attaches this {@link WindowTokenClient} to a {@code windowToken}.
+     *
+     * @param client The {@link WindowTokenClient} to attach.
+     * @param windowToken the window token to associated with
+     */
+    public void attachToWindowToken(@NonNull WindowTokenClient client,
+            @NonNull IBinder windowToken) {
+        try {
+            getWindowManagerService().attachWindowContextToWindowToken(
+                    mAppThread, client, windowToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        // We don't report configuration change for now.
+        synchronized (mLock) {
+            mWindowTokenClientMap.put(client.asBinder(), client);
+        }
+    }
+
+    /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */
+    public void detachIfNeeded(@NonNull WindowTokenClient client) {
+        synchronized (mLock) {
+            if (mWindowTokenClientMap.remove(client.asBinder()) == null) {
+                return;
+            }
+        }
+        try {
+            getWindowManagerService().detachWindowContext(client);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private void onWindowContextTokenAttached(@NonNull WindowTokenClient client, int displayId,
+            @NonNull Configuration configuration) {
+        synchronized (mLock) {
+            mWindowTokenClientMap.put(client.asBinder(), client);
+        }
+        client.onConfigurationChanged(configuration, displayId,
+                false /* shouldReportConfigChange */);
+    }
+
+    /** Called when receives {@link WindowContextConfigurationChangeItem}. */
+    public void onWindowContextConfigurationChanged(@NonNull IBinder clientToken,
+            @NonNull Configuration configuration, int displayId) {
+        final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken);
+        if (windowTokenClient != null) {
+            windowTokenClient.onConfigurationChanged(configuration, displayId);
+        }
+    }
+
+    /** Called when receives {@link WindowContextWindowRemovalItem}. */
+    public void onWindowContextWindowRemoved(@NonNull IBinder clientToken) {
+        final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken);
+        if (windowTokenClient != null) {
+            windowTokenClient.onWindowTokenRemoved();
+        }
+    }
+
+    @Nullable
+    private WindowTokenClient getWindowTokenClient(@NonNull IBinder clientToken) {
+        final WindowTokenClient windowTokenClient;
+        synchronized (mLock) {
+            windowTokenClient = mWindowTokenClientMap.get(clientToken);
+        }
+        if (windowTokenClient == null) {
+            Log.w(TAG, "Can't find attached WindowTokenClient for " + clientToken);
+        }
+        return windowTokenClient;
+    }
+}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 4295517..49543a1 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -87,6 +87,8 @@
             int userId);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS)")
     void clearBroadcastEvents(String callingPackage, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)")
+    boolean isPackageExemptedFromBroadcastResponseStats(String packageName, int userId);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG)")
     String getAppStandbyConstant(String key);
 }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 4b0ca28..ecf16439 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -1528,6 +1528,22 @@
         }
     }
 
+    /**
+     * Checks whether the given {@code packageName} is exempted from broadcast response tracking.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    @UserHandleAware
+    public boolean isPackageExemptedFromBroadcastResponseStats(@NonNull String packageName) {
+        try {
+            return mService.isPackageExemptedFromBroadcastResponseStats(packageName,
+                    mContext.getUserId());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     @Nullable
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index eb672dc..b159321 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -48,9 +48,11 @@
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.internal.os.BackgroundThread;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * Updates AppWidget state; gets information about installed AppWidget providers and other
@@ -785,7 +787,25 @@
             return;
         }
         try {
-            mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
+            if (RemoteViews.isAdapterConversionEnabled()) {
+                List<CompletableFuture<Void>> updateFutures = new ArrayList<>();
+                for (int i = 0; i < appWidgetIds.length; i++) {
+                    final int widgetId = appWidgetIds[i];
+                    updateFutures.add(CompletableFuture.runAsync(() -> {
+                        try {
+                            RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId);
+                            if (views.replaceRemoteCollections(viewId)) {
+                                updateAppWidget(widgetId, views);
+                            }
+                        } catch (Exception e) {
+                            Log.e(TAG, "Error notifying changes in RemoteViews", e);
+                        }
+                    }));
+                }
+                CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join();
+            } else {
+                mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index dac79e7..01a9373 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -41,11 +41,9 @@
         }
       ],
       "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java", "(/|^)ComponentCallbacksController.java"]
-    }
-  ],
-  "presubmit-large": [
+    },
     {
-      "name": "CtsContentTestCases",
+      "name": "CtsWindowManagerTestCases",
       "options": [
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/core/java/android/content/om/TEST_MAPPING b/core/java/android/content/om/TEST_MAPPING
index eb9254d..82c47a0 100644
--- a/core/java/android/content/om/TEST_MAPPING
+++ b/core/java/android/content/om/TEST_MAPPING
@@ -19,11 +19,9 @@
     },
     {
       "name": "CtsOverlayHostTestCases"
-    }
-  ],
-  "presubmit-large": [
+    },
     {
-      "name": "CtsContentTestCases",
+      "name": "CtsResourcesTestCases",
       "options": [
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index c9735b0..86087cb 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -61,7 +61,7 @@
 
     void resetThrottling(); // system only API for developer opsions
 
-    void onApplicationActive(String packageName, int userId); // system only API for sysUI
+    oneway void onApplicationActive(String packageName, int userId); // system only API for sysUI
 
     byte[] getBackupPayload(int user);
 
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index b43639a..19539c2 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2415,6 +2415,8 @@
         public int requireUserAction = USER_ACTION_UNSPECIFIED;
         /** {@hide} */
         public boolean applicationEnabledSettingPersistent = false;
+        /** {@hide} */
+        public int developmentInstallFlags = 0;
 
         private final ArrayMap<String, Integer> mPermissionStates;
 
@@ -2464,6 +2466,7 @@
             requireUserAction = source.readInt();
             packageSource = source.readInt();
             applicationEnabledSettingPersistent = source.readBoolean();
+            developmentInstallFlags = source.readInt();
         }
 
         /** {@hide} */
@@ -2495,6 +2498,7 @@
             ret.requireUserAction = requireUserAction;
             ret.packageSource = packageSource;
             ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
+            ret.developmentInstallFlags = developmentInstallFlags;
             return ret;
         }
 
@@ -3159,6 +3163,7 @@
             pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
             pw.printPair("applicationEnabledSettingPersistent",
                     applicationEnabledSettingPersistent);
+            pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
             pw.println();
         }
 
@@ -3200,6 +3205,7 @@
             dest.writeInt(requireUserAction);
             dest.writeInt(packageSource);
             dest.writeBoolean(applicationEnabledSettingPersistent);
+            dest.writeInt(developmentInstallFlags);
         }
 
         public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index bef023e..289519d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1437,6 +1437,16 @@
     public @interface InstallFlags {}
 
     /**
+     * Install flags that can only be used in development workflows (e.g. {@code adb install}).
+     * @hide
+     */
+    @IntDef(flag = true, prefix = { "INSTALL_DEVELOPMENT_" }, value = {
+            INSTALL_DEVELOPMENT_FORCE_NON_STAGED_APEX_UPDATE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DevelopmentInstallFlags {}
+
+    /**
      * Flag parameter for {@link #installPackage} to indicate that you want to
      * replace an already installed package, if one exists.
      *
@@ -1646,6 +1656,14 @@
      */
     public static final int INSTALL_FROM_MANAGED_USER_OR_PROFILE = 1 << 26;
 
+    /**
+     * Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is
+     * a development-only feature and should not be used on end user devices.
+     *
+     * @hide
+     */
+    public static final int INSTALL_DEVELOPMENT_FORCE_NON_STAGED_APEX_UPDATE = 1;
+
     /** @hide */
     @IntDef(flag = true, value = {
             DONT_KILL_APP,
@@ -2355,6 +2373,7 @@
             USER_MIN_ASPECT_RATIO_4_3,
             USER_MIN_ASPECT_RATIO_16_9,
             USER_MIN_ASPECT_RATIO_3_2,
+            USER_MIN_ASPECT_RATIO_FULLSCREEN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserMinAspectRatio {}
@@ -2376,8 +2395,9 @@
 
     /**
      * Aspect ratio override code: user forces app to the aspect ratio of the device display size.
-     * This will be the portrait aspect ratio of the device if the app is portrait or the landscape
-     * aspect ratio of the device if the app is landscape.
+     * This will be the portrait aspect ratio of the device if the app has fixed portrait
+     * orientation or the landscape aspect ratio of the device if the app has fixed landscape
+     * orientation.
      *
      * @hide
      */
@@ -2401,6 +2421,12 @@
      */
     public static final int USER_MIN_ASPECT_RATIO_3_2 = 5;
 
+    /**
+     * Aspect ratio override code: user forces app to fullscreen
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_FULLSCREEN = 6;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "DELETE_" }, value = {
             DELETE_KEEP_DATA,
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 048289f..508eeed 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1456,8 +1456,8 @@
 
     private static AssetManager newConfiguredAssetManager() {
         AssetManager assetManager = new AssetManager();
-        assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                Build.VERSION.RESOURCES_SDK_INT);
+        assetManager.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
         return assetManager;
     }
 
@@ -9011,8 +9011,8 @@
             }
 
             AssetManager assets = new AssetManager();
-            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    Build.VERSION.RESOURCES_SDK_INT);
+            assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
             assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
 
             mCachedAssetManager = assets;
@@ -9086,8 +9086,8 @@
 
         private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
             final AssetManager assets = new AssetManager();
-            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    Build.VERSION.RESOURCES_SDK_INT);
+            assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
             assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
             return assets;
         }
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 3364b7a..6419f8c 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -114,23 +114,20 @@
                     "exclude-annotation":"org.junit.Ignore"
                 }
             ]
-        }
-    ],
-    "presubmit-large":[
+        },
         {
-            "name":"CtsContentTestCases",
+            "name":"CtsPackageManagerTestCases",
             "options":[
                 {
                     "exclude-annotation":"androidx.test.filters.FlakyTest"
                 },
                 {
                     "exclude-annotation":"org.junit.Ignore"
-                },
-                {
-                    "include-filter":"android.content.pm.cts"
                 }
             ]
-        },
+        }
+    ],
+    "presubmit-large":[
         {
             "name":"CtsUsesNativeLibraryTest",
             "options":[
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index b225de4..23b9d0b 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1480,9 +1480,13 @@
             int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
             int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
             int majorVersion) {
-        synchronized (this) {
-            ensureValidLocked();
-            nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density,
+        if (locale != null) {
+            setConfiguration(mcc, mnc, null, new String[]{locale}, orientation, touchscreen,
+                    density, keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
+                    smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
+                    colorMode, grammaticalGender, majorVersion);
+        } else {
+            setConfiguration(mcc, mnc, null, null, orientation, touchscreen, density,
                     keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
                     smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
                     colorMode, grammaticalGender, majorVersion);
@@ -1490,6 +1494,25 @@
     }
 
     /**
+     * Change the configuration used when retrieving resources.  Not for use by
+     * applications.
+     * @hide
+     */
+    public void setConfiguration(int mcc, int mnc, String defaultLocale, String[] locales,
+            int orientation, int touchscreen, int density, int keyboard, int keyboardHidden,
+            int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp,
+            int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode,
+            int grammaticalGender, int majorVersion) {
+        synchronized (this) {
+            ensureValidLocked();
+            nativeSetConfiguration(mObject, mcc, mnc, defaultLocale, locales, orientation,
+                    touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth,
+                    screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp,
+                    screenLayout, uiMode, colorMode, grammaticalGender, majorVersion);
+        }
+    }
+
+    /**
      * @hide
      */
     @UnsupportedAppUsage
@@ -1572,10 +1595,11 @@
     private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
             boolean invalidateCaches);
     private static native void nativeSetConfiguration(long ptr, int mcc, int mnc,
-            @Nullable String locale, int orientation, int touchscreen, int density, int keyboard,
-            int keyboardHidden, int navigation, int screenWidth, int screenHeight,
-            int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout,
-            int uiMode, int colorMode, int grammaticalGender, int majorVersion);
+            @Nullable String defaultLocale, @NonNull String[] locales, int orientation,
+            int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
+            int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
+            int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
+            int majorVersion);
     private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
             long ptr, boolean includeOverlays, boolean includeLoaders);
 
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 1c8276c..62630c8 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -154,7 +154,6 @@
     /**
      * Current user preference for the grammatical gender.
      */
-    @GrammaticalGender
     private int mGrammaticalGender;
 
     /** @hide */
@@ -167,6 +166,13 @@
     public @interface GrammaticalGender {}
 
     /**
+     * Constant for grammatical gender: to indicate that the grammatical gender is undefined.
+     * Only for internal usage.
+     * @hide
+     */
+    public static final int GRAMMATICAL_GENDER_UNDEFINED = -1;
+
+    /**
      * Constant for grammatical gender: to indicate the user has not specified the terms
      * of address for the application.
      */
@@ -1120,12 +1126,12 @@
         } else {
             sb.append(" ?localeList");
         }
-        if (mGrammaticalGender != 0) {
+        if (mGrammaticalGender > 0) {
             switch (mGrammaticalGender) {
                 case GRAMMATICAL_GENDER_NEUTRAL: sb.append(" neuter"); break;
                 case GRAMMATICAL_GENDER_FEMININE: sb.append(" feminine"); break;
                 case GRAMMATICAL_GENDER_MASCULINE: sb.append(" masculine"); break;
-                case GRAMMATICAL_GENDER_NOT_SPECIFIED: sb.append(" ?grgend"); break;
+                default: sb.append(" ?grgend"); break;
             }
         }
         int layoutDir = (screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK);
@@ -1570,7 +1576,7 @@
         seq = 0;
         windowConfiguration.setToDefaults();
         fontWeightAdjustment = FONT_WEIGHT_ADJUSTMENT_UNDEFINED;
-        mGrammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        mGrammaticalGender = GRAMMATICAL_GENDER_UNDEFINED;
     }
 
     /**
@@ -1773,7 +1779,8 @@
             changed |= ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT;
             fontWeightAdjustment = delta.fontWeightAdjustment;
         }
-        if (delta.mGrammaticalGender != mGrammaticalGender) {
+        if (delta.mGrammaticalGender != GRAMMATICAL_GENDER_UNDEFINED
+                && delta.mGrammaticalGender != mGrammaticalGender) {
             changed |= ActivityInfo.CONFIG_GRAMMATICAL_GENDER;
             mGrammaticalGender = delta.mGrammaticalGender;
         }
@@ -1998,7 +2005,8 @@
             changed |= ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT;
         }
 
-        if (mGrammaticalGender != delta.mGrammaticalGender) {
+        if ((compareUndefined || delta.mGrammaticalGender != GRAMMATICAL_GENDER_UNDEFINED)
+                && mGrammaticalGender != delta.mGrammaticalGender) {
             changed |= ActivityInfo.CONFIG_GRAMMATICAL_GENDER;
         }
         return changed;
@@ -2284,6 +2292,17 @@
      */
     @GrammaticalGender
     public int getGrammaticalGender() {
+        return mGrammaticalGender == GRAMMATICAL_GENDER_UNDEFINED
+                ? GRAMMATICAL_GENDER_NOT_SPECIFIED : mGrammaticalGender;
+    }
+
+    /**
+     * Internal getter of grammatical gender, to get the raw value of grammatical gender,
+     * which include {@link #GRAMMATICAL_GENDER_UNDEFINED}.
+     * @hide
+     */
+
+    public int getGrammaticalGenderRaw() {
         return mGrammaticalGender;
     }
 
@@ -2972,7 +2991,7 @@
         configOut.fontWeightAdjustment = XmlUtils.readIntAttribute(parser,
                 XML_ATTR_FONT_WEIGHT_ADJUSTMENT, FONT_WEIGHT_ADJUSTMENT_UNDEFINED);
         configOut.mGrammaticalGender = XmlUtils.readIntAttribute(parser,
-                XML_ATTR_GRAMMATICAL_GENDER, GRAMMATICAL_GENDER_NOT_SPECIFIED);
+                XML_ATTR_GRAMMATICAL_GENDER, GRAMMATICAL_GENDER_UNDEFINED);
 
         // For persistence, we don't care about assetsSeq and WindowConfiguration, so do not read it
         // out.
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index 62a46b6..3e0ab90 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -16,6 +16,8 @@
 
 package android.content.res;
 
+import static android.os.SystemProperties.PROP_VALUE_MAX;
+
 import android.annotation.NonNull;
 import android.util.Pools.SimplePool;
 
@@ -23,9 +25,6 @@
 
 import com.android.internal.R;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 /**
  * Defines the string attribute length and child tag count restrictions for a xml element.
  *
@@ -34,8 +33,13 @@
 public class Element {
     private static final int DEFAULT_MAX_STRING_ATTR_LENGTH = 32_768;
     private static final int MAX_POOL_SIZE = 128;
-
-    private static final String ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android";
+    private static final int MAX_ATTR_LEN_URL_COMPONENT = 256;
+    private static final int MAX_ATTR_LEN_PERMISSION_GROUP = 256;
+    private static final int MAX_ATTR_LEN_PACKAGE = 256;
+    private static final int MAX_ATTR_LEN_MIMETYPE = 512;
+    public static final int MAX_ATTR_LEN_NAME = 1024;
+    public static final int MAX_ATTR_LEN_PATH = 4000;
+    public static final int MAX_ATTR_LEN_DATA_VALUE = 4000;
 
     protected static final String TAG_ACTION = "action";
     protected static final String TAG_ACTIVITY = "activity";
@@ -123,43 +127,9 @@
     protected static final String TAG_ATTR_VERSION_NAME = "versionName";
     protected static final String TAG_ATTR_WRITE_PERMISSION = "writePermission";
 
-    private static final String[] ACTIVITY_STR_ATTR_NAMES = {TAG_ATTR_NAME,
-            TAG_ATTR_PARENT_ACTIVITY_NAME, TAG_ATTR_PERMISSION, TAG_ATTR_PROCESS,
-            TAG_ATTR_TASK_AFFINITY};
-    private static final String[] ACTIVITY_ALIAS_STR_ATTR_NAMES = {TAG_ATTR_NAME,
-            TAG_ATTR_PERMISSION, TAG_ATTR_TARGET_ACTIVITY};
-    private static final String[] APPLICATION_STR_ATTR_NAMES = {TAG_ATTR_BACKUP_AGENT,
-            TAG_ATTR_MANAGE_SPACE_ACTIVITY, TAG_ATTR_NAME, TAG_ATTR_PERMISSION, TAG_ATTR_PROCESS,
-            TAG_ATTR_REQUIRED_ACCOUNT_TYPE, TAG_ATTR_RESTRICTED_ACCOUNT_TYPE,
-            TAG_ATTR_TASK_AFFINITY};
-    private static final String[] DATA_STR_ATTR_NAMES = {TAG_ATTR_SCHEME, TAG_ATTR_HOST,
-            TAG_ATTR_PORT, TAG_ATTR_PATH, TAG_ATTR_PATH_PATTERN, TAG_ATTR_PATH_PREFIX,
-            TAG_ATTR_PATH_SUFFIX, TAG_ATTR_PATH_ADVANCED_PATTERN, TAG_ATTR_MIMETYPE};
-    private static final String[] GRANT_URI_PERMISSION_STR_ATTR_NAMES = {TAG_ATTR_PATH,
-            TAG_ATTR_PATH_PATTERN, TAG_ATTR_PATH_PREFIX};
-    private static final String[] INSTRUMENTATION_STR_ATTR_NAMES = {TAG_ATTR_NAME,
-            TAG_ATTR_TARGET_PACKAGE, TAG_ATTR_TARGET_PROCESSES};
-    private static final String[] MANIFEST_STR_ATTR_NAMES = {TAG_ATTR_PACKAGE,
-            TAG_ATTR_SHARED_USER_ID, TAG_ATTR_VERSION_NAME};
-    private static final String[] OVERLAY_STR_ATTR_NAMES = {TAG_ATTR_CATEGORY,
-            TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_NAME, TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_VALUE,
-            TAG_ATTR_TARGET_PACKAGE, TAG_ATTR_TARGET_NAME};
-    private static final String[] PATH_PERMISSION_STR_ATTR_NAMES = {TAG_ATTR_PATH,
-            TAG_ATTR_PATH_PREFIX, TAG_ATTR_PATH_PATTERN, TAG_ATTR_PERMISSION,
-            TAG_ATTR_READ_PERMISSION, TAG_ATTR_WRITE_PERMISSION};
-    private static final String[] PERMISSION_STR_ATTR_NAMES = {TAG_ATTR_NAME,
-            TAG_ATTR_PERMISSION_GROUP};
-    private static final String[] PROVIDER_STR_ATTR_NAMES = {TAG_ATTR_NAME, TAG_ATTR_PERMISSION,
-            TAG_ATTR_PROCESS, TAG_ATTR_READ_PERMISSION, TAG_ATTR_WRITE_PERMISSION};
-    private static final String[] RECEIVER_SERVICE_STR_ATTR_NAMES = {TAG_ATTR_NAME,
-            TAG_ATTR_PERMISSION, TAG_ATTR_PROCESS};
-    private static final String[] NAME_ATTR = {TAG_ATTR_NAME};
-    private static final String[] NAME_VALUE_ATTRS = {TAG_ATTR_NAME, TAG_ATTR_VALUE};
-
-    private String[] mStringAttrNames = new String[0];
     // The length of mTagCounters corresponds to the number of tags defined in getCounterIdx. If new
     // tags are added then the size here should be increased to match.
-    private final TagCounter[] mTagCounters = new TagCounter[35];
+    private final TagCounter[] mTagCounters = new TagCounter[34];
 
     String mTag;
 
@@ -177,7 +147,6 @@
     }
 
     void recycle() {
-        mStringAttrNames = new String[0];
         mTag = null;
         sPool.get().release(this);
     }
@@ -230,33 +199,79 @@
                 return 20;
             case TAG_USES_CONFIGURATION:
                 return 21;
-            case TAG_USES_PERMISSION_SDK_23:
-                return 22;
             case TAG_USES_SDK:
-                return 23;
+                return 22;
             case TAG_COMPATIBLE_SCREENS:
-                return 24;
+                return 23;
             case TAG_QUERIES:
-                return 25;
+                return 24;
             case TAG_ATTRIBUTION:
-                return 26;
+                return 25;
             case TAG_USES_FEATURE:
-                return 27;
+                return 26;
             case TAG_PERMISSION:
-                return 28;
+                return 27;
             case TAG_USES_PERMISSION:
-                return 29;
+            case TAG_USES_PERMISSION_SDK_23:
+            case TAG_USES_PERMISSION_SDK_M:
+                return 28;
             case TAG_GRANT_URI_PERMISSION:
-                return 30;
+                return 29;
             case TAG_PATH_PERMISSION:
-                return 31;
+                return 30;
             case TAG_PACKAGE:
-                return 32;
+                return 31;
             case TAG_INTENT:
-                return 33;
+                return 32;
             default:
                 // The size of the mTagCounters array should be equal to this value+1
-                return 34;
+                return 33;
+        }
+    }
+
+    static boolean shouldValidate(String tag) {
+        switch (tag) {
+            case TAG_ACTION:
+            case TAG_ACTIVITY:
+            case TAG_ACTIVITY_ALIAS:
+            case TAG_APPLICATION:
+            case TAG_ATTRIBUTION:
+            case TAG_CATEGORY:
+            case TAG_COMPATIBLE_SCREENS:
+            case TAG_DATA:
+            case TAG_GRANT_URI_PERMISSION:
+            case TAG_INSTRUMENTATION:
+            case TAG_INTENT:
+            case TAG_INTENT_FILTER:
+            case TAG_LAYOUT:
+            case TAG_MANIFEST:
+            case TAG_META_DATA:
+            case TAG_OVERLAY:
+            case TAG_PACKAGE:
+            case TAG_PATH_PERMISSION:
+            case TAG_PERMISSION:
+            case TAG_PERMISSION_GROUP:
+            case TAG_PERMISSION_TREE:
+            case TAG_PROFILEABLE:
+            case TAG_PROPERTY:
+            case TAG_PROVIDER:
+            case TAG_QUERIES:
+            case TAG_RECEIVER:
+            case TAG_SCREEN:
+            case TAG_SERVICE:
+            case TAG_SUPPORTS_GL_TEXTURE:
+            case TAG_SUPPORTS_SCREENS:
+            case TAG_USES_CONFIGURATION:
+            case TAG_USES_FEATURE:
+            case TAG_USES_LIBRARY:
+            case TAG_USES_NATIVE_LIBRARY:
+            case TAG_USES_PERMISSION:
+            case TAG_USES_PERMISSION_SDK_23:
+            case TAG_USES_PERMISSION_SDK_M:
+            case TAG_USES_SDK:
+                return true;
+            default:
+                return false;
         }
     }
 
@@ -264,55 +279,31 @@
         this.mTag = tag;
         mChildTagMask = 0;
         switch (tag) {
-            case TAG_ACTION:
-            case TAG_CATEGORY:
-            case TAG_PACKAGE:
-            case TAG_PERMISSION_GROUP:
-            case TAG_PERMISSION_TREE:
-            case TAG_SUPPORTS_GL_TEXTURE:
-            case TAG_USES_FEATURE:
-            case TAG_USES_LIBRARY:
-            case TAG_USES_NATIVE_LIBRARY:
-            case TAG_USES_PERMISSION:
-            case TAG_USES_PERMISSION_SDK_23:
-            case TAG_USES_SDK:
-                setStringAttrNames(NAME_ATTR);
-                break;
             case TAG_ACTIVITY:
-                setStringAttrNames(ACTIVITY_STR_ATTR_NAMES);
                 initializeCounter(TAG_LAYOUT, 1000);
-                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_META_DATA, 1000);
                 initializeCounter(TAG_INTENT_FILTER, 20000);
                 break;
             case TAG_ACTIVITY_ALIAS:
-                setStringAttrNames(ACTIVITY_ALIAS_STR_ATTR_NAMES);
-                initializeCounter(TAG_META_DATA, 8000);
+            case TAG_RECEIVER:
+            case TAG_SERVICE:
+                initializeCounter(TAG_META_DATA, 1000);
                 initializeCounter(TAG_INTENT_FILTER, 20000);
                 break;
             case TAG_APPLICATION:
-                setStringAttrNames(APPLICATION_STR_ATTR_NAMES);
                 initializeCounter(TAG_PROFILEABLE, 100);
                 initializeCounter(TAG_USES_NATIVE_LIBRARY, 100);
                 initializeCounter(TAG_RECEIVER, 1000);
                 initializeCounter(TAG_SERVICE, 1000);
+                initializeCounter(TAG_META_DATA, 1000);
+                initializeCounter(TAG_USES_LIBRARY, 1000);
                 initializeCounter(TAG_ACTIVITY_ALIAS, 4000);
-                initializeCounter(TAG_USES_LIBRARY, 4000);
                 initializeCounter(TAG_PROVIDER, 8000);
-                initializeCounter(TAG_META_DATA, 8000);
                 initializeCounter(TAG_ACTIVITY, 40000);
                 break;
             case TAG_COMPATIBLE_SCREENS:
                 initializeCounter(TAG_SCREEN, 4000);
                 break;
-            case TAG_DATA:
-                setStringAttrNames(DATA_STR_ATTR_NAMES);
-                break;
-            case TAG_GRANT_URI_PERMISSION:
-                setStringAttrNames(GRANT_URI_PERMISSION_STR_ATTR_NAMES);
-                break;
-            case TAG_INSTRUMENTATION:
-                setStringAttrNames(INSTRUMENTATION_STR_ATTR_NAMES);
-                break;
             case TAG_INTENT:
             case TAG_INTENT_FILTER:
                 initializeCounter(TAG_ACTION, 20000);
@@ -320,7 +311,6 @@
                 initializeCounter(TAG_DATA, 40000);
                 break;
             case TAG_MANIFEST:
-                setStringAttrNames(MANIFEST_STR_ATTR_NAMES);
                 initializeCounter(TAG_APPLICATION, 100);
                 initializeCounter(TAG_OVERLAY, 100);
                 initializeCounter(TAG_INSTRUMENTATION, 100);
@@ -329,7 +319,6 @@
                 initializeCounter(TAG_SUPPORTS_GL_TEXTURE, 100);
                 initializeCounter(TAG_SUPPORTS_SCREENS, 100);
                 initializeCounter(TAG_USES_CONFIGURATION, 100);
-                initializeCounter(TAG_USES_PERMISSION_SDK_23, 100);
                 initializeCounter(TAG_USES_SDK, 100);
                 initializeCounter(TAG_COMPATIBLE_SCREENS, 200);
                 initializeCounter(TAG_QUERIES, 200);
@@ -338,24 +327,10 @@
                 initializeCounter(TAG_PERMISSION, 2000);
                 initializeCounter(TAG_USES_PERMISSION, 20000);
                 break;
-            case TAG_META_DATA:
-            case TAG_PROPERTY:
-                setStringAttrNames(NAME_VALUE_ATTRS);
-                break;
-            case TAG_OVERLAY:
-                setStringAttrNames(OVERLAY_STR_ATTR_NAMES);
-                break;
-            case TAG_PATH_PERMISSION:
-                setStringAttrNames(PATH_PERMISSION_STR_ATTR_NAMES);
-                break;
-            case TAG_PERMISSION:
-                setStringAttrNames(PERMISSION_STR_ATTR_NAMES);
-                break;
             case TAG_PROVIDER:
-                setStringAttrNames(PROVIDER_STR_ATTR_NAMES);
                 initializeCounter(TAG_GRANT_URI_PERMISSION, 100);
                 initializeCounter(TAG_PATH_PERMISSION, 100);
-                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_META_DATA, 1000);
                 initializeCounter(TAG_INTENT_FILTER, 20000);
                 break;
             case TAG_QUERIES:
@@ -363,39 +338,23 @@
                 initializeCounter(TAG_INTENT, 2000);
                 initializeCounter(TAG_PROVIDER, 8000);
                 break;
-            case TAG_RECEIVER:
-            case TAG_SERVICE:
-                setStringAttrNames(RECEIVER_SERVICE_STR_ATTR_NAMES);
-                initializeCounter(TAG_META_DATA, 8000);
-                initializeCounter(TAG_INTENT_FILTER, 20000);
-                break;
         }
     }
 
-    private void setStringAttrNames(String[] attrNames) {
-        mStringAttrNames = attrNames;
-    }
-
-    private static String getAttrNamespace(String attrName) {
-        if (attrName.equals(TAG_ATTR_PACKAGE)) {
-            return null;
-        }
-        return ANDROID_NAMESPACE;
-    }
-
-    private static int getAttrStringMaxLength(String attrName) {
+    private static int getAttrStrMaxLen(String attrName) {
         switch (attrName) {
             case TAG_ATTR_HOST:
-            case TAG_ATTR_PACKAGE:
-            case TAG_ATTR_PERMISSION_GROUP:
             case TAG_ATTR_PORT:
-            case TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_VALUE:
             case TAG_ATTR_SCHEME:
+                return MAX_ATTR_LEN_URL_COMPONENT;
+            case TAG_ATTR_PERMISSION_GROUP:
+                return MAX_ATTR_LEN_PERMISSION_GROUP;
             case TAG_ATTR_SHARED_USER_ID:
+            case TAG_ATTR_PACKAGE:
             case TAG_ATTR_TARGET_PACKAGE:
-                return 256;
+                return MAX_ATTR_LEN_PACKAGE;
             case TAG_ATTR_MIMETYPE:
-                return 512;
+                return MAX_ATTR_LEN_MIMETYPE;
             case TAG_ATTR_BACKUP_AGENT:
             case TAG_ATTR_CATEGORY:
             case TAG_ATTR_MANAGE_SPACE_ACTIVITY:
@@ -405,33 +364,343 @@
             case TAG_ATTR_PROCESS:
             case TAG_ATTR_READ_PERMISSION:
             case TAG_ATTR_REQUIRED_ACCOUNT_TYPE:
+            case TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_NAME:
             case TAG_ATTR_RESTRICTED_ACCOUNT_TYPE:
             case TAG_ATTR_TARGET_ACTIVITY:
             case TAG_ATTR_TARGET_NAME:
             case TAG_ATTR_TARGET_PROCESSES:
             case TAG_ATTR_TASK_AFFINITY:
             case TAG_ATTR_WRITE_PERMISSION:
-                return 1024;
+            case TAG_ATTR_VERSION_NAME:
+                return MAX_ATTR_LEN_NAME;
             case TAG_ATTR_PATH:
             case TAG_ATTR_PATH_ADVANCED_PATTERN:
             case TAG_ATTR_PATH_PATTERN:
             case TAG_ATTR_PATH_PREFIX:
             case TAG_ATTR_PATH_SUFFIX:
-            case TAG_ATTR_VERSION_NAME:
-                return 4000;
+                return MAX_ATTR_LEN_PATH;
+            case TAG_ATTR_VALUE:
+                return MAX_ATTR_LEN_DATA_VALUE;
+            case TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_VALUE:
+                return PROP_VALUE_MAX;
             default:
                 return DEFAULT_MAX_STRING_ATTR_LENGTH;
         }
     }
 
-    private static int getResStringMaxLength(@StyleableRes int index) {
+    private int getResStrMaxLen(@StyleableRes int index) {
+        switch (mTag) {
+            case TAG_ACTION:
+                return getActionResStrMaxLen(index);
+            case TAG_ACTIVITY:
+                return getActivityResStrMaxLen(index);
+            case TAG_ACTIVITY_ALIAS:
+                return getActivityAliasResStrMaxLen(index);
+            case TAG_APPLICATION:
+                return getApplicationResStrMaxLen(index);
+            case TAG_DATA:
+                return getDataResStrMaxLen(index);
+            case TAG_CATEGORY:
+                return getCategoryResStrMaxLen(index);
+            case TAG_GRANT_URI_PERMISSION:
+                return getGrantUriPermissionResStrMaxLen(index);
+            case TAG_INSTRUMENTATION:
+                return getInstrumentationResStrMaxLen(index);
+            case TAG_MANIFEST:
+                return getManifestResStrMaxLen(index);
+            case TAG_META_DATA:
+                return getMetaDataResStrMaxLen(index);
+            case TAG_OVERLAY:
+                return getOverlayResStrMaxLen(index);
+            case TAG_PATH_PERMISSION:
+                return getPathPermissionResStrMaxLen(index);
+            case TAG_PERMISSION:
+                return getPermissionResStrMaxLen(index);
+            case TAG_PERMISSION_GROUP:
+                return getPermissionGroupResStrMaxLen(index);
+            case TAG_PERMISSION_TREE:
+                return getPermissionTreeResStrMaxLen(index);
+            case TAG_PROPERTY:
+                return getPropertyResStrMaxLen(index);
+            case TAG_PROVIDER:
+                return getProviderResStrMaxLen(index);
+            case TAG_RECEIVER:
+                return getReceiverResStrMaxLen(index);
+            case TAG_SERVICE:
+                return getServiceResStrMaxLen(index);
+            case TAG_USES_FEATURE:
+                return getUsesFeatureResStrMaxLen(index);
+            case TAG_USES_LIBRARY:
+                return getUsesLibraryResStrMaxLen(index);
+            case TAG_USES_NATIVE_LIBRARY:
+                return getUsesNativeLibraryResStrMaxLen(index);
+            case TAG_USES_PERMISSION:
+            case TAG_USES_PERMISSION_SDK_23:
+            case TAG_USES_PERMISSION_SDK_M:
+                return getUsesPermissionResStrMaxLen(index);
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getActionResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestAction_name:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getActivityResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestActivity_name:
+            case R.styleable.AndroidManifestActivity_parentActivityName:
+            case R.styleable.AndroidManifestActivity_permission:
+            case R.styleable.AndroidManifestActivity_process:
+            case R.styleable.AndroidManifestActivity_taskAffinity:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getActivityAliasResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestActivityAlias_name:
+            case R.styleable.AndroidManifestActivityAlias_permission:
+            case R.styleable.AndroidManifestActivityAlias_targetActivity:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getApplicationResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestApplication_backupAgent:
+            case R.styleable.AndroidManifestApplication_manageSpaceActivity:
+            case R.styleable.AndroidManifestApplication_name:
+            case R.styleable.AndroidManifestApplication_permission:
+            case R.styleable.AndroidManifestApplication_process:
+            case R.styleable.AndroidManifestApplication_requiredAccountType:
+            case R.styleable.AndroidManifestApplication_restrictedAccountType:
+            case R.styleable.AndroidManifestApplication_taskAffinity:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getCategoryResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestCategory_name:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getDataResStrMaxLen(@StyleableRes int index) {
         switch (index) {
             case R.styleable.AndroidManifestData_host:
             case R.styleable.AndroidManifestData_port:
             case R.styleable.AndroidManifestData_scheme:
-                return 255;
+                return MAX_ATTR_LEN_URL_COMPONENT;
             case R.styleable.AndroidManifestData_mimeType:
-                return 512;
+                return MAX_ATTR_LEN_MIMETYPE;
+            case R.styleable.AndroidManifestData_path:
+            case R.styleable.AndroidManifestData_pathPattern:
+            case R.styleable.AndroidManifestData_pathPrefix:
+            case R.styleable.AndroidManifestData_pathSuffix:
+            case R.styleable.AndroidManifestData_pathAdvancedPattern:
+                return MAX_ATTR_LEN_PATH;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getGrantUriPermissionResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestGrantUriPermission_path:
+            case R.styleable.AndroidManifestGrantUriPermission_pathPattern:
+            case R.styleable.AndroidManifestGrantUriPermission_pathPrefix:
+                return MAX_ATTR_LEN_PATH;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getInstrumentationResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestInstrumentation_targetPackage:
+                return MAX_ATTR_LEN_PACKAGE;
+            case R.styleable.AndroidManifestInstrumentation_name:
+            case R.styleable.AndroidManifestInstrumentation_targetProcesses:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getManifestResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifest_sharedUserId:
+                return MAX_ATTR_LEN_PACKAGE;
+            case R.styleable.AndroidManifest_versionName:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getMetaDataResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestMetaData_name:
+                return MAX_ATTR_LEN_NAME;
+            case R.styleable.AndroidManifestMetaData_value:
+                return MAX_ATTR_LEN_DATA_VALUE;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getOverlayResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestResourceOverlay_targetPackage:
+                return MAX_ATTR_LEN_PACKAGE;
+            case R.styleable.AndroidManifestResourceOverlay_category:
+            case R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName:
+            case R.styleable.AndroidManifestResourceOverlay_targetName:
+                return MAX_ATTR_LEN_NAME;
+            case R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue:
+                return PROP_VALUE_MAX;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getPathPermissionResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestPathPermission_permission:
+            case R.styleable.AndroidManifestPathPermission_readPermission:
+            case R.styleable.AndroidManifestPathPermission_writePermission:
+                return MAX_ATTR_LEN_NAME;
+            case R.styleable.AndroidManifestPathPermission_path:
+            case R.styleable.AndroidManifestPathPermission_pathPattern:
+            case R.styleable.AndroidManifestPathPermission_pathPrefix:
+                return MAX_ATTR_LEN_PATH;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getPermissionResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestPermission_permissionGroup:
+                return MAX_ATTR_LEN_PERMISSION_GROUP;
+            case R.styleable.AndroidManifestPermission_name:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getPermissionGroupResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestPermissionGroup_name:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getPermissionTreeResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestPermissionTree_name:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getPropertyResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestProperty_name:
+                return MAX_ATTR_LEN_NAME;
+            case R.styleable.AndroidManifestProperty_value:
+                return MAX_ATTR_LEN_DATA_VALUE;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getProviderResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestProvider_name:
+            case R.styleable.AndroidManifestProvider_permission:
+            case R.styleable.AndroidManifestProvider_process:
+            case R.styleable.AndroidManifestProvider_readPermission:
+            case R.styleable.AndroidManifestProvider_writePermission:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getReceiverResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestReceiver_name:
+            case R.styleable.AndroidManifestReceiver_permission:
+            case R.styleable.AndroidManifestReceiver_process:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getServiceResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestReceiver_name:
+            case R.styleable.AndroidManifestReceiver_permission:
+            case R.styleable.AndroidManifestReceiver_process:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getUsesFeatureResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestUsesFeature_name:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getUsesLibraryResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestUsesLibrary_name:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getUsesNativeLibraryResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestUsesNativeLibrary_name:
+                return MAX_ATTR_LEN_NAME;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getUsesPermissionResStrMaxLen(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestUsesPermission_name:
+                return MAX_ATTR_LEN_NAME;
             default:
                 return DEFAULT_MAX_STRING_ATTR_LENGTH;
         }
@@ -450,31 +719,25 @@
         return (mChildTagMask & (1 << getCounterIdx(tag))) != 0;
     }
 
-    void validateStringAttrs(@NonNull XmlPullParser attrs) throws XmlPullParserException {
-        for (int i = 0; i < mStringAttrNames.length; i++) {
-            String attrName = mStringAttrNames[i];
-            String val = attrs.getAttributeValue(getAttrNamespace(attrName), attrName);
-            if (val != null && val.length() > getAttrStringMaxLength(attrName)) {
-                throw new XmlPullParserException("String length limit exceeded for "
-                        + "attribute " + attrName + " in " + mTag);
-            }
+    void validateStrAttr(String attrName, String attrValue) {
+        if (attrValue != null && attrValue.length() > getAttrStrMaxLen(attrName)) {
+            throw new SecurityException("String length limit exceeded for attribute " + attrName
+                    + " in " + mTag);
         }
     }
 
-    void validateResStringAttr(@StyleableRes int index, CharSequence stringValue)
-            throws XmlPullParserException {
-        if (stringValue != null && stringValue.length() > getResStringMaxLength(index)) {
-            throw new XmlPullParserException("String length limit exceeded for "
-                    + "attribute in " + mTag);
+    void validateResStrAttr(@StyleableRes int index, CharSequence stringValue) {
+        if (stringValue != null && stringValue.length() > getResStrMaxLen(index)) {
+            throw new SecurityException("String length limit exceeded for attribute in " + mTag);
         }
     }
 
-    void seen(@NonNull Element element) throws XmlPullParserException {
+    void seen(@NonNull Element element) {
         TagCounter counter = mTagCounters[getCounterIdx(element.mTag)];
         if (counter != null) {
             counter.increment();
             if (!counter.isValid()) {
-                throw new XmlPullParserException("The number of child " + element.mTag
+                throw new SecurityException("The number of child " + element.mTag
                         + " elements exceeded the max allowed in " + this.mTag);
             }
         }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 61d5aa6..f2468b5 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -407,14 +407,12 @@
                     mConfiguration.setLocales(locales);
                 }
 
+                String[] selectedLocales = null;
+                String defaultLocale = null;
                 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
                     if (locales.size() > 1) {
                         String[] availableLocales;
-
-                        LocaleList localeList = ResourcesManager.getInstance().getLocaleList();
-                        if (!localeList.isEmpty()) {
-                            availableLocales = localeList.toLanguageTags().split(",");
-                        } else {
+                        if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
                             // The LocaleList has changed. We must query the AssetManager's
                             // available Locales and figure out the best matching Locale in the new
                             // LocaleList.
@@ -426,16 +424,32 @@
                                     availableLocales = null;
                                 }
                             }
-                        }
-                        if (availableLocales != null) {
-                            final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
-                                    availableLocales);
-                            if (bestLocale != null && bestLocale != locales.get(0)) {
-                                mConfiguration.setLocales(new LocaleList(bestLocale, locales));
+                            if (availableLocales != null) {
+                                final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+                                        availableLocales);
+                                if (bestLocale != null) {
+                                    selectedLocales = new String[]{
+                                            adjustLanguageTag(bestLocale.toLanguageTag())};
+                                    if (!bestLocale.equals(locales.get(0))) {
+                                        mConfiguration.setLocales(
+                                                new LocaleList(bestLocale, locales));
+                                    }
+                                }
                             }
+                        } else {
+                            selectedLocales = locales.getIntersection(
+                                    ResourcesManager.getInstance().getLocaleList());
+                            defaultLocale = ResourcesManager.getInstance()
+                                    .getLocaleList().get(0).toLanguageTag();
                         }
                     }
                 }
+                if (selectedLocales == null) {
+                    selectedLocales = new String[locales.size()];
+                    for (int i = 0; i < locales.size(); i++) {
+                        selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
+                    }
+                }
 
                 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
                     mMetrics.densityDpi = mConfiguration.densityDpi;
@@ -470,7 +484,8 @@
                 }
 
                 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
-                        adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
+                        defaultLocale,
+                        selectedLocales,
                         mConfiguration.orientation,
                         mConfiguration.touchscreen,
                         mConfiguration.densityDpi, mConfiguration.keyboard,
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
index 3703f2e..c2febae 100644
--- a/core/java/android/content/res/TEST_MAPPING
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -10,11 +10,9 @@
   "presubmit": [
     {
       "name": "CtsResourcesLoaderTests"
-    }
-  ],
-  "presubmit-large": [
+    },
     {
-      "name": "CtsContentTestCases",
+      "name": "CtsResourcesTestCases",
       "options": [
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 2e84636..48adfb9 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -1393,16 +1393,17 @@
     private CharSequence loadStringValueAt(int index) {
         final int[] data = mData;
         final int cookie = data[index + STYLE_ASSET_COOKIE];
+        CharSequence value = null;
         if (cookie < 0) {
             if (mXml != null) {
-                return mXml.getPooledString(data[index + STYLE_DATA]);
+                value = mXml.getPooledString(data[index + STYLE_DATA]);
             }
-            return null;
+        } else {
+            value = mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]);
         }
-        CharSequence value = mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]);
-        if (mXml != null && mXml.mValidator != null) {
+        if (value != null && mXml != null && mXml.mValidator != null) {
             try {
-                mXml.mValidator.validateAttr(mXml, index, value);
+                mXml.mValidator.validateResStrAttr(mXml, index / STYLE_NUM_ENTRIES, value);
             } catch (XmlPullParserException e) {
                 throw new RuntimeException("Failed to validate resource string: " + e.getMessage());
             }
diff --git a/core/java/android/content/res/Validator.java b/core/java/android/content/res/Validator.java
index 8b5e6c6..cae353b 100644
--- a/core/java/android/content/res/Validator.java
+++ b/core/java/android/content/res/Validator.java
@@ -16,9 +16,8 @@
 
 package android.content.res;
 
-import static android.content.res.Element.TAG_MANIFEST;
-
 import android.annotation.NonNull;
+import android.annotation.StyleableRes;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -55,24 +54,19 @@
             return;
         }
         if (eventType == XmlPullParser.START_TAG) {
-            try {
-                String tag = parser.getName();
-                // only validate manifests
-                if (depth == 0 && mElements.size() == 0 && !TAG_MANIFEST.equals(tag)) {
-                    return;
-                }
+            String tag = parser.getName();
+            if (Element.shouldValidate(tag)) {
+                Element element = Element.obtain(tag);
                 Element parent = mElements.peek();
-                if (parent == null || parent.hasChild(tag)) {
-                    Element element = Element.obtain(tag);
-                    element.validateStringAttrs(parser);
-                    if (parent != null) {
+                if (parent != null && parent.hasChild(tag)) {
+                    try {
                         parent.seen(element);
+                    } catch (SecurityException e) {
+                        cleanUp();
+                        throw e;
                     }
-                    mElements.push(element);
                 }
-            } catch (XmlPullParserException e) {
-                cleanUp();
-                throw e;
+                mElements.push(element);
             }
         } else if (eventType == XmlPullParser.END_TAG && depth == mElements.size()) {
             mElements.pop().recycle();
@@ -84,11 +78,21 @@
     /**
      * Validates the resource string of a manifest tag attribute.
      */
-    public void validateAttr(@NonNull XmlPullParser parser, int index, CharSequence stringValue)
-            throws XmlPullParserException {
+    public void validateResStrAttr(@NonNull XmlPullParser parser, @StyleableRes int index,
+            CharSequence stringValue) throws XmlPullParserException {
         if (parser.getDepth() > mElements.size()) {
             return;
         }
-        mElements.peek().validateResStringAttr(index, stringValue);
+        mElements.peek().validateResStrAttr(index, stringValue);
+    }
+
+    /**
+     * Validates the string of a manifest tag attribute by name.
+     */
+    public void validateStrAttr(@NonNull XmlPullParser parser, String attrName, String attrValue) {
+        if (parser.getDepth() > mElements.size()) {
+            return;
+        }
+        mElements.peek().validateStrAttr(attrName, attrValue);
     }
 }
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 3afc830..7649b32 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -319,7 +319,11 @@
                         "Namespace=" + getAttributeNamespace(idx)
                         + "Name=" + getAttributeName(idx)
                         + ", Value=" + getAttributeValue(idx));
-                return getAttributeValue(idx);
+                String value = getAttributeValue(idx);
+                if (mValidator != null) {
+                    mValidator.validateStrAttr(this, name, value);
+                }
+                return value;
             }
             return null;
         }
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index b7489229..706e75e 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -113,9 +113,6 @@
     private final boolean mIsReadOnlyConnection;
     private PreparedStatement mPreparedStatementPool;
 
-    // A lock access to the statement cache.
-    private final Object mCacheLock = new Object();
-    @GuardedBy("mCacheLock")
     private final PreparedStatementCache mPreparedStatementCache;
 
     // The recent operations log.
@@ -596,9 +593,7 @@
         mConfiguration.updateParametersFrom(configuration);
 
         // Update prepared statement cache size.
-        synchronized (mCacheLock) {
-            mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
-        }
+        mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
 
         if (foreignKeyModeChanged) {
             setForeignKeyModeFromConfiguration();
@@ -630,12 +625,12 @@
         mOnlyAllowReadOnlyOperations = readOnly;
     }
 
-    // Called by SQLiteConnectionPool only.
-    // Returns true if the prepared statement cache contains the specified SQL.
+    // Called by SQLiteConnectionPool only to decide if this connection has the desired statement
+    // already prepared.  Returns true if the prepared statement cache contains the specified SQL.
+    // The statement may be stale, but that will be a rare occurrence and affects performance only
+    // a tiny bit, and only when database schema changes.
     boolean isPreparedStatementInCache(String sql) {
-        synchronized (mCacheLock) {
-            return mPreparedStatementCache.get(sql) != null;
-        }
+        return mPreparedStatementCache.get(sql) != null;
     }
 
     /**
@@ -1070,28 +1065,41 @@
     /**
      * Return a {@link #PreparedStatement}, possibly from the cache.
      */
-    @GuardedBy("mCacheLock")
     private PreparedStatement acquirePreparedStatementLI(String sql) {
         ++mPool.mTotalPrepareStatements;
-        PreparedStatement statement = mPreparedStatementCache.get(sql);
+        PreparedStatement statement = mPreparedStatementCache.getStatement(sql);
+        long seqNum = mPreparedStatementCache.getLastSeqNum();
+
         boolean skipCache = false;
         if (statement != null) {
             if (!statement.mInUse) {
-                statement.mInUse = true;
-                return statement;
+                if (statement.mSeqNum == seqNum) {
+                    // This is a valid statement.  Claim it and return it.
+                    statement.mInUse = true;
+                    return statement;
+                } else {
+                    // This is a stale statement.  Remove it from the cache.  Treat this as if the
+                    // statement was never found, which means we should not skip the cache.
+                    mPreparedStatementCache.remove(sql);
+                    statement = null;
+                    // Leave skipCache == false.
+                }
+            } else {
+                // The statement is already in the cache but is in use (this statement appears to
+                // be not only re-entrant but recursive!).  So prepare a new copy of the statement
+                // but do not cache it.
+                skipCache = true;
             }
-            // The statement is already in the cache but is in use (this statement appears
-            // to be not only re-entrant but recursive!).  So prepare a new copy of the
-            // statement but do not cache it.
-            skipCache = true;
         }
         ++mPool.mTotalPrepareStatementCacheMiss;
-        final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
+        final long statementPtr = mPreparedStatementCache.createStatement(sql);
+        seqNum = mPreparedStatementCache.getLastSeqNum();
         try {
             final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
             final int type = DatabaseUtils.getSqlStatementType(sql);
             final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
-            statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
+            statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly,
+                    seqNum);
             if (!skipCache && isCacheable(type)) {
                 mPreparedStatementCache.put(sql, statement);
                 statement.mInCache = true;
@@ -1112,15 +1120,12 @@
      * Return a {@link #PreparedStatement}, possibly from the cache.
      */
     PreparedStatement acquirePreparedStatement(String sql) {
-        synchronized (mCacheLock) {
-            return acquirePreparedStatementLI(sql);
-        }
+        return acquirePreparedStatementLI(sql);
     }
 
     /**
      * Release a {@link #PreparedStatement} that was originally supplied by this connection.
      */
-    @GuardedBy("mCacheLock")
     private void releasePreparedStatementLI(PreparedStatement statement) {
         statement.mInUse = false;
         if (statement.mInCache) {
@@ -1148,9 +1153,7 @@
      * Release a {@link #PreparedStatement} that was originally supplied by this connection.
      */
     void releasePreparedStatement(PreparedStatement statement) {
-        synchronized (mCacheLock) {
-            releasePreparedStatementLI(statement);
-        }
+        releasePreparedStatementLI(statement);
     }
 
     private void finalizePreparedStatement(PreparedStatement statement) {
@@ -1327,9 +1330,7 @@
         mRecentOperations.dump(printer);
 
         if (verbose) {
-            synchronized (mCacheLock) {
-                mPreparedStatementCache.dump(printer);
-            }
+            mPreparedStatementCache.dump(printer);
         }
     }
 
@@ -1430,7 +1431,7 @@
     }
 
     private PreparedStatement obtainPreparedStatement(String sql, long statementPtr,
-            int numParameters, int type, boolean readOnly) {
+            int numParameters, int type, boolean readOnly, long seqNum) {
         PreparedStatement statement = mPreparedStatementPool;
         if (statement != null) {
             mPreparedStatementPool = statement.mPoolNext;
@@ -1444,6 +1445,7 @@
         statement.mNumParameters = numParameters;
         statement.mType = type;
         statement.mReadOnly = readOnly;
+        statement.mSeqNum = seqNum;
         return statement;
     }
 
@@ -1461,10 +1463,10 @@
         return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
     }
 
-    void clearPreparedStatementCache() {
-        synchronized (mCacheLock) {
-            mPreparedStatementCache.evictAll();
-        }
+    // Update the database sequence number.  This number is stored in the prepared statement
+    // cache.
+    void setDatabaseSeqNum(long n) {
+        mPreparedStatementCache.setDatabaseSeqNum(n);
     }
 
     /**
@@ -1502,6 +1504,10 @@
         // True if the statement is in the cache.
         public boolean mInCache;
 
+        // The database schema ID at the time this statement was created.  The ID is left zero for
+        // statements that are not cached.  This value is meaningful only if mInCache is true.
+        public long mSeqNum;
+
         // True if the statement is in use (currently executing).
         // We need this flag because due to the use of custom functions in triggers, it's
         // possible for SQLite calls to be re-entrant.  Consequently we need to prevent
@@ -1510,10 +1516,41 @@
     }
 
     private final class PreparedStatementCache extends LruCache<String, PreparedStatement> {
+        // The database sequence number.  This changes every time the database schema changes.
+        private long mDatabaseSeqNum = 0;
+
+        // The database sequence number from the last getStatement() or createStatement()
+        // call. The proper use of this variable depends on the caller being single threaded.
+        private long mLastSeqNum = 0;
+
         public PreparedStatementCache(int size) {
             super(size);
         }
 
+        public synchronized void setDatabaseSeqNum(long n) {
+            mDatabaseSeqNum = n;
+        }
+
+        // Return the last database sequence number.
+        public long getLastSeqNum() {
+            return mLastSeqNum;
+        }
+
+        // Return a statement from the cache.  Save the database sequence number for the caller.
+        public synchronized PreparedStatement getStatement(String sql) {
+            mLastSeqNum = mDatabaseSeqNum;
+            return get(sql);
+        }
+
+        // Return a new native prepared statement and save the database sequence number for the
+        // caller.  This does not modify the cache in any way.  However, by being synchronized,
+        // callers are guaranteed that the sequence number did not change across the native
+        // preparation step.
+        public synchronized long createStatement(String sql) {
+            mLastSeqNum = mDatabaseSeqNum;
+            return nativePrepareStatement(mConnectionPtr, sql);
+        }
+
         @Override
         protected void entryRemoved(boolean evicted, String key,
                 PreparedStatement oldValue, PreparedStatement newValue) {
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index b35a2e4..ad335b6 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -111,6 +111,13 @@
     @GuardedBy("mLock")
     private IdleConnectionHandler mIdleConnectionHandler;
 
+    // The database schema sequence number.  This counter is incremented every time a schema
+    // change is detected.  Every prepared statement records its schema sequence when the
+    // statement is created.  The prepared statement is not put back in the cache if the sequence
+    // number has changed.  The counter starts at 1, which allows clients to use 0 as a
+    // distinguished value.
+    private long mDatabaseSeqNum = 1;
+
     // whole execution time for this connection in milliseconds.
     private final AtomicLong mTotalStatementsTime = new AtomicLong(0);
 
@@ -1127,10 +1134,12 @@
     }
 
     void clearAcquiredConnectionsPreparedStatementCache() {
+        // Invalidate prepared statements that have an earlier schema sequence number.
         synchronized (mLock) {
+            mDatabaseSeqNum++;
             if (!mAcquiredConnections.isEmpty()) {
                 for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
-                    connection.clearPreparedStatementCache();
+                    connection.setDatabaseSeqNum(mDatabaseSeqNum);
                 }
             }
         }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 022f3c4..4323bf8 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1780,6 +1780,15 @@
          * @hide
          */
         String KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER = "use_normal_brightness_mode_controller";
+
+        /**
+         * Key for disabling screen wake locks while apps are in cached state.
+         * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
+         * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
+         * @hide
+         */
+        String KEY_DISABLE_SCREEN_WAKE_LOCKS_WHILE_CACHED =
+                "disable_screen_wake_locks_while_cached";
     }
 
     /**
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 70b72c8..b99996ff 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -433,7 +433,7 @@
     @BinderThread
     @Override
     public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
-            int flags, ResultReceiver resultReceiver) {
+            @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
         mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
                 flags, showInputToken, resultReceiver, statsToken));
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index e472a40..60b11b4 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -606,6 +606,7 @@
     InputConnection mStartedInputConnection;
     EditorInfo mInputEditorInfo;
 
+    @InputMethod.ShowFlags
     int mShowInputFlags;
     boolean mShowInputRequested;
     boolean mLastShowInputRequested;
@@ -930,8 +931,9 @@
          */
         @MainThread
         @Override
-        public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-                IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
+        public void showSoftInputWithToken(@InputMethod.ShowFlags int flags,
+                ResultReceiver resultReceiver, IBinder showInputToken,
+                @Nullable ImeTracker.Token statsToken) {
             mSystemCallingShowSoftInput = true;
             mCurShowInputToken = showInputToken;
             mCurStatsToken = statsToken;
@@ -949,7 +951,7 @@
          */
         @MainThread
         @Override
-        public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+        public void showSoftInput(@InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
             ImeTracker.forLogging().onProgress(
                     mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "showSoftInput()");
@@ -1325,7 +1327,8 @@
          * InputMethodService#requestShowSelf} or {@link InputMethodService#requestHideSelf}
          */
         @Deprecated
-        public void toggleSoftInput(int showFlags, int hideFlags) {
+        public void toggleSoftInput(@InputMethodManager.ShowFlags int showFlags,
+                @InputMethodManager.HideFlags int hideFlags) {
             InputMethodService.this.onToggleSoftInput(showFlags, hideFlags);
         }
 
@@ -2797,18 +2800,16 @@
      * {@link #onEvaluateInputViewShown()}, {@link #onEvaluateFullscreenMode()},
      * and the current configuration to decide whether the input view should
      * be shown at this point.
-     * 
-     * @param flags Provides additional information about the show request,
-     * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}.
+     *
      * @param configChange This is true if we are re-showing due to a
      * configuration change.
      * @return Returns true to indicate that the window should be shown.
      */
-    public boolean onShowInputRequested(int flags, boolean configChange) {
+    public boolean onShowInputRequested(@InputMethod.ShowFlags int flags, boolean configChange) {
         if (!onEvaluateInputViewShown()) {
             return false;
         }
-        if ((flags&InputMethod.SHOW_EXPLICIT) == 0) {
+        if ((flags & InputMethod.SHOW_EXPLICIT) == 0) {
             if (!configChange && onEvaluateFullscreenMode() && !isInputViewShown()) {
                 // Don't show if this is not explicitly requested by the user and
                 // the input method is fullscreen unless it is already shown. That
@@ -2834,14 +2835,14 @@
      * exposed to IME authors as an overridable public method without {@code @CallSuper}, we have
      * to have this method to ensure that those internal states are always updated no matter how
      * {@link #onShowInputRequested(int, boolean)} is overridden by the IME author.
-     * @param flags Provides additional information about the show request,
-     * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}.
+     *
      * @param configChange This is true if we are re-showing due to a
      * configuration change.
      * @return Returns true to indicate that the window should be shown.
      * @see #onShowInputRequested(int, boolean)
      */
-    private boolean dispatchOnShowInputRequested(int flags, boolean configChange) {
+    private boolean dispatchOnShowInputRequested(@InputMethod.ShowFlags int flags,
+            boolean configChange) {
         final boolean result = onShowInputRequested(flags, configChange);
         mInlineSuggestionSessionController.notifyOnShowInputRequested(result);
         if (result) {
@@ -2985,8 +2986,6 @@
         ImeTracing.getInstance().triggerServiceDump(
                 "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper,
                 null /* icProto */);
-        ImeTracker.forLogging().onProgress(mCurStatsToken,
-                ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
         mPrivOps.applyImeVisibilityAsync(setVisible
                 ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken);
     }
@@ -3274,16 +3273,13 @@
      *
      * The input method will continue running, but the user can no longer use it to generate input
      * by touching the screen.
-     *
-     * @see InputMethodManager#HIDE_IMPLICIT_ONLY
-     * @see InputMethodManager#HIDE_NOT_ALWAYS
-     * @param flags Provides additional operating flags.
      */
-    public void requestHideSelf(int flags) {
+    public void requestHideSelf(@InputMethodManager.HideFlags int flags) {
         requestHideSelf(flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME);
     }
 
-    private void requestHideSelf(int flags, @SoftInputShowHideReason int reason) {
+    private void requestHideSelf(@InputMethodManager.HideFlags int flags,
+            @SoftInputShowHideReason int reason) {
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestHideSelf", mDumper,
                 null /* icProto */);
         mPrivOps.hideMySoftInput(flags, reason);
@@ -3292,12 +3288,8 @@
     /**
      * Show the input method's soft input area, so the user sees the input method window and can
      * interact with it.
-     *
-     * @see InputMethodManager#SHOW_IMPLICIT
-     * @see InputMethodManager#SHOW_FORCED
-     * @param flags Provides additional operating flags.
      */
-    public final void requestShowSelf(int flags) {
+    public final void requestShowSelf(@InputMethodManager.ShowFlags int flags) {
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestShowSelf", mDumper,
                 null /* icProto */);
         mPrivOps.showMySoftInput(flags);
@@ -3457,7 +3449,8 @@
     /**
      * Handle a request by the system to toggle the soft input area.
      */
-    private void onToggleSoftInput(int showFlags, int hideFlags) {
+    private void onToggleSoftInput(@InputMethodManager.ShowFlags int showFlags,
+            @InputMethodManager.HideFlags int hideFlags) {
         if (DEBUG) Log.v(TAG, "toggleSoftInput()");
         if (isInputViewShown()) {
             requestHideSelf(
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index ceaf337..2584f04 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -119,7 +119,7 @@
      *   crashes (if a handler is sometimes created on a thread without a Looper active), or race
      *   conditions, where the thread a handler is associated with is not what the author
      *   anticipated. Instead, use an {@link java.util.concurrent.Executor} or specify the Looper
-     *   explicitly, using {@link Looper#getMainLooper}, {link android.view.View#getHandler}, or
+     *   explicitly, using {@link Looper#getMainLooper}, {@link android.view.View#getHandler}, or
      *   similar. If the implicit thread local behavior is required for compatibility, use
      *   {@code new Handler(Looper.myLooper())} to make it clear to readers.
      *
@@ -144,7 +144,7 @@
      *   crashes (if a handler is sometimes created on a thread without a Looper active), or race
      *   conditions, where the thread a handler is associated with is not what the author
      *   anticipated. Instead, use an {@link java.util.concurrent.Executor} or specify the Looper
-     *   explicitly, using {@link Looper#getMainLooper}, {link android.view.View#getHandler}, or
+     *   explicitly, using {@link Looper#getMainLooper}, {@link android.view.View#getHandler}, or
      *   similar. If the implicit thread local behavior is required for compatibility, use
      *   {@code new Handler(Looper.myLooper(), callback)} to make it clear to readers.
      */
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index b74bb33..82cdd28 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -30,6 +30,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -151,6 +152,25 @@
     }
 
     /**
+     * Find the intersection between this LocaleList and another
+     * @return a String array of the Locales in both LocaleLists
+     * {@hide}
+     */
+    @NonNull
+    public String[] getIntersection(@NonNull LocaleList other) {
+        List<String> intersection = new ArrayList<>();
+        for (Locale l1 : mList) {
+            for (Locale l2 : other.mList) {
+                if (matchesLanguageAndScript(l2, l1)) {
+                    intersection.add(l1.toLanguageTag());
+                    break;
+                }
+            }
+        }
+        return intersection.toArray(new String[0]);
+    }
+
+    /**
      * Creates a new {@link LocaleList}.
      *
      * If two or more same locales are passed, the repeated locales will be dropped.
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index 5c4aa4a..954ee3c 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -1,6 +1,32 @@
 {
   "presubmit": [
     {
+      "file_patterns": [
+        "[^/]*(Vibrator|Vibration)[^/]*\\.java",
+        "vibrator/.*"
+      ],
+      "name": "FrameworksVibratorCoreTests",
+      "options": [
+        {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    },
+    {
+      "file_patterns": [
+        "[^/]*(Vibrator|Vibration)[^/]*\\.java",
+        "vibrator/.*"
+      ],
+      "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": "org.junit.Ignore"}
+      ]
+    },
+    {
       "file_patterns": ["Bugreport[^/]*\\.java"],
       "name": "BugreportManagerTestCases",
       "options": [
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index f256210..447c3bc 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -27,6 +27,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Typeface;
 import android.icu.lang.UCharacter;
 import android.icu.text.CaseMap;
 import android.icu.text.Edits;
@@ -2482,12 +2483,28 @@
         if (ellipsizeDip == 0) {
             return gettingCleaned.toString();
         } else {
-            // Truncate
-            final TextPaint paint = new TextPaint();
-            paint.setTextSize(42);
+            final float assumedFontSizePx = 42;
+            if (Typeface.getSystemFontMap().isEmpty()) {
+                // In the system server, the font files may not be loaded, so unable to perform
+                // ellipsize, so use the estimated char count for the ellipsize.
 
-            return TextUtils.ellipsize(gettingCleaned.toString(), paint, ellipsizeDip,
-                    TextUtils.TruncateAt.END);
+                // The median of glyph widths of the Roboto is 0.57em, so use it as a reference
+                // of the glyph width.
+                final float assumedCharWidthInEm = 0.57f;
+                final float assumedCharWidthInPx = assumedFontSizePx * assumedCharWidthInEm;
+
+                // Even if the argument name is `ellipsizeDip`, the unit of this argument is pixels.
+                final int charCount = (int) ((ellipsizeDip + 0.5f) / assumedCharWidthInPx);
+                return TextUtils.trimToSize(gettingCleaned.toString(), charCount)
+                        + getEllipsisString(TruncateAt.END);
+            } else {
+                // Truncate
+                final TextPaint paint = new TextPaint();
+                paint.setTextSize(assumedFontSizePx);
+
+                return TextUtils.ellipsize(gettingCleaned.toString(), paint, ellipsizeDip,
+                        TextUtils.TruncateAt.END);
+            }
         }
     }
 
diff --git a/core/java/android/util/apk/TEST_MAPPING b/core/java/android/util/apk/TEST_MAPPING
index e182521..b26a38b 100644
--- a/core/java/android/util/apk/TEST_MAPPING
+++ b/core/java/android/util/apk/TEST_MAPPING
@@ -7,17 +7,15 @@
           "include-filter": "android.util.apk.SourceStampVerifierTest"
         }
       ]
-    }
-  ],
-  "presubmit-large": [
+    },
     {
-      "name": "CtsContentTestCases",
+      "name": "CtsPackageManagerTestCases",
       "options": [
         {
           "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest"
         },
         {
-          "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest"
+          "include-filter": "android.content.pm.cts.PackageManagerShellCommandInstallerTest"
         }
       ]
     }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index bbd8255..c1474eb 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -21,6 +21,7 @@
 import com.android.internal.policy.IKeyguardLockedStateListener;
 import com.android.internal.policy.IShortcutService;
 
+import android.app.IApplicationThread;
 import android.app.IAssistDataReceiver;
 import android.content.ComponentName;
 import android.content.res.CompatibilityInfo;
@@ -850,6 +851,7 @@
      * system server. {@link #attachWindowContextToWindowToken(IBinder, IBinder)} could be used in
      * this case to attach the WindowContext to the WindowToken.</p>
      *
+     * @param appThread the process that the window context is on.
      * @param clientToken {@link android.window.WindowContext#getWindowContextToken()
      * the WindowContext's token}
      * @param type Window type of the window context
@@ -859,8 +861,8 @@
      * @return the DisplayArea's {@link android.app.res.Configuration} if the WindowContext is
      * attached to the DisplayArea successfully. {@code null}, otherwise.
      */
-    Configuration attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
-            in Bundle options);
+    @nullable Configuration attachWindowContextToDisplayArea(in IApplicationThread appThread,
+            IBinder clientToken, int type, int displayId, in @nullable Bundle options);
 
     /**
      * Attaches a {@link android.window.WindowContext} to a {@code WindowToken}.
@@ -872,6 +874,7 @@
      * {@link android.window.WindowTokenClient#attachContext(Context)}
      * </p>
      *
+     * @param appThread the process that the window context is on.
      * @param clientToken {@link android.window.WindowContext#getWindowContextToken()
      * the WindowContext's token}
      * @param token the WindowToken to attach
@@ -881,7 +884,8 @@
      *
      * @see #attachWindowContextToDisplayArea(IBinder, int, int, Bundle)
      */
-    void attachWindowContextToWindowToken(IBinder clientToken, IBinder token);
+    void attachWindowContextToWindowToken(in IApplicationThread appThread, IBinder clientToken,
+            IBinder token);
 
     /**
      * Attaches a {@code clientToken} to associate with DisplayContent.
@@ -890,6 +894,7 @@
      * {@link android.window.WindowTokenClient#attachContext(Context)}
      * </p>
      *
+     * @param appThread the process that the window context is on.
      * @param clientToken {@link android.window.WindowContext#getWindowContextToken()
      * the WindowContext's token}
      * @param displayId The display associated with the window context
@@ -898,7 +903,8 @@
      * attached to the DisplayContent successfully. {@code null}, otherwise.
      * @throws android.view.WindowManager.InvalidDisplayException if the display ID is invalid
      */
-    Configuration attachToDisplayContent(IBinder clientToken, int displayId);
+    @nullable Configuration attachWindowContextToDisplayContent(in IApplicationThread appThread,
+            IBinder clientToken, int displayId);
 
     /**
      * Detaches {@link android.window.WindowContext} from the window manager node it's currently
@@ -906,7 +912,7 @@
      *
      * @param clientToken the window context's token
      */
-    void detachWindowContextFromWindowContainer(IBinder clientToken);
+    void detachWindowContext(IBinder clientToken);
 
     /**
      * Registers a listener, which is to be called whenever cross-window blur is enabled/disabled.
diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java
index 64f62c7..c843bbe 100644
--- a/core/java/android/view/InputEventSender.java
+++ b/core/java/android/view/InputEventSender.java
@@ -18,6 +18,7 @@
 
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.util.Log;
@@ -25,6 +26,10 @@
 import dalvik.system.CloseGuard;
 
 import java.lang.ref.WeakReference;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.RunnableFuture;
 
 /**
  * Provides a low-level mechanism for an application to send input events.
@@ -37,10 +42,10 @@
 
     private long mSenderPtr;
 
-    // We keep references to the input channel and message queue objects here so that
-    // they are not GC'd while the native peer of the receiver is using them.
+    // We keep references to the input channel and message queue objects (indirectly through
+    // Handler) here so that they are not GC'd while the native peer of the receiver is using them.
     private InputChannel mInputChannel;
-    private MessageQueue mMessageQueue;
+    private Handler mHandler;
 
     private static native long nativeInit(WeakReference<InputEventSender> sender,
             InputChannel inputChannel, MessageQueue messageQueue);
@@ -63,9 +68,9 @@
         }
 
         mInputChannel = inputChannel;
-        mMessageQueue = looper.getQueue();
+        mHandler = new Handler(looper);
         mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this),
-                mInputChannel, mMessageQueue);
+                mInputChannel, looper.getQueue());
 
         mCloseGuard.open("InputEventSender.dispose");
     }
@@ -98,8 +103,8 @@
             nativeDispose(mSenderPtr);
             mSenderPtr = 0;
         }
+        mHandler = null;
         mInputChannel = null;
-        mMessageQueue = null;
     }
 
     /**
@@ -122,8 +127,8 @@
     }
 
     /**
-     * Sends an input event.
-     * Must be called on the same Looper thread to which the sender is attached.
+     * Sends an input event. Can be called from any thread. Do not call this if the looper thread
+     * is blocked! It would cause a deadlock.
      *
      * @param seq The input event sequence number.
      * @param event The input event to send.
@@ -140,6 +145,28 @@
             return false;
         }
 
+        if (mHandler.getLooper().isCurrentThread()) {
+            return sendInputEventInternal(seq, event);
+        }
+        // This is being called on another thread. Post a runnable to the looper thread
+        // with the event injection, and wait until it's processed.
+        final RunnableFuture<Boolean> task = new FutureTask<>(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                return sendInputEventInternal(seq, event);
+            }
+        });
+        mHandler.post(task);
+        try {
+            return task.get();
+        } catch (InterruptedException exc) {
+            throw new IllegalStateException("Interrupted while sending " + event + ": " + exc);
+        } catch (ExecutionException exc) {
+            throw new IllegalStateException("Couldn't send " + event + ": " + exc);
+        }
+    }
+
+    private boolean sendInputEventInternal(int seq, InputEvent event) {
         if (event instanceof KeyEvent) {
             return nativeSendKeyEvent(mSenderPtr, seq, (KeyEvent)event);
         } else {
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 99a4f6b..6aa8506 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -34,6 +34,7 @@
 import android.util.Log;
 import android.view.inputmethod.InputMethodManager;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FastPrintWriter;
 
 import java.io.FileDescriptor;
@@ -184,6 +185,15 @@
         }
     }
 
+    /** Overrides the {@link #getWindowManagerService()} for test only. */
+    @VisibleForTesting
+    public static void overrideWindowManagerServiceForTesting(
+            @NonNull IWindowManager windowManager) {
+        synchronized (WindowManagerGlobal.class) {
+            sWindowManagerService = windowManager;
+        }
+    }
+
     @UnsupportedAppUsage
     public static IWindowSession getWindowSession() {
         synchronized (WindowManagerGlobal.class) {
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index ce2c180..467daa0 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -295,8 +295,8 @@
 
     @AnyThread
     static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
-            @Nullable ResultReceiver resultReceiver,
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            int lastClickToolType, @Nullable ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -312,7 +312,7 @@
 
     @AnyThread
     static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags,
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
             @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         final IInputMethodManager service = getService();
         if (service == null) {
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 6340388..5b4efd8 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -17,6 +17,7 @@
 package android.view.inputmethod;
 
 import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -36,6 +37,8 @@
 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -269,6 +272,14 @@
      */
     @MainThread
     public void revokeSession(InputMethodSession session);
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "SHOW_" }, value = {
+            SHOW_EXPLICIT,
+            SHOW_FORCED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ShowFlags {}
     
     /**
      * Flag for {@link #showSoftInput}: this show has been explicitly
@@ -292,8 +303,6 @@
     /**
      * Request that any soft input part of the input method be shown to the user.
      *
-     * @param flags Provides additional information about the show request.
-     * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
      * The result code should be
@@ -308,7 +317,7 @@
      * @hide
      */
     @MainThread
-    public default void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+    public default void showSoftInputWithToken(@ShowFlags int flags, ResultReceiver resultReceiver,
             IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
         showSoftInput(flags, resultReceiver);
     }
@@ -316,8 +325,6 @@
     /**
      * Request that any soft input part of the input method be shown to the user.
      * 
-     * @param flags Provides additional information about the show request.
-     * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
      * The result code should be
@@ -327,11 +334,12 @@
      * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
      */
     @MainThread
-    public void showSoftInput(int flags, ResultReceiver resultReceiver);
+    public void showSoftInput(@ShowFlags int flags, ResultReceiver resultReceiver);
 
     /**
      * Request that any soft input part of the input method be hidden from the user.
-     * @param flags Provides additional information about the show request.
+     *
+     * @param flags Provides additional information about the hide request.
      * Currently always 0.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
@@ -354,7 +362,8 @@
 
     /**
      * Request that any soft input part of the input method be hidden from the user.
-     * @param flags Provides additional information about the show request.
+     *
+     * @param flags Provides additional information about the hide request.
      * Currently always 0.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 48bf973..e4cc6d5 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -39,6 +39,7 @@
 import android.annotation.DisplayContext;
 import android.annotation.DrawableRes;
 import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
@@ -122,6 +123,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Proxy;
 import java.util.Arrays;
 import java.util.Collections;
@@ -2034,6 +2037,14 @@
         }
     }
 
+    /** @hide */
+    @IntDef(flag = true, prefix = { "SHOW_" }, value = {
+            SHOW_IMPLICIT,
+            SHOW_FORCED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ShowFlags {}
+
     /**
      * Flag for {@link #showSoftInput} to indicate that this is an implicit
      * request to show the input window, not as the result of a direct request
@@ -2065,10 +2076,8 @@
      *             {@link View#isFocused view focus}, and its containing window has
      *             {@link View#hasWindowFocus window focus}. Otherwise the call fails and
      *             returns {@code false}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #SHOW_IMPLICIT} bit set.
      */
-    public boolean showSoftInput(View view, int flags) {
+    public boolean showSoftInput(View view, @ShowFlags int flags) {
         // Re-dispatch if there is a context mismatch.
         final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
         if (fallbackImm != null) {
@@ -2131,21 +2140,20 @@
      *             {@link View#isFocused view focus}, and its containing window has
      *             {@link View#hasWindowFocus window focus}. Otherwise the call fails and
      *             returns {@code false}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #SHOW_IMPLICIT} bit set.
      * @param resultReceiver If non-null, this will be called by the IME when
      * it has processed your request to tell you what it has done.  The result
      * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
      * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
      * {@link #RESULT_HIDDEN}.
      */
-    public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
+    public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) {
         return showSoftInput(view, null /* statsToken */, flags, resultReceiver,
                 SoftInputShowHideReason.SHOW_SOFT_INPUT);
     }
 
-    private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+    private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken,
+            @ShowFlags int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         if (statsToken == null) {
             statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
                     Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
@@ -2199,7 +2207,7 @@
      */
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
-    public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
+    public void showSoftInputUnchecked(@ShowFlags int flags, ResultReceiver resultReceiver) {
         synchronized (mH) {
             final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow(
                     null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
@@ -2230,6 +2238,14 @@
         }
     }
 
+    /** @hide */
+    @IntDef(flag = true, prefix = { "HIDE_" }, value = {
+            HIDE_IMPLICIT_ONLY,
+            HIDE_NOT_ALWAYS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HideFlags {}
+
     /**
      * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)}
      * to indicate that the soft input window should only be hidden if it was not explicitly shown
@@ -2251,10 +2267,8 @@
      *
      * @param windowToken The token of the window that is making the request,
      * as returned by {@link View#getWindowToken() View.getWindowToken()}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
      */
-    public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
+    public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags) {
         return hideSoftInputFromWindow(windowToken, flags, null);
     }
 
@@ -2276,21 +2290,19 @@
      *
      * @param windowToken The token of the window that is making the request,
      * as returned by {@link View#getWindowToken() View.getWindowToken()}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
      * @param resultReceiver If non-null, this will be called by the IME when
      * it has processed your request to tell you what it has done.  The result
      * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
      * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
      * {@link #RESULT_HIDDEN}.
      */
-    public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+    public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
             ResultReceiver resultReceiver) {
         return hideSoftInputFromWindow(windowToken, flags, resultReceiver,
                 SoftInputShowHideReason.HIDE_SOFT_INPUT);
     }
 
-    private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+    private boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
                 null /* component */, Process.myUid(),
@@ -2493,12 +2505,6 @@
      * If not the input window will be displayed.
      * @param windowToken The token of the window that is making the request,
      * as returned by {@link View#getWindowToken() View.getWindowToken()}.
-     * @param showFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #SHOW_IMPLICIT},
-     * {@link #SHOW_FORCED} bit set.
-     * @param hideFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
-     * {@link #HIDE_NOT_ALWAYS} bit set.
      *
      * @deprecated Use {@link #showSoftInput(View, int)} or
      * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
@@ -2507,7 +2513,8 @@
      * has an effect if the calling app is the current IME focus.
      */
     @Deprecated
-    public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
+    public void toggleSoftInputFromWindow(IBinder windowToken, @ShowFlags int showFlags,
+            @HideFlags int hideFlags) {
         ImeTracing.getInstance().triggerClientDump(
                 "InputMethodManager#toggleSoftInputFromWindow", InputMethodManager.this,
                 null /* icProto */);
@@ -2525,12 +2532,6 @@
      *
      * If the input window is already displayed, it gets hidden.
      * If not the input window will be displayed.
-     * @param showFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #SHOW_IMPLICIT},
-     * {@link #SHOW_FORCED} bit set.
-     * @param hideFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
-     * {@link #HIDE_NOT_ALWAYS} bit set.
      *
      * @deprecated Use {@link #showSoftInput(View, int)} or
      * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
@@ -2539,7 +2540,7 @@
      * has an effect if the calling app is the current IME focus.
      */
     @Deprecated
-    public void toggleSoftInput(int showFlags, int hideFlags) {
+    public void toggleSoftInput(@ShowFlags int showFlags, @HideFlags int hideFlags) {
         ImeTracing.getInstance().triggerClientDump(
                 "InputMethodManager#toggleSoftInput", InputMethodManager.this,
                 null /* icProto */);
@@ -3552,15 +3553,12 @@
      * @param token Supplies the identifying token given to an input method
      * when it was started, which allows it to perform this operation on
      * itself.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
-     * {@link #HIDE_NOT_ALWAYS} bit set.
      * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was
      * intended for IME developers who should be accessing APIs through the service. APIs in this
      * class are intended for app developers interacting with the IME.
      */
     @Deprecated
-    public void hideSoftInputFromInputMethod(IBinder token, int flags) {
+    public void hideSoftInputFromInputMethod(IBinder token, @HideFlags int flags) {
         InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(
                 flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION);
     }
@@ -3574,15 +3572,12 @@
      * @param token Supplies the identifying token given to an input method
      * when it was started, which allows it to perform this operation on
      * itself.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #SHOW_IMPLICIT} or
-     * {@link #SHOW_FORCED} bit set.
      * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was
      * intended for IME developers who should be accessing APIs through the service. APIs in this
      * class are intended for app developers interacting with the IME.
      */
     @Deprecated
-    public void showSoftInputFromInputMethod(IBinder token, int flags) {
+    public void showSoftInputFromInputMethod(IBinder token, @ShowFlags int flags) {
         InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(flags);
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index af6af14..4f48cb6 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -169,12 +169,6 @@
     /**
      * Toggle the soft input window.
      * Applications can toggle the state of the soft input window.
-     * @param showFlags Provides additional operating flags.  May be
-     * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT},
-     * {@link InputMethodManager#SHOW_FORCED} bit set.
-     * @param hideFlags Provides additional operating flags.  May be
-     * 0 or have the {@link  InputMethodManager#HIDE_IMPLICIT_ONLY},
-     * {@link  InputMethodManager#HIDE_NOT_ALWAYS} bit set.
      *
      * @deprecated Starting in {@link android.os.Build.VERSION_CODES#S} the system no longer invokes
      * this method, instead it explicitly shows or hides the IME. An {@code InputMethodService}
@@ -182,7 +176,8 @@
      * InputMethodService#requestShowSelf} or {@link InputMethodService#requestHideSelf}
      */
     @Deprecated
-    public void toggleSoftInput(int showFlags, int hideFlags);
+    public void toggleSoftInput(@InputMethodManager.ShowFlags int showFlags,
+            @InputMethodManager.HideFlags int hideFlags);
 
     /**
      * This method is called when the cursor and/or the character position relevant to text input
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index e9d7b9b..a95748c 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -43,7 +43,6 @@
 import android.view.View;
 import android.view.ViewRootImpl;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -54,6 +53,7 @@
 import java.lang.ref.WeakReference;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
@@ -158,18 +158,13 @@
         boolean cancellable();
     }
 
-    @GuardedBy("mLock")
-    @Nullable
-    private InputConnection mInputConnection;
+    @NonNull
+    private final AtomicReference<InputConnection> mInputConnectionRef;
 
     @NonNull
     private final Looper mLooper;
     private final Handler mH;
 
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private boolean mFinished = false;
-
     private final InputMethodManager mParentInputMethodManager;
     private final WeakReference<View> mServedView;
 
@@ -185,7 +180,7 @@
     RemoteInputConnectionImpl(@NonNull Looper looper,
             @NonNull InputConnection inputConnection,
             @NonNull InputMethodManager inputMethodManager, @Nullable View servedView) {
-        mInputConnection = inputConnection;
+        mInputConnectionRef = new AtomicReference<>(inputConnection);
         mLooper = looper;
         mH = new Handler(mLooper);
         mParentInputMethodManager = inputMethodManager;
@@ -197,9 +192,7 @@
      */
     @Nullable
     public InputConnection getInputConnection() {
-        synchronized (mLock) {
-            return mInputConnection;
-        }
+        return mInputConnectionRef.get();
     }
 
     /**
@@ -215,9 +208,7 @@
      * {@link InputConnection#closeConnection()} as a result of {@link #deactivate()}.
      */
     private boolean isFinished() {
-        synchronized (mLock) {
-            return mFinished;
-        }
+        return mInputConnectionRef.get() == null;
     }
 
     private boolean isActive() {
@@ -386,10 +377,7 @@
                     // TODO(b/199934664): See if we can remove this by providing a default impl.
                 }
             } finally {
-                synchronized (mLock) {
-                    mInputConnection = null;
-                    mFinished = true;
-                }
+                mInputConnectionRef.set(null);
                 Trace.traceEnd(Trace.TRACE_TAG_INPUT);
             }
 
@@ -441,7 +429,6 @@
     public String toString() {
         return "RemoteInputConnectionImpl{"
                 + "connection=" + getInputConnection()
-                + " finished=" + isFinished()
                 + " mParentInputMethodManager.isActive()=" + mParentInputMethodManager.isActive()
                 + " mServedView=" + mServedView.get()
                 + "}";
@@ -455,16 +442,14 @@
      *                {@link DumpableInputConnection#dumpDebug(ProtoOutputStream, long)}.
      */
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        synchronized (mLock) {
-            // Check that the call is initiated in the target thread of the current InputConnection
-            // {@link InputConnection#getHandler} since the messages to IInputConnectionWrapper are
-            // executed on this thread. Otherwise the messages are dispatched to the correct thread
-            // in IInputConnectionWrapper, but this is not wanted while dumpng, for performance
-            // reasons.
-            if ((mInputConnection instanceof DumpableInputConnection)
-                    && mLooper.isCurrentThread()) {
-                ((DumpableInputConnection) mInputConnection).dumpDebug(proto, fieldId);
-            }
+        final InputConnection ic = mInputConnectionRef.get();
+        // Check that the call is initiated in the target thread of the current InputConnection
+        // {@link InputConnection#getHandler} since the messages to IInputConnectionWrapper are
+        // executed on this thread. Otherwise the messages are dispatched to the correct thread
+        // in IInputConnectionWrapper, but this is not wanted while dumping, for performance
+        // reasons.
+        if ((ic instanceof DumpableInputConnection) && mLooper.isCurrentThread()) {
+            ((DumpableInputConnection) ic).dumpDebug(proto, fieldId);
         }
     }
 
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 66aa66c..b0e5f777 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -786,6 +786,42 @@
         }
     }
 
+    /**
+     * @hide
+     * @return True if there is a change
+     */
+    public boolean replaceRemoteCollections(int viewId) {
+        boolean isActionReplaced = false;
+        if (mActions != null) {
+            for (int i = 0; i < mActions.size(); i++) {
+                Action action = mActions.get(i);
+                if (action instanceof SetRemoteCollectionItemListAdapterAction itemsAction
+                        && itemsAction.viewId == viewId
+                        && itemsAction.mServiceIntent != null) {
+                    mActions.set(i, new SetRemoteCollectionItemListAdapterAction(itemsAction.viewId,
+                            itemsAction.mServiceIntent));
+                    isActionReplaced = true;
+                } else if (action instanceof ViewGroupActionAdd groupAction
+                        && groupAction.mNestedViews != null) {
+                    isActionReplaced |= groupAction.mNestedViews.replaceRemoteCollections(viewId);
+                }
+            }
+        }
+        if (mSizedRemoteViews != null) {
+            for (int i = 0; i < mSizedRemoteViews.size(); i++) {
+                isActionReplaced |= mSizedRemoteViews.get(i).replaceRemoteCollections(viewId);
+            }
+        }
+        if (mLandscape != null) {
+            isActionReplaced |= mLandscape.replaceRemoteCollections(viewId);
+        }
+        if (mPortrait != null) {
+            isActionReplaced |= mPortrait.replaceRemoteCollections(viewId);
+        }
+
+        return isActionReplaced;
+    }
+
     private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
         if (icon != null && (icon.getType() == Icon.TYPE_URI
                 || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
@@ -1059,18 +1095,21 @@
 
     private class SetRemoteCollectionItemListAdapterAction extends Action {
         private @NonNull CompletableFuture<RemoteCollectionItems> mItemsFuture;
+        final Intent mServiceIntent;
 
         SetRemoteCollectionItemListAdapterAction(@IdRes int id,
                 @NonNull RemoteCollectionItems items) {
             viewId = id;
             items.setHierarchyRootData(getHierarchyRootData());
             mItemsFuture = CompletableFuture.completedFuture(items);
+            mServiceIntent = null;
         }
 
         SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) {
             viewId = id;
             mItemsFuture = getItemsFutureFromIntentWithTimeout(intent);
             setHierarchyRootData(getHierarchyRootData());
+            mServiceIntent = intent;
         }
 
         private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
@@ -1119,6 +1158,7 @@
             viewId = parcel.readInt();
             mItemsFuture = CompletableFuture.completedFuture(
                     new RemoteCollectionItems(parcel, getHierarchyRootData()));
+            mServiceIntent = parcel.readTypedObject(Intent.CREATOR);
         }
 
         @Override
@@ -1148,6 +1188,7 @@
             dest.writeInt(viewId);
             RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
             items.writeToParcel(dest, flags, /* attached= */ true);
+            dest.writeTypedObject(mServiceIntent, flags);
         }
 
         @Override
@@ -4768,9 +4809,7 @@
      */
     @Deprecated
     public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
-        if (AppGlobals.getIntCoreSetting(
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1) {
+        if (isAdapterConversionEnabled()) {
             addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
             return;
         }
@@ -4778,6 +4817,16 @@
     }
 
     /**
+     * @hide
+     * @return True if the remote adapter conversion is enabled
+     */
+    public static boolean isAdapterConversionEnabled() {
+        return AppGlobals.getIntCoreSetting(
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1;
+    }
+
+    /**
      * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
      * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
      * This is a simpler but less flexible approach to populating collection widgets. Its use is
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index e153bb7..43fa0be6 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -80,6 +80,14 @@
      */
     public static final int OP_TYPE_REORDER_TO_FRONT = 10;
 
+    /**
+     * Sets the activity navigation to be isolated, where the activity navigation on the
+     * TaskFragment is separated from the rest activities in the Task. Activities cannot be
+     * started on an isolated TaskFragment unless the activities are launched from the same
+     * TaskFragment or explicitly requested to.
+     */
+    public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -92,7 +100,8 @@
             OP_TYPE_SET_COMPANION_TASK_FRAGMENT,
             OP_TYPE_SET_ANIMATION_PARAMS,
             OP_TYPE_SET_RELATIVE_BOUNDS,
-            OP_TYPE_REORDER_TO_FRONT
+            OP_TYPE_REORDER_TO_FRONT,
+            OP_TYPE_SET_ISOLATED_NAVIGATION
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
@@ -118,11 +127,14 @@
     @Nullable
     private final TaskFragmentAnimationParams mAnimationParams;
 
+    private final boolean mIsolatedNav;
+
     private TaskFragmentOperation(@OperationType int opType,
             @Nullable TaskFragmentCreationParams taskFragmentCreationParams,
             @Nullable IBinder activityToken, @Nullable Intent activityIntent,
             @Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
-            @Nullable TaskFragmentAnimationParams animationParams) {
+            @Nullable TaskFragmentAnimationParams animationParams,
+            boolean isolatedNav) {
         mOpType = opType;
         mTaskFragmentCreationParams = taskFragmentCreationParams;
         mActivityToken = activityToken;
@@ -130,6 +142,7 @@
         mBundle = bundle;
         mSecondaryFragmentToken = secondaryFragmentToken;
         mAnimationParams = animationParams;
+        mIsolatedNav = isolatedNav;
     }
 
     private TaskFragmentOperation(Parcel in) {
@@ -140,6 +153,7 @@
         mBundle = in.readBundle(getClass().getClassLoader());
         mSecondaryFragmentToken = in.readStrongBinder();
         mAnimationParams = in.readTypedObject(TaskFragmentAnimationParams.CREATOR);
+        mIsolatedNav = in.readBoolean();
     }
 
     @Override
@@ -151,6 +165,7 @@
         dest.writeBundle(mBundle);
         dest.writeStrongBinder(mSecondaryFragmentToken);
         dest.writeTypedObject(mAnimationParams, flags);
+        dest.writeBoolean(mIsolatedNav);
     }
 
     @NonNull
@@ -223,6 +238,14 @@
         return mAnimationParams;
     }
 
+    /**
+     * Returns whether the activity navigation on this TaskFragment is isolated. This is only
+     * useful when the op type is {@link OP_TYPE_SET_ISOLATED_NAVIGATION}.
+     */
+    public boolean isIsolatedNav() {
+        return mIsolatedNav;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
@@ -245,6 +268,7 @@
         if (mAnimationParams != null) {
             sb.append(", animationParams=").append(mAnimationParams);
         }
+        sb.append(", isolatedNav=").append(mIsolatedNav);
 
         sb.append('}');
         return sb.toString();
@@ -253,7 +277,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
-                mBundle, mSecondaryFragmentToken, mAnimationParams);
+                mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav);
     }
 
     @Override
@@ -268,7 +292,8 @@
                 && Objects.equals(mActivityIntent, other.mActivityIntent)
                 && Objects.equals(mBundle, other.mBundle)
                 && Objects.equals(mSecondaryFragmentToken, other.mSecondaryFragmentToken)
-                && Objects.equals(mAnimationParams, other.mAnimationParams);
+                && Objects.equals(mAnimationParams, other.mAnimationParams)
+                && mIsolatedNav == other.mIsolatedNav;
     }
 
     @Override
@@ -300,6 +325,8 @@
         @Nullable
         private TaskFragmentAnimationParams mAnimationParams;
 
+        private boolean mIsolatedNav;
+
         /**
          * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
          */
@@ -363,12 +390,22 @@
         }
 
         /**
+         * Sets the activity navigation of this TaskFragment to be isolated.
+         */
+        @NonNull
+        public Builder setIsolatedNav(boolean isolatedNav) {
+            mIsolatedNav = isolatedNav;
+            return this;
+        }
+
+        /**
          * Constructs the {@link TaskFragmentOperation}.
          */
         @NonNull
         public TaskFragmentOperation build() {
             return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
-                    mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams);
+                    mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
+                    mIsolatedNav);
         }
     }
 }
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 4b9a957..e78056e 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.servertransaction.WindowTokenClientController;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -104,7 +105,8 @@
             throw new IllegalStateException("A Window Context can be only attached to "
                     + "a DisplayArea once.");
         }
-        mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options)
+        mAttachedToDisplayArea = WindowTokenClientController.getInstance().attachToDisplayArea(
+                mToken, type, displayId, options)
                 ? AttachStatus.STATUS_ATTACHED : AttachStatus.STATUS_FAILED;
         if (mAttachedToDisplayArea == AttachStatus.STATUS_FAILED) {
             Log.w(TAG, "attachToDisplayArea fail, type:" + type + ", displayId:"
@@ -133,20 +135,20 @@
      * {@link #attachToDisplayArea(int, int, Bundle)}.
      *
      * @see WindowProviderService#attachToWindowToken(IBinder))
-     * @see IWindowManager#attachWindowContextToWindowToken(IBinder, IBinder)
+     * @see IWindowManager#attachWindowContextToWindowToken
      */
     public void attachToWindowToken(IBinder windowToken) {
         if (mAttachedToDisplayArea != AttachStatus.STATUS_ATTACHED) {
             throw new IllegalStateException("The Window Context should have been attached"
                     + " to a DisplayArea. AttachToDisplayArea:" + mAttachedToDisplayArea);
         }
-        mToken.attachToWindowToken(windowToken);
+        WindowTokenClientController.getInstance().attachToWindowToken(mToken, windowToken);
     }
 
     /** Detaches the window context from the node it's currently associated with. */
     public void detachIfNeeded() {
         if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) {
-            mToken.detachFromWindowContainerIfNeeded();
+            WindowTokenClientController.getInstance().detachIfNeeded(mToken);
             mAttachedToDisplayArea = AttachStatus.STATUS_DETACHED;
             if (DEBUG_ATTACH) {
                 Log.d(TAG, "Detach Window Context.");
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index a208634..c7af25b 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -20,13 +20,12 @@
 import static android.window.ConfigurationHelper.shouldUpdateResources;
 
 import android.annotation.AnyThread;
-import android.annotation.BinderThread;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.app.IWindowToken;
 import android.app.ResourcesManager;
+import android.app.servertransaction.WindowTokenClientController;
 import android.content.Context;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
@@ -36,14 +35,9 @@
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.util.Log;
-import android.view.IWindowManager;
-import android.view.WindowManager.LayoutParams.WindowType;
-import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.lang.ref.WeakReference;
@@ -55,7 +49,7 @@
  * {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}.
  *
  * @see WindowContext
- * @see android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)
+ * @see android.view.IWindowManager#attachWindowContextToDisplayArea
  *
  * @hide
  */
@@ -70,15 +64,11 @@
 
     private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
 
-    private IWindowManager mWms;
-
     @GuardedBy("itself")
     private final Configuration mConfiguration = new Configuration();
 
     private boolean mShouldDumpConfigForIme;
 
-    private boolean mAttachToWindowContainer;
-
     private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
 
     /**
@@ -101,96 +91,15 @@
     }
 
     /**
-     * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}.
-     *
-     * @param type The window type of the {@link WindowContext}
-     * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
-     * @param options The window context launched option
-     * @return {@code true} if attaching successfully.
-     */
-    public boolean attachToDisplayArea(@WindowType int type, int displayId,
-            @Nullable Bundle options) {
-        try {
-            final Configuration configuration = getWindowManagerService()
-                    .attachWindowContextToDisplayArea(this, type, displayId, options);
-            if (configuration == null) {
-                return false;
-            }
-            onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
-            mAttachToWindowContainer = true;
-            return true;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}.
-     *
-     * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
-     * @return {@code true} if attaching successfully.
-     */
-    public boolean attachToDisplayContent(int displayId) {
-        final IWindowManager wms = getWindowManagerService();
-        // #createSystemUiContext may call this method before WindowManagerService is initialized.
-        if (wms == null) {
-            return false;
-        }
-        try {
-            final Configuration configuration = wms.attachToDisplayContent(this, displayId);
-            if (configuration == null) {
-                return false;
-            }
-            onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
-            mAttachToWindowContainer = true;
-            return true;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Attaches this {@link WindowTokenClient} to a {@code windowToken}.
-     *
-     * @param windowToken the window token to associated with
-     */
-    public void attachToWindowToken(IBinder windowToken) {
-        try {
-            getWindowManagerService().attachWindowContextToWindowToken(this, windowToken);
-            mAttachToWindowContainer = true;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */
-    public void detachFromWindowContainerIfNeeded() {
-        if (!mAttachToWindowContainer) {
-            return;
-        }
-        try {
-            getWindowManagerService().detachWindowContextFromWindowContainer(this);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private IWindowManager getWindowManagerService() {
-        if (mWms == null) {
-            mWms = WindowManagerGlobal.getWindowManagerService();
-        }
-        return mWms;
-    }
-
-    /**
      * Called when {@link Configuration} updates from the server side receive.
      *
      * @param newConfig the updated {@link Configuration}
      * @param newDisplayId the updated {@link android.view.Display} ID
      */
-    @BinderThread
+    @AnyThread
     @Override
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
+        // TODO(b/290876897): No need to post on mHandler after migrating to ClientTransaction
         mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig,
                 newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
     }
@@ -207,15 +116,14 @@
      * {@code shouldReportConfigChange} is {@code true}, which is usually from
      * {@link IWindowToken#onConfigurationChanged(Configuration, int)}
      * directly, while this method could be run on any thread if it is used to initialize
-     * Context's {@code Configuration} via {@link #attachToDisplayArea(int, int, Bundle)}
-     * or {@link #attachToDisplayContent(int)}.
+     * Context's {@code Configuration} via {@link WindowTokenClientController#attachToDisplayArea}
+     * or {@link WindowTokenClientController#attachToDisplayContent}.
      *
      * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration}
      *                                 should be dispatched to listeners.
      *
      */
     @AnyThread
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId,
             boolean shouldReportConfigChange) {
         final Context context = mContextRef.get();
@@ -280,9 +188,10 @@
         }
     }
 
-    @BinderThread
+    @AnyThread
     @Override
     public void onWindowTokenRemoved() {
+        // TODO(b/290876897): No need to post on mHandler after migrating to ClientTransaction
         mHandler.post(PooledLambda.obtainRunnable(
                 WindowTokenClient::onWindowTokenRemovedInner, this).recycleOnUse());
     }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 81cd280..10336bd 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -66,10 +66,6 @@
         public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
                 releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
 
-        /** Gating the ability for users to dismiss ongoing event notifications */
-        public static final Flag ALLOW_DISMISS_ONGOING =
-                releasedFlag("persist.sysui.notification.ongoing_dismissal");
-
         /** Gating the redaction of OTP notifications on the lockscreen */
         public static final Flag OTP_REDACTION =
                 devFlag("persist.sysui.notification.otp_redaction");
diff --git a/core/java/com/android/internal/content/om/TEST_MAPPING b/core/java/com/android/internal/content/om/TEST_MAPPING
index 98dadce7..ab3abb1 100644
--- a/core/java/com/android/internal/content/om/TEST_MAPPING
+++ b/core/java/com/android/internal/content/om/TEST_MAPPING
@@ -10,11 +10,9 @@
     },
     {
       "name": "SelfTargetingOverlayDeviceTests"
-    }
-  ],
-  "presubmit-large": [
+    },
     {
-      "name": "CtsContentTestCases",
+      "name": "CtsResourcesTestCases",
       "options": [
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 66e3333..30ebbe2 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -26,6 +26,7 @@
 import android.util.Log;
 import android.view.View;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.GuardedBy;
@@ -253,13 +254,11 @@
     /**
      * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)}
      *
-     * @param flags additional operating flags
      * @param reason the reason to hide soft input
-     * @see android.view.inputmethod.InputMethodManager#HIDE_IMPLICIT_ONLY
-     * @see android.view.inputmethod.InputMethodManager#HIDE_NOT_ALWAYS
      */
     @AnyThread
-    public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason) {
+    public void hideMySoftInput(@InputMethodManager.HideFlags int flags,
+            @SoftInputShowHideReason int reason) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
@@ -275,13 +274,9 @@
 
     /**
      * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int, AndroidFuture)}
-     *
-     * @param flags additional operating flags
-     * @see android.view.inputmethod.InputMethodManager#SHOW_IMPLICIT
-     * @see android.view.inputmethod.InputMethodManager#SHOW_FORCED
      */
     @AnyThread
-    public void showMySoftInput(int flags) {
+    public void showMySoftInput(@InputMethodManager.ShowFlags int flags) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
@@ -391,8 +386,12 @@
             @Nullable ImeTracker.Token statsToken) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
+            ImeTracker.forLogging().onFailed(statsToken,
+                    ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
             return;
         }
+        ImeTracker.forLogging().onProgress(statsToken,
+                ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
         try {
             ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible, statsToken);
         } catch (RemoteException e) {
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index c2cfcd6..66b0158 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -1565,6 +1565,13 @@
         mInStealthMode = ss.isInStealthMode();
     }
 
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        setSystemGestureExclusionRects(List.of(new Rect(left, top, right, bottom)));
+    }
+
     /**
      * The parecelable for saving and restoring a lock pattern view.
      */
diff --git a/core/jni/TEST_MAPPING b/core/jni/TEST_MAPPING
index 2844856..ea0b01e 100644
--- a/core/jni/TEST_MAPPING
+++ b/core/jni/TEST_MAPPING
@@ -15,11 +15,9 @@
     {
       "name": "SelfTargetingOverlayDeviceTests",
       "file_patterns": ["Overlay"]
-    }
-  ],
-  "presubmit-large": [
+    },
     {
-      "name": "CtsContentTestCases",
+      "name": "CtsResourcesTestCases",
       "options": [
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 979c9e3..1afae29 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -347,14 +347,23 @@
 }
 
 static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc,
-                                   jstring locale, jint orientation, jint touchscreen, jint density,
-                                   jint keyboard, jint keyboard_hidden, jint navigation,
-                                   jint screen_width, jint screen_height,
-                                   jint smallest_screen_width_dp, jint screen_width_dp,
-                                   jint screen_height_dp, jint screen_layout, jint ui_mode,
-                                   jint color_mode, jint grammatical_gender, jint major_version) {
+                                   jstring default_locale, jobjectArray locales, jint orientation,
+                                   jint touchscreen, jint density, jint keyboard,
+                                   jint keyboard_hidden, jint navigation, jint screen_width,
+                                   jint screen_height, jint smallest_screen_width_dp,
+                                   jint screen_width_dp, jint screen_height_dp, jint screen_layout,
+                                   jint ui_mode, jint color_mode, jint grammatical_gender,
+                                   jint major_version) {
   ATRACE_NAME("AssetManager::SetConfiguration");
 
+  const jsize locale_count = (locales == NULL) ? 0 : env->GetArrayLength(locales);
+
+  // Constants duplicated from Java class android.content.res.Configuration.
+  static const jint kScreenLayoutRoundMask = 0x300;
+  static const jint kScreenLayoutRoundShift = 8;
+
+  std::vector<ResTable_config> configs;
+
   ResTable_config configuration;
   memset(&configuration, 0, sizeof(configuration));
   configuration.mcc = static_cast<uint16_t>(mcc);
@@ -375,25 +384,37 @@
   configuration.colorMode = static_cast<uint8_t>(color_mode);
   configuration.grammaticalInflection = static_cast<uint8_t>(grammatical_gender);
   configuration.sdkVersion = static_cast<uint16_t>(major_version);
-
-  if (locale != nullptr) {
-    ScopedUtfChars locale_utf8(env, locale);
-    CHECK(locale_utf8.c_str() != nullptr);
-    configuration.setBcp47Locale(locale_utf8.c_str());
-  }
-
-  // Constants duplicated from Java class android.content.res.Configuration.
-  static const jint kScreenLayoutRoundMask = 0x300;
-  static const jint kScreenLayoutRoundShift = 8;
-
   // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
   // in C++. We must extract the round qualifier out of the Java screenLayout and put it
   // into screenLayout2.
   configuration.screenLayout2 =
-      static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
+          static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
+
+  if (locale_count > 0) {
+    configs.resize(locale_count, configuration);
+    for (int i = 0; i < locale_count; i++) {
+      jstring locale = (jstring)(env->GetObjectArrayElement(locales, i));
+      ScopedUtfChars locale_utf8(env, locale);
+      CHECK(locale_utf8.c_str() != nullptr);
+      configs[i].setBcp47Locale(locale_utf8.c_str());
+    }
+  } else {
+    configs.push_back(configuration);
+  }
+
+  uint32_t default_locale_int = 0;
+  if (default_locale != nullptr) {
+    ResTable_config config;
+    static_assert(std::is_same_v<decltype(config.locale), decltype(default_locale_int)>);
+    ScopedUtfChars locale_utf8(env, default_locale);
+    CHECK(locale_utf8.c_str() != nullptr);
+    config.setBcp47Locale(locale_utf8.c_str());
+    default_locale_int = config.locale;
+  }
 
   auto assetmanager = LockAndStartAssetManager(ptr);
-  assetmanager->SetConfiguration(configuration);
+  assetmanager->SetConfigurations(configs);
+  assetmanager->SetDefaultLocale(default_locale_int);
 }
 
 static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr,
@@ -1498,94 +1519,97 @@
 
 // JNI registration.
 static const JNINativeMethod gAssetManagerMethods[] = {
-    // AssetManager setup methods.
-    {"nativeCreate", "()J", (void*)NativeCreate},
-    {"nativeDestroy", "(J)V", (void*)NativeDestroy},
-    {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
-    {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIIII)V",
-     (void*)NativeSetConfiguration},
-    {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
-     (void*)NativeGetAssignedPackageIdentifiers},
+        // AssetManager setup methods.
+        {"nativeCreate", "()J", (void*)NativeCreate},
+        {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+        {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
+        {"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIII)V",
+         (void*)NativeSetConfiguration},
+        {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
+         (void*)NativeGetAssignedPackageIdentifiers},
 
-    // AssetManager file methods.
-    {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
-    {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
-    {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
-    {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
-     (void*)NativeOpenAssetFd},
-    {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
-    {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
-     (void*)NativeOpenNonAssetFd},
-    {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
-    {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
+        // AssetManager file methods.
+        {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
+        {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
+        {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
+        {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+         (void*)NativeOpenAssetFd},
+        {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
+        {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+         (void*)NativeOpenNonAssetFd},
+        {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+        {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
 
-    // AssetManager resource methods.
-    {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
-    {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
-     (void*)NativeGetResourceBagValue},
-    {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
-    {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
-     (void*)NativeGetResourceStringArray},
-    {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
-    {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
-    {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
-    {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
-    {"nativeGetParentThemeIdentifier", "(JI)I",
-     (void*)NativeGetParentThemeIdentifier},
+        // AssetManager resource methods.
+        {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I",
+         (void*)NativeGetResourceValue},
+        {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
+         (void*)NativeGetResourceBagValue},
+        {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
+        {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
+         (void*)NativeGetResourceStringArray},
+        {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
+        {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
+        {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
+        {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
+        {"nativeGetParentThemeIdentifier", "(JI)I", (void*)NativeGetParentThemeIdentifier},
 
-    // AssetManager resource name/ID methods.
-    {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
-     (void*)NativeGetResourceIdentifier},
-    {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
-    {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName},
-    {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
-    {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
-    {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
-     (void*) NativeSetResourceResolutionLoggingEnabled},
-    {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
-     (void*) NativeGetLastResourceResolution},
-    {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
-    {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
-     (void*)NativeGetSizeConfigurations},
-    {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
-     (void*)NativeGetSizeAndUiModeConfigurations},
+        // AssetManager resource name/ID methods.
+        {"nativeGetResourceIdentifier",
+         "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+         (void*)NativeGetResourceIdentifier},
+        {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
+        {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;",
+         (void*)NativeGetResourcePackageName},
+        {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
+        {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
+        {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
+         (void*)NativeSetResourceResolutionLoggingEnabled},
+        {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
+         (void*)NativeGetLastResourceResolution},
+        {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
+        {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
+         (void*)NativeGetSizeConfigurations},
+        {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
+         (void*)NativeGetSizeAndUiModeConfigurations},
 
-    // Style attribute related methods.
-    {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
-    {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
-    {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
-    {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
+        // Style attribute related methods.
+        {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
+        {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
+        {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
+        {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
 
-    // Theme related methods.
-    {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
-    {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
-    {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
-    {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
+        // Theme related methods.
+        {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
+        {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
+        {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
+        {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
 
-    {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
-    {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
-     (void*)NativeThemeGetAttributeValue},
-    {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
-    {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations},
+        {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
+        {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
+         (void*)NativeThemeGetAttributeValue},
+        {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
+        {"nativeThemeGetChangingConfigurations", "(J)I",
+         (void*)NativeThemeGetChangingConfigurations},
 
-    // AssetInputStream methods.
-    {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
-    {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
-    {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
-    {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
-    {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
-    {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
+        // AssetInputStream methods.
+        {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
+        {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
+        {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
+        {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
+        {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
+        {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
 
-    // System/idmap related methods.
-    {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
-     (void*)NativeGetOverlayableMap},
-    {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
-     (void*)NativeGetOverlayablesToString},
+        // System/idmap related methods.
+        {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
+         (void*)NativeGetOverlayableMap},
+        {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
+         (void*)NativeGetOverlayablesToString},
 
-    // Global management/debug methods.
-    {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
-    {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
-    {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
+        // Global management/debug methods.
+        {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
+        {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
+        {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
 };
 
 int register_android_content_AssetManager(JNIEnv* env) {
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 15270ef..061f669 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -18,10 +18,10 @@
 
 //#define LOG_NDEBUG 0
 
+#include <android-base/logging.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <input/InputTransport.h>
 #include <inttypes.h>
-#include <log/log.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedLocalRef.h>
 #include <utils/Looper.h>
@@ -91,7 +91,8 @@
         mMessageQueue(messageQueue),
         mNextPublishedSeq(1) {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Initializing input event sender.", getInputChannelName().c_str());
+        LOG(DEBUG) << "channel '" << getInputChannelName()
+                   << "' ~ Initializing input event sender.";
     }
 }
 
@@ -108,7 +109,7 @@
 
 void NativeInputEventSender::dispose() {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Disposing input event sender.", getInputChannelName().c_str());
+        LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender.";
     }
 
     mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
@@ -116,7 +117,7 @@
 
 status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Sending key event, seq=%u.", getInputChannelName().c_str(), seq);
+        LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Sending key event, seq=" << seq;
     }
 
     uint32_t publishedSeq = mNextPublishedSeq++;
@@ -128,8 +129,8 @@
                                             event->getMetaState(), event->getRepeatCount(),
                                             event->getDownTime(), event->getEventTime());
     if (status) {
-        ALOGW("Failed to send key event on channel '%s'.  status=%d",
-                getInputChannelName().c_str(), status);
+        LOG(WARNING) << "Failed to send key event on channel '" << getInputChannelName()
+                     << "'.  status=" << statusToString(status);
         return status;
     }
     mPublishedSeqMap.emplace(publishedSeq, seq);
@@ -138,7 +139,8 @@
 
 status_t NativeInputEventSender::sendMotionEvent(uint32_t seq, const MotionEvent* event) {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Sending motion event, seq=%u.", getInputChannelName().c_str(), seq);
+        LOG(DEBUG) << "channel '" << getInputChannelName()
+                   << "' ~ Sending motion event, seq=" << seq;
     }
 
     uint32_t publishedSeq;
@@ -162,8 +164,8 @@
                                                    event->getPointerProperties(),
                                                    event->getHistoricalRawPointerCoords(0, i));
         if (status) {
-            ALOGW("Failed to send motion event sample on channel '%s'.  status=%d",
-                    getInputChannelName().c_str(), status);
+            LOG(WARNING) << "Failed to send motion event sample on channel '"
+                         << getInputChannelName() << "'.  status=" << statusToString(status);
             return status;
         }
         // mPublishedSeqMap tracks all sequences published from this sender. Only the last
@@ -183,16 +185,18 @@
         // as part of finishing an IME session, in which case the publisher will
         // soon be disposed as well.
         if (kDebugDispatchCycle) {
-            ALOGD("channel '%s' ~ Consumer closed input channel or an error occurred.  events=0x%x",
-                  getInputChannelName().c_str(), events);
+            LOG(DEBUG) << "channel '" << getInputChannelName()
+                       << "' ~ Consumer closed input channel or an error occurred.  events=0x"
+                       << std::hex << events;
         }
 
         return 0; // remove the callback
     }
 
     if (!(events & ALOOPER_EVENT_INPUT)) {
-        ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  events=0x%x",
-              getInputChannelName().c_str(), events);
+        LOG(WARNING) << "channel '" << getInputChannelName()
+                     << "' ~ Received spurious callback for unhandled poll event.  events=0x"
+                     << std::hex << events;
         return 1;
     }
 
@@ -204,13 +208,13 @@
 
 status_t NativeInputEventSender::processConsumerResponse(JNIEnv* env) {
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName().c_str());
+        LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Receiving finished signals.";
     }
 
     ScopedLocalRef<jobject> senderObj(env, GetReferent(env, mSenderWeakGlobal));
     if (!senderObj.get()) {
-        ALOGW("channel '%s' ~ Sender object was finalized without being disposed.",
-              getInputChannelName().c_str());
+        LOG(WARNING) << "channel '" << getInputChannelName()
+                     << "' ~ Sender object was finalized without being disposed.";
         return DEAD_OBJECT;
     }
     bool skipCallbacks = false; // stop calling Java functions after an exception occurs
@@ -221,8 +225,9 @@
             if (status == WOULD_BLOCK) {
                 return OK;
             }
-            ALOGE("channel '%s' ~ Failed to process consumer response.  status=%d",
-                  getInputChannelName().c_str(), status);
+            LOG(ERROR) << "channel '" << getInputChannelName()
+                       << "' ~ Failed to process consumer response.  status="
+                       << statusToString(status);
             return status;
         }
 
@@ -250,24 +255,25 @@
         const InputPublisher::Timeline& timeline = std::get<InputPublisher::Timeline>(response);
 
         if (kDebugDispatchCycle) {
-            ALOGD("channel '%s' ~ Received timeline, inputEventId=%" PRId32
-                  ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
-                  getInputChannelName().c_str(), timeline.inputEventId,
-                  timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
-                  timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+            LOG(DEBUG) << "channel '" << getInputChannelName()
+                       << "' ~ Received timeline, inputEventId=" << timeline.inputEventId
+                       << ", gpuCompletedTime="
+                       << timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME]
+                       << ", presentTime="
+                       << timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
         }
 
         if (skipCallbacks) {
-            ALOGW("Java exception occurred. Skipping dispatchTimelineReported for "
-                  "inputEventId=%" PRId32,
-                  timeline.inputEventId);
+            LOG(WARNING) << "Java exception occurred. Skipping dispatchTimelineReported for "
+                            "inputEventId="
+                         << timeline.inputEventId;
             return true;
         }
 
         env->CallVoidMethod(sender, gInputEventSenderClassInfo.dispatchTimelineReported,
                             timeline.inputEventId, timeline.graphicsTimeline);
         if (env->ExceptionCheck()) {
-            ALOGE("Exception dispatching timeline, inputEventId=%" PRId32, timeline.inputEventId);
+            LOG(ERROR) << "Exception dispatching timeline, inputEventId=" << timeline.inputEventId;
             return false;
         }
 
@@ -279,7 +285,7 @@
 
     auto it = mPublishedSeqMap.find(finished.seq);
     if (it == mPublishedSeqMap.end()) {
-        ALOGW("Received 'finished' signal for unknown seq number = %" PRIu32, finished.seq);
+        LOG(WARNING) << "Received 'finished' signal for unknown seq number = " << finished.seq;
         // Since this is coming from the receiver (typically app), it's possible that an app
         // does something wrong and sends bad data. Just ignore and process other events.
         return true;
@@ -296,9 +302,9 @@
     const uint32_t seq = seqOptional.value();
 
     if (kDebugDispatchCycle) {
-        ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.",
-              getInputChannelName().c_str(), seq, finished.handled ? "true" : "false",
-              mPublishedSeqMap.size());
+        LOG(DEBUG) << "channel '" << getInputChannelName()
+                   << "' ~ Received finished signal, seq=" << seq << ", handled=" << std::boolalpha
+                   << finished.handled << ", pendingEvents=" << mPublishedSeqMap.size();
     }
     if (skipCallbacks) {
         return true;
@@ -307,7 +313,7 @@
     env->CallVoidMethod(sender, gInputEventSenderClassInfo.dispatchInputEventFinished,
                         static_cast<jint>(seq), static_cast<jboolean>(finished.handled));
     if (env->ExceptionCheck()) {
-        ALOGE("Exception dispatching finished signal for seq=%" PRIu32, seq);
+        LOG(ERROR) << "Exception dispatching finished signal for seq=" << seq;
         return false;
     }
     return true;
diff --git a/core/res/res/drawable-round-watch/progress_indeterminate_horizontal_material.xml b/core/res/res/drawable-round-watch/progress_indeterminate_horizontal_material.xml
new file mode 100644
index 0000000..bd77e59
--- /dev/null
+++ b/core/res/res/drawable-round-watch/progress_indeterminate_horizontal_material.xml
@@ -0,0 +1,944 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="983"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0.1"
+                    android:valueTo="0.1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="983"
+                    android:valueFrom="0.1"
+                    android:valueTo="0.1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="983"
+                    android:pathData="M 50,50C 50,50 50,50 50,50"
+                    android:propertyName="translateXY"
+                    android:propertyXName="translateX"
+                    android:propertyYName="translateY"
+                    android:startOffset="0">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:pathData="M 50,50C 50,50 50,50 50,50"
+                    android:propertyName="translateXY"
+                    android:propertyXName="translateX"
+                    android:propertyYName="translateY"
+                    android:startOffset="983">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_N_1_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="983"
+                    android:pathData="M 32,12C 32,12 32,12 32,12"
+                    android:propertyName="translateXY"
+                    android:propertyXName="translateX"
+                    android:propertyYName="translateY"
+                    android:startOffset="0">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:pathData="M 32,12C 32,12 32,12 32,12"
+                    android:propertyName="translateXY"
+                    android:propertyXName="translateX"
+                    android:propertyYName="translateY"
+                    android:startOffset="983">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="83"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="83"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.999,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="400"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="583"
+                    android:valueFrom="1"
+                    android:valueTo="0.0008500000000000001"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.201,0.967 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="983"
+                    android:valueFrom="0.0008500000000000001"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.329,0.663 0.662,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M-2053 -413 C-2064.2,-412.04 -2074.66,-410.29 -2084.48,-407.87 C-2094.29,-405.46 -2103.47,-402.36 -2112.09,-398.71 C-2120.71,-395.06 -2128.79,-390.85 -2136.41,-386.19 C-2144.02,-381.54 -2151.19,-376.43 -2158,-371 C-2168.19,-362.86 -2177.05,-354.51 -2184.8,-345.79 C-2192.55,-337.07 -2199.2,-327.99 -2204.98,-318.41 C-2210.77,-308.83 -2215.7,-298.74 -2220,-288 C-2237.22,-245.04 -2237.06,-202.14 -2226.93,-164.3 C-2216.8,-126.46 -2196.69,-93.69 -2174,-71 C-2150.2,-47.2 -2115.92,-28.38 -2077.59,-19.79 C-2039.27,-11.2 -1996.92,-12.85 -1957,-30 C-1925.35,-43.59 -1896.01,-64.58 -1873.88,-92.68 C-1851.74,-120.77 -1836.82,-155.98 -1834,-198 C-1831.53,-234.86 -1837.92,-266 -1849.1,-292.1 C-1860.29,-318.21 -1876.28,-339.28 -1893,-356 C-1910.36,-373.36 -1932.3,-389.15 -1958.94,-399.84 C-1985.57,-410.52 -2016.89,-416.09 -2053,-413c "
+                    android:valueTo="M-2053 -413 C-2072.13,-411.36 -2090.2,-406.86 -2106.82,-400.38 C-2123.44,-393.91 -2138.61,-385.46 -2151.93,-375.94 C-2165.26,-366.41 -2176.74,-355.8 -2186,-345 C-2200.2,-328.44 -2213.75,-307.38 -2222.86,-282.11 C-2231.96,-256.85 -2236.61,-227.38 -2233,-194 C-2229.85,-164.84 -2221.85,-140.07 -2210.02,-118.6 C-2198.19,-97.13 -2182.51,-78.96 -2164,-63 C-2146.03,-47.52 -2123.67,-34.03 -2098.23,-25.16 C-2072.79,-16.29 -2044.27,-12.03 -2014,-15 C-1985.05,-17.84 -1959.94,-25.74 -1938.13,-37.35 C-1916.32,-48.97 -1897.79,-64.3 -1882,-82 C-1866.83,-98.99 -1853.27,-120.85 -1844.28,-146.28 C-1835.29,-171.72 -1830.87,-200.72 -1834,-232 C-1836.98,-261.8 -1845.16,-287.41 -1856.88,-309.38 C-1868.6,-331.36 -1883.86,-349.71 -1901,-365 C-1917.51,-379.74 -1939.59,-393.1 -1965.5,-402.1 C-1991.42,-411.09 -2021.16,-415.73 -2053,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="17"
+                    android:valueFrom="M-2053 -413 C-2072.13,-411.36 -2090.2,-406.86 -2106.82,-400.38 C-2123.44,-393.91 -2138.61,-385.46 -2151.93,-375.94 C-2165.26,-366.41 -2176.74,-355.8 -2186,-345 C-2200.2,-328.44 -2213.75,-307.38 -2222.86,-282.11 C-2231.96,-256.85 -2236.61,-227.38 -2233,-194 C-2229.85,-164.84 -2221.85,-140.07 -2210.02,-118.6 C-2198.19,-97.13 -2182.51,-78.96 -2164,-63 C-2146.03,-47.52 -2123.67,-34.03 -2098.23,-25.16 C-2072.79,-16.29 -2044.27,-12.03 -2014,-15 C-1985.05,-17.84 -1959.94,-25.74 -1938.13,-37.35 C-1916.32,-48.97 -1897.79,-64.3 -1882,-82 C-1866.83,-98.99 -1853.27,-120.85 -1844.28,-146.28 C-1835.29,-171.72 -1830.87,-200.72 -1834,-232 C-1836.98,-261.8 -1845.16,-287.41 -1856.88,-309.38 C-1868.6,-331.36 -1883.86,-349.71 -1901,-365 C-1917.51,-379.74 -1939.59,-393.1 -1965.5,-402.1 C-1991.42,-411.09 -2021.16,-415.73 -2053,-413c "
+                    android:valueTo="M-2052 -413 C-2071.28,-411.42 -2089.49,-406.96 -2106.24,-400.51 C-2122.98,-394.05 -2138.26,-385.61 -2151.69,-376.07 C-2165.12,-366.52 -2176.68,-355.87 -2186,-345 C-2200.23,-328.39 -2213.89,-307.21 -2223.04,-281.77 C-2232.19,-256.33 -2236.82,-226.63 -2233,-193 C-2229.75,-164.4 -2221.68,-139.52 -2209.83,-117.85 C-2197.98,-96.18 -2182.35,-77.73 -2164,-62 C-2146.19,-46.74 -2123.18,-33.34 -2097.25,-24.47 C-2071.32,-15.59 -2042.48,-11.22 -2013,-14 C-1983.8,-16.75 -1958.61,-24.73 -1936.82,-36.53 C-1915.02,-48.33 -1896.62,-63.95 -1881,-82 C-1865.71,-99.67 -1851.88,-121.33 -1842.66,-146.49 C-1833.45,-171.65 -1828.84,-200.32 -1832,-232 C-1835.01,-262.26 -1843.42,-287.88 -1855.37,-309.62 C-1867.32,-331.36 -1882.81,-349.22 -1900,-364 C-1917.39,-378.94 -1938.96,-392.54 -1964.39,-401.73 C-1989.83,-410.92 -2019.14,-415.7 -2052,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="33"
+                    android:valueFrom="M-2052 -413 C-2071.28,-411.42 -2089.49,-406.96 -2106.24,-400.51 C-2122.98,-394.05 -2138.26,-385.61 -2151.69,-376.07 C-2165.12,-366.52 -2176.68,-355.87 -2186,-345 C-2200.23,-328.39 -2213.89,-307.21 -2223.04,-281.77 C-2232.19,-256.33 -2236.82,-226.63 -2233,-193 C-2229.75,-164.4 -2221.68,-139.52 -2209.83,-117.85 C-2197.98,-96.18 -2182.35,-77.73 -2164,-62 C-2146.19,-46.74 -2123.18,-33.34 -2097.25,-24.47 C-2071.32,-15.59 -2042.48,-11.22 -2013,-14 C-1983.8,-16.75 -1958.61,-24.73 -1936.82,-36.53 C-1915.02,-48.33 -1896.62,-63.95 -1881,-82 C-1865.71,-99.67 -1851.88,-121.33 -1842.66,-146.49 C-1833.45,-171.65 -1828.84,-200.32 -1832,-232 C-1835.01,-262.26 -1843.42,-287.88 -1855.37,-309.62 C-1867.32,-331.36 -1882.81,-349.22 -1900,-364 C-1917.39,-378.94 -1938.96,-392.54 -1964.39,-401.73 C-1989.83,-410.92 -2019.14,-415.7 -2052,-413c "
+                    android:valueTo="M-2052 -413 C-2071.66,-411.39 -2090,-406.88 -2106.76,-400.35 C-2123.51,-393.82 -2138.67,-385.28 -2151.97,-375.59 C-2165.26,-365.91 -2176.7,-355.08 -2186,-344 C-2200.29,-326.96 -2214.12,-305.92 -2223.33,-280.54 C-2232.55,-255.16 -2237.15,-225.42 -2233,-191 C-2229.56,-162.51 -2221.15,-137.78 -2208.93,-116.29 C-2196.71,-94.8 -2180.68,-76.54 -2162,-61 C-2143.85,-45.9 -2120.72,-32.5 -2094.69,-23.57 C-2068.65,-14.65 -2039.73,-10.19 -2010,-13 C-1981.12,-15.72 -1955.63,-23.67 -1933.48,-35.47 C-1911.33,-47.26 -1892.52,-62.9 -1877,-81 C-1861.92,-98.59 -1848.2,-120.71 -1839.11,-146.38 C-1830.02,-172.04 -1825.57,-201.24 -1829,-233 C-1832.23,-262.84 -1840.89,-288.53 -1853.04,-310.41 C-1865.19,-332.3 -1880.82,-350.37 -1898,-365 C-1915.02,-379.5 -1937.52,-392.81 -1963.76,-401.84 C-1990.01,-410.88 -2020,-415.63 -2052,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="50"
+                    android:valueFrom="M-2052 -413 C-2071.66,-411.39 -2090,-406.88 -2106.76,-400.35 C-2123.51,-393.82 -2138.67,-385.28 -2151.97,-375.59 C-2165.26,-365.91 -2176.7,-355.08 -2186,-344 C-2200.29,-326.96 -2214.12,-305.92 -2223.33,-280.54 C-2232.55,-255.16 -2237.15,-225.42 -2233,-191 C-2229.56,-162.51 -2221.15,-137.78 -2208.93,-116.29 C-2196.71,-94.8 -2180.68,-76.54 -2162,-61 C-2143.85,-45.9 -2120.72,-32.5 -2094.69,-23.57 C-2068.65,-14.65 -2039.73,-10.19 -2010,-13 C-1981.12,-15.72 -1955.63,-23.67 -1933.48,-35.47 C-1911.33,-47.26 -1892.52,-62.9 -1877,-81 C-1861.92,-98.59 -1848.2,-120.71 -1839.11,-146.38 C-1830.02,-172.04 -1825.57,-201.24 -1829,-233 C-1832.23,-262.84 -1840.89,-288.53 -1853.04,-310.41 C-1865.19,-332.3 -1880.82,-350.37 -1898,-365 C-1915.02,-379.5 -1937.52,-392.81 -1963.76,-401.84 C-1990.01,-410.88 -2020,-415.63 -2052,-413c "
+                    android:valueTo="M-2052 -413 C-2061.59,-412.21 -2070.55,-410.81 -2079.03,-408.89 C-2087.5,-406.97 -2095.48,-404.54 -2103.1,-401.69 C-2110.72,-398.84 -2117.98,-395.58 -2125,-392 C-2154.86,-376.79 -2181.38,-355.21 -2200.81,-326.98 C-2220.23,-298.75 -2232.55,-263.85 -2234,-222 C-2235.06,-191.38 -2230.21,-165.23 -2221.52,-142.55 C-2212.83,-119.88 -2200.3,-100.69 -2186,-84 C-2171.48,-67.06 -2154.35,-52.83 -2134.66,-41.45 C-2114.98,-30.08 -2092.74,-21.55 -2068,-16 C-2009.13,-2.8 -1958.21,-14.57 -1918.33,-39.75 C-1878.46,-64.93 -1849.64,-103.53 -1835,-144 C-1825.51,-170.22 -1822.73,-198.65 -1824.83,-225.69 C-1826.94,-252.72 -1833.93,-278.36 -1844,-299 C-1853.43,-318.34 -1865.87,-336.49 -1881.35,-352.36 C-1896.84,-368.23 -1915.37,-381.81 -1937,-392 C-1951.85,-398.99 -1969.2,-405.18 -1988.54,-409.15 C-2007.87,-413.12 -2029.2,-414.87 -2052,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="67"
+                    android:valueFrom="M-2052 -413 C-2061.59,-412.21 -2070.55,-410.81 -2079.03,-408.89 C-2087.5,-406.97 -2095.48,-404.54 -2103.1,-401.69 C-2110.72,-398.84 -2117.98,-395.58 -2125,-392 C-2154.86,-376.79 -2181.38,-355.21 -2200.81,-326.98 C-2220.23,-298.75 -2232.55,-263.85 -2234,-222 C-2235.06,-191.38 -2230.21,-165.23 -2221.52,-142.55 C-2212.83,-119.88 -2200.3,-100.69 -2186,-84 C-2171.48,-67.06 -2154.35,-52.83 -2134.66,-41.45 C-2114.98,-30.08 -2092.74,-21.55 -2068,-16 C-2009.13,-2.8 -1958.21,-14.57 -1918.33,-39.75 C-1878.46,-64.93 -1849.64,-103.53 -1835,-144 C-1825.51,-170.22 -1822.73,-198.65 -1824.83,-225.69 C-1826.94,-252.72 -1833.93,-278.36 -1844,-299 C-1853.43,-318.34 -1865.87,-336.49 -1881.35,-352.36 C-1896.84,-368.23 -1915.37,-381.81 -1937,-392 C-1951.85,-398.99 -1969.2,-405.18 -1988.54,-409.15 C-2007.87,-413.12 -2029.2,-414.87 -2052,-413c "
+                    android:valueTo="M-2052 -413 C-2063.16,-412.08 -2073.74,-410.37 -2083.77,-407.96 C-2093.79,-405.56 -2103.26,-402.47 -2112.17,-398.81 C-2121.07,-395.16 -2129.43,-390.93 -2137.23,-386.26 C-2145.03,-381.58 -2152.29,-376.46 -2159,-371 C-2169.4,-362.54 -2178.31,-353.88 -2186.05,-344.85 C-2193.79,-335.82 -2200.37,-326.42 -2206.08,-316.5 C-2211.8,-306.57 -2216.67,-296.13 -2221,-285 C-2238.37,-240.34 -2237.17,-196.9 -2225.73,-159.1 C-2214.28,-121.3 -2192.59,-89.13 -2169,-67 C-2142.29,-41.95 -2105.22,-23.59 -2065.35,-15.17 C-2025.49,-6.75 -1982.85,-8.27 -1945,-23 C-1912.4,-35.68 -1882.18,-56.87 -1859.35,-85.41 C-1836.52,-113.96 -1821.07,-149.86 -1818,-192 C-1815.28,-229.38 -1822.17,-262.27 -1834.17,-289.87 C-1846.17,-317.47 -1863.28,-339.78 -1881,-356 C-1899.69,-373.11 -1924.59,-388.83 -1953.74,-399.53 C-1982.89,-410.24 -2016.29,-415.93 -2052,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="83"
+                    android:valueFrom="M-2052 -413 C-2063.16,-412.08 -2073.74,-410.37 -2083.77,-407.96 C-2093.79,-405.56 -2103.26,-402.47 -2112.17,-398.81 C-2121.07,-395.16 -2129.43,-390.93 -2137.23,-386.26 C-2145.03,-381.58 -2152.29,-376.46 -2159,-371 C-2169.4,-362.54 -2178.31,-353.88 -2186.05,-344.85 C-2193.79,-335.82 -2200.37,-326.42 -2206.08,-316.5 C-2211.8,-306.57 -2216.67,-296.13 -2221,-285 C-2238.37,-240.34 -2237.17,-196.9 -2225.73,-159.1 C-2214.28,-121.3 -2192.59,-89.13 -2169,-67 C-2142.29,-41.95 -2105.22,-23.59 -2065.35,-15.17 C-2025.49,-6.75 -1982.85,-8.27 -1945,-23 C-1912.4,-35.68 -1882.18,-56.87 -1859.35,-85.41 C-1836.52,-113.96 -1821.07,-149.86 -1818,-192 C-1815.28,-229.38 -1822.17,-262.27 -1834.17,-289.87 C-1846.17,-317.47 -1863.28,-339.78 -1881,-356 C-1899.69,-373.11 -1924.59,-388.83 -1953.74,-399.53 C-1982.89,-410.24 -2016.29,-415.93 -2052,-413c "
+                    android:valueTo="M-2052 -413 C-2072.22,-411.34 -2091,-406.66 -2108.1,-399.87 C-2125.21,-393.08 -2140.66,-384.18 -2154.21,-374.07 C-2167.77,-363.95 -2179.44,-352.63 -2189,-341 C-2203.41,-323.47 -2216.63,-300.89 -2224.89,-274.11 C-2233.14,-247.32 -2236.44,-216.33 -2231,-182 C-2226.51,-153.63 -2217.1,-128.79 -2203.78,-107.33 C-2190.45,-85.88 -2173.19,-67.81 -2153,-53 C-2133.35,-38.58 -2108.08,-26.05 -2080.23,-17.76 C-2052.38,-9.48 -2021.96,-5.43 -1992,-8 C-1961.34,-10.63 -1935.39,-18.87 -1913.09,-31.22 C-1890.78,-43.56 -1872.12,-60 -1856,-79 C-1840.68,-97.06 -1827.09,-119.88 -1818.63,-146.65 C-1810.16,-173.42 -1806.82,-204.14 -1812,-238 C-1816.36,-266.51 -1826.56,-291.74 -1840.31,-313.35 C-1854.07,-334.96 -1871.39,-352.95 -1890,-367 C-1908.07,-380.64 -1932.92,-393.43 -1961.07,-402.16 C-1989.23,-410.89 -2020.69,-415.57 -2052,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="100"
+                    android:valueFrom="M-2052 -413 C-2072.22,-411.34 -2091,-406.66 -2108.1,-399.87 C-2125.21,-393.08 -2140.66,-384.18 -2154.21,-374.07 C-2167.77,-363.95 -2179.44,-352.63 -2189,-341 C-2203.41,-323.47 -2216.63,-300.89 -2224.89,-274.11 C-2233.14,-247.32 -2236.44,-216.33 -2231,-182 C-2226.51,-153.63 -2217.1,-128.79 -2203.78,-107.33 C-2190.45,-85.88 -2173.19,-67.81 -2153,-53 C-2133.35,-38.58 -2108.08,-26.05 -2080.23,-17.76 C-2052.38,-9.48 -2021.96,-5.43 -1992,-8 C-1961.34,-10.63 -1935.39,-18.87 -1913.09,-31.22 C-1890.78,-43.56 -1872.12,-60 -1856,-79 C-1840.68,-97.06 -1827.09,-119.88 -1818.63,-146.65 C-1810.16,-173.42 -1806.82,-204.14 -1812,-238 C-1816.36,-266.51 -1826.56,-291.74 -1840.31,-313.35 C-1854.07,-334.96 -1871.39,-352.95 -1890,-367 C-1908.07,-380.64 -1932.92,-393.43 -1961.07,-402.16 C-1989.23,-410.89 -2020.69,-415.57 -2052,-413c "
+                    android:valueTo="M-2052 -413 C-2083.49,-410.41 -2110.62,-400.99 -2133.57,-387.67 C-2156.51,-374.35 -2175.26,-357.14 -2190,-339 C-2204.11,-321.63 -2217.58,-298.34 -2225.92,-270.66 C-2234.26,-242.97 -2237.46,-210.91 -2231,-176 C-2223.12,-133.42 -2203.82,-99.93 -2176.63,-73.96 C-2149.45,-47.99 -2114.39,-29.53 -2075,-17 C-2060.09,-12.26 -2044.98,-8.59 -2029.65,-6.43 C-2014.33,-4.28 -1998.78,-3.65 -1983,-5 C-1951.72,-7.67 -1925.53,-16 -1903.13,-28.62 C-1880.73,-41.24 -1862.11,-58.16 -1846,-78 C-1830.98,-96.49 -1817.15,-119.48 -1808.74,-146.6 C-1800.33,-173.72 -1797.34,-204.98 -1804,-240 C-1809.39,-268.37 -1820.04,-293.46 -1834.27,-314.92 C-1848.5,-336.37 -1866.3,-354.19 -1886,-368 C-1896.13,-375.1 -1907.27,-380.86 -1919.42,-385.96 C-1931.58,-391.07 -1944.76,-395.51 -1959,-400 C-1987.83,-409.09 -2018.76,-415.73 -2052,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="117"
+                    android:valueFrom="M-2052 -413 C-2083.49,-410.41 -2110.62,-400.99 -2133.57,-387.67 C-2156.51,-374.35 -2175.26,-357.14 -2190,-339 C-2204.11,-321.63 -2217.58,-298.34 -2225.92,-270.66 C-2234.26,-242.97 -2237.46,-210.91 -2231,-176 C-2223.12,-133.42 -2203.82,-99.93 -2176.63,-73.96 C-2149.45,-47.99 -2114.39,-29.53 -2075,-17 C-2060.09,-12.26 -2044.98,-8.59 -2029.65,-6.43 C-2014.33,-4.28 -1998.78,-3.65 -1983,-5 C-1951.72,-7.67 -1925.53,-16 -1903.13,-28.62 C-1880.73,-41.24 -1862.11,-58.16 -1846,-78 C-1830.98,-96.49 -1817.15,-119.48 -1808.74,-146.6 C-1800.33,-173.72 -1797.34,-204.98 -1804,-240 C-1809.39,-268.37 -1820.04,-293.46 -1834.27,-314.92 C-1848.5,-336.37 -1866.3,-354.19 -1886,-368 C-1896.13,-375.1 -1907.27,-380.86 -1919.42,-385.96 C-1931.58,-391.07 -1944.76,-395.51 -1959,-400 C-1987.83,-409.09 -2018.76,-415.73 -2052,-413c "
+                    android:valueTo="M-2052 -413 C-2068.16,-411.67 -2083.44,-408.39 -2097.42,-403.83 C-2111.4,-399.27 -2124.07,-393.43 -2135,-387 C-2147.19,-379.82 -2157.4,-372.54 -2166.58,-364.44 C-2175.76,-356.34 -2183.92,-347.44 -2192,-337 C-2206.01,-318.91 -2219.24,-294.36 -2226.98,-265.73 C-2234.71,-237.1 -2236.95,-204.4 -2229,-170 C-2222.57,-142.18 -2211.58,-117.82 -2196.58,-96.96 C-2181.57,-76.11 -2162.54,-58.77 -2140,-45 C-2129.22,-38.41 -2117.43,-32.95 -2104.72,-27.98 C-2092.02,-23.02 -2078.4,-18.56 -2064,-14 C-2048.86,-9.2 -2033.94,-5.15 -2018.76,-2.7 C-2003.58,-0.26 -1988.15,0.6 -1972,-1 C-1909.86,-7.14 -1864.77,-35.48 -1833,-76 C-1803.02,-114.23 -1778.89,-172.33 -1794,-243 C-1806.16,-299.87 -1840.21,-341.67 -1882,-369 C-1902.73,-382.55 -1929.67,-390.89 -1958,-400 C-1986.85,-409.28 -2018.94,-415.71 -2052,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="133"
+                    android:valueFrom="M-2052 -413 C-2068.16,-411.67 -2083.44,-408.39 -2097.42,-403.83 C-2111.4,-399.27 -2124.07,-393.43 -2135,-387 C-2147.19,-379.82 -2157.4,-372.54 -2166.58,-364.44 C-2175.76,-356.34 -2183.92,-347.44 -2192,-337 C-2206.01,-318.91 -2219.24,-294.36 -2226.98,-265.73 C-2234.71,-237.1 -2236.95,-204.4 -2229,-170 C-2222.57,-142.18 -2211.58,-117.82 -2196.58,-96.96 C-2181.57,-76.11 -2162.54,-58.77 -2140,-45 C-2129.22,-38.41 -2117.43,-32.95 -2104.72,-27.98 C-2092.02,-23.02 -2078.4,-18.56 -2064,-14 C-2048.86,-9.2 -2033.94,-5.15 -2018.76,-2.7 C-2003.58,-0.26 -1988.15,0.6 -1972,-1 C-1909.86,-7.14 -1864.77,-35.48 -1833,-76 C-1803.02,-114.23 -1778.89,-172.33 -1794,-243 C-1806.16,-299.87 -1840.21,-341.67 -1882,-369 C-1902.73,-382.55 -1929.67,-390.89 -1958,-400 C-1986.85,-409.28 -2018.94,-415.71 -2052,-413c "
+                    android:valueTo="M-2052 -413 C-2068.7,-411.63 -2084.13,-408.26 -2098.16,-403.54 C-2112.2,-398.82 -2124.85,-392.76 -2136,-386 C-2147.98,-378.74 -2158.36,-371.57 -2167.81,-363.43 C-2177.26,-355.28 -2185.77,-346.16 -2194,-335 C-2207.5,-316.7 -2220.59,-290.87 -2228.01,-261.03 C-2235.43,-231.2 -2237.18,-197.35 -2228,-163 C-2220.91,-136.45 -2208.67,-112.16 -2192.46,-91.28 C-2176.24,-70.4 -2156.04,-52.92 -2133,-40 C-2121.53,-33.57 -2108.74,-28.52 -2095.21,-23.98 C-2081.68,-19.44 -2067.42,-15.41 -2053,-11 C-2036.87,-6.07 -2022.2,-1.69 -2007.14,1.02 C-1992.09,3.72 -1976.65,4.76 -1959,3 C-1895.61,-3.31 -1849.91,-32.53 -1818,-74 C-1787.82,-113.22 -1765.01,-174.6 -1783,-245 C-1797.41,-301.38 -1831.8,-344.78 -1876,-370 C-1898.81,-383.01 -1927.34,-389.73 -1956,-399 C-1985.38,-408.5 -2017.46,-415.84 -2052,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="150"
+                    android:valueFrom="M-2052 -413 C-2068.7,-411.63 -2084.13,-408.26 -2098.16,-403.54 C-2112.2,-398.82 -2124.85,-392.76 -2136,-386 C-2147.98,-378.74 -2158.36,-371.57 -2167.81,-363.43 C-2177.26,-355.28 -2185.77,-346.16 -2194,-335 C-2207.5,-316.7 -2220.59,-290.87 -2228.01,-261.03 C-2235.43,-231.2 -2237.18,-197.35 -2228,-163 C-2220.91,-136.45 -2208.67,-112.16 -2192.46,-91.28 C-2176.24,-70.4 -2156.04,-52.92 -2133,-40 C-2121.53,-33.57 -2108.74,-28.52 -2095.21,-23.98 C-2081.68,-19.44 -2067.42,-15.41 -2053,-11 C-2036.87,-6.07 -2022.2,-1.69 -2007.14,1.02 C-1992.09,3.72 -1976.65,4.76 -1959,3 C-1895.61,-3.31 -1849.91,-32.53 -1818,-74 C-1787.82,-113.22 -1765.01,-174.6 -1783,-245 C-1797.41,-301.38 -1831.8,-344.78 -1876,-370 C-1898.81,-383.01 -1927.34,-389.73 -1956,-399 C-1985.38,-408.5 -2017.46,-415.84 -2052,-413c "
+                    android:valueTo="M-2052 -413 C-2081.79,-410.55 -2107.78,-401.91 -2129.84,-389.82 C-2151.89,-377.73 -2169.99,-362.21 -2184,-346 C-2197.87,-329.95 -2211.62,-310.23 -2221.28,-286 C-2230.94,-261.78 -2236.5,-233.06 -2234,-199 C-2231.84,-169.67 -2223.83,-144.4 -2212.24,-122.59 C-2200.65,-100.79 -2185.48,-82.45 -2169,-67 C-2151.26,-50.36 -2130.31,-38.88 -2107.26,-29.54 C-2084.22,-20.21 -2059.09,-13.03 -2033,-5 C-2017.74,-0.3 -2004.84,3.33 -1991.59,5.6 C-1978.33,7.86 -1964.71,8.77 -1948,8 C-1905.51,6.05 -1868.9,-9.21 -1839.73,-31.86 C-1810.55,-54.51 -1788.79,-84.54 -1776,-116 C-1771.1,-128.04 -1767.01,-141.61 -1764.31,-155.76 C-1761.61,-169.9 -1760.31,-184.63 -1761,-199 C-1765.31,-289.18 -1818.08,-347.02 -1882,-375 C-1905.42,-385.25 -1933.09,-392.49 -1960,-401 C-1987.46,-409.69 -2016.78,-415.89 -2052,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="167"
+                    android:valueFrom="M-2052 -413 C-2081.79,-410.55 -2107.78,-401.91 -2129.84,-389.82 C-2151.89,-377.73 -2169.99,-362.21 -2184,-346 C-2197.87,-329.95 -2211.62,-310.23 -2221.28,-286 C-2230.94,-261.78 -2236.5,-233.06 -2234,-199 C-2231.84,-169.67 -2223.83,-144.4 -2212.24,-122.59 C-2200.65,-100.79 -2185.48,-82.45 -2169,-67 C-2151.26,-50.36 -2130.31,-38.88 -2107.26,-29.54 C-2084.22,-20.21 -2059.09,-13.03 -2033,-5 C-2017.74,-0.3 -2004.84,3.33 -1991.59,5.6 C-1978.33,7.86 -1964.71,8.77 -1948,8 C-1905.51,6.05 -1868.9,-9.21 -1839.73,-31.86 C-1810.55,-54.51 -1788.79,-84.54 -1776,-116 C-1771.1,-128.04 -1767.01,-141.61 -1764.31,-155.76 C-1761.61,-169.9 -1760.31,-184.63 -1761,-199 C-1765.31,-289.18 -1818.08,-347.02 -1882,-375 C-1905.42,-385.25 -1933.09,-392.49 -1960,-401 C-1987.46,-409.69 -2016.78,-415.89 -2052,-413c "
+                    android:valueTo="M-2052 -413 C-2069.88,-411.53 -2085.89,-407.88 -2100.3,-402.79 C-2114.71,-397.7 -2127.52,-391.19 -2139,-384 C-2162.8,-369.1 -2183.13,-350.89 -2198.84,-328.39 C-2214.56,-305.89 -2225.66,-279.09 -2231,-247 C-2234.03,-228.79 -2234.51,-211.38 -2232.87,-194.73 C-2231.22,-178.08 -2227.46,-162.19 -2222,-147 C-2212.49,-120.53 -2198.25,-96.52 -2179.64,-76.38 C-2161.02,-56.24 -2138.03,-39.97 -2111,-29 C-2097.39,-23.47 -2083.02,-18.98 -2068.36,-14.74 C-2053.7,-10.5 -2038.75,-6.51 -2024,-2 C-2009.05,2.58 -1994.19,7.3 -1978.25,10.39 C-1962.31,13.48 -1945.29,14.94 -1926,13 C-1858.97,6.24 -1812.71,-25.63 -1781,-71 C-1751.71,-112.9 -1731.01,-182.53 -1755,-252 C-1774.66,-308.93 -1813.12,-347.39 -1866,-370 C-1891.58,-380.93 -1922.29,-388.48 -1952,-398 C-1982.5,-407.77 -2015.77,-415.97 -2052,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="183"
+                    android:valueFrom="M-2052 -413 C-2069.88,-411.53 -2085.89,-407.88 -2100.3,-402.79 C-2114.71,-397.7 -2127.52,-391.19 -2139,-384 C-2162.8,-369.1 -2183.13,-350.89 -2198.84,-328.39 C-2214.56,-305.89 -2225.66,-279.09 -2231,-247 C-2234.03,-228.79 -2234.51,-211.38 -2232.87,-194.73 C-2231.22,-178.08 -2227.46,-162.19 -2222,-147 C-2212.49,-120.53 -2198.25,-96.52 -2179.64,-76.38 C-2161.02,-56.24 -2138.03,-39.97 -2111,-29 C-2097.39,-23.47 -2083.02,-18.98 -2068.36,-14.74 C-2053.7,-10.5 -2038.75,-6.51 -2024,-2 C-2009.05,2.58 -1994.19,7.3 -1978.25,10.39 C-1962.31,13.48 -1945.29,14.94 -1926,13 C-1858.97,6.24 -1812.71,-25.63 -1781,-71 C-1751.71,-112.9 -1731.01,-182.53 -1755,-252 C-1774.66,-308.93 -1813.12,-347.39 -1866,-370 C-1891.58,-380.93 -1922.29,-388.48 -1952,-398 C-1982.5,-407.77 -2015.77,-415.97 -2052,-413c "
+                    android:valueTo="M-2052 -413 C-2070.21,-411.5 -2086.7,-407.63 -2101.51,-402.32 C-2116.33,-397.01 -2129.47,-390.26 -2141,-383 C-2164.73,-368.06 -2185.58,-349.25 -2201.57,-325.58 C-2217.56,-301.91 -2228.69,-273.38 -2233,-239 C-2235.24,-221.1 -2234.79,-202.88 -2232.26,-185.49 C-2229.72,-168.1 -2225.1,-151.55 -2219,-137 C-2208.09,-110.99 -2192.21,-87.72 -2171.75,-68.46 C-2151.29,-49.2 -2126.25,-33.96 -2097,-24 C-2082.29,-18.99 -2067.22,-14.21 -2052.11,-9.58 C-2037.01,-4.94 -2021.86,-0.45 -2007,4 C-1992.53,8.33 -1976.87,12.99 -1960.04,16.12 C-1943.21,19.24 -1925.19,20.83 -1906,19 C-1837.8,12.51 -1789.31,-21.44 -1758,-68 C-1727.51,-113.34 -1711.25,-189.69 -1738,-256 C-1760.5,-311.77 -1803.61,-350.74 -1859,-370 C-1888.1,-380.12 -1918.72,-387.3 -1950,-397 C-1981.82,-406.86 -2013.69,-416.14 -2052,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="200"
+                    android:valueFrom="M-2052 -413 C-2070.21,-411.5 -2086.7,-407.63 -2101.51,-402.32 C-2116.33,-397.01 -2129.47,-390.26 -2141,-383 C-2164.73,-368.06 -2185.58,-349.25 -2201.57,-325.58 C-2217.56,-301.91 -2228.69,-273.38 -2233,-239 C-2235.24,-221.1 -2234.79,-202.88 -2232.26,-185.49 C-2229.72,-168.1 -2225.1,-151.55 -2219,-137 C-2208.09,-110.99 -2192.21,-87.72 -2171.75,-68.46 C-2151.29,-49.2 -2126.25,-33.96 -2097,-24 C-2082.29,-18.99 -2067.22,-14.21 -2052.11,-9.58 C-2037.01,-4.94 -2021.86,-0.45 -2007,4 C-1992.53,8.33 -1976.87,12.99 -1960.04,16.12 C-1943.21,19.24 -1925.19,20.83 -1906,19 C-1837.8,12.51 -1789.31,-21.44 -1758,-68 C-1727.51,-113.34 -1711.25,-189.69 -1738,-256 C-1760.5,-311.77 -1803.61,-350.74 -1859,-370 C-1888.1,-380.12 -1918.72,-387.3 -1950,-397 C-1981.82,-406.86 -2013.69,-416.14 -2052,-413c "
+                    android:valueTo="M-2048 -413 C-2066.96,-411.52 -2083.65,-407.87 -2098.63,-402.66 C-2113.62,-397.46 -2126.89,-390.7 -2139,-383 C-2163.5,-367.42 -2184.61,-348.88 -2200.66,-325.26 C-2216.71,-301.64 -2227.71,-272.93 -2232,-237 C-2234.19,-218.68 -2233.63,-200.16 -2230.93,-182.61 C-2228.24,-165.06 -2223.39,-148.47 -2217,-134 C-2205.27,-107.47 -2188.65,-84.32 -2167.4,-65.37 C-2146.15,-46.43 -2120.26,-31.69 -2090,-22 C-2075,-17.19 -2059.6,-12.47 -2044.27,-7.81 C-2028.95,-3.14 -2013.69,1.45 -1999,6 C-1983.7,10.73 -1967.89,16.01 -1951.43,20.09 C-1934.97,24.17 -1917.86,27.05 -1900,27 C-1823.07,26.78 -1770.47,-11.8 -1738,-57 C-1720.71,-81.07 -1708.33,-109.31 -1703,-145 C-1691.18,-224.09 -1728.76,-287.55 -1768,-323 C-1810.65,-361.52 -1879.25,-375.58 -1942,-395 C-1973.74,-404.82 -2008.85,-416.06 -2048,-413c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="217"
+                    android:valueFrom="M-2048 -413 C-2066.96,-411.52 -2083.65,-407.87 -2098.63,-402.66 C-2113.62,-397.46 -2126.89,-390.7 -2139,-383 C-2163.5,-367.42 -2184.61,-348.88 -2200.66,-325.26 C-2216.71,-301.64 -2227.71,-272.93 -2232,-237 C-2234.19,-218.68 -2233.63,-200.16 -2230.93,-182.61 C-2228.24,-165.06 -2223.39,-148.47 -2217,-134 C-2205.27,-107.47 -2188.65,-84.32 -2167.4,-65.37 C-2146.15,-46.43 -2120.26,-31.69 -2090,-22 C-2075,-17.19 -2059.6,-12.47 -2044.27,-7.81 C-2028.95,-3.14 -2013.69,1.45 -1999,6 C-1983.7,10.73 -1967.89,16.01 -1951.43,20.09 C-1934.97,24.17 -1917.86,27.05 -1900,27 C-1823.07,26.78 -1770.47,-11.8 -1738,-57 C-1720.71,-81.07 -1708.33,-109.31 -1703,-145 C-1691.18,-224.09 -1728.76,-287.55 -1768,-323 C-1810.65,-361.52 -1879.25,-375.58 -1942,-395 C-1973.74,-404.82 -2008.85,-416.06 -2048,-413c "
+                    android:valueTo="M-2044 -412 C-2063.62,-410.55 -2080.88,-406.72 -2096.49,-401.17 C-2112.09,-395.62 -2126.03,-388.34 -2139,-380 C-2164.03,-363.91 -2185.97,-343.3 -2202.06,-317.15 C-2218.15,-290.99 -2228.39,-259.29 -2230,-221 C-2231.65,-181.81 -2222.43,-147.58 -2207.28,-119.14 C-2192.13,-90.7 -2171.06,-68.05 -2149,-52 C-2136.68,-43.04 -2123.52,-36.2 -2109.14,-30.27 C-2094.77,-24.34 -2079.18,-19.32 -2062,-14 C-2046.23,-9.11 -2030.66,-4.02 -2014.94,1.05 C-1999.21,6.13 -1983.35,11.18 -1967,16 C-1950.6,20.83 -1933.88,26.02 -1916.12,29.66 C-1898.36,33.3 -1879.56,35.38 -1859,34 C-1785.73,29.09 -1734.33,-12.76 -1704,-61 C-1687.14,-87.82 -1677.54,-119.8 -1675,-157 C-1669.82,-232.93 -1712.56,-294.98 -1754,-326 C-1800.38,-360.72 -1874.96,-373.95 -1937,-393 C-1970.55,-403.3 -2003.97,-414.96 -2044,-412c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="233"
+                    android:valueFrom="M-2044 -412 C-2063.62,-410.55 -2080.88,-406.72 -2096.49,-401.17 C-2112.09,-395.62 -2126.03,-388.34 -2139,-380 C-2164.03,-363.91 -2185.97,-343.3 -2202.06,-317.15 C-2218.15,-290.99 -2228.39,-259.29 -2230,-221 C-2231.65,-181.81 -2222.43,-147.58 -2207.28,-119.14 C-2192.13,-90.7 -2171.06,-68.05 -2149,-52 C-2136.68,-43.04 -2123.52,-36.2 -2109.14,-30.27 C-2094.77,-24.34 -2079.18,-19.32 -2062,-14 C-2046.23,-9.11 -2030.66,-4.02 -2014.94,1.05 C-1999.21,6.13 -1983.35,11.18 -1967,16 C-1950.6,20.83 -1933.88,26.02 -1916.12,29.66 C-1898.36,33.3 -1879.56,35.38 -1859,34 C-1785.73,29.09 -1734.33,-12.76 -1704,-61 C-1687.14,-87.82 -1677.54,-119.8 -1675,-157 C-1669.82,-232.93 -1712.56,-294.98 -1754,-326 C-1800.38,-360.72 -1874.96,-373.95 -1937,-393 C-1970.55,-403.3 -2003.97,-414.96 -2044,-412c "
+                    android:valueTo="M-2043 -410 C-2062.47,-408.33 -2080.12,-404.05 -2096.08,-397.95 C-2112.04,-391.85 -2126.3,-383.94 -2139,-375 C-2163.84,-357.52 -2185.95,-334.8 -2201.41,-306.44 C-2216.88,-278.08 -2225.71,-244.07 -2224,-204 C-2222.32,-164.72 -2211.27,-131.77 -2194.29,-104.74 C-2177.32,-77.71 -2154.41,-56.59 -2129,-41 C-2115.9,-32.96 -2101.29,-26.6 -2085.64,-20.93 C-2069.99,-15.26 -2053.29,-10.28 -2036,-5 C-2019.94,-0.1 -2004.01,4.86 -1987.69,9.87 C-1971.37,14.87 -1954.65,19.92 -1937,25 C-1905.8,33.99 -1865.86,45.81 -1827,42 C-1752.62,34.71 -1698.64,-7.41 -1670,-59 C-1654,-87.82 -1644.06,-123.59 -1645,-163 C-1645.93,-201.81 -1658.75,-235.41 -1675,-262 C-1714,-325.81 -1760.88,-340.83 -1833,-362 C-1865.62,-371.58 -1898.36,-381.3 -1932,-392 C-1966.74,-403.05 -2001.4,-413.56 -2043,-410c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="250"
+                    android:valueFrom="M-2043 -410 C-2062.47,-408.33 -2080.12,-404.05 -2096.08,-397.95 C-2112.04,-391.85 -2126.3,-383.94 -2139,-375 C-2163.84,-357.52 -2185.95,-334.8 -2201.41,-306.44 C-2216.88,-278.08 -2225.71,-244.07 -2224,-204 C-2222.32,-164.72 -2211.27,-131.77 -2194.29,-104.74 C-2177.32,-77.71 -2154.41,-56.59 -2129,-41 C-2115.9,-32.96 -2101.29,-26.6 -2085.64,-20.93 C-2069.99,-15.26 -2053.29,-10.28 -2036,-5 C-2019.94,-0.1 -2004.01,4.86 -1987.69,9.87 C-1971.37,14.87 -1954.65,19.92 -1937,25 C-1905.8,33.99 -1865.86,45.81 -1827,42 C-1752.62,34.71 -1698.64,-7.41 -1670,-59 C-1654,-87.82 -1644.06,-123.59 -1645,-163 C-1645.93,-201.81 -1658.75,-235.41 -1675,-262 C-1714,-325.81 -1760.88,-340.83 -1833,-362 C-1865.62,-371.58 -1898.36,-381.3 -1932,-392 C-1966.74,-403.05 -2001.4,-413.56 -2043,-410c "
+                    android:valueTo="M-2028 -408 C-2048.69,-406.56 -2067.62,-402.42 -2084.53,-396.46 C-2101.45,-390.49 -2116.35,-382.71 -2129,-374 C-2153.83,-356.9 -2177.14,-333.29 -2193.47,-303.62 C-2209.79,-273.96 -2219.13,-238.26 -2216,-197 C-2212.99,-157.39 -2201.12,-124.14 -2183.05,-97.04 C-2164.99,-69.95 -2140.74,-49 -2113,-34 C-2099.07,-26.47 -2082.86,-20.28 -2065.99,-14.7 C-2049.12,-9.11 -2031.58,-4.13 -2015,1 C-1998.21,6.19 -1981.3,11.15 -1964.16,16.08 C-1947.03,21.01 -1929.67,25.91 -1912,31 C-1876.63,41.19 -1842.13,53.72 -1799,52 C-1720.79,48.87 -1665.28,1.83 -1636,-51 C-1619.77,-80.28 -1609.29,-118.72 -1612,-158 C-1617.51,-237.84 -1661.13,-292.87 -1714,-322 C-1743.39,-338.2 -1774.63,-344.74 -1813,-356 C-1847.11,-366.01 -1880.41,-376.12 -1915,-387 C-1949.7,-397.91 -1987.46,-410.81 -2028,-408c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="267"
+                    android:valueFrom="M-2028 -408 C-2048.69,-406.56 -2067.62,-402.42 -2084.53,-396.46 C-2101.45,-390.49 -2116.35,-382.71 -2129,-374 C-2153.83,-356.9 -2177.14,-333.29 -2193.47,-303.62 C-2209.79,-273.96 -2219.13,-238.26 -2216,-197 C-2212.99,-157.39 -2201.12,-124.14 -2183.05,-97.04 C-2164.99,-69.95 -2140.74,-49 -2113,-34 C-2099.07,-26.47 -2082.86,-20.28 -2065.99,-14.7 C-2049.12,-9.11 -2031.58,-4.13 -2015,1 C-1998.21,6.19 -1981.3,11.15 -1964.16,16.08 C-1947.03,21.01 -1929.67,25.91 -1912,31 C-1876.63,41.19 -1842.13,53.72 -1799,52 C-1720.79,48.87 -1665.28,1.83 -1636,-51 C-1619.77,-80.28 -1609.29,-118.72 -1612,-158 C-1617.51,-237.84 -1661.13,-292.87 -1714,-322 C-1743.39,-338.2 -1774.63,-344.74 -1813,-356 C-1847.11,-366.01 -1880.41,-376.12 -1915,-387 C-1949.7,-397.91 -1987.46,-410.81 -2028,-408c "
+                    android:valueTo="M-2009 -405 C-2051.87,-404.67 -2087.83,-392.14 -2116.71,-372.95 C-2145.6,-353.77 -2167.42,-327.93 -2182,-301 C-2190.39,-285.5 -2196.97,-267.54 -2201.03,-248.43 C-2205.1,-229.32 -2206.65,-209.07 -2205,-189 C-2201.67,-148.43 -2188.4,-114.91 -2168.6,-87.92 C-2148.8,-60.93 -2122.46,-40.46 -2093,-26 C-2077.81,-18.55 -2060.68,-12.37 -2043.04,-6.78 C-2025.39,-1.18 -2007.24,3.84 -1990,9 C-1972.83,14.14 -1955.59,19.23 -1938.01,24.37 C-1920.43,29.51 -1902.51,34.69 -1884,40 C-1848.91,50.07 -1808.39,64.36 -1769,63 C-1685.56,60.11 -1628.04,14.47 -1598,-41 C-1581.7,-71.11 -1570.85,-111.53 -1574,-152 C-1580.19,-231.59 -1628.54,-288.29 -1685,-316 C-1717.08,-331.74 -1751.19,-337.82 -1790,-349 C-1827.01,-359.66 -1860.79,-370.76 -1895,-381 C-1930.29,-391.56 -1967.46,-405.32 -2009,-405c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="283"
+                    android:valueFrom="M-2009 -405 C-2051.87,-404.67 -2087.83,-392.14 -2116.71,-372.95 C-2145.6,-353.77 -2167.42,-327.93 -2182,-301 C-2190.39,-285.5 -2196.97,-267.54 -2201.03,-248.43 C-2205.1,-229.32 -2206.65,-209.07 -2205,-189 C-2201.67,-148.43 -2188.4,-114.91 -2168.6,-87.92 C-2148.8,-60.93 -2122.46,-40.46 -2093,-26 C-2077.81,-18.55 -2060.68,-12.37 -2043.04,-6.78 C-2025.39,-1.18 -2007.24,3.84 -1990,9 C-1972.83,14.14 -1955.59,19.23 -1938.01,24.37 C-1920.43,29.51 -1902.51,34.69 -1884,40 C-1848.91,50.07 -1808.39,64.36 -1769,63 C-1685.56,60.11 -1628.04,14.47 -1598,-41 C-1581.7,-71.11 -1570.85,-111.53 -1574,-152 C-1580.19,-231.59 -1628.54,-288.29 -1685,-316 C-1717.08,-331.74 -1751.19,-337.82 -1790,-349 C-1827.01,-359.66 -1860.79,-370.76 -1895,-381 C-1930.29,-391.56 -1967.46,-405.32 -2009,-405c "
+                    android:valueTo="M-2011 -400 C-2053.42,-396.52 -2087.52,-381.94 -2114.34,-361.15 C-2141.17,-340.36 -2160.71,-313.35 -2174,-285 C-2181.72,-268.54 -2187.51,-250.33 -2190.47,-230.84 C-2193.44,-211.34 -2193.58,-190.57 -2190,-169 C-2183.7,-131.08 -2168.49,-98.81 -2147.08,-72.91 C-2125.67,-47.01 -2098.06,-27.47 -2067,-15 C-2050.55,-8.4 -2033.29,-3.15 -2015.51,1.83 C-1997.74,6.81 -1979.46,11.51 -1961,17 C-1926.67,27.21 -1890.41,37.86 -1854,48 C-1817.75,58.1 -1779.46,72.79 -1741,74 C-1651.6,76.8 -1593.42,34.23 -1560,-21 C-1542.31,-50.24 -1529.38,-88.39 -1530,-129 C-1531.26,-212.05 -1581.77,-272.22 -1635,-301 C-1665.71,-317.6 -1706.86,-326.2 -1739,-335 C-1775.31,-344.94 -1811.03,-355.54 -1846,-366 C-1882.13,-376.81 -1917.72,-389.72 -1954,-397 C-1969.82,-400.17 -1990.77,-401.66 -2011,-400c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="300"
+                    android:valueFrom="M-2011 -400 C-2053.42,-396.52 -2087.52,-381.94 -2114.34,-361.15 C-2141.17,-340.36 -2160.71,-313.35 -2174,-285 C-2181.72,-268.54 -2187.51,-250.33 -2190.47,-230.84 C-2193.44,-211.34 -2193.58,-190.57 -2190,-169 C-2183.7,-131.08 -2168.49,-98.81 -2147.08,-72.91 C-2125.67,-47.01 -2098.06,-27.47 -2067,-15 C-2050.55,-8.4 -2033.29,-3.15 -2015.51,1.83 C-1997.74,6.81 -1979.46,11.51 -1961,17 C-1926.67,27.21 -1890.41,37.86 -1854,48 C-1817.75,58.1 -1779.46,72.79 -1741,74 C-1651.6,76.8 -1593.42,34.23 -1560,-21 C-1542.31,-50.24 -1529.38,-88.39 -1530,-129 C-1531.26,-212.05 -1581.77,-272.22 -1635,-301 C-1665.71,-317.6 -1706.86,-326.2 -1739,-335 C-1775.31,-344.94 -1811.03,-355.54 -1846,-366 C-1882.13,-376.81 -1917.72,-389.72 -1954,-397 C-1969.82,-400.17 -1990.77,-401.66 -2011,-400c "
+                    android:valueTo="M-1993 -395 C-2035.52,-391.51 -2071.23,-376.75 -2099.59,-354.5 C-2127.94,-332.24 -2148.93,-302.49 -2162,-269 C-2168.66,-251.95 -2173.35,-230.87 -2174.9,-208.84 C-2176.46,-186.81 -2174.88,-163.83 -2169,-143 C-2158.02,-104.12 -2139.35,-74.22 -2113.89,-51.11 C-2088.42,-28.01 -2056.16,-11.7 -2018,0 C-1979.52,11.8 -1942.29,22.92 -1904.79,33.78 C-1867.3,44.64 -1829.54,55.24 -1790,66 C-1770.25,71.37 -1750.38,77.31 -1729.76,81.43 C-1709.14,85.56 -1687.76,87.87 -1665,86 C-1622.2,82.49 -1586.43,67.71 -1558.12,45.7 C-1529.81,23.68 -1508.96,-5.56 -1496,-38 C-1488.42,-56.98 -1484.03,-78.42 -1482.85,-100.14 C-1481.68,-121.87 -1483.72,-143.88 -1489,-164 C-1508.99,-240.16 -1563.51,-287.94 -1639,-308 C-1716.8,-328.67 -1792.02,-350.18 -1868,-373 C-1906.44,-384.54 -1946.52,-398.82 -1993,-395c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="317"
+                    android:valueFrom="M-1993 -395 C-2035.52,-391.51 -2071.23,-376.75 -2099.59,-354.5 C-2127.94,-332.24 -2148.93,-302.49 -2162,-269 C-2168.66,-251.95 -2173.35,-230.87 -2174.9,-208.84 C-2176.46,-186.81 -2174.88,-163.83 -2169,-143 C-2158.02,-104.12 -2139.35,-74.22 -2113.89,-51.11 C-2088.42,-28.01 -2056.16,-11.7 -2018,0 C-1979.52,11.8 -1942.29,22.92 -1904.79,33.78 C-1867.3,44.64 -1829.54,55.24 -1790,66 C-1770.25,71.37 -1750.38,77.31 -1729.76,81.43 C-1709.14,85.56 -1687.76,87.87 -1665,86 C-1622.2,82.49 -1586.43,67.71 -1558.12,45.7 C-1529.81,23.68 -1508.96,-5.56 -1496,-38 C-1488.42,-56.98 -1484.03,-78.42 -1482.85,-100.14 C-1481.68,-121.87 -1483.72,-143.88 -1489,-164 C-1508.99,-240.16 -1563.51,-287.94 -1639,-308 C-1716.8,-328.67 -1792.02,-350.18 -1868,-373 C-1906.44,-384.54 -1946.52,-398.82 -1993,-395c "
+                    android:valueTo="M-1972 -389 C-2015.53,-385.6 -2052.71,-370.1 -2082.05,-346.58 C-2111.38,-323.06 -2132.86,-291.51 -2145,-256 C-2148.29,-246.37 -2151.04,-236.62 -2152.97,-226.03 C-2154.89,-215.43 -2156,-204 -2156,-191 C-2156,-154.4 -2147.68,-123.61 -2134.39,-97.87 C-2121.1,-72.13 -2102.85,-51.43 -2083,-35 C-2068.94,-23.36 -2053.07,-14.7 -2035.52,-7.4 C-2017.98,-0.11 -1998.76,5.83 -1978,12 C-1938.06,23.88 -1899,35.2 -1859.7,46.27 C-1820.4,57.33 -1780.87,68.14 -1740,79 C-1699.56,89.74 -1657.14,104.77 -1609,101 C-1522.04,94.2 -1462.2,36.39 -1438,-32 C-1423.14,-74 -1423.94,-121.55 -1437,-161 C-1449.47,-198.68 -1467.69,-227.4 -1498,-253 C-1526.28,-276.89 -1561.4,-288.27 -1604,-299 C-1684.97,-319.39 -1764.33,-341.91 -1843,-365 C-1883.1,-376.77 -1923.46,-392.79 -1972,-389c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="333"
+                    android:valueFrom="M-1972 -389 C-2015.53,-385.6 -2052.71,-370.1 -2082.05,-346.58 C-2111.38,-323.06 -2132.86,-291.51 -2145,-256 C-2148.29,-246.37 -2151.04,-236.62 -2152.97,-226.03 C-2154.89,-215.43 -2156,-204 -2156,-191 C-2156,-154.4 -2147.68,-123.61 -2134.39,-97.87 C-2121.1,-72.13 -2102.85,-51.43 -2083,-35 C-2068.94,-23.36 -2053.07,-14.7 -2035.52,-7.4 C-2017.98,-0.11 -1998.76,5.83 -1978,12 C-1938.06,23.88 -1899,35.2 -1859.7,46.27 C-1820.4,57.33 -1780.87,68.14 -1740,79 C-1699.56,89.74 -1657.14,104.77 -1609,101 C-1522.04,94.2 -1462.2,36.39 -1438,-32 C-1423.14,-74 -1423.94,-121.55 -1437,-161 C-1449.47,-198.68 -1467.69,-227.4 -1498,-253 C-1526.28,-276.89 -1561.4,-288.27 -1604,-299 C-1684.97,-319.39 -1764.33,-341.91 -1843,-365 C-1883.1,-376.77 -1923.46,-392.79 -1972,-389c "
+                    android:valueTo="M-1947 -382 C-1992.63,-378.43 -2031.23,-362.21 -2061.25,-337.24 C-2091.27,-312.27 -2112.7,-278.55 -2124,-240 C-2138.21,-191.5 -2133.14,-145.62 -2117.43,-107.06 C-2101.71,-68.5 -2075.36,-37.25 -2047,-18 C-2031.95,-7.78 -2013.76,0.26 -1994.19,7.23 C-1974.61,14.19 -1953.63,20.08 -1933,26 C-1891.74,37.84 -1850.85,49.22 -1809.47,60.33 C-1768.09,71.44 -1726.22,82.27 -1683,93 C-1662.04,98.2 -1640.37,104.9 -1618.02,109.82 C-1595.68,114.74 -1572.65,117.89 -1549,116 C-1503.84,112.4 -1465.18,96.01 -1435.01,71.21 C-1404.85,46.41 -1383.17,13.21 -1372,-24 C-1358.2,-69.97 -1362.29,-119.5 -1378,-158 C-1393.11,-195.04 -1414.99,-223.76 -1448,-247 C-1479.65,-269.28 -1518.77,-277.61 -1564,-289 C-1647.07,-309.92 -1731.94,-331.77 -1814,-356 C-1855.02,-368.11 -1897.6,-385.86 -1947,-382c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="350"
+                    android:valueFrom="M-1947 -382 C-1992.63,-378.43 -2031.23,-362.21 -2061.25,-337.24 C-2091.27,-312.27 -2112.7,-278.55 -2124,-240 C-2138.21,-191.5 -2133.14,-145.62 -2117.43,-107.06 C-2101.71,-68.5 -2075.36,-37.25 -2047,-18 C-2031.95,-7.78 -2013.76,0.26 -1994.19,7.23 C-1974.61,14.19 -1953.63,20.08 -1933,26 C-1891.74,37.84 -1850.85,49.22 -1809.47,60.33 C-1768.09,71.44 -1726.22,82.27 -1683,93 C-1662.04,98.2 -1640.37,104.9 -1618.02,109.82 C-1595.68,114.74 -1572.65,117.89 -1549,116 C-1503.84,112.4 -1465.18,96.01 -1435.01,71.21 C-1404.85,46.41 -1383.17,13.21 -1372,-24 C-1358.2,-69.97 -1362.29,-119.5 -1378,-158 C-1393.11,-195.04 -1414.99,-223.76 -1448,-247 C-1479.65,-269.28 -1518.77,-277.61 -1564,-289 C-1647.07,-309.92 -1731.94,-331.77 -1814,-356 C-1855.02,-368.11 -1897.6,-385.86 -1947,-382c "
+                    android:valueTo="M-1915 -374 C-1963.11,-370.66 -2003.62,-353.89 -2034.73,-327.71 C-2065.84,-301.53 -2087.53,-265.95 -2098,-225 C-2111.65,-171.6 -2104.14,-125.11 -2085.35,-87.34 C-2066.56,-49.57 -2036.48,-20.52 -2005,-2 C-1987.6,8.23 -1967.69,15.77 -1946.7,22.23 C-1925.72,28.68 -1903.67,34.07 -1882,40 C-1828.45,54.66 -1774.32,69.41 -1719.51,83.62 C-1664.71,97.83 -1609.24,111.5 -1553,124 C-1540.64,126.75 -1528.83,129.45 -1517.02,131.24 C-1505.2,133.02 -1493.38,133.9 -1481,133 C-1431.84,129.42 -1391.55,112.94 -1360.82,87.22 C-1330.09,61.5 -1308.92,26.54 -1298,-14 C-1284.79,-63.02 -1292.08,-115.37 -1310,-153 C-1326.77,-188.23 -1354.33,-219.1 -1389,-239 C-1423.4,-258.74 -1470.69,-267.55 -1515,-278 C-1603.39,-298.85 -1690.13,-322.03 -1777,-346 C-1817.41,-357.15 -1865.58,-377.43 -1915,-374c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="367"
+                    android:valueFrom="M-1915 -374 C-1963.11,-370.66 -2003.62,-353.89 -2034.73,-327.71 C-2065.84,-301.53 -2087.53,-265.95 -2098,-225 C-2111.65,-171.6 -2104.14,-125.11 -2085.35,-87.34 C-2066.56,-49.57 -2036.48,-20.52 -2005,-2 C-1987.6,8.23 -1967.69,15.77 -1946.7,22.23 C-1925.72,28.68 -1903.67,34.07 -1882,40 C-1828.45,54.66 -1774.32,69.41 -1719.51,83.62 C-1664.71,97.83 -1609.24,111.5 -1553,124 C-1540.64,126.75 -1528.83,129.45 -1517.02,131.24 C-1505.2,133.02 -1493.38,133.9 -1481,133 C-1431.84,129.42 -1391.55,112.94 -1360.82,87.22 C-1330.09,61.5 -1308.92,26.54 -1298,-14 C-1284.79,-63.02 -1292.08,-115.37 -1310,-153 C-1326.77,-188.23 -1354.33,-219.1 -1389,-239 C-1423.4,-258.74 -1470.69,-267.55 -1515,-278 C-1603.39,-298.85 -1690.13,-322.03 -1777,-346 C-1817.41,-357.15 -1865.58,-377.43 -1915,-374c "
+                    android:valueTo="M-1890 -364 C-1915.49,-361.91 -1938.32,-355.57 -1958.37,-346.18 C-1978.42,-336.78 -1995.67,-324.33 -2010,-310 C-2024.52,-295.48 -2037.55,-279.77 -2047.93,-261.42 C-2058.31,-243.07 -2066.05,-222.08 -2070,-197 C-2074.59,-167.83 -2073.02,-142.02 -2067.58,-119.16 C-2062.15,-96.3 -2052.85,-76.38 -2042,-59 C-2030.6,-40.74 -2017.28,-25.07 -2001.72,-11.92 C-1986.16,1.22 -1968.36,11.84 -1948,20 C-1905.62,36.98 -1860,46.68 -1815,59 C-1725.92,83.38 -1631.46,106.29 -1538,128 C-1491.98,138.69 -1441.13,155.58 -1390,151 C-1292.44,142.26 -1225.72,75.29 -1210,-14 C-1200.46,-68.19 -1215.31,-117.16 -1237,-153 C-1258.29,-188.17 -1290.09,-216.37 -1330,-233 C-1350.41,-241.5 -1374.56,-245.91 -1398,-251 C-1516.15,-276.67 -1631.32,-305.3 -1744,-337 C-1787.58,-349.26 -1835.91,-368.44 -1890,-364c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="383"
+                    android:valueFrom="M-1890 -364 C-1915.49,-361.91 -1938.32,-355.57 -1958.37,-346.18 C-1978.42,-336.78 -1995.67,-324.33 -2010,-310 C-2024.52,-295.48 -2037.55,-279.77 -2047.93,-261.42 C-2058.31,-243.07 -2066.05,-222.08 -2070,-197 C-2074.59,-167.83 -2073.02,-142.02 -2067.58,-119.16 C-2062.15,-96.3 -2052.85,-76.38 -2042,-59 C-2030.6,-40.74 -2017.28,-25.07 -2001.72,-11.92 C-1986.16,1.22 -1968.36,11.84 -1948,20 C-1905.62,36.98 -1860,46.68 -1815,59 C-1725.92,83.38 -1631.46,106.29 -1538,128 C-1491.98,138.69 -1441.13,155.58 -1390,151 C-1292.44,142.26 -1225.72,75.29 -1210,-14 C-1200.46,-68.19 -1215.31,-117.16 -1237,-153 C-1258.29,-188.17 -1290.09,-216.37 -1330,-233 C-1350.41,-241.5 -1374.56,-245.91 -1398,-251 C-1516.15,-276.67 -1631.32,-305.3 -1744,-337 C-1787.58,-349.26 -1835.91,-368.44 -1890,-364c "
+                    android:valueTo="M-1841 -354 C-1869.44,-353.16 -1894.26,-347.31 -1915.8,-337.97 C-1937.35,-328.63 -1955.63,-315.8 -1971,-301 C-1986.17,-286.39 -1999.54,-271.29 -2010.18,-252.51 C-2020.82,-233.74 -2028.74,-211.3 -2033,-182 C-2041.16,-125.91 -2026.91,-77.42 -2000.49,-39.83 C-1974.07,-2.23 -1935.49,24.47 -1895,37 C-1871.46,44.28 -1847.67,50.61 -1823.78,56.71 C-1799.89,62.8 -1775.91,68.66 -1752,75 C-1632.18,106.77 -1509.29,135.8 -1384,161 C-1357.24,166.38 -1331.35,172.76 -1304,172 C-1248.4,170.45 -1205.4,147.31 -1175,120 C-1143.38,91.59 -1119.42,51.91 -1112,2 C-1103.47,-55.42 -1120.14,-106.53 -1145,-142 C-1169.17,-176.48 -1205.94,-205.29 -1250,-219 C-1273.89,-226.43 -1299.44,-229.88 -1324,-235 C-1449.37,-261.13 -1570.6,-289.56 -1691,-322 C-1735.34,-333.94 -1789.57,-355.52 -1841,-354c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="400"
+                    android:valueFrom="M-1841 -354 C-1869.44,-353.16 -1894.26,-347.31 -1915.8,-337.97 C-1937.35,-328.63 -1955.63,-315.8 -1971,-301 C-1986.17,-286.39 -1999.54,-271.29 -2010.18,-252.51 C-2020.82,-233.74 -2028.74,-211.3 -2033,-182 C-2041.16,-125.91 -2026.91,-77.42 -2000.49,-39.83 C-1974.07,-2.23 -1935.49,24.47 -1895,37 C-1871.46,44.28 -1847.67,50.61 -1823.78,56.71 C-1799.89,62.8 -1775.91,68.66 -1752,75 C-1632.18,106.77 -1509.29,135.8 -1384,161 C-1357.24,166.38 -1331.35,172.76 -1304,172 C-1248.4,170.45 -1205.4,147.31 -1175,120 C-1143.38,91.59 -1119.42,51.91 -1112,2 C-1103.47,-55.42 -1120.14,-106.53 -1145,-142 C-1169.17,-176.48 -1205.94,-205.29 -1250,-219 C-1273.89,-226.43 -1299.44,-229.88 -1324,-235 C-1449.37,-261.13 -1570.6,-289.56 -1691,-322 C-1735.34,-333.94 -1789.57,-355.52 -1841,-354c "
+                    android:valueTo="M-1796 -342 C-1811.94,-341.79 -1825.94,-339.83 -1838.82,-336.66 C-1851.71,-333.48 -1863.49,-329.09 -1875,-324 C-1907.54,-309.62 -1935.19,-288.4 -1955.51,-260 C-1975.82,-231.61 -1988.8,-196.05 -1992,-153 C-1994.25,-122.72 -1989.36,-95.65 -1980.35,-72.07 C-1971.34,-48.48 -1958.22,-28.37 -1944,-12 C-1928.17,6.22 -1910.48,19.99 -1890.22,30.95 C-1869.96,41.91 -1847.13,50.05 -1821,57 C-1732.48,80.56 -1643.53,102.75 -1552.93,123.58 C-1462.34,144.4 -1370.1,163.87 -1275,182 C-1247.01,187.34 -1218.58,194.81 -1191,194 C-1131.03,192.25 -1087.63,168.15 -1056,138 C-1024.96,108.42 -997.59,63.62 -995,7 C-992.15,-55.28 -1013.07,-100.38 -1043,-136 C-1072.69,-171.34 -1113.24,-194.03 -1167,-204 C-1328.4,-233.93 -1484.44,-268.19 -1638,-308 C-1687.14,-320.74 -1741.73,-342.71 -1796,-342c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="417"
+                    android:valueFrom="M-1796 -342 C-1811.94,-341.79 -1825.94,-339.83 -1838.82,-336.66 C-1851.71,-333.48 -1863.49,-329.09 -1875,-324 C-1907.54,-309.62 -1935.19,-288.4 -1955.51,-260 C-1975.82,-231.61 -1988.8,-196.05 -1992,-153 C-1994.25,-122.72 -1989.36,-95.65 -1980.35,-72.07 C-1971.34,-48.48 -1958.22,-28.37 -1944,-12 C-1928.17,6.22 -1910.48,19.99 -1890.22,30.95 C-1869.96,41.91 -1847.13,50.05 -1821,57 C-1732.48,80.56 -1643.53,102.75 -1552.93,123.58 C-1462.34,144.4 -1370.1,163.87 -1275,182 C-1247.01,187.34 -1218.58,194.81 -1191,194 C-1131.03,192.25 -1087.63,168.15 -1056,138 C-1024.96,108.42 -997.59,63.62 -995,7 C-992.15,-55.28 -1013.07,-100.38 -1043,-136 C-1072.69,-171.34 -1113.24,-194.03 -1167,-204 C-1328.4,-233.93 -1484.44,-268.19 -1638,-308 C-1687.14,-320.74 -1741.73,-342.71 -1796,-342c "
+                    android:valueTo="M-1758 -328 C-1773.72,-326.77 -1788.05,-324.1 -1801.28,-320.2 C-1814.52,-316.3 -1826.65,-311.16 -1838,-305 C-1867.83,-288.8 -1896.87,-263.63 -1916.93,-230.09 C-1936.99,-196.55 -1948.07,-154.65 -1942,-105 C-1938.26,-74.41 -1929.05,-49.04 -1916.25,-27.63 C-1903.46,-6.22 -1887.08,11.24 -1869,26 C-1849.22,42.16 -1826.74,52.66 -1802.07,61 C-1777.41,69.34 -1750.55,75.51 -1722,83 C-1627.76,107.73 -1531.68,130.26 -1433.57,150.84 C-1335.47,171.42 -1235.33,190.06 -1133,207 C-1102.48,212.05 -1071.97,219.42 -1041,217 C-980.71,212.29 -935.73,182.99 -906,149 C-876.19,114.92 -850.64,62 -858,-5 C-867.57,-92.16 -925.05,-149.77 -996,-173 C-1020.84,-181.13 -1049.84,-184.7 -1081,-190 C-1283.95,-224.53 -1479.06,-266.92 -1668,-316 C-1696.45,-323.39 -1726.55,-330.46 -1758,-328c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="433"
+                    android:valueFrom="M-1758 -328 C-1773.72,-326.77 -1788.05,-324.1 -1801.28,-320.2 C-1814.52,-316.3 -1826.65,-311.16 -1838,-305 C-1867.83,-288.8 -1896.87,-263.63 -1916.93,-230.09 C-1936.99,-196.55 -1948.07,-154.65 -1942,-105 C-1938.26,-74.41 -1929.05,-49.04 -1916.25,-27.63 C-1903.46,-6.22 -1887.08,11.24 -1869,26 C-1849.22,42.16 -1826.74,52.66 -1802.07,61 C-1777.41,69.34 -1750.55,75.51 -1722,83 C-1627.76,107.73 -1531.68,130.26 -1433.57,150.84 C-1335.47,171.42 -1235.33,190.06 -1133,207 C-1102.48,212.05 -1071.97,219.42 -1041,217 C-980.71,212.29 -935.73,182.99 -906,149 C-876.19,114.92 -850.64,62 -858,-5 C-867.57,-92.16 -925.05,-149.77 -996,-173 C-1020.84,-181.13 -1049.84,-184.7 -1081,-190 C-1283.95,-224.53 -1479.06,-266.92 -1668,-316 C-1696.45,-323.39 -1726.55,-330.46 -1758,-328c "
+                    android:valueTo="M-1702 -313 C-1718.97,-311.67 -1734.27,-308.63 -1748.31,-304.2 C-1762.34,-299.76 -1775.11,-293.92 -1787,-287 C-1818.73,-268.52 -1848.63,-238.43 -1867.4,-200.25 C-1886.16,-162.07 -1893.8,-115.81 -1881,-65 C-1873.66,-35.86 -1861.23,-11.42 -1844.97,9 C-1828.71,29.43 -1808.63,45.86 -1786,59 C-1763.78,71.9 -1735.76,80.93 -1706.18,88.48 C-1676.6,96.02 -1645.45,102.07 -1617,109 C-1514.25,134.02 -1409.51,156.86 -1302.58,177.39 C-1195.65,197.93 -1086.52,216.17 -975,232 C-941.5,236.76 -907.26,243.23 -874,241 C-807.88,236.57 -764.02,203.88 -733,164 C-703.96,126.67 -678.96,62.64 -696,-6 C-710.57,-64.7 -744.81,-104.43 -790,-131 C-838.34,-159.43 -903.19,-162.4 -968,-172 C-1188.55,-204.67 -1401.48,-249.48 -1605,-300 C-1635.42,-307.55 -1668.51,-315.62 -1702,-313c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="450"
+                    android:valueFrom="M-1702 -313 C-1718.97,-311.67 -1734.27,-308.63 -1748.31,-304.2 C-1762.34,-299.76 -1775.11,-293.92 -1787,-287 C-1818.73,-268.52 -1848.63,-238.43 -1867.4,-200.25 C-1886.16,-162.07 -1893.8,-115.81 -1881,-65 C-1873.66,-35.86 -1861.23,-11.42 -1844.97,9 C-1828.71,29.43 -1808.63,45.86 -1786,59 C-1763.78,71.9 -1735.76,80.93 -1706.18,88.48 C-1676.6,96.02 -1645.45,102.07 -1617,109 C-1514.25,134.02 -1409.51,156.86 -1302.58,177.39 C-1195.65,197.93 -1086.52,216.17 -975,232 C-941.5,236.76 -907.26,243.23 -874,241 C-807.88,236.57 -764.02,203.88 -733,164 C-703.96,126.67 -678.96,62.64 -696,-6 C-710.57,-64.7 -744.81,-104.43 -790,-131 C-838.34,-159.43 -903.19,-162.4 -968,-172 C-1188.55,-204.67 -1401.48,-249.48 -1605,-300 C-1635.42,-307.55 -1668.51,-315.62 -1702,-313c "
+                    android:valueTo="M-1629 -297 C-1647.68,-296 -1664.53,-292.94 -1679.91,-288.29 C-1695.29,-283.64 -1709.2,-277.39 -1722,-270 C-1735.13,-262.42 -1746.45,-254.66 -1756.69,-245.68 C-1766.94,-236.7 -1776.13,-226.49 -1785,-214 C-1801.02,-191.45 -1813.31,-160.96 -1818.44,-127.98 C-1823.58,-95 -1821.57,-59.52 -1809,-27 C-1798.02,1.4 -1782.25,25.85 -1762.24,45.72 C-1742.22,65.59 -1717.96,80.88 -1690,91 C-1661.64,101.26 -1627.93,107.27 -1595,115 C-1338.91,175.14 -1069.51,224.52 -786,256 C-749.41,260.06 -712.09,266.16 -678,265 C-602.8,262.44 -555.29,227.14 -523,183 C-491.08,139.36 -471.68,63.07 -497,-4 C-518,-59.64 -556.44,-100.48 -616,-122 C-645.9,-132.8 -681.94,-135 -718,-139 C-999.71,-170.28 -1271.16,-219.92 -1527,-280 C-1559.85,-287.71 -1594.62,-298.85 -1629,-297c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="467"
+                    android:valueFrom="M-1629 -297 C-1647.68,-296 -1664.53,-292.94 -1679.91,-288.29 C-1695.29,-283.64 -1709.2,-277.39 -1722,-270 C-1735.13,-262.42 -1746.45,-254.66 -1756.69,-245.68 C-1766.94,-236.7 -1776.13,-226.49 -1785,-214 C-1801.02,-191.45 -1813.31,-160.96 -1818.44,-127.98 C-1823.58,-95 -1821.57,-59.52 -1809,-27 C-1798.02,1.4 -1782.25,25.85 -1762.24,45.72 C-1742.22,65.59 -1717.96,80.88 -1690,91 C-1661.64,101.26 -1627.93,107.27 -1595,115 C-1338.91,175.14 -1069.51,224.52 -786,256 C-749.41,260.06 -712.09,266.16 -678,265 C-602.8,262.44 -555.29,227.14 -523,183 C-491.08,139.36 -471.68,63.07 -497,-4 C-518,-59.64 -556.44,-100.48 -616,-122 C-645.9,-132.8 -681.94,-135 -718,-139 C-999.71,-170.28 -1271.16,-219.92 -1527,-280 C-1559.85,-287.71 -1594.62,-298.85 -1629,-297c "
+                    android:valueTo="M-1554 -279 C-1573.95,-278.03 -1592.16,-274.76 -1608.9,-269.49 C-1625.63,-264.23 -1640.91,-256.96 -1655,-248 C-1681.11,-231.4 -1704.11,-209.28 -1720.58,-181.38 C-1737.06,-153.48 -1747,-119.78 -1747,-80 C-1747,-40.36 -1736.63,-6.11 -1720.07,22.22 C-1703.51,50.56 -1680.76,72.99 -1656,89 C-1642.79,97.54 -1627.94,103.93 -1611.81,109.23 C-1595.69,114.54 -1578.3,118.77 -1560,123 C-1243.1,196.29 -905.52,251.65 -545,279 C-503.41,282.15 -461.51,286.97 -421,286 C-342.32,284.12 -289.14,239.96 -258,189 C-241.23,161.57 -229.41,126.9 -229,88 C-228.57,48.09 -239.97,14.95 -257,-14 C-288.07,-66.84 -336.69,-106.64 -418,-113 C-495.07,-119.03 -576.44,-125.73 -659,-133 C-929.35,-156.79 -1195.73,-207.11 -1442,-260 C-1478.55,-267.85 -1516.39,-280.83 -1554,-279c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="483"
+                    android:valueFrom="M-1554 -279 C-1573.95,-278.03 -1592.16,-274.76 -1608.9,-269.49 C-1625.63,-264.23 -1640.91,-256.96 -1655,-248 C-1681.11,-231.4 -1704.11,-209.28 -1720.58,-181.38 C-1737.06,-153.48 -1747,-119.78 -1747,-80 C-1747,-40.36 -1736.63,-6.11 -1720.07,22.22 C-1703.51,50.56 -1680.76,72.99 -1656,89 C-1642.79,97.54 -1627.94,103.93 -1611.81,109.23 C-1595.69,114.54 -1578.3,118.77 -1560,123 C-1243.1,196.29 -905.52,251.65 -545,279 C-503.41,282.15 -461.51,286.97 -421,286 C-342.32,284.12 -289.14,239.96 -258,189 C-241.23,161.57 -229.41,126.9 -229,88 C-228.57,48.09 -239.97,14.95 -257,-14 C-288.07,-66.84 -336.69,-106.64 -418,-113 C-495.07,-119.03 -576.44,-125.73 -659,-133 C-929.35,-156.79 -1195.73,-207.11 -1442,-260 C-1478.55,-267.85 -1516.39,-280.83 -1554,-279c "
+                    android:valueTo="M-1475 -259 C-1497.94,-257.3 -1517.43,-252.76 -1534.68,-246.04 C-1551.94,-239.32 -1566.97,-230.42 -1581,-220 C-1607.62,-200.23 -1631.26,-172.32 -1645.92,-138.18 C-1660.58,-104.04 -1666.27,-63.68 -1657,-19 C-1649.07,19.22 -1632.37,51.34 -1608.87,76.59 C-1585.37,101.84 -1555.09,120.22 -1520,131 C-1502.34,136.42 -1483.19,140.77 -1463.28,144.83 C-1443.37,148.9 -1422.7,152.69 -1402,157 C-1023.46,235.88 -622.54,284.09 -179,298 C-155.11,298.75 -130.67,300.57 -108,299 C-25.73,293.3 33.98,241.56 61,181 C76.53,146.2 82.53,100.81 74,59 C58.4,-17.45 8.72,-69.55 -62,-92 C-97.88,-103.39 -148.34,-103 -195,-103 C-282.94,-103 -387.12,-111.21 -468,-117 C-774.59,-138.96 -1075.42,-182.91 -1350,-240 C-1389.82,-248.28 -1432.72,-262.13 -1475,-259c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="500"
+                    android:valueFrom="M-1475 -259 C-1497.94,-257.3 -1517.43,-252.76 -1534.68,-246.04 C-1551.94,-239.32 -1566.97,-230.42 -1581,-220 C-1607.62,-200.23 -1631.26,-172.32 -1645.92,-138.18 C-1660.58,-104.04 -1666.27,-63.68 -1657,-19 C-1649.07,19.22 -1632.37,51.34 -1608.87,76.59 C-1585.37,101.84 -1555.09,120.22 -1520,131 C-1502.34,136.42 -1483.19,140.77 -1463.28,144.83 C-1443.37,148.9 -1422.7,152.69 -1402,157 C-1023.46,235.88 -622.54,284.09 -179,298 C-155.11,298.75 -130.67,300.57 -108,299 C-25.73,293.3 33.98,241.56 61,181 C76.53,146.2 82.53,100.81 74,59 C58.4,-17.45 8.72,-69.55 -62,-92 C-97.88,-103.39 -148.34,-103 -195,-103 C-282.94,-103 -387.12,-111.21 -468,-117 C-774.59,-138.96 -1075.42,-182.91 -1350,-240 C-1389.82,-248.28 -1432.72,-262.13 -1475,-259c "
+                    android:valueTo="M-1373 -238 C-1397.37,-236.31 -1418.62,-231.33 -1437.53,-223.76 C-1456.44,-216.19 -1473,-206.04 -1488,-194 C-1503.21,-181.79 -1516.33,-168.45 -1527.22,-153.33 C-1538.1,-138.21 -1546.75,-121.31 -1553,-102 C-1568.58,-53.91 -1564.76,-8.07 -1550.21,30.82 C-1535.66,69.72 -1510.38,101.67 -1483,122 C-1452.82,144.41 -1414.34,154.7 -1368,164 C-1237.54,190.18 -1096.12,213.69 -965,233 C-643.35,280.36 -299.33,300 68,300 C120,300 172,299.45 222,296 C316.17,289.51 375.99,234.85 401,161 C433.46,65.17 388.09,-21.48 333,-63 C304.2,-84.71 265.24,-102.48 216,-103 C161,-103.58 109.46,-100 62,-100 C-42.22,-100 -151.92,-100.95 -248,-105 C-597.18,-119.72 -929.37,-158.41 -1236,-217 C-1278.46,-225.11 -1327.89,-241.13 -1373,-238c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="517"
+                    android:valueFrom="M-1373 -238 C-1397.37,-236.31 -1418.62,-231.33 -1437.53,-223.76 C-1456.44,-216.19 -1473,-206.04 -1488,-194 C-1503.21,-181.79 -1516.33,-168.45 -1527.22,-153.33 C-1538.1,-138.21 -1546.75,-121.31 -1553,-102 C-1568.58,-53.91 -1564.76,-8.07 -1550.21,30.82 C-1535.66,69.72 -1510.38,101.67 -1483,122 C-1452.82,144.41 -1414.34,154.7 -1368,164 C-1237.54,190.18 -1096.12,213.69 -965,233 C-643.35,280.36 -299.33,300 68,300 C120,300 172,299.45 222,296 C316.17,289.51 375.99,234.85 401,161 C433.46,65.17 388.09,-21.48 333,-63 C304.2,-84.71 265.24,-102.48 216,-103 C161,-103.58 109.46,-100 62,-100 C-42.22,-100 -151.92,-100.95 -248,-105 C-597.18,-119.72 -929.37,-158.41 -1236,-217 C-1278.46,-225.11 -1327.89,-241.13 -1373,-238c "
+                    android:valueTo="M-1265 -215 C-1290.3,-212.92 -1313.06,-206.47 -1333,-197.26 C-1352.93,-188.06 -1370.03,-176.1 -1384,-163 C-1398.71,-149.21 -1411.64,-133.82 -1422.06,-116.08 C-1432.48,-98.34 -1440.37,-78.24 -1445,-55 C-1455.6,-1.79 -1444.84,45.36 -1423.06,83.13 C-1401.29,120.91 -1368.49,149.3 -1335,165 C-1316.23,173.8 -1293.86,179.41 -1270.12,183.89 C-1246.37,188.37 -1221.26,191.72 -1197,196 C-1052.86,221.45 -899.36,243.11 -753,259 C-353.75,302.34 120.02,314.63 538,279 C633.75,270.84 700.5,206.17 718,121 C740.05,13.71 679.18,-65.56 609,-100 C589.87,-109.39 569.5,-116.07 544,-119 C520.02,-121.75 489.94,-117.74 464,-116 C409.15,-112.32 351.08,-109.29 304,-107 C-196.98,-82.59 -686.9,-120.79 -1115,-195 C-1161.65,-203.09 -1214.46,-219.15 -1265,-215c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="533"
+                    android:valueFrom="M-1265 -215 C-1290.3,-212.92 -1313.06,-206.47 -1333,-197.26 C-1352.93,-188.06 -1370.03,-176.1 -1384,-163 C-1398.71,-149.21 -1411.64,-133.82 -1422.06,-116.08 C-1432.48,-98.34 -1440.37,-78.24 -1445,-55 C-1455.6,-1.79 -1444.84,45.36 -1423.06,83.13 C-1401.29,120.91 -1368.49,149.3 -1335,165 C-1316.23,173.8 -1293.86,179.41 -1270.12,183.89 C-1246.37,188.37 -1221.26,191.72 -1197,196 C-1052.86,221.45 -899.36,243.11 -753,259 C-353.75,302.34 120.02,314.63 538,279 C633.75,270.84 700.5,206.17 718,121 C740.05,13.71 679.18,-65.56 609,-100 C589.87,-109.39 569.5,-116.07 544,-119 C520.02,-121.75 489.94,-117.74 464,-116 C409.15,-112.32 351.08,-109.29 304,-107 C-196.98,-82.59 -686.9,-120.79 -1115,-195 C-1161.65,-203.09 -1214.46,-219.15 -1265,-215c "
+                    android:valueTo="M-1118 -192 C-1145.42,-191.79 -1170.54,-186.13 -1192.5,-177.22 C-1214.45,-168.32 -1233.24,-156.17 -1248,-143 C-1263.27,-129.37 -1277,-113.05 -1288.05,-94.18 C-1299.1,-75.3 -1307.46,-53.87 -1312,-30 C-1332.86,79.71 -1267.54,162.8 -1189,193 C-1147.25,209.05 -1092.45,214.06 -1041,222 C-887.29,245.72 -722.44,264.69 -565,277 C-240.57,302.37 128.06,309.73 459,285 C570.39,276.68 679.51,267.39 784,256 C887.81,244.69 957.02,188.83 976,94 C987.3,37.57 972.22,-12.23 950,-49 C917.49,-102.8 862.33,-144.29 781,-145 C750.06,-145.27 727.56,-139.91 700,-137 C541.6,-120.26 360.16,-106.76 203,-103 C92.15,-100.34 -28.22,-98.3 -146,-102 C-465.5,-112.02 -757.79,-137.12 -1038,-183 C-1064.09,-187.27 -1095.31,-192.17 -1118,-192c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="550"
+                    android:valueFrom="M-1118 -192 C-1145.42,-191.79 -1170.54,-186.13 -1192.5,-177.22 C-1214.45,-168.32 -1233.24,-156.17 -1248,-143 C-1263.27,-129.37 -1277,-113.05 -1288.05,-94.18 C-1299.1,-75.3 -1307.46,-53.87 -1312,-30 C-1332.86,79.71 -1267.54,162.8 -1189,193 C-1147.25,209.05 -1092.45,214.06 -1041,222 C-887.29,245.72 -722.44,264.69 -565,277 C-240.57,302.37 128.06,309.73 459,285 C570.39,276.68 679.51,267.39 784,256 C887.81,244.69 957.02,188.83 976,94 C987.3,37.57 972.22,-12.23 950,-49 C917.49,-102.8 862.33,-144.29 781,-145 C750.06,-145.27 727.56,-139.91 700,-137 C541.6,-120.26 360.16,-106.76 203,-103 C92.15,-100.34 -28.22,-98.3 -146,-102 C-465.5,-112.02 -757.79,-137.12 -1038,-183 C-1064.09,-187.27 -1095.31,-192.17 -1118,-192c "
+                    android:valueTo="M970 -171 C943.29,-168.81 916.34,-163.76 889,-160 C702.71,-134.36 510.97,-116.08 311,-107 C84.92,-96.73 -172.89,-100.57 -391,-112 C-497.92,-117.6 -602.84,-127.1 -723,-140 C-773.04,-145.37 -825.42,-152.52 -883,-160 C-909.72,-163.47 -935.68,-168.58 -965,-168 C-996.16,-167.38 -1018.31,-161.2 -1040,-152 C-1115.75,-119.87 -1181.79,-30.18 -1153,82 C-1140.72,129.86 -1115.71,165.17 -1082,191 C-1045.71,218.81 -1002.91,227.92 -946,236 C-630.61,280.78 -288.29,300 71,300 C188.01,300 304.89,292.44 419,287 C585.2,279.08 754.78,261.17 912,240 C964.06,232.99 1024.03,228.96 1065,212 C1142.61,179.87 1208.86,90.77 1181,-21 C1169.65,-66.53 1143.86,-103.88 1110,-130 C1077.04,-155.43 1028.35,-175.79 970,-171c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="567"
+                    android:valueFrom="M970 -171 C943.29,-168.81 916.34,-163.76 889,-160 C702.71,-134.36 510.97,-116.08 311,-107 C84.92,-96.73 -172.89,-100.57 -391,-112 C-497.92,-117.6 -602.84,-127.1 -723,-140 C-773.04,-145.37 -825.42,-152.52 -883,-160 C-909.72,-163.47 -935.68,-168.58 -965,-168 C-996.16,-167.38 -1018.31,-161.2 -1040,-152 C-1115.75,-119.87 -1181.79,-30.18 -1153,82 C-1140.72,129.86 -1115.71,165.17 -1082,191 C-1045.71,218.81 -1002.91,227.92 -946,236 C-630.61,280.78 -288.29,300 71,300 C188.01,300 304.89,292.44 419,287 C585.2,279.08 754.78,261.17 912,240 C964.06,232.99 1024.03,228.96 1065,212 C1142.61,179.87 1208.86,90.77 1181,-21 C1169.65,-66.53 1143.86,-103.88 1110,-130 C1077.04,-155.43 1028.35,-175.79 970,-171c "
+                    android:valueTo="M1139 -198 C1112.96,-195.96 1086.75,-190.39 1060,-186 C724.42,-130.9 368.62,-100 -21,-100 C-206.52,-100 -352.82,-106.58 -538,-122 C-587.36,-126.11 -643.08,-130.48 -702,-137 C-729.04,-139.99 -755.08,-146.24 -784,-145 C-814,-143.72 -835.74,-137.28 -857,-128 C-930.63,-95.87 -1001.24,-8.83 -971,105 C-958.3,152.82 -933.55,187.66 -900,214 C-863.65,242.54 -822.66,251.49 -764,258 C-604.41,275.72 -434.69,289.25 -263,295 C-87.78,300.87 91.29,299.55 266,295 C381.34,291.99 494.86,283.26 604,274 C762.94,260.51 929.9,239.45 1083,215 C1134.03,206.85 1192.04,202.57 1233,185 C1309.97,151.99 1375.09,64.69 1348,-47 C1336.52,-94.34 1310.62,-129.78 1277,-156 C1244.09,-181.67 1197.01,-202.54 1139,-198c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="583"
+                    android:valueFrom="M1139 -198 C1112.96,-195.96 1086.75,-190.39 1060,-186 C724.42,-130.9 368.62,-100 -21,-100 C-206.52,-100 -352.82,-106.58 -538,-122 C-587.36,-126.11 -643.08,-130.48 -702,-137 C-729.04,-139.99 -755.08,-146.24 -784,-145 C-814,-143.72 -835.74,-137.28 -857,-128 C-930.63,-95.87 -1001.24,-8.83 -971,105 C-958.3,152.82 -933.55,187.66 -900,214 C-863.65,242.54 -822.66,251.49 -764,258 C-604.41,275.72 -434.69,289.25 -263,295 C-87.78,300.87 91.29,299.55 266,295 C381.34,291.99 494.86,283.26 604,274 C762.94,260.51 929.9,239.45 1083,215 C1134.03,206.85 1192.04,202.57 1233,185 C1309.97,151.99 1375.09,64.69 1348,-47 C1336.52,-94.34 1310.62,-129.78 1277,-156 C1244.09,-181.67 1197.01,-202.54 1139,-198c "
+                    android:valueTo="M1273 -223 C1260.98,-222.01 1248.39,-219.92 1235.6,-217.5 C1222.81,-215.07 1209.81,-212.32 1197,-210 C966.02,-168.25 716.41,-134.99 456.35,-116.17 C196.29,-97.35 -74.22,-92.98 -347,-109 C-371.45,-110.44 -398.42,-112.07 -426.29,-113.9 C-454.16,-115.73 -482.93,-117.76 -511,-120 C-538.24,-122.17 -566.8,-124.89 -593,-121 C-620.44,-116.92 -639.32,-109.31 -659,-99 C-728.07,-62.79 -787.77,29.83 -754,136 C-739.63,181.19 -715.06,213.22 -681,238 C-645.58,263.77 -604.66,274.8 -547,279 C-330.87,294.73 -102.21,301.44 131,299 C529.86,294.82 898.81,248.97 1245,188 C1293.44,179.47 1348.93,172.3 1385,153 C1454.2,115.97 1514.13,26.39 1482,-82 C1468.82,-126.48 1443.86,-160.23 1410,-185 C1376.3,-209.65 1329.97,-227.68 1273,-223c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="600"
+                    android:valueFrom="M1273 -223 C1260.98,-222.01 1248.39,-219.92 1235.6,-217.5 C1222.81,-215.07 1209.81,-212.32 1197,-210 C966.02,-168.25 716.41,-134.99 456.35,-116.17 C196.29,-97.35 -74.22,-92.98 -347,-109 C-371.45,-110.44 -398.42,-112.07 -426.29,-113.9 C-454.16,-115.73 -482.93,-117.76 -511,-120 C-538.24,-122.17 -566.8,-124.89 -593,-121 C-620.44,-116.92 -639.32,-109.31 -659,-99 C-728.07,-62.79 -787.77,29.83 -754,136 C-739.63,181.19 -715.06,213.22 -681,238 C-645.58,263.77 -604.66,274.8 -547,279 C-330.87,294.73 -102.21,301.44 131,299 C529.86,294.82 898.81,248.97 1245,188 C1293.44,179.47 1348.93,172.3 1385,153 C1454.2,115.97 1514.13,26.39 1482,-82 C1468.82,-126.48 1443.86,-160.23 1410,-185 C1376.3,-209.65 1329.97,-227.68 1273,-223c "
+                    android:valueTo="M1396 -247 C1384.29,-246.25 1372.12,-244.54 1359.84,-242.37 C1347.55,-240.21 1335.16,-237.59 1323,-235 C1242.35,-217.84 1159.48,-202.2 1075.58,-188.17 C991.68,-174.14 906.76,-161.72 822,-151 C647.45,-128.92 465.42,-113.12 278.92,-105.12 C92.42,-97.11 -98.55,-96.91 -291,-106 C-319.6,-107.35 -344.62,-105.15 -366.73,-99.83 C-388.84,-94.52 -408.04,-86.09 -425,-75 C-456,-54.72 -484.76,-22.13 -501.39,18.1 C-518.02,58.32 -522.52,106.19 -505,157 C-491.11,197.3 -467.19,230.65 -435,254 C-401.08,278.61 -361.34,290.67 -306,293 C-92.09,302.01 134.66,300.83 346,291 C720.7,273.57 1058.02,226.02 1386,161 C1433.15,151.65 1480.38,141.86 1513,121 C1574.49,81.68 1629.52,-8.18 1595,-111 C1569.5,-186.95 1497.99,-253.57 1396,-247c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="617"
+                    android:valueFrom="M1396 -247 C1384.29,-246.25 1372.12,-244.54 1359.84,-242.37 C1347.55,-240.21 1335.16,-237.59 1323,-235 C1242.35,-217.84 1159.48,-202.2 1075.58,-188.17 C991.68,-174.14 906.76,-161.72 822,-151 C647.45,-128.92 465.42,-113.12 278.92,-105.12 C92.42,-97.11 -98.55,-96.91 -291,-106 C-319.6,-107.35 -344.62,-105.15 -366.73,-99.83 C-388.84,-94.52 -408.04,-86.09 -425,-75 C-456,-54.72 -484.76,-22.13 -501.39,18.1 C-518.02,58.32 -522.52,106.19 -505,157 C-491.11,197.3 -467.19,230.65 -435,254 C-401.08,278.61 -361.34,290.67 -306,293 C-92.09,302.01 134.66,300.83 346,291 C720.7,273.57 1058.02,226.02 1386,161 C1433.15,151.65 1480.38,141.86 1513,121 C1574.49,81.68 1629.52,-8.18 1595,-111 C1569.5,-186.95 1497.99,-253.57 1396,-247c "
+                    android:valueTo="M1487 -268 C1475.75,-267.12 1464.42,-265.34 1453.07,-263.17 C1441.72,-261 1430.34,-258.44 1419,-256 C1408.2,-253.68 1397.48,-251.27 1386.82,-248.89 C1376.16,-246.52 1365.56,-244.17 1355,-242 C1158.69,-201.57 958.13,-168.1 748.31,-143.85 C538.48,-119.59 319.39,-104.56 86,-101 C64.01,-100.66 37.31,-101.63 10.2,-101.81 C-16.9,-102 -44.41,-101.42 -68,-98 C-92.39,-94.46 -112.22,-87.84 -129.29,-79.04 C-146.36,-70.24 -160.66,-59.26 -174,-47 C-201.16,-22.03 -221.68,10.56 -232,51 C-243.2,94.93 -237.31,146.32 -220,184 C-188.2,253.2 -124.98,300 -22,300 C554.96,300 1059.34,235.05 1531,130 C1615.03,111.29 1675.73,61.99 1696,-18 C1707.22,-62.26 1701.41,-113.05 1684,-151 C1653.53,-217.42 1586.39,-275.77 1487,-268c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="633"
+                    android:valueFrom="M1487 -268 C1475.75,-267.12 1464.42,-265.34 1453.07,-263.17 C1441.72,-261 1430.34,-258.44 1419,-256 C1408.2,-253.68 1397.48,-251.27 1386.82,-248.89 C1376.16,-246.52 1365.56,-244.17 1355,-242 C1158.69,-201.57 958.13,-168.1 748.31,-143.85 C538.48,-119.59 319.39,-104.56 86,-101 C64.01,-100.66 37.31,-101.63 10.2,-101.81 C-16.9,-102 -44.41,-101.42 -68,-98 C-92.39,-94.46 -112.22,-87.84 -129.29,-79.04 C-146.36,-70.24 -160.66,-59.26 -174,-47 C-201.16,-22.03 -221.68,10.56 -232,51 C-243.2,94.93 -237.31,146.32 -220,184 C-188.2,253.2 -124.98,300 -22,300 C554.96,300 1059.34,235.05 1531,130 C1615.03,111.29 1675.73,61.99 1696,-18 C1707.22,-62.26 1701.41,-113.05 1684,-151 C1653.53,-217.42 1586.39,-275.77 1487,-268c "
+                    android:valueTo="M1580 -288 C1569.02,-287.82 1558.33,-286.59 1547.73,-284.76 C1537.13,-282.94 1526.62,-280.53 1516,-278 C1329.96,-233.65 1137.53,-196.27 937.45,-167.41 C737.37,-138.56 529.64,-118.24 313,-108 C289.85,-106.91 266.93,-106.34 245.25,-104.34 C223.58,-102.33 203.15,-98.87 185,-92 C151.24,-79.21 121.91,-58.96 99.69,-31.9 C77.47,-4.85 62.35,29.01 57,69 C54.11,90.57 54.97,112.34 58.75,132.84 C62.53,153.33 69.22,172.54 78,189 C109.79,248.58 169.07,295 261,295 C352.67,295 445.29,286.74 535,280 C894.48,253 1223.58,200.63 1539,128 C1578.68,118.86 1619.85,111.36 1654,99 C1720.76,74.84 1771.83,17.2 1783,-61 C1789.05,-103.34 1779.11,-148.02 1762,-181 C1731.69,-239.43 1668.35,-289.44 1580,-288c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="650"
+                    android:valueFrom="M1580 -288 C1569.02,-287.82 1558.33,-286.59 1547.73,-284.76 C1537.13,-282.94 1526.62,-280.53 1516,-278 C1329.96,-233.65 1137.53,-196.27 937.45,-167.41 C737.37,-138.56 529.64,-118.24 313,-108 C289.85,-106.91 266.93,-106.34 245.25,-104.34 C223.58,-102.33 203.15,-98.87 185,-92 C151.24,-79.21 121.91,-58.96 99.69,-31.9 C77.47,-4.85 62.35,29.01 57,69 C54.11,90.57 54.97,112.34 58.75,132.84 C62.53,153.33 69.22,172.54 78,189 C109.79,248.58 169.07,295 261,295 C352.67,295 445.29,286.74 535,280 C894.48,253 1223.58,200.63 1539,128 C1578.68,118.86 1619.85,111.36 1654,99 C1720.76,74.84 1771.83,17.2 1783,-61 C1789.05,-103.34 1779.11,-148.02 1762,-181 C1731.69,-239.43 1668.35,-289.44 1580,-288c "
+                    android:valueTo="M1640 -305 C1621.47,-303.55 1603.27,-299.67 1585.17,-295.09 C1567.08,-290.52 1549.09,-285.24 1531,-281 C1477.75,-268.52 1423.74,-256.18 1369.55,-244.5 C1315.36,-232.82 1260.99,-221.81 1207,-212 C1111.62,-194.67 1018.32,-179.75 923.77,-166.49 C829.22,-153.24 733.41,-141.65 633,-131 C612.82,-128.86 592.3,-127.43 572.08,-125.88 C551.87,-124.33 531.96,-122.65 513,-120 C438.98,-109.65 388.59,-64.25 362,-9 C329.07,59.43 340.86,143.66 378,195 C413.52,244.1 470.86,281.96 558,278 C637.88,274.37 717.93,263.28 796,254 C1110.58,216.59 1403.92,162.28 1683,93 C1720.1,83.79 1748.74,73.39 1775,54 C1800,35.54 1820.18,12.52 1835,-17 C1869.4,-85.51 1855.22,-170.32 1819,-221 C1784.97,-268.62 1722.8,-311.47 1640,-305c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="667"
+                    android:valueFrom="M1640 -305 C1621.47,-303.55 1603.27,-299.67 1585.17,-295.09 C1567.08,-290.52 1549.09,-285.24 1531,-281 C1477.75,-268.52 1423.74,-256.18 1369.55,-244.5 C1315.36,-232.82 1260.99,-221.81 1207,-212 C1111.62,-194.67 1018.32,-179.75 923.77,-166.49 C829.22,-153.24 733.41,-141.65 633,-131 C612.82,-128.86 592.3,-127.43 572.08,-125.88 C551.87,-124.33 531.96,-122.65 513,-120 C438.98,-109.65 388.59,-64.25 362,-9 C329.07,59.43 340.86,143.66 378,195 C413.52,244.1 470.86,281.96 558,278 C637.88,274.37 717.93,263.28 796,254 C1110.58,216.59 1403.92,162.28 1683,93 C1720.1,83.79 1748.74,73.39 1775,54 C1800,35.54 1820.18,12.52 1835,-17 C1869.4,-85.51 1855.22,-170.32 1819,-221 C1784.97,-268.62 1722.8,-311.47 1640,-305c "
+                    android:valueTo="M1701 -321 C1684.31,-319.63 1667.92,-316.14 1651.65,-311.99 C1635.38,-307.84 1619.22,-303.03 1603,-299 C1476.28,-267.54 1347.03,-238.76 1214.58,-213.61 C1082.13,-188.46 946.5,-166.94 807,-150 C789.03,-147.82 771.91,-145.33 755.97,-141.75 C740.03,-138.18 725.27,-133.52 712,-127 C685.66,-114.05 663.18,-96.41 645.39,-74.45 C627.61,-52.5 614.53,-26.22 607,4 C597.72,41.22 599.94,76.18 608.87,106.85 C617.8,137.53 633.44,163.92 651,184 C686.76,224.89 741.19,258.64 818,252 C887.92,245.96 957.59,233.95 1026,224 C1266.28,189.04 1496.64,142.4 1714,85 C1747,76.29 1778.9,70.6 1805,58 C1856.52,33.13 1894.52,-10.44 1911,-72 C1930.34,-144.21 1903.92,-211.1 1869,-252 C1830.94,-296.58 1772.68,-326.88 1701,-321c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="683"
+                    android:valueFrom="M1701 -321 C1684.31,-319.63 1667.92,-316.14 1651.65,-311.99 C1635.38,-307.84 1619.22,-303.03 1603,-299 C1476.28,-267.54 1347.03,-238.76 1214.58,-213.61 C1082.13,-188.46 946.5,-166.94 807,-150 C789.03,-147.82 771.91,-145.33 755.97,-141.75 C740.03,-138.18 725.27,-133.52 712,-127 C685.66,-114.05 663.18,-96.41 645.39,-74.45 C627.61,-52.5 614.53,-26.22 607,4 C597.72,41.22 599.94,76.18 608.87,106.85 C617.8,137.53 633.44,163.92 651,184 C686.76,224.89 741.19,258.64 818,252 C887.92,245.96 957.59,233.95 1026,224 C1266.28,189.04 1496.64,142.4 1714,85 C1747,76.29 1778.9,70.6 1805,58 C1856.52,33.13 1894.52,-10.44 1911,-72 C1930.34,-144.21 1903.92,-211.1 1869,-252 C1830.94,-296.58 1772.68,-326.88 1701,-321c "
+                    android:valueTo="M1762 -336 C1746.19,-334.9 1731.2,-332.08 1716.53,-328.57 C1701.87,-325.06 1687.52,-320.86 1673,-317 C1574.29,-290.78 1473.48,-266.39 1370.87,-244.37 C1268.27,-222.35 1163.87,-202.72 1058,-186 C1025.18,-180.82 995.89,-176.47 969.45,-168.96 C943.01,-161.46 919.42,-150.8 898,-133 C879.29,-117.45 862.09,-98.59 849.04,-75.55 C836,-52.51 827.1,-25.29 825,7 C822.78,41.02 828.46,71.1 838.84,97.01 C849.23,122.92 864.34,144.66 881,162 C915.41,197.81 964.38,224.82 1035,222 C1064.82,220.81 1096.18,213.17 1127,208 C1342.04,171.91 1545.18,128.66 1743,78 C1802.17,62.85 1856.59,51.3 1897,19 C1934.73,-11.15 1965.34,-55.38 1971,-120 C1976.73,-185.43 1950.08,-240.04 1916,-276 C1883.45,-310.34 1827.33,-340.53 1762,-336c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="700"
+                    android:valueFrom="M1762 -336 C1746.19,-334.9 1731.2,-332.08 1716.53,-328.57 C1701.87,-325.06 1687.52,-320.86 1673,-317 C1574.29,-290.78 1473.48,-266.39 1370.87,-244.37 C1268.27,-222.35 1163.87,-202.72 1058,-186 C1025.18,-180.82 995.89,-176.47 969.45,-168.96 C943.01,-161.46 919.42,-150.8 898,-133 C879.29,-117.45 862.09,-98.59 849.04,-75.55 C836,-52.51 827.1,-25.29 825,7 C822.78,41.02 828.46,71.1 838.84,97.01 C849.23,122.92 864.34,144.66 881,162 C915.41,197.81 964.38,224.82 1035,222 C1064.82,220.81 1096.18,213.17 1127,208 C1342.04,171.91 1545.18,128.66 1743,78 C1802.17,62.85 1856.59,51.3 1897,19 C1934.73,-11.15 1965.34,-55.38 1971,-120 C1976.73,-185.43 1950.08,-240.04 1916,-276 C1883.45,-310.34 1827.33,-340.53 1762,-336c "
+                    android:valueTo="M1808 -349 C1793.7,-348.01 1780.06,-345.52 1766.69,-342.4 C1753.32,-339.27 1740.22,-335.52 1727,-332 C1713.99,-328.54 1700.95,-324.87 1688.18,-321.27 C1675.41,-317.68 1662.91,-314.16 1651,-311 C1572.35,-290.15 1494.41,-272.11 1415.73,-255.17 C1337.05,-238.24 1257.62,-222.42 1176,-206 C1149.31,-200.63 1126.51,-191.65 1106.78,-179.38 C1087.04,-167.11 1070.39,-151.55 1056,-133 C1027.49,-96.25 1007.2,-47.97 1014,14 C1019.89,67.66 1045.55,110.83 1080,141 C1113.35,170.21 1161.55,194.66 1223,190 C1277.44,185.87 1332.96,171.84 1387,161 C1548.03,128.69 1702.89,88.79 1854,48 C1905.15,34.19 1945.33,13.53 1975,-24 C2002.3,-58.53 2024.95,-108.71 2018,-172 C2012.18,-225.03 1984.89,-270 1952,-299 C1918.54,-328.51 1867.48,-353.12 1808,-349c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="717"
+                    android:valueFrom="M1808 -349 C1793.7,-348.01 1780.06,-345.52 1766.69,-342.4 C1753.32,-339.27 1740.22,-335.52 1727,-332 C1713.99,-328.54 1700.95,-324.87 1688.18,-321.27 C1675.41,-317.68 1662.91,-314.16 1651,-311 C1572.35,-290.15 1494.41,-272.11 1415.73,-255.17 C1337.05,-238.24 1257.62,-222.42 1176,-206 C1149.31,-200.63 1126.51,-191.65 1106.78,-179.38 C1087.04,-167.11 1070.39,-151.55 1056,-133 C1027.49,-96.25 1007.2,-47.97 1014,14 C1019.89,67.66 1045.55,110.83 1080,141 C1113.35,170.21 1161.55,194.66 1223,190 C1277.44,185.87 1332.96,171.84 1387,161 C1548.03,128.69 1702.89,88.79 1854,48 C1905.15,34.19 1945.33,13.53 1975,-24 C2002.3,-58.53 2024.95,-108.71 2018,-172 C2012.18,-225.03 1984.89,-270 1952,-299 C1918.54,-328.51 1867.48,-353.12 1808,-349c "
+                    android:valueTo="M1853 -361 C1826.42,-359.57 1802.2,-354.21 1778.74,-347.63 C1755.28,-341.06 1732.57,-333.27 1709,-327 C1663.45,-314.88 1616.15,-302.72 1568.63,-290.92 C1521.12,-279.12 1473.39,-267.68 1427,-257 C1404.39,-251.79 1378.86,-247.68 1354,-242.54 C1329.14,-237.41 1304.95,-231.27 1285,-222 C1248.81,-205.19 1214.39,-175.2 1192.48,-135.53 C1170.57,-95.86 1161.16,-46.51 1175,9 C1186.23,54.06 1211.95,90.39 1244,116 C1275.98,141.56 1320.49,162.26 1377,160 C1401.44,159.02 1426.8,152.54 1452,147 C1573.59,120.28 1692.02,92.2 1806,61 C1851.55,48.53 1902.21,39.28 1943,21 C1981.01,3.96 2014.53,-25.54 2035,-63 C2054.98,-99.57 2068.68,-155.45 2055,-210 C2043.49,-255.87 2017.77,-291.27 1986,-317 C1955.82,-341.45 1906.33,-363.87 1853,-361c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="733"
+                    android:valueFrom="M1853 -361 C1826.42,-359.57 1802.2,-354.21 1778.74,-347.63 C1755.28,-341.06 1732.57,-333.27 1709,-327 C1663.45,-314.88 1616.15,-302.72 1568.63,-290.92 C1521.12,-279.12 1473.39,-267.68 1427,-257 C1404.39,-251.79 1378.86,-247.68 1354,-242.54 C1329.14,-237.41 1304.95,-231.27 1285,-222 C1248.81,-205.19 1214.39,-175.2 1192.48,-135.53 C1170.57,-95.86 1161.16,-46.51 1175,9 C1186.23,54.06 1211.95,90.39 1244,116 C1275.98,141.56 1320.49,162.26 1377,160 C1401.44,159.02 1426.8,152.54 1452,147 C1573.59,120.28 1692.02,92.2 1806,61 C1851.55,48.53 1902.21,39.28 1943,21 C1981.01,3.96 2014.53,-25.54 2035,-63 C2054.98,-99.57 2068.68,-155.45 2055,-210 C2043.49,-255.87 2017.77,-291.27 1986,-317 C1955.82,-341.45 1906.33,-363.87 1853,-361c "
+                    android:valueTo="M1879 -371 C1855.84,-369.1 1833.88,-363.8 1812.31,-357.5 C1790.74,-351.2 1769.57,-343.9 1748,-338 C1705.55,-326.38 1662.66,-314.62 1619.58,-303.26 C1576.5,-291.91 1533.22,-280.97 1490,-271 C1477.96,-268.22 1466.77,-265.89 1456.19,-263.3 C1445.61,-260.7 1435.63,-257.84 1426,-254 C1397.93,-242.8 1372.88,-225.68 1352.62,-204.17 C1332.36,-182.66 1316.9,-156.75 1308,-128 C1301.29,-106.31 1298.77,-82.16 1300.06,-58.26 C1301.35,-34.35 1306.46,-10.7 1315,10 C1328.97,43.86 1354.44,76.47 1388.92,99.44 C1423.41,122.41 1466.93,135.73 1517,131 C1538.99,128.93 1561.43,122.33 1584,117 C1694.61,90.86 1801.5,62.81 1906,33 C1950.61,20.28 1990.32,9.42 2021,-15 C2077.17,-59.7 2121.6,-152.41 2082,-249 C2052.82,-320.16 1979.29,-379.23 1879,-371c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="750"
+                    android:valueFrom="M1879 -371 C1855.84,-369.1 1833.88,-363.8 1812.31,-357.5 C1790.74,-351.2 1769.57,-343.9 1748,-338 C1705.55,-326.38 1662.66,-314.62 1619.58,-303.26 C1576.5,-291.91 1533.22,-280.97 1490,-271 C1477.96,-268.22 1466.77,-265.89 1456.19,-263.3 C1445.61,-260.7 1435.63,-257.84 1426,-254 C1397.93,-242.8 1372.88,-225.68 1352.62,-204.17 C1332.36,-182.66 1316.9,-156.75 1308,-128 C1301.29,-106.31 1298.77,-82.16 1300.06,-58.26 C1301.35,-34.35 1306.46,-10.7 1315,10 C1328.97,43.86 1354.44,76.47 1388.92,99.44 C1423.41,122.41 1466.93,135.73 1517,131 C1538.99,128.93 1561.43,122.33 1584,117 C1694.61,90.86 1801.5,62.81 1906,33 C1950.61,20.28 1990.32,9.42 2021,-15 C2077.17,-59.7 2121.6,-152.41 2082,-249 C2052.82,-320.16 1979.29,-379.23 1879,-371c "
+                    android:valueTo="M1917 -381 C1894.47,-379.33 1874.18,-374.77 1854.43,-369.16 C1834.67,-363.54 1815.44,-356.87 1795,-351 C1775.41,-345.37 1755.69,-339.8 1735.98,-334.3 C1716.28,-328.8 1696.57,-323.37 1677,-318 C1655.71,-312.16 1635.71,-307.53 1615.91,-302.93 C1596.11,-298.33 1576.51,-293.75 1556,-288 C1518.95,-277.61 1486.57,-257.44 1461.65,-230.32 C1436.73,-203.2 1419.25,-169.14 1412,-131 C1407.72,-108.49 1407.85,-85.43 1411.23,-63.65 C1414.61,-41.87 1421.25,-21.38 1430,-4 C1445.1,25.99 1469.59,55.09 1501.94,75.77 C1534.28,96.45 1574.48,108.71 1621,105 C1662.49,101.69 1703.3,88.68 1744,78 C1824.94,56.76 1901.6,35.95 1980,12 C2055.14,-10.95 2109.6,-63.03 2125,-144 C2134.11,-191.88 2123.42,-237.96 2108,-270 C2078.07,-332.19 2008.91,-387.8 1917,-381c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="767"
+                    android:valueFrom="M1917 -381 C1894.47,-379.33 1874.18,-374.77 1854.43,-369.16 C1834.67,-363.54 1815.44,-356.87 1795,-351 C1775.41,-345.37 1755.69,-339.8 1735.98,-334.3 C1716.28,-328.8 1696.57,-323.37 1677,-318 C1655.71,-312.16 1635.71,-307.53 1615.91,-302.93 C1596.11,-298.33 1576.51,-293.75 1556,-288 C1518.95,-277.61 1486.57,-257.44 1461.65,-230.32 C1436.73,-203.2 1419.25,-169.14 1412,-131 C1407.72,-108.49 1407.85,-85.43 1411.23,-63.65 C1414.61,-41.87 1421.25,-21.38 1430,-4 C1445.1,25.99 1469.59,55.09 1501.94,75.77 C1534.28,96.45 1574.48,108.71 1621,105 C1662.49,101.69 1703.3,88.68 1744,78 C1824.94,56.76 1901.6,35.95 1980,12 C2055.14,-10.95 2109.6,-63.03 2125,-144 C2134.11,-191.88 2123.42,-237.96 2108,-270 C2078.07,-332.19 2008.91,-387.8 1917,-381c "
+                    android:valueTo="M1938 -389 C1918.02,-387.29 1899.13,-382.71 1880.56,-377.22 C1861.99,-371.74 1843.73,-365.34 1825,-360 C1807.04,-354.88 1788.76,-349.64 1770.36,-344.43 C1751.95,-339.21 1733.43,-334.02 1715,-329 C1695.02,-323.55 1676.29,-319.2 1658.55,-314.13 C1640.81,-309.06 1624.04,-303.29 1608,-295 C1579.39,-280.22 1552.9,-257.61 1533.32,-228.25 C1513.73,-198.88 1501.03,-162.77 1500,-121 C1499.48,-99.88 1502.66,-79.69 1508.25,-61.13 C1513.84,-42.57 1521.86,-25.63 1531,-11 C1565.04,43.45 1626.35,88.61 1719,81 C1756.71,77.9 1795.59,64.05 1832,54 C1869.38,43.69 1905.63,33.72 1942,23 C1979,12.1 2017.29,3.77 2048,-12 C2106.27,-41.91 2155.33,-100.64 2157,-185 C2157.86,-228.59 2143.96,-268.45 2127,-296 C2092.42,-352.19 2025.63,-396.5 1938,-389c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="783"
+                    android:valueFrom="M1938 -389 C1918.02,-387.29 1899.13,-382.71 1880.56,-377.22 C1861.99,-371.74 1843.73,-365.34 1825,-360 C1807.04,-354.88 1788.76,-349.64 1770.36,-344.43 C1751.95,-339.21 1733.43,-334.02 1715,-329 C1695.02,-323.55 1676.29,-319.2 1658.55,-314.13 C1640.81,-309.06 1624.04,-303.29 1608,-295 C1579.39,-280.22 1552.9,-257.61 1533.32,-228.25 C1513.73,-198.88 1501.03,-162.77 1500,-121 C1499.48,-99.88 1502.66,-79.69 1508.25,-61.13 C1513.84,-42.57 1521.86,-25.63 1531,-11 C1565.04,43.45 1626.35,88.61 1719,81 C1756.71,77.9 1795.59,64.05 1832,54 C1869.38,43.69 1905.63,33.72 1942,23 C1979,12.1 2017.29,3.77 2048,-12 C2106.27,-41.91 2155.33,-100.64 2157,-185 C2157.86,-228.59 2143.96,-268.45 2127,-296 C2092.42,-352.19 2025.63,-396.5 1938,-389c "
+                    android:valueTo="M1968 -397 C1948.07,-395.44 1929.72,-391.19 1912.16,-386.08 C1894.59,-380.97 1877.8,-375 1861,-370 C1843.57,-364.82 1826.22,-359.67 1809.03,-354.64 C1791.83,-349.62 1774.8,-344.71 1758,-340 C1738.06,-334.41 1721.02,-329.65 1705.61,-323.91 C1690.2,-318.18 1676.43,-311.48 1663,-302 C1651,-293.53 1639.65,-284.24 1629.09,-272.96 C1618.54,-261.69 1608.79,-248.42 1600,-232 C1582.06,-198.48 1576.15,-159.81 1579.3,-122.98 C1582.45,-86.15 1594.66,-51.16 1613,-25 C1648.02,24.96 1705.28,67.84 1791,61 C1828.15,58.04 1863.89,45.73 1898,36 C1932.04,26.29 1967.24,16.14 2001,6 C2034.31,-4 2068.94,-15.13 2095,-33 C2143.84,-66.49 2187.78,-128.14 2182,-212 C2179.47,-248.64 2164.22,-286.95 2148,-311 C2114.12,-361.25 2048.55,-403.3 1968,-397c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="800"
+                    android:valueFrom="M1968 -397 C1948.07,-395.44 1929.72,-391.19 1912.16,-386.08 C1894.59,-380.97 1877.8,-375 1861,-370 C1843.57,-364.82 1826.22,-359.67 1809.03,-354.64 C1791.83,-349.62 1774.8,-344.71 1758,-340 C1738.06,-334.41 1721.02,-329.65 1705.61,-323.91 C1690.2,-318.18 1676.43,-311.48 1663,-302 C1651,-293.53 1639.65,-284.24 1629.09,-272.96 C1618.54,-261.69 1608.79,-248.42 1600,-232 C1582.06,-198.48 1576.15,-159.81 1579.3,-122.98 C1582.45,-86.15 1594.66,-51.16 1613,-25 C1648.02,24.96 1705.28,67.84 1791,61 C1828.15,58.04 1863.89,45.73 1898,36 C1932.04,26.29 1967.24,16.14 2001,6 C2034.31,-4 2068.94,-15.13 2095,-33 C2143.84,-66.49 2187.78,-128.14 2182,-212 C2179.47,-248.64 2164.22,-286.95 2148,-311 C2114.12,-361.25 2048.55,-403.3 1968,-397c "
+                    android:valueTo="M1994 -404 C1974.92,-402.77 1957.48,-399.23 1940.77,-394.77 C1924.07,-390.3 1908.1,-384.92 1892,-380 C1875.77,-375.04 1859.61,-370.12 1843.46,-365.27 C1827.3,-360.42 1811.17,-355.65 1795,-351 C1762.37,-341.62 1734.85,-328.32 1712.13,-309.52 C1689.4,-290.71 1671.47,-266.4 1658,-235 C1643.1,-200.25 1640.2,-162.74 1645.36,-128.05 C1650.51,-93.35 1663.71,-61.48 1681,-38 C1697.9,-15.06 1720.48,6.55 1748.8,21.74 C1777.12,36.94 1811.17,45.72 1851,43 C1869.38,41.75 1886.68,38.15 1903.48,33.67 C1920.29,29.18 1936.6,23.81 1953,19 C1986.59,9.16 2019.21,-0.58 2050,-10 C2116.98,-30.5 2161.4,-66.4 2187,-126 C2200.41,-157.22 2207.11,-195.75 2202,-233 C2196.75,-271.26 2182.39,-298.2 2165,-322 C2131.47,-367.9 2073.87,-409.14 1994,-404c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="817"
+                    android:valueFrom="M1994 -404 C1974.92,-402.77 1957.48,-399.23 1940.77,-394.77 C1924.07,-390.3 1908.1,-384.92 1892,-380 C1875.77,-375.04 1859.61,-370.12 1843.46,-365.27 C1827.3,-360.42 1811.17,-355.65 1795,-351 C1762.37,-341.62 1734.85,-328.32 1712.13,-309.52 C1689.4,-290.71 1671.47,-266.4 1658,-235 C1643.1,-200.25 1640.2,-162.74 1645.36,-128.05 C1650.51,-93.35 1663.71,-61.48 1681,-38 C1697.9,-15.06 1720.48,6.55 1748.8,21.74 C1777.12,36.94 1811.17,45.72 1851,43 C1869.38,41.75 1886.68,38.15 1903.48,33.67 C1920.29,29.18 1936.6,23.81 1953,19 C1986.59,9.16 2019.21,-0.58 2050,-10 C2116.98,-30.5 2161.4,-66.4 2187,-126 C2200.41,-157.22 2207.11,-195.75 2202,-233 C2196.75,-271.26 2182.39,-298.2 2165,-322 C2131.47,-367.9 2073.87,-409.14 1994,-404c "
+                    android:valueTo="M2013 -410 C2003.61,-409.39 1994.32,-408.36 1985.41,-406.87 C1976.49,-405.39 1967.94,-403.44 1960,-401 C1936.86,-393.89 1913.03,-387.2 1889.78,-380.25 C1866.53,-373.31 1843.85,-366.11 1823,-358 C1795.57,-347.33 1771.25,-331.83 1751.32,-311.26 C1731.4,-290.68 1715.87,-265.02 1706,-234 C1694.29,-197.19 1694.75,-160.83 1702.28,-128.48 C1709.8,-96.13 1724.41,-67.79 1741,-47 C1758.18,-25.47 1780.08,-5.59 1807.49,8.35 C1834.89,22.29 1867.8,30.27 1907,28 C1923.88,27.02 1940.1,23.7 1956.12,19.41 C1972.14,15.12 1987.95,9.86 2004,5 C2019.65,0.27 2035.34,-4.2 2050.65,-8.88 C2065.96,-13.55 2080.89,-18.44 2095,-24 C2153.39,-47.02 2192.43,-90.22 2212,-148 C2235.67,-217.89 2212.62,-293.74 2180,-335 C2149.08,-374.11 2090.43,-414.98 2013,-410c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="833"
+                    android:valueFrom="M2013 -410 C2003.61,-409.39 1994.32,-408.36 1985.41,-406.87 C1976.49,-405.39 1967.94,-403.44 1960,-401 C1936.86,-393.89 1913.03,-387.2 1889.78,-380.25 C1866.53,-373.31 1843.85,-366.11 1823,-358 C1795.57,-347.33 1771.25,-331.83 1751.32,-311.26 C1731.4,-290.68 1715.87,-265.02 1706,-234 C1694.29,-197.19 1694.75,-160.83 1702.28,-128.48 C1709.8,-96.13 1724.41,-67.79 1741,-47 C1758.18,-25.47 1780.08,-5.59 1807.49,8.35 C1834.89,22.29 1867.8,30.27 1907,28 C1923.88,27.02 1940.1,23.7 1956.12,19.41 C1972.14,15.12 1987.95,9.86 2004,5 C2019.65,0.27 2035.34,-4.2 2050.65,-8.88 C2065.96,-13.55 2080.89,-18.44 2095,-24 C2153.39,-47.02 2192.43,-90.22 2212,-148 C2235.67,-217.89 2212.62,-293.74 2180,-335 C2149.08,-374.11 2090.43,-414.98 2013,-410c "
+                    android:valueTo="M2026 -415 C2008.21,-413.68 1992.19,-410.31 1976.88,-406.07 C1961.57,-401.83 1946.96,-396.74 1932,-392 C1916.81,-387.18 1901.5,-382.75 1887.03,-377.86 C1872.55,-372.97 1858.89,-367.63 1847,-361 C1823.18,-347.72 1801.58,-330.64 1784.24,-309.04 C1766.9,-287.45 1753.81,-261.34 1747,-230 C1738.93,-192.85 1741.7,-158.46 1750.72,-128.55 C1759.75,-98.64 1775.03,-73.21 1792,-54 C1809.62,-34.04 1831.33,-15.84 1858.28,-3.23 C1885.23,9.38 1917.42,16.4 1956,14 C1972.29,12.99 1987.98,9.55 2003.26,5.22 C2018.53,0.88 2033.39,-4.37 2048,-9 C2062.62,-13.63 2077.58,-18.1 2092.03,-23.05 C2106.48,-28 2120.42,-33.44 2133,-40 C2183.73,-66.48 2220.06,-111.51 2234,-170 C2251.17,-242.03 2221.03,-311.29 2190,-347 C2155.44,-386.78 2098.94,-420.39 2026,-415c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="850"
+                    android:valueFrom="M2026 -415 C2008.21,-413.68 1992.19,-410.31 1976.88,-406.07 C1961.57,-401.83 1946.96,-396.74 1932,-392 C1916.81,-387.18 1901.5,-382.75 1887.03,-377.86 C1872.55,-372.97 1858.89,-367.63 1847,-361 C1823.18,-347.72 1801.58,-330.64 1784.24,-309.04 C1766.9,-287.45 1753.81,-261.34 1747,-230 C1738.93,-192.85 1741.7,-158.46 1750.72,-128.55 C1759.75,-98.64 1775.03,-73.21 1792,-54 C1809.62,-34.04 1831.33,-15.84 1858.28,-3.23 C1885.23,9.38 1917.42,16.4 1956,14 C1972.29,12.99 1987.98,9.55 2003.26,5.22 C2018.53,0.88 2033.39,-4.37 2048,-9 C2062.62,-13.63 2077.58,-18.1 2092.03,-23.05 C2106.48,-28 2120.42,-33.44 2133,-40 C2183.73,-66.48 2220.06,-111.51 2234,-170 C2251.17,-242.03 2221.03,-311.29 2190,-347 C2155.44,-386.78 2098.94,-420.39 2026,-415c "
+                    android:valueTo="M2035 -419 C2018.03,-417.61 2002.83,-414.17 1988.22,-409.92 C1973.61,-405.68 1959.6,-400.62 1945,-396 C1929.99,-391.24 1915.88,-386.54 1902.83,-381.12 C1889.77,-375.71 1877.78,-369.59 1867,-362 C1846.17,-347.34 1826.91,-328.8 1811.85,-305.82 C1796.8,-282.85 1785.97,-255.42 1782,-223 C1777.51,-186.32 1782.26,-154.15 1792.5,-126.62 C1802.75,-99.08 1818.49,-76.18 1836,-58 C1853.47,-39.86 1875.92,-23.08 1903.02,-11.72 C1930.13,-0.36 1961.89,5.57 1998,2 C2013.61,0.46 2028.33,-2.98 2042.78,-7.18 C2057.24,-11.39 2071.43,-16.37 2086,-21 C2099.99,-25.45 2114.29,-30 2127.82,-35.29 C2141.34,-40.57 2154.1,-46.6 2165,-54 C2209.24,-84.04 2242.69,-131.41 2251,-192 C2261.06,-265.31 2230.86,-322.96 2198,-358 C2164.79,-393.41 2109.99,-425.16 2035,-419c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="867"
+                    android:valueFrom="M2035 -419 C2018.03,-417.61 2002.83,-414.17 1988.22,-409.92 C1973.61,-405.68 1959.6,-400.62 1945,-396 C1929.99,-391.24 1915.88,-386.54 1902.83,-381.12 C1889.77,-375.71 1877.78,-369.59 1867,-362 C1846.17,-347.34 1826.91,-328.8 1811.85,-305.82 C1796.8,-282.85 1785.97,-255.42 1782,-223 C1777.51,-186.32 1782.26,-154.15 1792.5,-126.62 C1802.75,-99.08 1818.49,-76.18 1836,-58 C1853.47,-39.86 1875.92,-23.08 1903.02,-11.72 C1930.13,-0.36 1961.89,5.57 1998,2 C2013.61,0.46 2028.33,-2.98 2042.78,-7.18 C2057.24,-11.39 2071.43,-16.37 2086,-21 C2099.99,-25.45 2114.29,-30 2127.82,-35.29 C2141.34,-40.57 2154.1,-46.6 2165,-54 C2209.24,-84.04 2242.69,-131.41 2251,-192 C2261.06,-265.31 2230.86,-322.96 2198,-358 C2164.79,-393.41 2109.99,-425.16 2035,-419c "
+                    android:valueTo="M2048 -423 C2034.52,-421.89 2023.13,-419.7 2012.3,-416.87 C2001.47,-414.05 1991.22,-410.61 1980,-407 C1969.12,-403.5 1958.34,-400.34 1948.04,-396.88 C1937.74,-393.43 1927.93,-389.67 1919,-385 C1899.26,-374.66 1883.06,-362.52 1869.52,-348.78 C1855.98,-335.04 1845.1,-319.71 1836,-303 C1826.17,-284.95 1818.62,-262.83 1814.8,-239.16 C1810.98,-215.48 1810.9,-190.26 1816,-166 C1824.81,-124.13 1845.68,-87.42 1875.54,-59.91 C1905.39,-32.39 1944.24,-14.08 1989,-9 C2015.48,-5.99 2038.19,-8.51 2059.92,-13.64 C2081.65,-18.77 2102.41,-26.52 2125,-34 C2162.45,-46.39 2197.45,-69.04 2223.09,-101.01 C2248.73,-132.98 2265,-174.29 2265,-224 C2265,-257.55 2257.85,-285.51 2246.41,-309.23 C2234.96,-332.95 2219.21,-352.42 2202,-369 C2168.19,-401.57 2116.15,-428.59 2048,-423c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="883"
+                    android:valueFrom="M2048 -423 C2034.52,-421.89 2023.13,-419.7 2012.3,-416.87 C2001.47,-414.05 1991.22,-410.61 1980,-407 C1969.12,-403.5 1958.34,-400.34 1948.04,-396.88 C1937.74,-393.43 1927.93,-389.67 1919,-385 C1899.26,-374.66 1883.06,-362.52 1869.52,-348.78 C1855.98,-335.04 1845.1,-319.71 1836,-303 C1826.17,-284.95 1818.62,-262.83 1814.8,-239.16 C1810.98,-215.48 1810.9,-190.26 1816,-166 C1824.81,-124.13 1845.68,-87.42 1875.54,-59.91 C1905.39,-32.39 1944.24,-14.08 1989,-9 C2015.48,-5.99 2038.19,-8.51 2059.92,-13.64 C2081.65,-18.77 2102.41,-26.52 2125,-34 C2162.45,-46.39 2197.45,-69.04 2223.09,-101.01 C2248.73,-132.98 2265,-174.29 2265,-224 C2265,-257.55 2257.85,-285.51 2246.41,-309.23 C2234.96,-332.95 2219.21,-352.42 2202,-369 C2168.19,-401.57 2116.15,-428.59 2048,-423c "
+                    android:valueTo="M2056 -426 C2040.73,-424.75 2025.49,-421.51 2010.94,-417.46 C1996.39,-413.4 1982.52,-408.53 1970,-404 C1956.36,-399.06 1944.24,-393.17 1933.34,-386.46 C1922.45,-379.75 1912.77,-372.22 1904,-364 C1886.94,-348 1870.57,-328.74 1858.28,-304.99 C1845.98,-281.24 1837.76,-252.98 1837,-219 C1836.25,-185.71 1842.79,-156.92 1854.12,-132.22 C1865.45,-107.52 1881.57,-86.92 1900,-70 C1917.59,-53.86 1939.31,-38.58 1965.28,-28.23 C1991.24,-17.87 2021.45,-12.45 2056,-16 C2069.73,-17.41 2083.86,-20.52 2098,-24.48 C2112.14,-28.43 2126.27,-33.23 2140,-38 C2177.19,-50.93 2209.98,-73.51 2233.83,-104.56 C2257.68,-135.61 2272.58,-175.15 2274,-222 C2275.03,-256.11 2267.97,-285.17 2256.56,-309.79 C2245.15,-334.42 2229.38,-354.62 2213,-371 C2179.81,-404.19 2126.45,-431.78 2056,-426c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="900"
+                    android:valueFrom="M2056 -426 C2040.73,-424.75 2025.49,-421.51 2010.94,-417.46 C1996.39,-413.4 1982.52,-408.53 1970,-404 C1956.36,-399.06 1944.24,-393.17 1933.34,-386.46 C1922.45,-379.75 1912.77,-372.22 1904,-364 C1886.94,-348 1870.57,-328.74 1858.28,-304.99 C1845.98,-281.24 1837.76,-252.98 1837,-219 C1836.25,-185.71 1842.79,-156.92 1854.12,-132.22 C1865.45,-107.52 1881.57,-86.92 1900,-70 C1917.59,-53.86 1939.31,-38.58 1965.28,-28.23 C1991.24,-17.87 2021.45,-12.45 2056,-16 C2069.73,-17.41 2083.86,-20.52 2098,-24.48 C2112.14,-28.43 2126.27,-33.23 2140,-38 C2177.19,-50.93 2209.98,-73.51 2233.83,-104.56 C2257.68,-135.61 2272.58,-175.15 2274,-222 C2275.03,-256.11 2267.97,-285.17 2256.56,-309.79 C2245.15,-334.42 2229.38,-354.62 2213,-371 C2179.81,-404.19 2126.45,-431.78 2056,-426c "
+                    android:valueTo="M2070 -429 C2054.11,-427.83 2039.39,-424.99 2025.5,-421.24 C2011.62,-417.49 1998.56,-412.83 1986,-408 C1972.35,-402.75 1960.84,-396.75 1950.5,-390.06 C1940.16,-383.37 1930.98,-375.99 1922,-368 C1904.86,-352.74 1888.86,-333.04 1877.15,-309 C1865.43,-284.96 1858,-256.59 1858,-224 C1858,-191.48 1864.22,-163.11 1874.96,-138.68 C1885.69,-114.24 1900.94,-93.75 1919,-77 C1936.46,-60.8 1957.91,-45.91 1983.4,-35.64 C2008.89,-25.36 2038.42,-19.71 2072,-22 C2085.63,-22.93 2099.7,-25.74 2113.57,-29.54 C2127.45,-33.34 2141.13,-38.13 2154,-43 C2188.92,-56.23 2220.88,-79.24 2244.25,-110.32 C2267.62,-141.39 2282.41,-180.53 2283,-226 C2283.44,-259.42 2276.49,-288.29 2265.28,-312.75 C2254.07,-337.2 2238.61,-357.24 2222,-373 C2189.33,-404.01 2137.11,-433.96 2070,-429c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="917"
+                    android:valueFrom="M2070 -429 C2054.11,-427.83 2039.39,-424.99 2025.5,-421.24 C2011.62,-417.49 1998.56,-412.83 1986,-408 C1972.35,-402.75 1960.84,-396.75 1950.5,-390.06 C1940.16,-383.37 1930.98,-375.99 1922,-368 C1904.86,-352.74 1888.86,-333.04 1877.15,-309 C1865.43,-284.96 1858,-256.59 1858,-224 C1858,-191.48 1864.22,-163.11 1874.96,-138.68 C1885.69,-114.24 1900.94,-93.75 1919,-77 C1936.46,-60.8 1957.91,-45.91 1983.4,-35.64 C2008.89,-25.36 2038.42,-19.71 2072,-22 C2085.63,-22.93 2099.7,-25.74 2113.57,-29.54 C2127.45,-33.34 2141.13,-38.13 2154,-43 C2188.92,-56.23 2220.88,-79.24 2244.25,-110.32 C2267.62,-141.39 2282.41,-180.53 2283,-226 C2283.44,-259.42 2276.49,-288.29 2265.28,-312.75 C2254.07,-337.2 2238.61,-357.24 2222,-373 C2189.33,-404.01 2137.11,-433.96 2070,-429c "
+                    android:valueTo="M2074 -431 C2067.27,-430.47 2061.91,-429.95 2057.16,-429.32 C2052.4,-428.69 2048.27,-427.96 2044,-427 C2016.54,-420.84 1992.45,-411.46 1971.52,-399.05 C1950.6,-386.65 1932.83,-371.23 1918,-353 C1905.73,-337.91 1892.68,-315.95 1883.98,-289.37 C1875.27,-262.79 1870.91,-231.58 1876,-198 C1880.07,-171.13 1888.38,-147.32 1900.64,-126.25 C1912.9,-105.19 1929.11,-86.87 1949,-71 C1967.14,-56.52 1990.42,-43.76 2016.89,-35.75 C2043.37,-27.74 2073.05,-24.48 2104,-29 C2131.69,-33.04 2156.62,-41.35 2178.66,-53 C2200.71,-64.65 2219.86,-79.63 2236,-97 C2251.9,-114.12 2266.06,-135.28 2275.66,-159.96 C2285.26,-184.64 2290.3,-212.83 2288,-244 C2285.7,-275.1 2278.29,-301.18 2267.16,-323.44 C2256.03,-345.7 2241.18,-364.14 2224,-380 C2192.75,-408.84 2138.58,-436.05 2074,-431c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="933"
+                    android:valueFrom="M2074 -431 C2067.27,-430.47 2061.91,-429.95 2057.16,-429.32 C2052.4,-428.69 2048.27,-427.96 2044,-427 C2016.54,-420.84 1992.45,-411.46 1971.52,-399.05 C1950.6,-386.65 1932.83,-371.23 1918,-353 C1905.73,-337.91 1892.68,-315.95 1883.98,-289.37 C1875.27,-262.79 1870.91,-231.58 1876,-198 C1880.07,-171.13 1888.38,-147.32 1900.64,-126.25 C1912.9,-105.19 1929.11,-86.87 1949,-71 C1967.14,-56.52 1990.42,-43.76 2016.89,-35.75 C2043.37,-27.74 2073.05,-24.48 2104,-29 C2131.69,-33.04 2156.62,-41.35 2178.66,-53 C2200.71,-64.65 2219.86,-79.63 2236,-97 C2251.9,-114.12 2266.06,-135.28 2275.66,-159.96 C2285.26,-184.64 2290.3,-212.83 2288,-244 C2285.7,-275.1 2278.29,-301.18 2267.16,-323.44 C2256.03,-345.7 2241.18,-364.14 2224,-380 C2192.75,-408.84 2138.58,-436.05 2074,-431c "
+                    android:valueTo="M2090 -433 C2079.15,-432.82 2068.99,-431.84 2059.32,-430.22 C2049.66,-428.59 2040.5,-426.32 2031.67,-423.56 C2022.84,-420.81 2014.34,-417.57 2006,-414 C1972.49,-399.68 1942.32,-376.89 1920.49,-346.2 C1898.66,-315.52 1885.16,-276.93 1885,-231 C1884.89,-199.3 1891.54,-172.16 1902.39,-148.76 C1913.24,-125.35 1928.3,-105.7 1945,-89 C1961.99,-72.01 1982.49,-57.13 2006.61,-46.69 C2030.74,-36.25 2058.5,-30.24 2090,-31 C2118.98,-31.69 2146.38,-38.09 2170.71,-48.49 C2195.04,-58.89 2216.3,-73.3 2233,-90 C2249.92,-106.92 2265.03,-126.77 2275.99,-150.22 C2286.94,-173.67 2293.73,-200.71 2294,-232 C2294.27,-263.44 2287.4,-291.32 2276.52,-315.27 C2265.64,-339.22 2250.75,-359.25 2235,-375 C2218.75,-391.25 2198.15,-406 2173.79,-416.57 C2149.42,-427.13 2121.3,-433.51 2090,-433c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="950"
+                    android:valueFrom="M2090 -433 C2079.15,-432.82 2068.99,-431.84 2059.32,-430.22 C2049.66,-428.59 2040.5,-426.32 2031.67,-423.56 C2022.84,-420.81 2014.34,-417.57 2006,-414 C1972.49,-399.68 1942.32,-376.89 1920.49,-346.2 C1898.66,-315.52 1885.16,-276.93 1885,-231 C1884.89,-199.3 1891.54,-172.16 1902.39,-148.76 C1913.24,-125.35 1928.3,-105.7 1945,-89 C1961.99,-72.01 1982.49,-57.13 2006.61,-46.69 C2030.74,-36.25 2058.5,-30.24 2090,-31 C2118.98,-31.69 2146.38,-38.09 2170.71,-48.49 C2195.04,-58.89 2216.3,-73.3 2233,-90 C2249.92,-106.92 2265.03,-126.77 2275.99,-150.22 C2286.94,-173.67 2293.73,-200.71 2294,-232 C2294.27,-263.44 2287.4,-291.32 2276.52,-315.27 C2265.64,-339.22 2250.75,-359.25 2235,-375 C2218.75,-391.25 2198.15,-406 2173.79,-416.57 C2149.42,-427.13 2121.3,-433.51 2090,-433c "
+                    android:valueTo="M2088 -434 C2073.56,-433.07 2058.65,-430.77 2044.55,-427.34 C2030.45,-423.92 2017.17,-419.39 2006,-414 C1994.71,-408.55 1983.93,-402.05 1974.11,-394.92 C1964.29,-387.78 1955.43,-380.01 1948,-372 C1932.68,-355.48 1918.25,-334.46 1907.99,-309.83 C1897.73,-285.2 1891.64,-256.96 1893,-226 C1894.35,-195.33 1901.36,-168.86 1912.41,-146.04 C1923.45,-123.22 1938.53,-104.06 1956,-88 C1973.16,-72.23 1994.54,-57.77 2019.07,-47.74 C2043.6,-37.7 2071.27,-32.08 2101,-34 C2130.44,-35.9 2156.54,-42.38 2179.6,-52.91 C2202.66,-63.43 2222.69,-77.98 2240,-96 C2256.04,-112.7 2270.94,-132.52 2281.5,-156.25 C2292.07,-179.98 2298.29,-207.63 2297,-240 C2295.78,-270.62 2288.45,-297.7 2277.35,-320.96 C2266.24,-344.22 2251.35,-363.66 2235,-379 C2201.84,-410.09 2151.36,-438.08 2088,-434c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="pathData"
+                    android:startOffset="967"
+                    android:valueFrom="M2088 -434 C2073.56,-433.07 2058.65,-430.77 2044.55,-427.34 C2030.45,-423.92 2017.17,-419.39 2006,-414 C1994.71,-408.55 1983.93,-402.05 1974.11,-394.92 C1964.29,-387.78 1955.43,-380.01 1948,-372 C1932.68,-355.48 1918.25,-334.46 1907.99,-309.83 C1897.73,-285.2 1891.64,-256.96 1893,-226 C1894.35,-195.33 1901.36,-168.86 1912.41,-146.04 C1923.45,-123.22 1938.53,-104.06 1956,-88 C1973.16,-72.23 1994.54,-57.77 2019.07,-47.74 C2043.6,-37.7 2071.27,-32.08 2101,-34 C2130.44,-35.9 2156.54,-42.38 2179.6,-52.91 C2202.66,-63.43 2222.69,-77.98 2240,-96 C2256.04,-112.7 2270.94,-132.52 2281.5,-156.25 C2292.07,-179.98 2298.29,-207.63 2297,-240 C2295.78,-270.62 2288.45,-297.7 2277.35,-320.96 C2266.24,-344.22 2251.35,-363.66 2235,-379 C2201.84,-410.09 2151.36,-438.08 2088,-434c "
+                    android:valueTo="M2081 -434 C2061.85,-432.43 2043.71,-428.03 2026.99,-421.68 C2010.26,-415.32 1994.96,-407.01 1981.49,-397.61 C1968.02,-388.21 1956.39,-377.72 1947,-367 C1932.41,-350.35 1918.99,-329.51 1909.93,-304.5 C1900.87,-279.49 1896.16,-250.32 1899,-217 C1901.47,-187.99 1909.18,-162.6 1920.78,-140.53 C1932.39,-118.47 1947.91,-99.72 1966,-84 C1983.82,-68.52 2006.42,-55.12 2032.02,-46.29 C2057.61,-37.46 2086.2,-33.2 2116,-36 C2145.54,-38.78 2170.61,-46.49 2192.26,-58.02 C2213.91,-69.56 2232.15,-84.92 2248,-103 C2263.25,-120.39 2277.16,-141.16 2286.55,-165.69 C2295.94,-190.23 2300.82,-218.54 2298,-251 C2295.43,-280.64 2287.42,-306.3 2275.82,-328.47 C2264.21,-350.64 2248.99,-369.32 2232,-385 C2215.77,-399.98 2193.85,-413.53 2168.07,-422.69 C2142.29,-431.84 2112.66,-436.6 2081,-434c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="983"
+                    android:pathData="M 50,50C 50,50 50,50 50,50"
+                    android:propertyName="translateXY"
+                    android:propertyXName="translateX"
+                    android:propertyYName="translateY"
+                    android:startOffset="0">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:pathData="M 50,50C 50,50 50,50 50,50"
+                    android:propertyName="translateXY"
+                    android:propertyXName="translateX"
+                    android:propertyYName="translateY"
+                    android:startOffset="983">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_N_1_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="983"
+                    android:pathData="M 32,12C 32,12 32,12 32,12"
+                    android:propertyName="translateXY"
+                    android:propertyXName="translateX"
+                    android:propertyYName="translateY"
+                    android:startOffset="0">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:pathData="M 32,12C 32,12 32,12 32,12"
+                    android:propertyName="translateXY"
+                    android:propertyXName="translateX"
+                    android:propertyYName="translateY"
+                    android:startOffset="983">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="1000"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="64dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="64">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_2_G"
+                    android:translateX="32"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_2_G_D_0_P_0"
+                        android:fillAlpha="0"
+                        android:fillColor="?attr/colorControlNormal"
+                        android:fillType="nonZero"
+                        android:pathData=" M32 -12 C32,-12 32,12 32,12 C32,12 -32,12 -32,12 C-32,12 -32,-12 -32,-12 C-32,-12 32,-12 32,-12c " />
+                </group>
+                <group
+                    android:name="_R_G_L_1_G_N_1_T_1"
+                    android:scaleX="0.01"
+                    android:scaleY="0.01"
+                    android:translateX="32"
+                    android:translateY="12">
+                    <group
+                        android:name="_R_G_L_1_G_N_1_T_0"
+                        android:translateX="-50"
+                        android:translateY="-50">
+                        <group
+                            android:name="_R_G_L_1_G"
+                            android:translateX="50"
+                            android:translateY="50">
+                            <path
+                                android:name="_R_G_L_1_G_D_0_P_0"
+                                android:fillAlpha="0.2"
+                                android:fillColor="?attr/colorProgressBackgroundNormal"
+                                android:fillType="nonZero"
+                                android:pathData=" M2080 -434 C2053.41,-431.82 2032.66,-423.65 2009,-416 C1677.83,-308.94 1328.18,-225.31 948,-169 C552.55,-110.43 82.93,-85.03 -368,-110 C-945.44,-141.98 -1453.99,-244.34 -1920,-388 C-1941.56,-394.65 -1959.08,-402.44 -1986,-409 C-2037.81,-421.62 -2094.99,-409.05 -2131,-389 C-2199,-351.14 -2259.29,-260.99 -2224,-153 C-2209.83,-109.65 -2185.04,-77.07 -2151,-52 C-2117.95,-27.66 -2071.89,-16.07 -2026,-2 C-1671.19,106.76 -1289.85,190.97 -878,245 C-470.97,298.4 -1.15,313.9 448,286 C879.56,259.19 1286.12,192.07 1657,100 C1789.03,67.23 1935.92,26.61 2065,-14 C2109.16,-27.89 2157.33,-39.88 2194,-60 C2261.75,-97.17 2324.86,-186 2290,-295 C2263.92,-376.54 2191.93,-443.19 2080,-434c " />
+                        </group>
+                    </group>
+                </group>
+                <group
+                    android:name="_R_G_L_0_G_N_1_T_1"
+                    android:scaleX="0.01"
+                    android:scaleY="0.01"
+                    android:translateX="32"
+                    android:translateY="12">
+                    <group
+                        android:name="_R_G_L_0_G_N_1_T_0"
+                        android:translateX="-50"
+                        android:translateY="-50">
+                        <group
+                            android:name="_R_G_L_0_G"
+                            android:translateX="50"
+                            android:translateY="50">
+                            <path
+                                android:name="_R_G_L_0_G_D_0_P_0"
+                                android:fillAlpha="0"
+                                android:fillColor="?attr/colorControlNormal"
+                                android:fillType="nonZero"
+                                android:pathData=" M-2053 -413 C-2064.2,-412.04 -2074.66,-410.29 -2084.48,-407.87 C-2094.29,-405.46 -2103.47,-402.36 -2112.09,-398.71 C-2120.71,-395.06 -2128.79,-390.85 -2136.41,-386.19 C-2144.02,-381.54 -2151.19,-376.43 -2158,-371 C-2168.19,-362.86 -2177.05,-354.51 -2184.8,-345.79 C-2192.55,-337.07 -2199.2,-327.99 -2204.98,-318.41 C-2210.77,-308.83 -2215.7,-298.74 -2220,-288 C-2237.22,-245.04 -2237.06,-202.14 -2226.93,-164.3 C-2216.8,-126.46 -2196.69,-93.69 -2174,-71 C-2150.2,-47.2 -2115.92,-28.38 -2077.59,-19.79 C-2039.27,-11.2 -1996.92,-12.85 -1957,-30 C-1925.35,-43.59 -1896.01,-64.58 -1873.88,-92.68 C-1851.74,-120.77 -1836.82,-155.98 -1834,-198 C-1831.53,-234.86 -1837.92,-266 -1849.1,-292.1 C-1860.29,-318.21 -1876.28,-339.28 -1893,-356 C-1910.36,-373.36 -1932.3,-389.15 -1958.94,-399.84 C-1985.57,-410.52 -2016.89,-416.09 -2053,-413c " />
+                        </group>
+                    </group>
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/progress_horizontal_material.xml b/core/res/res/drawable-watch/progress_horizontal_material.xml
new file mode 100644
index 0000000..8c52a41
--- /dev/null
+++ b/core/res/res/drawable-watch/progress_horizontal_material.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@id/background"
+          android:gravity="center_vertical|fill_horizontal">
+        <shape android:shape="rectangle">
+            <corners android:radius="?attr/progressBarCornerRadius" />
+            <size android:height="@dimen/progress_bar_height_material" />
+            <solid android:color="@color/material_grey_900" />
+        </shape>
+    </item>
+    <item android:id="@id/secondaryProgress"
+          android:gravity="center_vertical|fill_horizontal">
+        <scale android:scaleWidth="100%">
+            <shape android:shape="rectangle">
+                <corners android:radius="?attr/progressBarCornerRadius" />
+                <size android:height="@dimen/progress_bar_height_material" />
+                <solid android:color="@color/material_grey_900" />
+            </shape>
+        </scale>
+    </item>
+    <item android:id="@id/progress"
+          android:gravity="center_vertical|fill_horizontal">
+        <scale android:scaleWidth="100%">
+            <shape android:shape="rectangle">
+                <corners android:radius="?attr/progressBarCornerRadius" />
+                <size android:height="@dimen/progress_bar_height_material" />
+                <solid android:color="@color/white" />
+            </shape>
+        </scale>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/core/res/res/values-watch/dimens_material.xml b/core/res/res/values-watch/dimens_material.xml
index 51d4018..40673c1 100644
--- a/core/res/res/values-watch/dimens_material.xml
+++ b/core/res/res/values-watch/dimens_material.xml
@@ -44,6 +44,7 @@
     <dimen name="progress_bar_size_small">16dip</dimen>
     <dimen name="progress_bar_size_medium">32dip</dimen>
     <dimen name="progress_bar_size_large">64dip</dimen>
+    <dimen name="progress_bar_height">24dp</dimen>
 
     <!-- Progress bar message dimens -->
     <dimen name="message_progress_dialog_text_size">18sp</dimen>
diff --git a/core/res/res/values-watch/strings.xml b/core/res/res/values-watch/strings.xml
index 6d6cfc7..9fa4bef 100644
--- a/core/res/res/values-watch/strings.xml
+++ b/core/res/res/values-watch/strings.xml
@@ -26,6 +26,7 @@
     <string name="global_action_emergency">Emergency SOS</string>
 
    <!-- Reboot to Recovery Progress Dialog. This is shown before it reboots to recovery. -->
+    <string name="reboot_to_update_prepare">Preparing to update</string>
     <string name="reboot_to_update_title">Wear OS system update</string>
 
    <!-- Title of the pop-up dialog in which the user switches keyboard, also known as input method. -->
diff --git a/core/res/res/values-watch/styles_material.xml b/core/res/res/values-watch/styles_material.xml
index 12bc406..8698e86 100644
--- a/core/res/res/values-watch/styles_material.xml
+++ b/core/res/res/values-watch/styles_material.xml
@@ -107,4 +107,14 @@
         <item name="paddingStart">@dimen/message_progress_dialog_start_padding</item>
         <item name="paddingTop">@dimen/message_progress_dialog_top_padding</item>
     </style>
+
+    <!-- Material progress part (indeterminate/horizontal) for Wear -->
+    <style name="Widget.Material.ProgressBar.Horizontal" parent="Widget.ProgressBar.Horizontal">
+        <item name="progressDrawable">@drawable/progress_horizontal_material</item>
+        <item name="indeterminateDrawable">
+            @drawable/progress_indeterminate_horizontal_material
+        </item>
+        <item name="minHeight">@dimen/progress_bar_height</item>
+        <item name="maxHeight">@dimen/progress_bar_height</item>
+    </style>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 42a249c..f63f8b3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -198,6 +198,9 @@
          service. Off by default, since the service may not be available on some devices. -->
     <bool name="config_enableProximityService">false</bool>
 
+    <!-- Enable or disable android.companion.virtual.VirtualDeviceManager. Enabled by default. -->
+    <bool name="config_enableVirtualDeviceManager">true</bool>
+
     <!-- Whether dialogs should close automatically when the user touches outside
          of them.  This should not normally be modified. -->
     <bool name="config_closeDialogWhenTouchOutside">true</bool>
@@ -2624,6 +2627,17 @@
          assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
     <bool name="config_dismissDreamOnActivityStart">false</bool>
 
+    <!-- Whether to send a user activity event to PowerManager when a dream quits unexpectedly so
+         that the screen won't immediately shut off.
+
+         When a dream stops unexpectedly, such as due to an app update, if the device has been
+         inactive less than the user's screen timeout, the device goes to keyguard and times out
+         back to dreaming after a few seconds. If the device has been inactive longer, the screen
+         will immediately turn off. With this flag on, the device will go back to keyguard in all
+         scenarios rather than turning off, which gives the device a chance to start dreaming
+         again. -->
+    <bool name="config_resetScreenTimeoutOnUnexpectedDreamExit">false</bool>
+
     <!-- The prefixes of dream component names that are loggable.
          Matched against ComponentName#flattenToString() for dream components.
          If empty, logs "other" for all. -->
@@ -2694,6 +2708,9 @@
          backlight values -->
     <bool name="config_displayBrightnessBucketsInDoze">false</bool>
 
+    <!-- True to skip the fade animation on display off event -->
+    <bool name="config_displayColorFadeDisabled">false</bool>
+
     <!-- Power Management: Specifies whether to decouple the auto-suspend state of the
          device from the display on/off state.
 
@@ -5603,7 +5620,7 @@
         the Option 3 is selected for R.integer.config_letterboxBackgroundType.
         Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead. -->
     <item name="config_letterboxBackgroundWallaperDarkScrimAlpha" format="float" type="dimen">
-        0.68
+        0.75
     </item>
 
     <!-- Corners appearance of the letterbox background.
@@ -5628,7 +5645,7 @@
             but isn't supported on the device or both dark scrim alpha and blur radius aren't
             provided.
      -->
-    <color name="config_letterboxBackgroundColor">@color/system_on_secondary_fixed</color>
+    <color name="config_letterboxBackgroundColor">@color/system_neutral1_1000</color>
 
     <!-- Horizontal position of a center of the letterboxed app window.
         0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
@@ -6542,4 +6559,12 @@
          serialization, a default vibration will be used.
          Note that, indefinitely repeating vibrations are not allowed as shutdown vibrations. -->
     <string name="config_defaultShutdownVibrationFile" />
+    <!-- The file path in which custom vibrations are provided for haptic feedbacks.
+         If the device does not specify any such file path here, if the file path specified here
+         does not exist, or if the contents of the file does not make up a valid customization
+         serialization, the system default vibrations for haptic feedback will be used.
+         If the content of the customization file is valid, the system will use the provided
+         vibrations for the customized haptic feedback IDs, and continue to use the system defaults
+         for the non-customized ones. -->
+    <string name="config_hapticFeedbackCustomizationFile" />
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0951aec..8e52633 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -418,6 +418,7 @@
   <java-symbol type="bool" name="config_localDisplaysMirrorContent" />
   <java-symbol type="bool" name="config_ignoreUdfpsVote" />
   <java-symbol type="bool" name="config_enableProximityService" />
+  <java-symbol type="bool" name="config_enableVirtualDeviceManager" />
   <java-symbol type="array" name="config_localPrivateDisplayPorts" />
   <java-symbol type="integer" name="config_defaultDisplayDefaultColorMode" />
   <java-symbol type="bool" name="config_enableAppWidgetService" />
@@ -2219,6 +2220,7 @@
   <java-symbol type="array" name="config_supportedDreamComplications" />
   <java-symbol type="array" name="config_disabledDreamComponents" />
   <java-symbol type="bool" name="config_dismissDreamOnActivityStart" />
+  <java-symbol type="bool" name="config_resetScreenTimeoutOnUnexpectedDreamExit" />
   <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
   <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
   <java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
@@ -3866,6 +3868,7 @@
   <java-symbol type="bool" name="config_dozeSupportsAodWallpaper" />
   <java-symbol type="bool" name="config_displayBlanksAfterDoze" />
   <java-symbol type="bool" name="config_displayBrightnessBucketsInDoze" />
+  <java-symbol type="bool" name="config_displayColorFadeDisabled" />
   <java-symbol type="integer" name="config_storageManagerDaystoRetainDefault" />
   <java-symbol type="string" name="config_headlineFontFamily" />
   <java-symbol type="string" name="config_headlineFontFamilyMedium" />
@@ -5176,4 +5179,5 @@
   <java-symbol type="drawable" name="focus_event_pressed_key_background" />
   <java-symbol type="string" name="config_defaultShutdownVibrationFile" />
   <java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" />
+  <java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
 </resources>
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 4857741..c1b55cd 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -21,6 +21,7 @@
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -29,6 +30,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -46,6 +49,7 @@
 import android.app.servertransaction.NewIntentItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.app.servertransaction.StopActivityItem;
+import android.app.servertransaction.WindowTokenClientController;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.CompatibilityInfo;
@@ -70,6 +74,7 @@
 import com.android.internal.content.ReferrerIntent;
 
 import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -83,7 +88,7 @@
 /**
  * Test for verifying {@link android.app.ActivityThread} class.
  * Build/Install/Run:
- *  atest FrameworksCoreTests:android.app.activity.ActivityThreadTest
+ *  atest FrameworksCoreTests:ActivityThreadTest
  */
 @RunWith(AndroidJUnit4.class)
 @MediumTest
@@ -100,14 +105,24 @@
             new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
                     false /* launchActivity */);
 
+    private WindowTokenClientController mOriginalWindowTokenClientController;
+
     private ArrayList<VirtualDisplay> mCreatedVirtualDisplays;
 
+    @Before
+    public void setup() {
+        // Keep track of the original controller, so that it can be used to restore in tearDown()
+        // when there is override in some test cases.
+        mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
+    }
+
     @After
     public void tearDown() {
         if (mCreatedVirtualDisplays != null) {
             mCreatedVirtualDisplays.forEach(VirtualDisplay::release);
             mCreatedVirtualDisplays = null;
         }
+        WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController);
     }
 
     @Test
@@ -730,6 +745,39 @@
         assertFalse(activity.enterPipSkipped());
     }
 
+    @Test
+    public void testHandleWindowContextConfigurationChanged() {
+        final Activity activity = mActivityTestRule.launchActivity(new Intent());
+        final ActivityThread activityThread = activity.getActivityThread();
+        final WindowTokenClientController windowTokenClientController =
+                mock(WindowTokenClientController.class);
+        WindowTokenClientController.overrideForTesting(windowTokenClientController);
+        final IBinder clientToken = mock(IBinder.class);
+        final Configuration configuration = new Configuration();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread
+                .handleWindowContextConfigurationChanged(
+                        clientToken, configuration, DEFAULT_DISPLAY));
+
+        verify(windowTokenClientController).onWindowContextConfigurationChanged(
+                clientToken, configuration, DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testHandleWindowContextWindowRemoval() {
+        final Activity activity = mActivityTestRule.launchActivity(new Intent());
+        final ActivityThread activityThread = activity.getActivityThread();
+        final WindowTokenClientController windowTokenClientController =
+                mock(WindowTokenClientController.class);
+        WindowTokenClientController.overrideForTesting(windowTokenClientController);
+        final IBinder clientToken = mock(IBinder.class);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread
+                .handleWindowContextWindowRemoval(clientToken));
+
+        verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken);
+    }
+
     /**
      * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
      * Configuration, int)} to try to push activity configuration to the activity for the given
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java
new file mode 100644
index 0000000..7811e1a
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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 android.app.servertransaction;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.ClientTransactionHandler;
+import android.content.res.Configuration;
+import android.os.IBinder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link WindowContextConfigurationChangeItem}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:WindowContextConfigurationChangeItemTest
+ */
+public class WindowContextConfigurationChangeItemTest {
+
+    @Mock
+    private ClientTransactionHandler mHandler;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private PendingTransactionActions mPendingActions;
+    @Mock
+    private IBinder mClientToken;
+    // Can't mock final class.
+    private final Configuration mConfiguration = new Configuration();
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testExecute() {
+        final WindowContextConfigurationChangeItem item = WindowContextConfigurationChangeItem
+                .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY);
+        item.execute(mHandler, mToken, mPendingActions);
+
+        verify(mHandler).handleWindowContextConfigurationChanged(mClientToken, mConfiguration,
+                DEFAULT_DISPLAY);
+    }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java
new file mode 100644
index 0000000..2c83c70
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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 android.app.servertransaction;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link WindowContextWindowRemovalItem}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:WindowContextWindowRemovalItemTest
+ */
+public class WindowContextWindowRemovalItemTest {
+
+    @Mock
+    private ClientTransactionHandler mHandler;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private PendingTransactionActions mPendingActions;
+    @Mock
+    private IBinder mClientToken;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testExecute() {
+        final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain(
+                mClientToken);
+        item.execute(mHandler, mToken, mPendingActions);
+
+        verify(mHandler).handleWindowContextWindowRemoval(mClientToken);
+    }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowTokenClientControllerTest.java
new file mode 100644
index 0000000..59a55bb
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/WindowTokenClientControllerTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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 android.app.servertransaction;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import android.app.ActivityThread;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+import android.window.WindowTokenClient;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link WindowTokenClientController}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:WindowTokenClientControllerTest
+ */
+@SmallTest
+@Presubmit
+public class WindowTokenClientControllerTest {
+
+    @Mock
+    private IWindowManager mWindowManagerService;
+    @Mock
+    private WindowTokenClient mWindowTokenClient;
+    @Mock
+    private IBinder mClientToken;
+    @Mock
+    private IBinder mWindowToken;
+    // Can't mock final class.
+    private final Configuration mConfiguration = new Configuration();
+
+    private IWindowManager mOriginalWindowManagerService;
+
+    private WindowTokenClientController mController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mOriginalWindowManagerService = WindowManagerGlobal.getWindowManagerService();
+        WindowManagerGlobal.overrideWindowManagerServiceForTesting(mWindowManagerService);
+        doReturn(mClientToken).when(mWindowTokenClient).asBinder();
+        mController = spy(WindowTokenClientController.getInstance());
+    }
+
+    @After
+    public void tearDown() {
+        WindowManagerGlobal.overrideWindowManagerServiceForTesting(mOriginalWindowManagerService);
+    }
+
+    @Test
+    public void testAttachToDisplayArea() throws RemoteException {
+        doReturn(null).when(mWindowManagerService).attachWindowContextToDisplayArea(
+                any(), any(), anyInt(), anyInt(), any());
+
+        assertFalse(mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY,
+                DEFAULT_DISPLAY, null /* options */));
+        verify(mWindowManagerService).attachWindowContextToDisplayArea(
+                ActivityThread.currentActivityThread().getApplicationThread(), mWindowTokenClient,
+                TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY, null /* options */);
+        verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean());
+
+        doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayArea(
+                any(), any(), anyInt(), anyInt(), any());
+
+        assertTrue(mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY,
+                DEFAULT_DISPLAY, null /* options */));
+        verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY,
+                false /* shouldReportConfigChange */);
+    }
+
+    @Test
+    public void testAttachToDisplayArea_detachIfNeeded() throws RemoteException {
+        mController.detachIfNeeded(mWindowTokenClient);
+
+        verify(mWindowManagerService, never()).detachWindowContext(any());
+
+        doReturn(null).when(mWindowManagerService).attachWindowContextToDisplayArea(
+                any(), any(), anyInt(), anyInt(), any());
+        mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY,
+                DEFAULT_DISPLAY, null /* options */);
+        mController.detachIfNeeded(mWindowTokenClient);
+
+        verify(mWindowManagerService, never()).detachWindowContext(any());
+
+        doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayArea(
+                any(), any(), anyInt(), anyInt(), any());
+        mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY,
+                DEFAULT_DISPLAY, null /* options */);
+        mController.detachIfNeeded(mWindowTokenClient);
+
+        verify(mWindowManagerService).detachWindowContext(any());
+    }
+
+    @Test
+    public void testAttachToDisplayContent() throws RemoteException {
+        doReturn(null).when(mWindowManagerService).attachWindowContextToDisplayContent(
+                any(), any(), anyInt());
+
+        assertFalse(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY));
+        verify(mWindowManagerService).attachWindowContextToDisplayContent(
+                ActivityThread.currentActivityThread().getApplicationThread(), mWindowTokenClient,
+                DEFAULT_DISPLAY);
+        verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean());
+
+        doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayContent(
+                any(), any(), anyInt());
+
+        assertTrue(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY));
+        verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY,
+                false /* shouldReportConfigChange */);
+    }
+
+    @Test
+    public void testAttachToDisplayContent_detachIfNeeded() throws RemoteException {
+        mController.detachIfNeeded(mWindowTokenClient);
+
+        verify(mWindowManagerService, never()).detachWindowContext(any());
+
+        doReturn(null).when(mWindowManagerService).attachWindowContextToDisplayContent(
+                any(), any(), anyInt());
+        mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY);
+        mController.detachIfNeeded(mWindowTokenClient);
+
+        verify(mWindowManagerService, never()).detachWindowContext(any());
+
+        doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayContent(
+                any(), any(), anyInt());
+        mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY);
+        mController.detachIfNeeded(mWindowTokenClient);
+
+        verify(mWindowManagerService).detachWindowContext(any());
+    }
+
+    @Test
+    public void testAttachToWindowToken() throws RemoteException {
+        mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
+
+        verify(mWindowManagerService).attachWindowContextToWindowToken(
+                ActivityThread.currentActivityThread().getApplicationThread(), mWindowTokenClient,
+                mWindowToken);
+    }
+
+    @Test
+    public void testAttachToWindowToken_detachIfNeeded() throws RemoteException {
+        mController.detachIfNeeded(mWindowTokenClient);
+
+        verify(mWindowManagerService, never()).detachWindowContext(any());
+
+        mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
+        mController.detachIfNeeded(mWindowTokenClient);
+
+        verify(mWindowManagerService).detachWindowContext(any());
+    }
+
+    @Test
+    public void testOnWindowContextConfigurationChanged() {
+        mController.onWindowContextConfigurationChanged(
+                mClientToken, mConfiguration, DEFAULT_DISPLAY);
+
+        verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt());
+
+        mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
+
+        mController.onWindowContextConfigurationChanged(
+                mClientToken, mConfiguration, DEFAULT_DISPLAY);
+
+        verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testOnWindowContextWindowRemoved() {
+        mController.onWindowContextWindowRemoved(mClientToken);
+
+        verify(mWindowTokenClient, never()).onWindowTokenRemoved();
+
+        mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
+
+        mController.onWindowContextWindowRemoved(mClientToken);
+
+        verify(mWindowTokenClient).onWindowTokenRemoved();
+    }
+}
diff --git a/core/tests/coretests/src/android/database/DatabaseErrorHandlerTest.java b/core/tests/coretests/src/android/database/DatabaseErrorHandlerTest.java
index 91c7687..9677584 100644
--- a/core/tests/coretests/src/android/database/DatabaseErrorHandlerTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseErrorHandlerTest.java
@@ -19,10 +19,13 @@
 import android.content.Context;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDiskIOException;
+import android.database.sqlite.SQLiteDatabaseCorruptException;
+
 import android.database.sqlite.SQLiteException;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileWriter;
@@ -61,7 +64,6 @@
         assertTrue(mDatabaseFile.exists());
     }
 
-
     public void testDatabaseIsCorrupt() throws IOException {
         mDatabase.execSQL("create table t (i int);");
         // write junk into the database file
@@ -70,30 +72,23 @@
         writer.close();
         assertTrue(mDatabaseFile.exists());
         // since the database file is now corrupt, doing any sql on this database connection
-        // should trigger call to MyDatabaseCorruptionHandler.onCorruption
+        // should trigger call to MyDatabaseCorruptionHandler.onCorruption.  A corruption
+        // exception will also be throws.  This seems redundant.
         try {
             mDatabase.execSQL("select * from t;");
             fail("expected exception");
-        } catch (SQLiteDiskIOException e) {
-            /**
-             * this test used to produce a corrupted db. but with new sqlite it instead reports
-             * Disk I/O error. meh..
-             * need to figure out how to cause corruption in db
-             */
-            // expected
-            if (mDatabaseFile.exists()) {
-                mDatabaseFile.delete();
-            }
-        } catch (SQLiteException e) {
-            
+        } catch (SQLiteDatabaseCorruptException e) {
+            // Expected result.
         }
-        // database file should be gone
+
+        // The database file should be gone.
         assertFalse(mDatabaseFile.exists());
-        // after corruption handler is called, the database file should be free of
-        // database corruption
-        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null,
+        // After corruption handler is called, the database file should be free of
+        // database corruption.   Reopen it.
+        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null,
                 new MyDatabaseCorruptionHandler());
-        assertTrue(db.isDatabaseIntegrityOk());
+        assertTrue(mDatabase.isDatabaseIntegrityOk());
+        // The teadDown() routine will close the database.
     }
 
     /**
@@ -102,8 +97,21 @@
      * corrupt before deleting the file.
      */
     public class MyDatabaseCorruptionHandler implements DatabaseErrorHandler {
+        private final AtomicBoolean mEntered = new AtomicBoolean(false);
         public void onCorruption(SQLiteDatabase dbObj) {
-            boolean databaseOk = dbObj.isDatabaseIntegrityOk();
+            boolean databaseOk = false;
+            if (!mEntered.get()) {
+                // The integrity check can retrigger the corruption handler if the database is,
+                // indeed, corrupted.  Use mEntered to detect recursion and to skip retrying the
+                // integrity check on recursion.
+                mEntered.set(true);
+                databaseOk = dbObj.isDatabaseIntegrityOk();
+            }
+            // At this point the database state has been detected and there is no further danger
+            // of recursion.  Setting mEntered to false allows this object to be reused, although
+            // it is not obvious how such reuse would work.
+            mEntered.set(false);
+
             // close the database
             try {
                 dbObj.close();
@@ -122,4 +130,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
index 95b0e32..e8d90f5 100644
--- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
@@ -914,6 +914,24 @@
         verifyLookasideStats(true);
     }
 
+    void verifyLookasideStats(boolean expectDisabled) {
+        boolean dbStatFound = false;
+        SQLiteDebug.PagerStats info = SQLiteDebug.getDatabaseInfo();
+        for (SQLiteDebug.DbStats dbStat : info.dbStats) {
+            if (dbStat.dbName.endsWith(mDatabaseFile.getName()) && !dbStat.arePoolStats) {
+                dbStatFound = true;
+                Log.i(TAG, "Lookaside for " + dbStat.dbName + " " + dbStat.lookaside);
+                if (expectDisabled) {
+                    assertTrue("lookaside slots count should be zero", dbStat.lookaside == 0);
+                } else {
+                    assertTrue("lookaside slots count should be greater than zero",
+                            dbStat.lookaside > 0);
+                }
+            }
+        }
+        assertTrue("No dbstat found for " + mDatabaseFile.getName(), dbStatFound);
+    }
+
     @SmallTest
     public void testOpenParamsSetLookasideConfigValidation() {
         try {
@@ -930,24 +948,6 @@
         }
     }
 
-    void verifyLookasideStats(boolean expectDisabled) {
-        boolean dbStatFound = false;
-        SQLiteDebug.PagerStats info = SQLiteDebug.getDatabaseInfo();
-        for (SQLiteDebug.DbStats dbStat : info.dbStats) {
-            if (dbStat.dbName.endsWith(mDatabaseFile.getName())) {
-                dbStatFound = true;
-                Log.i(TAG, "Lookaside for " + dbStat.dbName + " " + dbStat.lookaside);
-                if (expectDisabled) {
-                    assertTrue("lookaside slots count should be zero", dbStat.lookaside == 0);
-                } else {
-                    assertTrue("lookaside slots count should be greater than zero",
-                            dbStat.lookaside > 0);
-                }
-            }
-        }
-        assertTrue("No dbstat found for " + mDatabaseFile.getName(), dbStatFound);
-    }
-
     @LargeTest
     public void testDefaultDatabaseErrorHandler() {
         DefaultDatabaseErrorHandler errorHandler = new DefaultDatabaseErrorHandler();
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index a52d2e8..5f2aecc 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -24,17 +24,20 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.app.servertransaction.WindowTokenClientController;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -56,14 +59,26 @@
 public class WindowContextControllerTest {
     private WindowContextController mController;
     @Mock
+    private WindowTokenClientController mWindowTokenClientController;
+    @Mock
     private WindowTokenClient mMockToken;
 
+    private WindowTokenClientController mOriginalController;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mController = new WindowContextController(mMockToken);
         doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean());
-        doReturn(true).when(mMockToken).attachToDisplayArea(anyInt(), anyInt(), any());
+        mOriginalController = WindowTokenClientController.getInstance();
+        WindowTokenClientController.overrideForTesting(mWindowTokenClientController);
+        doReturn(true).when(mWindowTokenClientController).attachToDisplayArea(
+                eq(mMockToken), anyInt(), anyInt(), any());
+    }
+
+    @After
+    public void tearDown() {
+        WindowTokenClientController.overrideForTesting(mOriginalController);
     }
 
     @Test(expected = IllegalStateException.class)
@@ -78,7 +93,7 @@
     public void testDetachIfNeeded_NotAttachedYet_DoNothing() {
         mController.detachIfNeeded();
 
-        verify(mMockToken, never()).detachFromWindowContainerIfNeeded();
+        verify(mWindowTokenClientController, never()).detachIfNeeded(any());
     }
 
     @Test
diff --git a/core/tests/vibrator/Android.bp b/core/tests/vibrator/Android.bp
new file mode 100644
index 0000000..829409a
--- /dev/null
+++ b/core/tests/vibrator/Android.bp
@@ -0,0 +1,40 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "FrameworksVibratorCoreTests",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    static_libs: [
+        "frameworks-base-testutils",
+        "guava",
+        "androidx.core_core",
+        "androidx.test.ext.junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+        "truth-prebuilt",
+        "testng",
+    ],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "framework",
+        "framework-res",
+    ],
+
+    sdk_version: "core_platform",
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+
+    certificate: "platform",
+}
diff --git a/core/tests/vibrator/AndroidManifest.xml b/core/tests/vibrator/AndroidManifest.xml
new file mode 100644
index 0000000..1ce6071
--- /dev/null
+++ b/core/tests/vibrator/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.core.tests.vibrator">
+
+    <!-- vibrator test permissions -->
+    <uses-permission android:name="android.permission.VIBRATE" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.frameworks.core.tests.vibrator"
+            android:label="Frameworks Vibrator Core Tests" />
+</manifest>
diff --git a/core/tests/coretests/src/android/os/vibrator/OWNERS b/core/tests/vibrator/OWNERS
similarity index 78%
rename from core/tests/coretests/src/android/os/vibrator/OWNERS
rename to core/tests/vibrator/OWNERS
index b54d6bf..00446f2 100644
--- a/core/tests/coretests/src/android/os/vibrator/OWNERS
+++ b/core/tests/vibrator/OWNERS
@@ -1 +1,2 @@
+# Bug component: 345036
 include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
\ No newline at end of file
diff --git a/core/tests/vibrator/TEST_MAPPING b/core/tests/vibrator/TEST_MAPPING
new file mode 100644
index 0000000..f3333d8
--- /dev/null
+++ b/core/tests/vibrator/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksVibratorCoreTests",
+      "options": [
+        {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksVibratorCoreTests",
+      "options": [
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
+  ]
+}
+
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationTest.java b/core/tests/vibrator/src/android/os/CombinedVibrationTest.java
similarity index 99%
rename from core/tests/coretests/src/android/os/CombinedVibrationTest.java
rename to core/tests/vibrator/src/android/os/CombinedVibrationTest.java
index 508856b..244fcff 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationTest.java
+++ b/core/tests/vibrator/src/android/os/CombinedVibrationTest.java
@@ -22,8 +22,6 @@
 
 import static org.testng.Assert.assertThrows;
 
-import android.platform.test.annotations.Presubmit;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -31,7 +29,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 
-@Presubmit
 @RunWith(JUnit4.class)
 public class CombinedVibrationTest {
     private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255);
diff --git a/core/tests/coretests/src/android/os/ExternalVibrationTest.java b/core/tests/vibrator/src/android/os/ExternalVibrationTest.java
similarity index 96%
rename from core/tests/coretests/src/android/os/ExternalVibrationTest.java
rename to core/tests/vibrator/src/android/os/ExternalVibrationTest.java
index 3b872d5..587594d 100644
--- a/core/tests/coretests/src/android/os/ExternalVibrationTest.java
+++ b/core/tests/vibrator/src/android/os/ExternalVibrationTest.java
@@ -22,12 +22,14 @@
 
 import android.media.AudioAttributes;
 
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ExternalVibrationTest {
+    @Ignore("b/291713224")
     @Test
     public void testSerialization() {
         AudioAttributes audio = new AudioAttributes.Builder().build();
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
similarity index 99%
rename from core/tests/coretests/src/android/os/VibrationEffectTest.java
rename to core/tests/vibrator/src/android/os/VibrationEffectTest.java
index 73954da..8be489e 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -40,7 +40,6 @@
 import android.os.VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.StepSegment;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -54,7 +53,6 @@
 import java.time.Duration;
 import java.util.Arrays;
 
-@Presubmit
 @RunWith(MockitoJUnitRunner.class)
 public class VibrationEffectTest {
 
diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
similarity index 99%
rename from core/tests/coretests/src/android/os/VibratorInfoTest.java
rename to core/tests/vibrator/src/android/os/VibratorInfoTest.java
index 88766e2..ff917aa 100644
--- a/core/tests/coretests/src/android/os/VibratorInfoTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
@@ -24,13 +24,11 @@
 
 import android.hardware.vibrator.Braking;
 import android.hardware.vibrator.IVibrator;
-import android.platform.test.annotations.Presubmit;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-@Presubmit
 @RunWith(JUnit4.class)
 public class VibratorInfoTest {
     private static final float TEST_TOLERANCE = 1e-5f;
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
similarity index 99%
rename from core/tests/coretests/src/android/os/VibratorTest.java
rename to core/tests/vibrator/src/android/os/VibratorTest.java
index 375fdac..c559e34 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -40,7 +40,6 @@
 import android.hardware.vibrator.IVibrator;
 import android.media.AudioAttributes;
 import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -55,13 +54,6 @@
 import org.mockito.InOrder;
 import org.mockito.junit.MockitoJUnitRunner;
 
-/**
- * Tests for {@link Vibrator}.
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:VibratorTest
- */
-@Presubmit
 @RunWith(MockitoJUnitRunner.class)
 public class VibratorTest {
 
diff --git a/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
similarity index 99%
rename from core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java
rename to core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
index 9099274..3231192 100644
--- a/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -29,7 +29,6 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -37,7 +36,6 @@
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnitRunner;
 
-@Presubmit
 @RunWith(MockitoJUnitRunner.class)
 public class PrebakedSegmentTest {
 
diff --git a/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
similarity index 99%
rename from core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java
rename to core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index 298438f..955d6ac 100644
--- a/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -29,7 +29,6 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -37,7 +36,6 @@
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnitRunner;
 
-@Presubmit
 @RunWith(MockitoJUnitRunner.class)
 public class PrimitiveSegmentTest {
     private static final float TOLERANCE = 1e-2f;
diff --git a/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
similarity index 99%
rename from core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java
rename to core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index 6f8c205..dcbb56e 100644
--- a/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -29,7 +29,6 @@
 import android.os.Parcel;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
-import android.platform.test.annotations.Presubmit;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -39,7 +38,6 @@
 import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.junit.MockitoRule;
 
-@Presubmit
 @RunWith(MockitoJUnitRunner.class)
 public class RampSegmentTest {
     private static final float TOLERANCE = 1e-2f;
diff --git a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
similarity index 99%
rename from core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
rename to core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
index ade2161..f9f1c08 100644
--- a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -27,7 +27,6 @@
 import android.os.Parcel;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
-import android.platform.test.annotations.Presubmit;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -37,7 +36,6 @@
 import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.junit.MockitoRule;
 
-@Presubmit
 @RunWith(MockitoJUnitRunner.class)
 public class StepSegmentTest {
     private static final float TOLERANCE = 1e-2f;
diff --git a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
similarity index 99%
rename from core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
rename to core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
index b31af89..ce17170 100644
--- a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -28,7 +28,6 @@
 
 import android.os.VibrationEffect;
 import android.os.vibrator.PrebakedSegment;
-import android.platform.test.annotations.Presubmit;
 import android.util.Xml;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -50,7 +49,6 @@
  * <p>The {@link VibrationEffect} public APIs are covered by CTS to enforce the schema defined at
  * services/core/xsd/vibrator/vibration/vibration.xsd.
  */
-@Presubmit
 @RunWith(JUnit4.class)
 public class VibrationEffectXmlSerializationTest {
 
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 94e23e7..87e6b18 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -427,12 +427,6 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/BackNavigationController.java"
     },
-    "-1715268616": {
-      "message": "Last window, removing starting window %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "-1710206702": {
       "message": "Display id=%d is frozen while keyguard locked, return %d",
       "level": "VERBOSE",
@@ -463,6 +457,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
+    "-1671601441": {
+      "message": "attachWindowContextToDisplayContent: calling from non-existing process pid=%d uid=%d",
+      "level": "WARN",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-1670695197": {
       "message": "Attempted to add presentation window to a non-suitable display.  Aborting.",
       "level": "WARN",
@@ -1225,6 +1225,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-961053385": {
+      "message": "attachWindowContextToDisplayArea: calling from non-existing process pid=%d uid=%d",
+      "level": "WARN",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-957060823": {
       "message": "Moving to PAUSING: %s",
       "level": "VERBOSE",
@@ -4057,12 +4063,6 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
-    "1671994402": {
-      "message": "Nulling last startingData",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "1674747211": {
       "message": "%s forcing orientation to %d for display id=%d",
       "level": "VERBOSE",
@@ -4243,12 +4243,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "1853793312": {
-      "message": "Notify removed startingWindow %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "1856783490": {
       "message": "resumeTopActivity: Restarting %s",
       "level": "DEBUG",
@@ -4279,6 +4273,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "1879463933": {
+      "message": "attachWindowContextToWindowToken: calling from non-existing process pid=%d uid=%d",
+      "level": "WARN",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "1891501279": {
       "message": "cancelAnimation(): reason=%s",
       "level": "DEBUG",
diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl
index 1da3ba6..893c87d 100644
--- a/data/keyboards/Vendor_0957_Product_0001.kl
+++ b/data/keyboards/Vendor_0957_Product_0001.kl
@@ -16,7 +16,7 @@
 # Key Layout file for Google Reference RCU Remote.
 #
 
-key 116   TV_POWER      WAKE
+key 116   POWER         WAKE
 key 217   ASSIST        WAKE
 
 key 103   DPAD_UP
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index a5184f2..635e78e 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -276,9 +276,7 @@
     @CriticalNative
     private static native void nResetDisplayListCanvas(long canvas, long node,
             int width, int height);
-    @CriticalNative
     private static native int nGetMaximumTextureWidth();
-    @CriticalNative
     private static native int nGetMaximumTextureHeight();
     @CriticalNative
     private static native void nEnableZ(long renderer, boolean enableZ);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index d94e8e4..4d73c20 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -17,7 +17,9 @@
 package androidx.window.extensions.embedding;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
 
 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
@@ -340,6 +342,20 @@
         wct.deleteTaskFragment(fragmentToken);
     }
 
+    void reorderTaskFragmentToFront(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken) {
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_REORDER_TO_FRONT).build();
+        wct.addTaskFragmentOperation(fragmentToken, operation);
+    }
+
+    void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken, boolean isolatedNav) {
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_SET_ISOLATED_NAVIGATION).setIsolatedNav(isolatedNav).build();
+        wct.addTaskFragmentOperation(fragmentToken, operation);
+    }
+
     void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
         mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
     }
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 a2f75e0..f95f3ff 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -250,6 +250,10 @@
             // Updates the Split
             final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
             final WindowContainerTransaction wct = transactionRecord.getTransaction();
+
+            mPresenter.setTaskFragmentIsolatedNavigation(wct,
+                    splitPinContainer.getSecondaryContainer().getTaskFragmentToken(),
+                    true /* isolatedNav */);
             mPresenter.updateSplitContainer(splitPinContainer, wct);
             transactionRecord.apply(false /* shouldApplyIndependently */);
             updateCallbackIfNecessary();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 4dafbd1..5de6acf 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -17,7 +17,6 @@
 package androidx.window.extensions.embedding;
 
 import static android.content.pm.PackageManager.MATCH_ALL;
-import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 
 import android.app.Activity;
 import android.app.ActivityThread;
@@ -40,7 +39,6 @@
 import android.view.WindowMetrics;
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentCreationParams;
-import android.window.TaskFragmentOperation;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.IntDef;
@@ -427,10 +425,8 @@
         final SplitPinContainer pinnedContainer =
                 container.getTaskContainer().getSplitPinContainer();
         if (pinnedContainer != null) {
-            final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
-                    OP_TYPE_REORDER_TO_FRONT).build();
-            wct.addTaskFragmentOperation(
-                    pinnedContainer.getSecondaryContainer().getTaskFragmentToken(), operation);
+            reorderTaskFragmentToFront(wct,
+                    pinnedContainer.getSecondaryContainer().getTaskFragmentToken());
         }
     }
 
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index a605e2b..8bd500e 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -166,7 +166,7 @@
         // *.kt sources are inside a filegroup.
         "kotlin-annotations",
     ],
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
     manifest: "AndroidManifest.xml",
     plugins: ["dagger2-compiler"],
 }
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index 0ca912e..d93e9ba 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -25,9 +25,8 @@
 
     <ImageButton
         android:id="@+id/caption_handle"
-        android:layout_width="176dp"
+        android:layout_width="128dp"
         android:layout_height="42dp"
-        android:paddingHorizontal="24dp"
         android:paddingVertical="19dp"
         android:contentDescription="@string/handle_text"
         android:src="@drawable/decor_handle_dark"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt
index d5d072a..122dcbb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt
@@ -94,7 +94,6 @@
          * non-overlapping.
          * @return The new bounds for this content.
          */
-        @JvmDefault
         fun calculateNewBoundsOnOverlap(
             overlappingContentBounds: Rect,
             otherContentBounds: List<Rect>
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 20c3bd2..f5c6a03 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
@@ -203,8 +203,10 @@
             Context context,
             @ShellMainThread Handler mainHandler,
             @ShellMainThread Choreographer mainChoreographer,
+            ShellInit shellInit,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
+            ShellController shellController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopModeController> desktopModeController,
@@ -214,8 +216,10 @@
                     context,
                     mainHandler,
                     mainChoreographer,
+                    shellInit,
                     taskOrganizer,
                     displayController,
+                    shellController,
                     syncQueue,
                     transitions,
                     desktopModeController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 711df0d..c05af73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -368,7 +368,6 @@
         /**
          * Called when the active tasks change in desktop mode.
          */
-        @JvmDefault
         fun onActiveTasksChanged(displayId: Int) {}
     }
 
@@ -379,17 +378,15 @@
         /**
          * Called when the desktop starts or stops showing freeform tasks.
          */
-        @JvmDefault
         fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
 
         /**
          * Called when the desktop stashed status changes.
          */
-        @JvmDefault
         fun onStashedChanged(displayId: Int, stashed: Boolean) {}
     }
 }
 
 private fun <T> Iterable<T>.toDumpString(): String {
     return joinToString(separator = ", ", prefix = "[", postfix = "]")
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 1acf783..22929c76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -88,7 +88,7 @@
     }
 
     /**
-     * Starts Transition of type TRANSIT_START_MOVE_TO_DESKTOP_MODE
+     * Starts Transition of type TRANSIT_START_DRAG_TO_DESKTOP_MODE
      * @param wct WindowContainerTransaction for transition
      * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
      *                              to desktop animation
@@ -98,18 +98,18 @@
             @NonNull MoveToDesktopAnimator moveToDesktopAnimator,
             Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
         mMoveToDesktopAnimator = moveToDesktopAnimator;
-        startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
+        startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct,
                 onAnimationEndCallback);
     }
 
     /**
-     * Starts Transition of type TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
+     * Starts Transition of type TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
      * @param wct WindowContainerTransaction for transition
      * @param onAnimationEndCallback to be called after animation
      */
     public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct,
             Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
-        startTransition(Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, wct,
+        startTransition(Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, wct,
                 onAnimationEndCallback);
     }
 
@@ -124,7 +124,7 @@
             MoveToDesktopAnimator moveToDesktopAnimator,
             Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
         mMoveToDesktopAnimator = moveToDesktopAnimator;
-        startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct,
+        startTransition(Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE, wct,
                 onAnimationEndCallback);
     }
 
@@ -167,7 +167,7 @@
         }
 
         final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-        if (type == Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE
+        if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
             // to null and we don't require an animation
@@ -194,7 +194,7 @@
         }
 
         Rect endBounds = change.getEndAbsBounds();
-        if (type == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
+        if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
                 && !endBounds.isEmpty()) {
             // This Transition animates a task to freeform bounds after being dragged into freeform
@@ -246,7 +246,7 @@
             return true;
         }
 
-        if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+        if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             // This Transition animates a task to fullscreen after being dragged from the status
             // bar and then released back into the status bar area
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 4ca383f..e45dacf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -150,18 +150,18 @@
     public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
 
     /** Transition type for starting the move to desktop mode. */
-    public static final int TRANSIT_START_MOVE_TO_DESKTOP_MODE =
+    public static final int TRANSIT_START_DRAG_TO_DESKTOP_MODE =
             WindowManager.TRANSIT_FIRST_CUSTOM + 10;
 
     /** Transition type for finalizing the move to desktop mode. */
-    public static final int TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE =
+    public static final int TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE =
             WindowManager.TRANSIT_FIRST_CUSTOM + 11;
 
     /** Transition type to fullscreen from desktop mode. */
     public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
 
     /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */
-    public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
+    public static final int TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE =
             WindowManager.TRANSIT_FIRST_CUSTOM + 13;
 
     /** Transition type to animate the toggle resize between the max and default desktop sizes. */
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 80cf96a..2d7e6a6 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
@@ -72,6 +72,9 @@
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.TaskCornersListener;
 
@@ -89,6 +92,7 @@
     private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellTaskOrganizer mTaskOrganizer;
+    private final ShellController mShellController;
     private final Context mContext;
     private final Handler mMainHandler;
     private final Choreographer mMainChoreographer;
@@ -114,30 +118,37 @@
 
     private MoveToDesktopAnimator mMoveToDesktopAnimator;
     private final Rect mDragToDesktopAnimationStartBounds = new Rect();
+    private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener;
 
     public DesktopModeWindowDecorViewModel(
             Context context,
             Handler mainHandler,
             Choreographer mainChoreographer,
+            ShellInit shellInit,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
+            ShellController shellController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopModeController> desktopModeController,
-            Optional<DesktopTasksController> desktopTasksController) {
+            Optional<DesktopTasksController> desktopTasksController
+    ) {
         this(
                 context,
                 mainHandler,
                 mainChoreographer,
+                shellInit,
                 taskOrganizer,
                 displayController,
+                shellController,
                 syncQueue,
                 transitions,
                 desktopModeController,
                 desktopTasksController,
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory(),
-                SurfaceControl.Transaction::new);
+                SurfaceControl.Transaction::new,
+                new DesktopModeKeyguardChangeListener());
     }
 
     @VisibleForTesting
@@ -145,20 +156,24 @@
             Context context,
             Handler mainHandler,
             Choreographer mainChoreographer,
+            ShellInit shellInit,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
+            ShellController shellController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopModeController> desktopModeController,
             Optional<DesktopTasksController> desktopTasksController,
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory,
-            Supplier<SurfaceControl.Transaction> transactionFactory) {
+            Supplier<SurfaceControl.Transaction> transactionFactory,
+            DesktopModeKeyguardChangeListener desktopModeKeyguardChangeListener) {
         mContext = context;
         mMainHandler = mainHandler;
         mMainChoreographer = mainChoreographer;
         mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
         mTaskOrganizer = taskOrganizer;
+        mShellController = shellController;
         mDisplayController = displayController;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
@@ -168,6 +183,13 @@
         mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
         mInputMonitorFactory = inputMonitorFactory;
         mTransactionFactory = transactionFactory;
+        mDesktopModeKeyguardChangeListener = desktopModeKeyguardChangeListener;
+
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    private void onInit() {
+        mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
     }
 
     @Override
@@ -197,8 +219,8 @@
             @NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change) {
         if (change.getMode() == WindowManager.TRANSIT_CHANGE
-                && (info.getType() == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
-                || info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+                && (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
+                || info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
                 || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
                 || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) {
             mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
@@ -796,6 +818,10 @@
                 && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
             return false;
         }
+        if (mDesktopModeKeyguardChangeListener.isKeyguardVisibleAndOccluded()
+                && taskInfo.isFocused) {
+            return false;
+        }
         return DesktopModeStatus.isProto2Enabled()
                 && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
                 && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
@@ -884,6 +910,22 @@
             mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(taskId));
         }
     }
+
+    static class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
+        private boolean mIsKeyguardVisible;
+        private boolean mIsKeyguardOccluded;
+
+        @Override
+        public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+                boolean animatingDismiss) {
+            mIsKeyguardVisible = visible;
+            mIsKeyguardOccluded = occluded;
+        }
+
+        public boolean isKeyguardVisibleAndOccluded() {
+            return mIsKeyguardVisible && mIsKeyguardOccluded;
+        }
+    }
 }
 
 
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 208ae84..0947723 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -24,6 +24,11 @@
 }
 
 filegroup {
+    name: "WMShellFlickerTestsUtils-src",
+    srcs: ["src/com/android/wm/shell/flicker/utils/*.kt"],
+}
+
+filegroup {
     name: "WMShellFlickerTestsBase-src",
     srcs: ["src/com/android/wm/shell/flicker/*.kt"],
 }
@@ -39,10 +44,23 @@
 }
 
 filegroup {
-    name: "WMShellFlickerTestsSplitScreen-src",
+    name: "WMShellFlickerTestsSplitScreenBase-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt",
+    ],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsSplitScreenEnter-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/splitscreen/Enter*.kt",
+    ],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsSplitScreenOther-src",
     srcs: [
         "src/com/android/wm/shell/flicker/splitscreen/*.kt",
-        "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt",
     ],
 }
 
@@ -53,6 +71,28 @@
     ],
 }
 
+java_library {
+    name: "wm-shell-flicker-utils",
+    platform_apis: true,
+    optimize: {
+        enabled: false,
+    },
+    srcs: [
+        ":WMShellFlickerTestsUtils-src",
+    ],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "flickertestapplib",
+        "flickerlib",
+        "flickerlib-helpers",
+        "platform-test-annotations",
+        "wm-flicker-common-app-helpers",
+        "wm-flicker-common-assertions",
+        "launcher-helper-lib",
+        "launcher-aosp-tapl",
+    ],
+}
+
 java_defaults {
     name: "WMShellFlickerTestsDefault",
     manifest: "manifests/AndroidManifest.xml",
@@ -65,6 +105,7 @@
     test_suites: ["device-tests"],
     libs: ["android.test.runner"],
     static_libs: [
+        "wm-shell-flicker-utils",
         "androidx.test.ext.junit",
         "flickertestapplib",
         "flickerlib",
@@ -94,7 +135,9 @@
     exclude_srcs: [
         ":WMShellFlickerTestsBubbles-src",
         ":WMShellFlickerTestsPip-src",
-        ":WMShellFlickerTestsSplitScreen-src",
+        ":WMShellFlickerTestsSplitScreenEnter-src",
+        ":WMShellFlickerTestsSplitScreenOther-src",
+        ":WMShellFlickerTestsSplitScreenBase-src",
         ":WMShellFlickerServiceTests-src",
     ],
 }
@@ -124,14 +167,31 @@
 }
 
 android_test {
-    name: "WMShellFlickerTestsSplitScreen",
+    name: "WMShellFlickerTestsSplitScreenEnter",
     defaults: ["WMShellFlickerTestsDefault"],
     additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
     package_name: "com.android.wm.shell.flicker.splitscreen",
     instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
     srcs: [
         ":WMShellFlickerTestsBase-src",
-        ":WMShellFlickerTestsSplitScreen-src",
+        ":WMShellFlickerTestsSplitScreenBase-src",
+        ":WMShellFlickerTestsSplitScreenEnter-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsSplitScreenOther",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
+    package_name: "com.android.wm.shell.flicker.splitscreen",
+    instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsSplitScreenBase-src",
+        ":WMShellFlickerTestsSplitScreenOther-src",
+    ],
+    exclude_srcs: [
+        ":WMShellFlickerTestsSplitScreenEnter-src",
     ],
 }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index d2fe9fe..735fbfb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -21,6 +21,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.utils.ICommonAssertions
 
 /**
  * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR],
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 69c8ecd..77f14f1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -24,11 +24,10 @@
 import com.android.server.wm.flicker.helpers.LetterboxAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
-import com.android.wm.shell.flicker.appWindowKeepVisible
-import com.android.wm.shell.flicker.layerKeepVisible
-
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.utils.appWindowKeepVisible
+import com.android.wm.shell.flicker.utils.layerKeepVisible
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Rule
@@ -37,9 +36,7 @@
     protected val context: Context = instrumentation.context
     protected val letterboxApp = LetterboxAppHelper(instrumentation)
 
-    @JvmField
-    @Rule
-    val letterboxRule: LetterboxRule = LetterboxRule()
+    @JvmField @Rule val letterboxRule: LetterboxRule = LetterboxRule()
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
index 5a1136f..744e8c2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
@@ -23,16 +23,14 @@
 import org.junit.runner.Description
 import org.junit.runners.model.Statement
 
-/**
- * JUnit Rule to handle letterboxStyles and states
- */
+/** JUnit Rule to handle letterboxStyles and states */
 class LetterboxRule(
-        private val withLetterboxEducationEnabled: Boolean = false,
-        private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
-        private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+    private val withLetterboxEducationEnabled: Boolean = false,
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+    private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
 ) : TestRule {
 
-    private val execAdb: (String) -> String = {cmd -> cmdHelper.executeShellCommand(cmd)}
+    private val execAdb: (String) -> String = { cmd -> cmdHelper.executeShellCommand(cmd) }
     private lateinit var _letterboxStyle: MutableMap<String, String>
 
     val letterboxStyle: Map<String, String>
@@ -62,8 +60,7 @@
         var hasLetterboxEducationStateChanged = false
         if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) {
             hasLetterboxEducationStateChanged = true
-            execAdb("wm set-letterbox-style --isEducationEnabled " +
-                    withLetterboxEducationEnabled)
+            execAdb("wm set-letterbox-style --isEducationEnabled " + withLetterboxEducationEnabled)
         }
         return try {
             object : Statement() {
@@ -74,8 +71,8 @@
             }
         } finally {
             if (hasLetterboxEducationStateChanged) {
-                execAdb("wm set-letterbox-style --isEducationEnabled " +
-                        isLetterboxEducationEnabled
+                execAdb(
+                    "wm set-letterbox-style --isEducationEnabled " + isLetterboxEducationEnabled
                 )
             }
             resetLetterboxStyle()
@@ -100,9 +97,10 @@
         execAdb("wm reset-letterbox-style")
     }
 
-    private fun asInt(str: String?): Int? = try {
-        str?.toInt()
-    } catch (e: NumberFormatException) {
-        null
-    }
-}
\ No newline at end of file
+    private fun asInt(str: String?): Int? =
+        try {
+            str?.toInt()
+        } catch (e: NumberFormatException) {
+            null
+        }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
index e6ca261..2fa1ec3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -54,9 +54,7 @@
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            setup {
-                letterboxTranslucentLauncherApp.launchViaIntent(wmHelper)
-            }
+            setup { letterboxTranslucentLauncherApp.launchViaIntent(wmHelper) }
             transitions {
                 waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found")
             }
@@ -66,9 +64,7 @@
             }
         }
 
-    /**
-     * Checks the transparent activity is launched on top of the opaque one
-     */
+    /** Checks the transparent activity is launched on top of the opaque one */
     @Postsubmit
     @Test
     fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() {
@@ -79,18 +75,14 @@
         }
     }
 
-    /**
-     * Checks that the activity is letterboxed
-     */
+    /** Checks that the activity is letterboxed */
     @Postsubmit
     @Test
     fun translucentActivityIsLetterboxed() {
         flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
     }
 
-    /**
-     * Checks that the translucent activity inherits bounds from the opaque one.
-     */
+    /** Checks that the translucent activity inherits bounds from the opaque one. */
     @Postsubmit
     @Test
     fun translucentActivityInheritsBoundsFromOpaqueActivity() {
@@ -100,29 +92,26 @@
         }
     }
 
-    /**
-     * Checks that the translucent activity has rounded corners
-     */
+    /** Checks that the translucent activity has rounded corners */
     @Postsubmit
     @Test
     fun translucentActivityHasRoundedCorners() {
-        flicker.assertLayersEnd {
-            this.hasRoundedCorners(letterboxTranslucentApp)
-        }
+        flicker.assertLayersEnd { this.hasRoundedCorners(letterboxTranslucentApp) }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
-         * navigation modes.
+         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
+         * modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return LegacyFlickerTestFactory
-                .nonRotationTests(supportedRotations = listOf(Rotation.ROTATION_90))
+            return LegacyFlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(Rotation.ROTATION_90)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
index 47a7e65..b74aa1d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -49,8 +49,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) :
-    BaseAppCompat(flicker) {
+class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
index ea0392c..9792c85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
@@ -33,20 +33,20 @@
 
 abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     protected val context: Context = instrumentation.context
-    protected val letterboxTranslucentLauncherApp = LetterboxAppHelper(
-        instrumentation,
-        launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
-        component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
-    )
-    protected val letterboxTranslucentApp = LetterboxAppHelper(
-        instrumentation,
-        launcherName = ActivityOptions.TransparentActivity.LABEL,
-        component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
-    )
+    protected val letterboxTranslucentLauncherApp =
+        LetterboxAppHelper(
+            instrumentation,
+            launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
+            component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
+        )
+    protected val letterboxTranslucentApp =
+        LetterboxAppHelper(
+            instrumentation,
+            launcherName = ActivityOptions.TransparentActivity.LABEL,
+            component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
+        )
 
-    @JvmField
-    @Rule
-    val letterboxRule: LetterboxRule = LetterboxRule()
+    @JvmField @Rule val letterboxRule: LetterboxRule = LetterboxRule()
 
     @Before
     fun before() {
@@ -54,10 +54,7 @@
     }
 
     protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? =
-        device.wait(
-            Until.findObject(By.text("Launch Transparent")),
-            FIND_TIMEOUT
-        )
+        device.wait(Until.findObject(By.text("Launch Transparent")), FIND_TIMEOUT)
 
     protected fun FlickerTestData.goBack() = device.pressBack()
 }
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 f165cb1..9cc9fb9 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
@@ -42,8 +42,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FlakyTest(bugId = 217777115)
-class ChangeActiveActivityFromBubbleTest(flicker: LegacyFlickerTest) :
-    BaseBubbleScreen(flicker) {
+class ChangeActiveActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index 8bd44c3..c335d3d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -28,7 +28,7 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
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 2f7a25e..0492a18 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,6 +16,7 @@
 
 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
@@ -84,7 +85,7 @@
     }
 
     /** Checks that [pipApp] window is animated towards default position in right bottom corner */
-    @Presubmit
+    @FlakyTest(bugId = 255578530)
     @Test
     fun pipLayerMovesTowardsRightBottomCorner() {
         // in gestural nav the swipe makes PiP first go upwards
@@ -107,4 +108,10 @@
         Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
         super.focusChanges()
     }
+
+    @FlakyTest(bugId = 289943985)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 4f88184..421ad75 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -21,7 +21,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.Direction
+import com.android.wm.shell.flicker.utils.Direction
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index 9a2fa09..a8fb63d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -22,7 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
-import com.android.wm.shell.flicker.Direction
+import com.android.wm.shell.flicker.utils.Direction
 import org.junit.Test
 import org.junit.runners.Parameterized
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index afb4af6..992f1bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -21,7 +21,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.Direction
+import com.android.wm.shell.flicker.utils.Direction
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 0432a84..d4cd6da 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.UiObject2
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.wait
+import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.utils.wait
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
index 90406c5..4402e21 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
@@ -21,11 +21,11 @@
 import android.os.Bundle
 import android.service.notification.StatusBarNotification
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.NotificationListener.Companion.findNotification
-import com.android.wm.shell.flicker.NotificationListener.Companion.startNotificationListener
-import com.android.wm.shell.flicker.NotificationListener.Companion.stopNotificationListener
-import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToAppear
-import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToDisappear
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.findNotification
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.startNotificationListener
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.stopNotificationListener
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.waitForNotificationToAppear
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.waitForNotificationToDisappear
 import org.junit.After
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 6104b7b..47bff8d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -24,7 +24,7 @@
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import android.view.Surface.ROTATION_0
 import android.view.Surface.rotationToString
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assume.assumeTrue
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
index b0adbe1..4aee61a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
@@ -22,7 +22,7 @@
 import androidx.test.uiautomator.UiDevice
 import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME
 
 /** Id of the root view in the com.android.wm.shell.pip.tv.PipMenuActivity */
 private const val TV_PIP_MENU_ROOT_ID = "tv_pip_menu"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt
deleted file mode 100644
index e640dc4..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt
+++ /dev/null
@@ -1,387 +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.wm.shell.flicker.service.splitscreen
-
-import android.app.Instrumentation
-import android.graphics.Point
-import android.os.SystemClock
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.common.traces.component.IComponentMatcher
-import android.tools.common.traces.component.IComponentNameMatcher
-import android.tools.device.apphelpers.StandardAppHelper
-import android.tools.device.traces.parsers.WindowManagerStateHelper
-import android.tools.device.traces.parsers.toFlickerComponent
-import android.view.InputDevice
-import android.view.MotionEvent
-import android.view.ViewConfiguration
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
-import com.android.server.wm.flicker.helpers.NotificationAppHelper
-import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import org.junit.Assert.assertNotNull
-
-object SplitScreenUtils {
-    private const val TIMEOUT_MS = 3_000L
-    private const val DRAG_DURATION_MS = 1_000L
-    private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
-    private const val DIVIDER_BAR = "docked_divider_handle"
-    private const val OVERVIEW_SNAPSHOT = "snapshot"
-    private const val GESTURE_STEP_MS = 16L
-    private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
-    private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
-
-    private val notificationScrollerSelector: BySelector
-        get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
-    private val notificationContentSelector: BySelector
-        get() = By.text("Flicker Test Notification")
-    private val dividerBarSelector: BySelector
-        get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
-    private val overviewSnapshotSelector: BySelector
-        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
-
-    fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
-        SimpleAppHelper(
-            instrumentation,
-            ActivityOptions.SplitScreen.Primary.LABEL,
-            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
-        )
-
-    fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
-        SimpleAppHelper(
-            instrumentation,
-            ActivityOptions.SplitScreen.Secondary.LABEL,
-            ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
-        )
-
-    fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
-        NonResizeableAppHelper(instrumentation)
-
-    fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
-        NotificationAppHelper(instrumentation)
-
-    fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation)
-
-    fun waitForSplitComplete(
-        wmHelper: WindowManagerStateHelper,
-        primaryApp: IComponentMatcher,
-        secondaryApp: IComponentMatcher,
-    ) {
-        wmHelper
-            .StateSyncBuilder()
-            .withWindowSurfaceAppeared(primaryApp)
-            .withWindowSurfaceAppeared(secondaryApp)
-            .withSplitDividerVisible()
-            .waitForAndVerify()
-    }
-
-    fun enterSplit(
-        wmHelper: WindowManagerStateHelper,
-        tapl: LauncherInstrumentation,
-        device: UiDevice,
-        primaryApp: StandardAppHelper,
-        secondaryApp: StandardAppHelper
-    ) {
-        primaryApp.launchViaIntent(wmHelper)
-        secondaryApp.launchViaIntent(wmHelper)
-        tapl.goHome()
-        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-        splitFromOverview(tapl, device)
-        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-    }
-
-    fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
-        // Note: The initial split position in landscape is different between tablet and phone.
-        // In landscape, tablet will let the first app split to right side, and phone will
-        // split to left side.
-        if (tapl.isTablet) {
-            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
-            // contains more than 3 task views. We need to use uiautomator directly to find the
-            // second task to split.
-            tapl.workspace.switchToOverview().overviewActions.clickSplit()
-            val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
-            if (snapshots == null || snapshots.size < 1) {
-                error("Fail to find a overview snapshot to split.")
-            }
-
-            // Find the second task in the upper right corner in split select mode by sorting
-            // 'left' in descending order and 'top' in ascending order.
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t2.getVisibleBounds().left - t1.getVisibleBounds().left
-            }
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t1.getVisibleBounds().top - t2.getVisibleBounds().top
-            }
-            snapshots[0].click()
-        } else {
-            tapl.workspace
-                .switchToOverview()
-                .currentTask
-                .tapMenu()
-                .tapSplitMenuItem()
-                .currentTask
-                .open()
-        }
-        SystemClock.sleep(TIMEOUT_MS)
-    }
-
-    fun enterSplitViaIntent(
-        wmHelper: WindowManagerStateHelper,
-        primaryApp: StandardAppHelper,
-        secondaryApp: StandardAppHelper
-    ) {
-        val stringExtras =
-            mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true")
-        primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
-        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-    }
-
-    fun dragFromNotificationToSplit(
-        instrumentation: Instrumentation,
-        device: UiDevice,
-        wmHelper: WindowManagerStateHelper
-    ) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-
-        // Pull down the notifications
-        device.swipe(
-            displayBounds.centerX(),
-            5,
-            displayBounds.centerX(),
-            displayBounds.bottom,
-            50 /* steps */
-        )
-        SystemClock.sleep(TIMEOUT_MS)
-
-        // Find the target notification
-        val notificationScroller =
-            device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS)
-                ?: error("Unable to find view $notificationScrollerSelector")
-        var notificationContent = notificationScroller.findObject(notificationContentSelector)
-
-        while (notificationContent == null) {
-            device.swipe(
-                displayBounds.centerX(),
-                displayBounds.centerY(),
-                displayBounds.centerX(),
-                displayBounds.centerY() - 150,
-                20 /* steps */
-            )
-            notificationContent = notificationScroller.findObject(notificationContentSelector)
-        }
-
-        // Drag to split
-        val dragStart = notificationContent.visibleCenter
-        val dragMiddle = Point(dragStart.x + 50, dragStart.y)
-        val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
-        val downTime = SystemClock.uptimeMillis()
-
-        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart)
-        // It needs a horizontal movement to trigger the drag
-        touchMove(
-            instrumentation,
-            downTime,
-            SystemClock.uptimeMillis(),
-            DRAG_DURATION_MS,
-            dragStart,
-            dragMiddle
-        )
-        touchMove(
-            instrumentation,
-            downTime,
-            SystemClock.uptimeMillis(),
-            DRAG_DURATION_MS,
-            dragMiddle,
-            dragEnd
-        )
-        // Wait for a while to start splitting
-        SystemClock.sleep(TIMEOUT_MS)
-        touch(
-            instrumentation,
-            MotionEvent.ACTION_UP,
-            downTime,
-            SystemClock.uptimeMillis(),
-            GESTURE_STEP_MS,
-            dragEnd
-        )
-        SystemClock.sleep(TIMEOUT_MS)
-    }
-
-    fun touch(
-        instrumentation: Instrumentation,
-        action: Int,
-        downTime: Long,
-        eventTime: Long,
-        duration: Long,
-        point: Point
-    ) {
-        val motionEvent =
-            MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0)
-        motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
-        instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
-        motionEvent.recycle()
-        SystemClock.sleep(duration)
-    }
-
-    fun touchMove(
-        instrumentation: Instrumentation,
-        downTime: Long,
-        eventTime: Long,
-        duration: Long,
-        from: Point,
-        to: Point
-    ) {
-        val steps: Long = duration / GESTURE_STEP_MS
-        var currentTime = eventTime
-        var currentX = from.x.toFloat()
-        var currentY = from.y.toFloat()
-        val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
-        val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
-
-        for (i in 1..steps) {
-            val motionMove =
-                MotionEvent.obtain(
-                    downTime,
-                    currentTime,
-                    MotionEvent.ACTION_MOVE,
-                    currentX,
-                    currentY,
-                    0
-                )
-            motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
-            instrumentation.uiAutomation.injectInputEvent(motionMove, true)
-            motionMove.recycle()
-
-            currentTime += GESTURE_STEP_MS
-            if (i == steps - 1) {
-                currentX = to.x.toFloat()
-                currentY = to.y.toFloat()
-            } else {
-                currentX += stepX
-                currentY += stepY
-            }
-            SystemClock.sleep(GESTURE_STEP_MS)
-        }
-    }
-
-    fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
-        tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
-        val allApps = tapl.workspace.switchToAllApps()
-        allApps.freeze()
-        try {
-            allApps.getAppIcon(appName).dragToHotseat(0)
-        } finally {
-            allApps.unfreeze()
-        }
-    }
-
-    fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200)
-
-        wmHelper
-            .StateSyncBuilder()
-            .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
-            .waitForAndVerify()
-    }
-
-    fun dragDividerToDismissSplit(
-        device: UiDevice,
-        wmHelper: WindowManagerStateHelper,
-        dragToRight: Boolean,
-        dragToBottom: Boolean
-    ) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        dividerBar.drag(
-            Point(
-                if (dragToRight) {
-                    displayBounds.width * 4 / 5
-                } else {
-                    displayBounds.width * 1 / 5
-                },
-                if (dragToBottom) {
-                    displayBounds.height * 4 / 5
-                } else {
-                    displayBounds.height * 1 / 5
-                }
-            )
-        )
-    }
-
-    fun doubleTapDividerToSwitch(device: UiDevice) {
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        val interval =
-            (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2
-        dividerBar.click()
-        SystemClock.sleep(interval.toLong())
-        dividerBar.click()
-    }
-
-    fun copyContentInSplit(
-        instrumentation: Instrumentation,
-        device: UiDevice,
-        sourceApp: IComponentNameMatcher,
-        destinationApp: IComponentNameMatcher,
-    ) {
-        // Copy text from sourceApp
-        val textView =
-            device.wait(
-                Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
-                TIMEOUT_MS
-            )
-        assertNotNull("Unable to find the TextView", textView)
-        textView.click(LONG_PRESS_TIME_MS)
-
-        val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
-        assertNotNull("Unable to find the copy button", copyBtn)
-        copyBtn.click()
-
-        // Paste text to destinationApp
-        val editText =
-            device.wait(
-                Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
-                TIMEOUT_MS
-            )
-        assertNotNull("Unable to find the EditText", editText)
-        editText.click(LONG_PRESS_TIME_MS)
-
-        val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
-        assertNotNull("Unable to find the paste button", pasteBtn)
-        pasteBtn.click()
-
-        // Verify text
-        if (!textView.text.contentEquals(editText.text)) {
-            error("Fail to copy content in split")
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index 5bfc889..e530f63 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
index d07daff..e9fc437 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
index d428dea..416692c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index dc2a6ac..494a246 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index 74f441d..a3aae85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index 8d93c0d..50151f1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 36a458f..5d67dc7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index ed08041..ae5bb68 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index f164451..c2100f6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt
deleted file mode 100644
index 3831c65..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt
+++ /dev/null
@@ -1,387 +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.wm.shell.flicker.service.splitscreen.scenarios
-
-import android.app.Instrumentation
-import android.graphics.Point
-import android.os.SystemClock
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.common.traces.component.IComponentMatcher
-import android.tools.common.traces.component.IComponentNameMatcher
-import android.tools.device.apphelpers.StandardAppHelper
-import android.tools.device.traces.parsers.WindowManagerStateHelper
-import android.tools.device.traces.parsers.toFlickerComponent
-import android.view.InputDevice
-import android.view.MotionEvent
-import android.view.ViewConfiguration
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
-import com.android.server.wm.flicker.helpers.NotificationAppHelper
-import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import org.junit.Assert.assertNotNull
-
-object SplitScreenUtils {
-    private const val TIMEOUT_MS = 3_000L
-    private const val DRAG_DURATION_MS = 1_000L
-    private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
-    private const val DIVIDER_BAR = "docked_divider_handle"
-    private const val OVERVIEW_SNAPSHOT = "snapshot"
-    private const val GESTURE_STEP_MS = 16L
-    private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
-    private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
-
-    private val notificationScrollerSelector: BySelector
-        get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
-    private val notificationContentSelector: BySelector
-        get() = By.text("Flicker Test Notification")
-    private val dividerBarSelector: BySelector
-        get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
-    private val overviewSnapshotSelector: BySelector
-        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
-
-    fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
-        SimpleAppHelper(
-            instrumentation,
-            ActivityOptions.SplitScreen.Primary.LABEL,
-            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
-        )
-
-    fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
-        SimpleAppHelper(
-            instrumentation,
-            ActivityOptions.SplitScreen.Secondary.LABEL,
-            ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
-        )
-
-    fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
-        NonResizeableAppHelper(instrumentation)
-
-    fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
-        NotificationAppHelper(instrumentation)
-
-    fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation)
-
-    fun waitForSplitComplete(
-        wmHelper: WindowManagerStateHelper,
-        primaryApp: IComponentMatcher,
-        secondaryApp: IComponentMatcher,
-    ) {
-        wmHelper
-            .StateSyncBuilder()
-            .withWindowSurfaceAppeared(primaryApp)
-            .withWindowSurfaceAppeared(secondaryApp)
-            .withSplitDividerVisible()
-            .waitForAndVerify()
-    }
-
-    fun enterSplit(
-        wmHelper: WindowManagerStateHelper,
-        tapl: LauncherInstrumentation,
-        device: UiDevice,
-        primaryApp: StandardAppHelper,
-        secondaryApp: StandardAppHelper
-    ) {
-        primaryApp.launchViaIntent(wmHelper)
-        secondaryApp.launchViaIntent(wmHelper)
-        tapl.goHome()
-        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-        splitFromOverview(tapl, device)
-        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-    }
-
-    fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
-        // Note: The initial split position in landscape is different between tablet and phone.
-        // In landscape, tablet will let the first app split to right side, and phone will
-        // split to left side.
-        if (tapl.isTablet) {
-            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
-            // contains more than 3 task views. We need to use uiautomator directly to find the
-            // second task to split.
-            tapl.workspace.switchToOverview().overviewActions.clickSplit()
-            val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
-            if (snapshots == null || snapshots.size < 1) {
-                error("Fail to find a overview snapshot to split.")
-            }
-
-            // Find the second task in the upper right corner in split select mode by sorting
-            // 'left' in descending order and 'top' in ascending order.
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t2.getVisibleBounds().left - t1.getVisibleBounds().left
-            }
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t1.getVisibleBounds().top - t2.getVisibleBounds().top
-            }
-            snapshots[0].click()
-        } else {
-            tapl.workspace
-                .switchToOverview()
-                .currentTask
-                .tapMenu()
-                .tapSplitMenuItem()
-                .currentTask
-                .open()
-        }
-        SystemClock.sleep(TIMEOUT_MS)
-    }
-
-    fun enterSplitViaIntent(
-        wmHelper: WindowManagerStateHelper,
-        primaryApp: StandardAppHelper,
-        secondaryApp: StandardAppHelper
-    ) {
-        val stringExtras =
-            mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true")
-        primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
-        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-    }
-
-    fun dragFromNotificationToSplit(
-        instrumentation: Instrumentation,
-        device: UiDevice,
-        wmHelper: WindowManagerStateHelper
-    ) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-
-        // Pull down the notifications
-        device.swipe(
-            displayBounds.centerX(),
-            5,
-            displayBounds.centerX(),
-            displayBounds.bottom,
-            50 /* steps */
-        )
-        SystemClock.sleep(TIMEOUT_MS)
-
-        // Find the target notification
-        val notificationScroller =
-            device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS)
-                ?: error("Unable to find view $notificationScrollerSelector")
-        var notificationContent = notificationScroller.findObject(notificationContentSelector)
-
-        while (notificationContent == null) {
-            device.swipe(
-                displayBounds.centerX(),
-                displayBounds.centerY(),
-                displayBounds.centerX(),
-                displayBounds.centerY() - 150,
-                20 /* steps */
-            )
-            notificationContent = notificationScroller.findObject(notificationContentSelector)
-        }
-
-        // Drag to split
-        val dragStart = notificationContent.visibleCenter
-        val dragMiddle = Point(dragStart.x + 50, dragStart.y)
-        val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
-        val downTime = SystemClock.uptimeMillis()
-
-        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart)
-        // It needs a horizontal movement to trigger the drag
-        touchMove(
-            instrumentation,
-            downTime,
-            SystemClock.uptimeMillis(),
-            DRAG_DURATION_MS,
-            dragStart,
-            dragMiddle
-        )
-        touchMove(
-            instrumentation,
-            downTime,
-            SystemClock.uptimeMillis(),
-            DRAG_DURATION_MS,
-            dragMiddle,
-            dragEnd
-        )
-        // Wait for a while to start splitting
-        SystemClock.sleep(TIMEOUT_MS)
-        touch(
-            instrumentation,
-            MotionEvent.ACTION_UP,
-            downTime,
-            SystemClock.uptimeMillis(),
-            GESTURE_STEP_MS,
-            dragEnd
-        )
-        SystemClock.sleep(TIMEOUT_MS)
-    }
-
-    fun touch(
-        instrumentation: Instrumentation,
-        action: Int,
-        downTime: Long,
-        eventTime: Long,
-        duration: Long,
-        point: Point
-    ) {
-        val motionEvent =
-            MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0)
-        motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
-        instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
-        motionEvent.recycle()
-        SystemClock.sleep(duration)
-    }
-
-    fun touchMove(
-        instrumentation: Instrumentation,
-        downTime: Long,
-        eventTime: Long,
-        duration: Long,
-        from: Point,
-        to: Point
-    ) {
-        val steps: Long = duration / GESTURE_STEP_MS
-        var currentTime = eventTime
-        var currentX = from.x.toFloat()
-        var currentY = from.y.toFloat()
-        val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
-        val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
-
-        for (i in 1..steps) {
-            val motionMove =
-                MotionEvent.obtain(
-                    downTime,
-                    currentTime,
-                    MotionEvent.ACTION_MOVE,
-                    currentX,
-                    currentY,
-                    0
-                )
-            motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
-            instrumentation.uiAutomation.injectInputEvent(motionMove, true)
-            motionMove.recycle()
-
-            currentTime += GESTURE_STEP_MS
-            if (i == steps - 1) {
-                currentX = to.x.toFloat()
-                currentY = to.y.toFloat()
-            } else {
-                currentX += stepX
-                currentY += stepY
-            }
-            SystemClock.sleep(GESTURE_STEP_MS)
-        }
-    }
-
-    fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
-        tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
-        val allApps = tapl.workspace.switchToAllApps()
-        allApps.freeze()
-        try {
-            allApps.getAppIcon(appName).dragToHotseat(0)
-        } finally {
-            allApps.unfreeze()
-        }
-    }
-
-    fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200)
-
-        wmHelper
-            .StateSyncBuilder()
-            .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
-            .waitForAndVerify()
-    }
-
-    fun dragDividerToDismissSplit(
-        device: UiDevice,
-        wmHelper: WindowManagerStateHelper,
-        dragToRight: Boolean,
-        dragToBottom: Boolean
-    ) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        dividerBar.drag(
-            Point(
-                if (dragToRight) {
-                    displayBounds.width * 4 / 5
-                } else {
-                    displayBounds.width * 1 / 5
-                },
-                if (dragToBottom) {
-                    displayBounds.height * 4 / 5
-                } else {
-                    displayBounds.height * 1 / 5
-                }
-            )
-        )
-    }
-
-    fun doubleTapDividerToSwitch(device: UiDevice) {
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        val interval =
-            (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2
-        dividerBar.click()
-        SystemClock.sleep(interval.toLong())
-        dividerBar.click()
-    }
-
-    fun copyContentInSplit(
-        instrumentation: Instrumentation,
-        device: UiDevice,
-        sourceApp: IComponentNameMatcher,
-        destinationApp: IComponentNameMatcher,
-    ) {
-        // Copy text from sourceApp
-        val textView =
-            device.wait(
-                Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
-                TIMEOUT_MS
-            )
-        assertNotNull("Unable to find the TextView", textView)
-        textView.click(LONG_PRESS_TIME_MS)
-
-        val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
-        assertNotNull("Unable to find the copy button", copyBtn)
-        copyBtn.click()
-
-        // Paste text to destinationApp
-        val editText =
-            device.wait(
-                Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
-                TIMEOUT_MS
-            )
-        assertNotNull("Unable to find the EditText", editText)
-        editText.click(LONG_PRESS_TIME_MS)
-
-        val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
-        assertNotNull("Unable to find the paste button", pasteBtn)
-        pasteBtn.click()
-
-        // Verify text
-        if (!textView.text.contentEquals(editText.text)) {
-            error("Fail to copy content in split")
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index 805d987..70f3bed 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -26,6 +26,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 4229ebb..86f394d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index f2d56b9..d7b611e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index 9f9d4bb..3cc5df0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index e2c6ca6..4a9c32f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
index df98d8f..383a6b3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
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 1d4c4d2..3702be9 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
@@ -25,12 +25,12 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowKeepVisible
-import com.android.wm.shell.flicker.layerKeepVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowKeepVisible
+import com.android.wm.shell.flicker.utils.layerKeepVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsKeepVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 0b8f109..8b90630 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
@@ -24,14 +24,14 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesInvisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesInvisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesInvisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 38d4b40..50f6a38 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
@@ -23,12 +23,12 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.layerBecomesInvisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.utils.layerBecomesInvisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesInvisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 0d967eb..cc3b783 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
@@ -23,12 +23,12 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowKeepVisible
-import com.android.wm.shell.flicker.layerKeepVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
 import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowKeepVisible
+import com.android.wm.shell.flicker.utils.layerKeepVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsChanges
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -57,7 +57,7 @@
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @Presubmit
+    @FlakyTest(bugId = 291678271)
     @Test
     fun primaryAppLayerVisibilityChanges() {
         flicker.assertLayers {
@@ -69,7 +69,7 @@
         }
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 291678271)
     @Test
     fun secondaryAppLayerVisibilityChanges() {
         flicker.assertLayers {
@@ -87,7 +87,7 @@
     @Test
     fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp)
 
-    @FlakyTest(bugId = 245472831)
+    @FlakyTest(bugId = 291678271)
     @Test
     fun primaryAppBoundsChanges() {
         flicker.splitAppLayerBoundsChanges(
@@ -97,7 +97,7 @@
         )
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 291678271)
     @Test
     fun secondaryAppBoundsChanges() =
         flicker.splitAppLayerBoundsChanges(
@@ -106,6 +106,12 @@
             portraitPosTop = true
         )
 
+    @FlakyTest(bugId = 291678271)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
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 05c0480..f8d1e1f 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
@@ -25,16 +25,16 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 3a75fa6..ff5d935 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
@@ -25,15 +25,15 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 6d73f92..7c71077 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
@@ -24,14 +24,14 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 15cae69..8371706 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
@@ -25,16 +25,16 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 90399fc..0bfdbb4 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
@@ -23,14 +23,14 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index f236c2d..fac97c8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -24,13 +24,13 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerKeepVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerKeepVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 a406009..88bbc0e 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
@@ -24,12 +24,12 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 251bd10..e85dc24 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
@@ -24,12 +24,12 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 1dd45fe..f7a9ed0 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
@@ -24,12 +24,12 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 8aaa98a..66f9b85 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
@@ -23,15 +23,15 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesInvisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesInvisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsSnapToDivider
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
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 994d6cb..851391d 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
@@ -24,12 +24,12 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitscreen.benchmark.UnlockKeyguardToSplitScreenBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index d9d22de..4d90070 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -22,8 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index 7e8d60b4..8360e94 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -21,8 +21,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index 770e032..e745878 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -21,8 +21,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index 46570fd..c3beb36 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -21,8 +21,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 5c3d4ff..80ccaa1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -22,8 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
index 6b122c6..cd3fbab 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -22,8 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index 78f9bab..a06ae6b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -22,8 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index 78907f0..de4ec6d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -22,8 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index 2c91e84..be507d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -21,8 +21,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
index 3064bd7..2a07369 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.splitscreen
+package com.android.wm.shell.flicker.splitscreen.benchmark
 
 import android.content.Context
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseBenchmarkTest
-import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 
 abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(flicker) {
     protected val context: Context = instrumentation.context
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index fa09c2e..ed0debd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -25,8 +25,7 @@
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index ff22006..9b7939a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -22,8 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index 5787b02..9326ef3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -22,8 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index b2d5091..b928e40 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -22,8 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index f234e46..f314995 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -21,8 +21,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
index 61c3679..e71834d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
@@ -22,8 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.SplitScreenUtils
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
similarity index 99%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
index 9cc03a5..e5c124c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
@@ -16,7 +16,7 @@
 
 @file:JvmName("CommonAssertions")
 
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
 
 import android.tools.common.Rotation
 import android.tools.common.datatypes.Region
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
index 3bc1e2a..3b66d6a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
@@ -16,7 +16,7 @@
 
 @file:JvmName("CommonConstants")
 
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
 
 import android.tools.common.traces.component.ComponentNameMatcher
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
index 7b32901..7f58ced 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MultiWindowUtils.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MultiWindowUtils.kt
index 87b94ff..9b3a480 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MultiWindowUtils.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
 
 import android.app.Instrumentation
 import android.content.Context
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/NotificationListener.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/NotificationListener.kt
index e0ef924..529c125 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/NotificationListener.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
 
 import android.service.notification.NotificationListenerService
 import android.service.notification.StatusBarNotification
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
similarity index 99%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index 8a3c2c9..3f8a1ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
 
 import android.app.Instrumentation
 import android.graphics.Point
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/WaitUtils.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/WaitUtils.kt
index 556cb06..cf2df4e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/WaitUtils.kt
@@ -16,7 +16,7 @@
 
 @file:JvmName("WaitUtils")
 
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
 
 import android.os.SystemClock
 
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index ad4d97f..38e9f39 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -61,7 +61,7 @@
         "libstaticjvmtiagent",
     ],
 
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
 
     plugins: ["dagger2-compiler"],
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index 885ae38..772d97d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -99,7 +99,7 @@
         final int taskId = 1;
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
-                .startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
+                .startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct,
                         mEnterDesktopTaskTransitionHandler);
         doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
 
@@ -108,7 +108,7 @@
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
-        TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE,
+        TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE,
                 change);
 
 
@@ -121,7 +121,7 @@
 
     @Test
     public void testTransitEnterDesktopModeAnimation() throws Throwable {
-        final int transitionType = Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE;
+        final int transitionType = Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE;
         final int taskId = 1;
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
@@ -132,7 +132,7 @@
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
         change.setEndAbsBounds(new Rect(0, 0, 1, 1));
         TransitionInfo info = createTransitionInfo(
-                Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, change);
+                Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, change);
 
         runOnUiThread(() -> {
             try {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index adc2a6f..596d6dd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -18,12 +18,15 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
 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 static org.mockito.Mockito.when;
@@ -53,6 +56,8 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -91,6 +96,10 @@
     @Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory;
     @Mock private SurfaceControl.Transaction mTransaction;
     @Mock private Display mDisplay;
+    @Mock private ShellController mShellController;
+    @Mock private ShellInit mShellInit;
+    @Mock private DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
+            mDesktopModeKeyguardChangeListener;
     private final List<InputManager> mMockInputManagers = new ArrayList<>();
 
     private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel;
@@ -104,15 +113,18 @@
                 mContext,
                 mMainHandler,
                 mMainChoreographer,
+                mShellInit,
                 mTaskOrganizer,
                 mDisplayController,
+                mShellController,
                 mSyncQueue,
                 mTransitions,
                 Optional.of(mDesktopModeController),
                 Optional.of(mDesktopTasksController),
                 mDesktopModeWindowDecorFactory,
                 mMockInputMonitorFactory,
-                mTransactionFactory
+                mTransactionFactory,
+                mDesktopModeKeyguardChangeListener
             );
 
         doReturn(mDesktopModeWindowDecoration)
@@ -121,6 +133,7 @@
         doReturn(mTransaction).when(mTransactionFactory).get();
         doReturn(mDisplayLayout).when(mDisplayController).getDisplayLayout(anyInt());
         doReturn(STABLE_INSETS).when(mDisplayLayout).stableInsets();
+        doNothing().when(mShellController).addKeyguardChangeListener(any());
 
         when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
         // InputChannel cannot be mocked because it passes to InputEventReceiver.
@@ -255,6 +268,32 @@
         verify(mInputMonitor, times(1)).dispose();
     }
 
+    @Test
+    public void testCaptionIsNotCreatedWhenKeyguardIsVisible() throws Exception {
+        doReturn(true).when(
+                mDesktopModeKeyguardChangeListener).isKeyguardVisibleAndOccluded();
+
+        final int taskId = 1;
+        final ActivityManager.RunningTaskInfo taskInfo =
+                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN);
+        taskInfo.isFocused = true;
+        SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        runOnMainThread(() -> {
+            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+            mDesktopModeWindowDecorViewModel.onTaskOpening(
+                    taskInfo, surfaceControl, startT, finishT);
+
+            taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+            taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+            mDesktopModeWindowDecorViewModel.onTaskChanging(
+                    taskInfo, surfaceControl, startT, finishT);
+        });
+        verify(mDesktopModeWindowDecorFactory, never())
+                .create(any(), any(), any(), any(), any(), any(), any(), any());
+    }
+
     private void runOnMainThread(Runnable r) throws Exception {
         final Handler mainHandler = new Handler(Looper.getMainLooper());
         final CountDownLatch latch = new CountDownLatch(1);
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index dcbaaec..ad09067 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -91,12 +91,17 @@
   StringPoolRef entry_string_ref;
 };
 
-AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration)
-    : configuration_(configuration) {
+AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) {
+  configurations_.push_back(configuration);
+
   // Don't invalidate caches here as there's nothing cached yet.
   SetApkAssets(apk_assets, false);
 }
 
+AssetManager2::AssetManager2() {
+  configurations_.resize(1);
+}
+
 bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
   BuildDynamicRefTable(apk_assets);
   RebuildFilterList();
@@ -421,9 +426,16 @@
   return false;
 }
 
-void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
-  const int diff = configuration_.diff(configuration);
-  configuration_ = configuration;
+void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) {
+  int diff = 0;
+  if (configurations_.size() != configurations.size()) {
+    diff = -1;
+  } else {
+    for (int i = 0; i < configurations_.size(); i++) {
+      diff |= configurations_[i].diff(configurations[i]);
+    }
+  }
+  configurations_ = std::move(configurations);
 
   if (diff) {
     RebuildFilterList();
@@ -620,16 +632,6 @@
 
   auto op = StartOperation();
 
-  // Might use this if density_override != 0.
-  ResTable_config density_override_config;
-
-  // Select our configuration or generate a density override configuration.
-  const ResTable_config* desired_config = &configuration_;
-  if (density_override != 0 && density_override != configuration_.density) {
-    density_override_config = configuration_;
-    density_override_config.density = density_override;
-    desired_config = &density_override_config;
-  }
 
   // Retrieve the package group from the package id of the resource id.
   if (UNLIKELY(!is_valid_resid(resid))) {
@@ -648,119 +650,160 @@
   }
 
   const PackageGroup& package_group = package_groups_[package_idx];
-  auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
-                                  stop_at_first_match, ignore_configuration);
-  if (UNLIKELY(!result.has_value())) {
-    return base::unexpected(result.error());
-  }
+  std::optional<FindEntryResult> final_result;
+  bool final_has_locale = false;
+  bool final_overlaid = false;
+  for (auto & config : configurations_) {
+    // Might use this if density_override != 0.
+    ResTable_config density_override_config;
 
-  bool overlaid = false;
-  if (!stop_at_first_match && !ignore_configuration) {
-    const auto& assets = GetApkAssets(result->cookie);
-    if (!assets) {
-      ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
-      return base::unexpected(std::nullopt);
+    // Select our configuration or generate a density override configuration.
+    const ResTable_config* desired_config = &config;
+    if (density_override != 0 && density_override != config.density) {
+      density_override_config = config;
+      density_override_config.density = density_override;
+      desired_config = &density_override_config;
     }
-    if (!assets->IsLoader()) {
-      for (const auto& id_map : package_group.overlays_) {
-        auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
-        if (!overlay_entry) {
-          // No id map entry exists for this target resource.
-          continue;
-        }
-        if (overlay_entry.IsInlineValue()) {
-          // The target resource is overlaid by an inline value not represented by a resource.
-          ConfigDescription best_frro_config;
-          Res_value best_frro_value;
-          bool frro_found = false;
-          for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
-            if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
-                && config.match(*desired_config)) {
-              frro_found = true;
-              best_frro_config = config;
-              best_frro_value = value;
-            }
-          }
-          if (!frro_found) {
+
+    auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
+                                    stop_at_first_match, ignore_configuration);
+    if (UNLIKELY(!result.has_value())) {
+      return base::unexpected(result.error());
+    }
+    bool overlaid = false;
+    if (!stop_at_first_match && !ignore_configuration) {
+      const auto& assets = GetApkAssets(result->cookie);
+      if (!assets) {
+        ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
+        return base::unexpected(std::nullopt);
+      }
+      if (!assets->IsLoader()) {
+        for (const auto& id_map : package_group.overlays_) {
+          auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
+          if (!overlay_entry) {
+            // No id map entry exists for this target resource.
             continue;
           }
-          result->entry = best_frro_value;
+          if (overlay_entry.IsInlineValue()) {
+            // The target resource is overlaid by an inline value not represented by a resource.
+            ConfigDescription best_frro_config;
+            Res_value best_frro_value;
+            bool frro_found = false;
+            for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+              if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
+                  && config.match(*desired_config)) {
+                frro_found = true;
+                best_frro_config = config;
+                best_frro_value = value;
+              }
+            }
+            if (!frro_found) {
+              continue;
+            }
+            result->entry = best_frro_value;
+            result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+            result->cookie = id_map.cookie;
+
+            if (UNLIKELY(logging_enabled)) {
+              last_resolution_.steps.push_back(Resolution::Step{
+                  Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
+              if (auto path = assets->GetPath()) {
+                const std::string overlay_path = path->data();
+                if (IsFabricatedOverlay(overlay_path)) {
+                  // FRRO don't have package name so we use the creating package here.
+                  String8 frro_name = String8("FRRO");
+                  // Get the first part of it since the expected one should be like
+                  // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
+                  // under /data/resource-cache/.
+                  const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
+                  const size_t end = name.find('-');
+                  if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
+                    frro_name.append(base::StringPrintf(" created by %s",
+                                                        name.substr(0 /* pos */,
+                                                                    end).c_str()).c_str());
+                  }
+                  last_resolution_.best_package_name = frro_name;
+                } else {
+                  last_resolution_.best_package_name = result->package_name->c_str();
+                }
+              }
+              overlaid = true;
+            }
+            continue;
+          }
+
+          auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
+                                          false /* stop_at_first_match */,
+                                          false /* ignore_configuration */);
+          if (UNLIKELY(IsIOError(overlay_result))) {
+            return base::unexpected(overlay_result.error());
+          }
+          if (!overlay_result.has_value()) {
+            continue;
+          }
+
+          if (!overlay_result->config.isBetterThan(result->config, desired_config)
+              && overlay_result->config.compare(result->config) != 0) {
+            // The configuration of the entry for the overlay must be equal to or better than the
+            // target configuration to be chosen as the better value.
+            continue;
+          }
+
+          result->cookie = overlay_result->cookie;
+          result->entry = overlay_result->entry;
+          result->config = overlay_result->config;
           result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
-          result->cookie = id_map.cookie;
 
           if (UNLIKELY(logging_enabled)) {
             last_resolution_.steps.push_back(
-                Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
-            if (auto path = assets->GetPath()) {
-              const std::string overlay_path = path->data();
-              if (IsFabricatedOverlay(overlay_path)) {
-                // FRRO don't have package name so we use the creating package here.
-                String8 frro_name = String8("FRRO");
-                // Get the first part of it since the expected one should be like
-                // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
-                // under /data/resource-cache/.
-                const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
-                const size_t end = name.find('-');
-                if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
-                  frro_name.append(base::StringPrintf(" created by %s",
-                                                      name.substr(0 /* pos */,
-                                                                  end).c_str()).c_str());
-                }
-                last_resolution_.best_package_name = frro_name;
-              } else {
-                last_resolution_.best_package_name = result->package_name->c_str();
-              }
-            }
+                Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
+                                 overlay_result->config.toString()});
+            last_resolution_.best_package_name =
+                overlay_result->package_name->c_str();
             overlaid = true;
           }
-          continue;
-        }
-
-        auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
-                                        false /* stop_at_first_match */,
-                                        false /* ignore_configuration */);
-        if (UNLIKELY(IsIOError(overlay_result))) {
-          return base::unexpected(overlay_result.error());
-        }
-        if (!overlay_result.has_value()) {
-          continue;
-        }
-
-        if (!overlay_result->config.isBetterThan(result->config, desired_config)
-            && overlay_result->config.compare(result->config) != 0) {
-          // The configuration of the entry for the overlay must be equal to or better than the target
-          // configuration to be chosen as the better value.
-          continue;
-        }
-
-        result->cookie = overlay_result->cookie;
-        result->entry = overlay_result->entry;
-        result->config = overlay_result->config;
-        result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
-
-        if (UNLIKELY(logging_enabled)) {
-          last_resolution_.steps.push_back(
-              Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
-                               overlay_result->config.toString()});
-          last_resolution_.best_package_name =
-              overlay_result->package_name->c_str();
-          overlaid = true;
         }
       }
     }
+
+    bool has_locale = false;
+    if (result->config.locale == 0) {
+      if (default_locale_ != 0) {
+        ResTable_config conf;
+        conf.locale = default_locale_;
+        // Since we know conf has a locale and only a locale, match will tell us if that locale
+        // matches
+        has_locale = conf.match(config);
+      }
+    } else {
+      has_locale = true;
+    }
+
+    // if we don't have a result yet
+    if (!final_result ||
+        // or this config is better before the locale than the existing result
+        result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
+        // or the existing config isn't better before locale and this one specifies a locale
+        // whereas the existing one doesn't
+        (!final_result->config.isBetterThanBeforeLocale(result->config, desired_config)
+            && has_locale && !final_has_locale)) {
+      final_result = result.value();
+      final_overlaid = overlaid;
+      final_has_locale = has_locale;
+    }
   }
 
   if (UNLIKELY(logging_enabled)) {
-    last_resolution_.cookie = result->cookie;
-    last_resolution_.type_string_ref = result->type_string_ref;
-    last_resolution_.entry_string_ref = result->entry_string_ref;
-    last_resolution_.best_config_name = result->config.toString();
-    if (!overlaid) {
-      last_resolution_.best_package_name = result->package_name->c_str();
+    last_resolution_.cookie = final_result->cookie;
+    last_resolution_.type_string_ref = final_result->type_string_ref;
+    last_resolution_.entry_string_ref = final_result->entry_string_ref;
+    last_resolution_.best_config_name = final_result->config.toString();
+    if (!final_overlaid) {
+      last_resolution_.best_package_name = final_result->package_name->c_str();
     }
   }
 
-  return result;
+  return *final_result;
 }
 
 base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
@@ -778,8 +821,10 @@
   // If `desired_config` is not the same as the set configuration or the caller will accept a value
   // from any configuration, then we cannot use our filtered list of types since it only it contains
   // types matched to the set configuration.
-  const bool use_filtered = !ignore_configuration && &desired_config == &configuration_;
-
+  const bool use_filtered = !ignore_configuration && std::find_if(
+      configurations_.begin(), configurations_.end(),
+      [&desired_config](auto& value) { return &desired_config == &value; })
+      != configurations_.end();
   const size_t package_count = package_group.packages_.size();
   for (size_t pi = 0; pi < package_count; pi++) {
     const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
@@ -934,10 +979,22 @@
   }
 
   std::stringstream log_stream;
-  log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
-                                   "\tFor config - %s", resid, resource_name_string.c_str(),
-                                   configuration_.toString().c_str());
-
+  if (configurations_.size() == 1) {
+    log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
+                                     "\tFor config - %s", resid, resource_name_string.c_str(),
+                                     configurations_[0].toString().c_str());
+  } else {
+    ResTable_config conf = configurations_[0];
+    conf.clearLocale();
+    log_stream << base::StringPrintf("Resolution for 0x%08x %s\n\tFor config - %s and locales",
+                                     resid, resource_name_string.c_str(), conf.toString().c_str());
+    char str[40];
+    str[0] = '\0';
+    for(auto iter = configurations_.begin(); iter < configurations_.end(); iter++) {
+      iter->getBcp47Locale(str);
+      log_stream << base::StringPrintf(" %s%s", str, iter < configurations_.end() ? "," : "");
+    }
+  }
   for (const Resolution::Step& step : last_resolution_.steps) {
     constexpr static std::array kStepStrings = {
         "Found initial",
@@ -1427,11 +1484,14 @@
       package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
         FilteredConfigGroup* group = nullptr;
         for (const auto& type_entry : type_spec.type_entries) {
-          if (type_entry.config.match(configuration_)) {
-            if (!group) {
-              group = &package.filtered_configs_.editItemAt(type_id - 1);
+          for (auto & config : configurations_) {
+            if (type_entry.config.match(config)) {
+              if (!group) {
+                group = &package.filtered_configs_.editItemAt(type_id - 1);
+              }
+              group->type_entries.push_back(&type_entry);
+              break;
             }
-            group->type_entries.push_back(&type_entry);
           }
         }
       });
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5a63612..06d19e0 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2568,6 +2568,22 @@
     return false;
 }
 
+bool ResTable_config::isBetterThanBeforeLocale(const ResTable_config& o,
+        const ResTable_config* requested) const {
+    if (requested) {
+        if (imsi || o.imsi) {
+            if ((mcc != o.mcc) && requested->mcc) {
+                return (mcc);
+            }
+
+            if ((mnc != o.mnc) && requested->mnc) {
+                return (mnc);
+            }
+        }
+    }
+    return false;
+}
+
 bool ResTable_config::isBetterThan(const ResTable_config& o,
         const ResTable_config* requested) const {
     if (requested) {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index f611d0d..d9ff35b 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -100,7 +100,7 @@
   using ApkAssetsWPtr = wp<const ApkAssets>;
   using ApkAssetsList = std::span<const ApkAssetsPtr>;
 
-  AssetManager2() = default;
+  AssetManager2();
   explicit AssetManager2(AssetManager2&& other) = default;
   AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration);
 
@@ -156,10 +156,14 @@
 
   // Sets/resets the configuration for this AssetManager. This will cause all
   // caches that are related to the configuration change to be invalidated.
-  void SetConfiguration(const ResTable_config& configuration);
+  void SetConfigurations(std::vector<ResTable_config> configurations);
 
-  inline const ResTable_config& GetConfiguration() const {
-    return configuration_;
+  inline const std::vector<ResTable_config>& GetConfigurations() const {
+    return configurations_;
+  }
+
+  inline void SetDefaultLocale(uint32_t default_locale) {
+    default_locale_ = default_locale;
   }
 
   // Returns all configurations for which there are resources defined, or an I/O error if reading
@@ -465,9 +469,11 @@
   // without taking too much memory.
   std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
 
-  // The current configuration set for this AssetManager. When this changes, cached resources
+  uint32_t default_locale_;
+
+  // The current configurations set for this AssetManager. When this changes, cached resources
   // may need to be purged.
-  ResTable_config configuration_ = {};
+  std::vector<ResTable_config> configurations_;
 
   // Cached set of bags. These are cached because they can inherit keys from parent bags,
   // which involves some calculation.
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 4eb1d7a..52666ab 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1375,6 +1375,8 @@
     // match the requested configuration at all.
     bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
 
+    bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
+
     String8 toString() const;
 };
 
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 6fae72a..2caa98c 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -228,10 +228,12 @@
 
   ResTable_config config;
   memset(&config, 0, sizeof(config));
+  std::vector<ResTable_config> configs;
+  configs.push_back(config);
 
   while (state.KeepRunning()) {
-    config.sdkVersion = ~config.sdkVersion;
-    assets.SetConfiguration(config);
+    configs[0].sdkVersion = ~configs[0].sdkVersion;
+    assets.SetConfigurations(configs);
   }
 }
 BENCHMARK(BM_AssetManagerSetConfigurationFramework);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index df3fa02..c62f095 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -113,7 +113,7 @@
   desired_config.language[1] = 'e';
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfiguration(desired_config);
+  assetmanager.SetConfigurations({desired_config});
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -137,7 +137,7 @@
   desired_config.language[1] = 'e';
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfiguration(desired_config);
+  assetmanager.SetConfigurations({desired_config});
   assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -466,10 +466,10 @@
 TEST_F(AssetManager2Test, DensityOverride) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_});
-  assetmanager.SetConfiguration({
+  assetmanager.SetConfigurations({{
     .density = ResTable_config::DENSITY_XHIGH,
     .sdkVersion = 21,
-  });
+  }});
 
   auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/);
   ASSERT_TRUE(value.has_value());
@@ -721,7 +721,7 @@
   ResTable_config desired_config;
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfiguration(desired_config);
+  assetmanager.SetConfigurations({desired_config});
   assetmanager.SetApkAssets({basic_assets_});
   assetmanager.SetResourceResolutionLoggingEnabled(false);
 
@@ -736,7 +736,7 @@
   ResTable_config desired_config;
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfiguration(desired_config);
+  assetmanager.SetConfigurations({desired_config});
   assetmanager.SetApkAssets({basic_assets_});
 
   auto result = assetmanager.GetLastResourceResolution();
@@ -751,7 +751,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfiguration(desired_config);
+  assetmanager.SetConfigurations({desired_config});
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -774,7 +774,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfiguration(desired_config);
+  assetmanager.SetConfigurations({desired_config});
   assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -796,7 +796,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfiguration(desired_config);
+  assetmanager.SetConfigurations({desired_config});
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -817,7 +817,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfiguration(desired_config);
+  assetmanager.SetConfigurations({desired_config});
   assetmanager.SetApkAssets({overlayable_assets_});
 
   const auto map = assetmanager.GetOverlayableMapForPackage(0x7f);
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index b97dd96..8b883f4 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -66,7 +66,7 @@
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets(apk_assets);
   if (config != nullptr) {
-    assetmanager.SetConfiguration(*config);
+    assetmanager.SetConfigurations({*config});
   }
 
   while (state.KeepRunning()) {
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index e08a6a7..181d141 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -260,7 +260,7 @@
   ResTable_config night{};
   night.uiMode = ResTable_config::UI_MODE_NIGHT_YES;
   night.version = 8u;
-  am_night.SetConfiguration(night);
+  am_night.SetConfigurations({night});
 
   auto theme = am.NewTheme();
   {
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
index 13e3c8e..764d1ef 100644
--- a/libs/hwui/Mesh.h
+++ b/libs/hwui/Mesh.h
@@ -19,6 +19,7 @@
 
 #include <GrDirectContext.h>
 #include <SkMesh.h>
+#include <include/gpu/ganesh/SkMeshGanesh.h>
 #include <jni.h>
 #include <log/log.h>
 
@@ -143,14 +144,26 @@
         }
 
         if (mIsDirty || genId != mGenerationId) {
-            auto vb = SkMesh::MakeVertexBuffer(
-                    context, reinterpret_cast<const void*>(mVertexBufferData.data()),
-                    mVertexBufferData.size());
+            auto vertexData = reinterpret_cast<const void*>(mVertexBufferData.data());
+#ifdef __ANDROID__
+            auto vb = SkMeshes::MakeVertexBuffer(context,
+                                                 vertexData,
+                                                 mVertexBufferData.size());
+#else
+            auto vb = SkMeshes::MakeVertexBuffer(vertexData,
+                                                 mVertexBufferData.size());
+#endif
             auto meshMode = SkMesh::Mode(mMode);
             if (!mIndexBufferData.empty()) {
-                auto ib = SkMesh::MakeIndexBuffer(
-                        context, reinterpret_cast<const void*>(mIndexBufferData.data()),
-                        mIndexBufferData.size());
+                auto indexData = reinterpret_cast<const void*>(mIndexBufferData.data());
+#ifdef __ANDROID__
+                auto ib = SkMeshes::MakeIndexBuffer(context,
+                                                    indexData,
+                                                    mIndexBufferData.size());
+#else
+                auto ib = SkMeshes::MakeIndexBuffer(indexData,
+                                                    mIndexBufferData.size());
+#endif
                 mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
                                             ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
                                             mBounds)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 3cd0e75..71f47e9 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -36,6 +36,7 @@
 #include "SkImageFilter.h"
 #include "SkImageInfo.h"
 #include "SkLatticeIter.h"
+#include "SkMesh.h"
 #include "SkPaint.h"
 #include "SkPicture.h"
 #include "SkRRect.h"
@@ -49,6 +50,7 @@
 #include "effects/GainmapRenderer.h"
 #include "include/gpu/GpuTypes.h"  // from Skia
 #include "include/gpu/GrDirectContext.h"
+#include "include/gpu/ganesh/SkMeshGanesh.h"
 #include "pipeline/skia/AnimatedDrawables.h"
 #include "pipeline/skia/FunctorDrawable.h"
 #ifdef __ANDROID__
@@ -527,12 +529,13 @@
     mutable bool isGpuBased;
     mutable GrDirectContext::DirectContextID contextId;
     void draw(SkCanvas* c, const SkMatrix&) const {
+#ifdef __ANDROID__
         GrDirectContext* directContext = c->recordingContext()->asDirectContext();
 
         GrDirectContext::DirectContextID id = directContext->directContextID();
         if (!isGpuBased || contextId != id) {
             sk_sp<SkMesh::VertexBuffer> vb =
-                    SkMesh::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
+                    SkMeshes::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
             if (!cpuMesh.indexBuffer()) {
                 gpuMesh = SkMesh::Make(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(),
                                        cpuMesh.vertexOffset(), cpuMesh.refUniforms(),
@@ -540,7 +543,7 @@
                                   .mesh;
             } else {
                 sk_sp<SkMesh::IndexBuffer> ib =
-                        SkMesh::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
+                        SkMeshes::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
                 gpuMesh = SkMesh::MakeIndexed(cpuMesh.refSpec(), cpuMesh.mode(), vb,
                                               cpuMesh.vertexCount(), cpuMesh.vertexOffset(), ib,
                                               cpuMesh.indexCount(), cpuMesh.indexOffset(),
@@ -553,6 +556,9 @@
         }
 
         c->drawMesh(gpuMesh, blender, paint);
+#else
+        c->drawMesh(cpuMesh, blender, paint);
+#endif
     }
 };
 
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index b785989..ced0224 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -175,7 +175,7 @@
                                   const Paint& paint, const SkPath& path, size_t start,
                                   size_t end) override;
 
-    void onFilterPaint(Paint& paint);
+    virtual void onFilterPaint(Paint& paint);
 
     Paint filterPaint(const Paint& src) {
         Paint dst(src);
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index f060bb3..426644e 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -84,7 +84,7 @@
     canvas->resetRecording(width, height, renderNode);
 }
 
-static jint android_view_DisplayListCanvas_getMaxTextureSize(CRITICAL_JNI_PARAMS) {
+static jint android_view_DisplayListCanvas_getMaxTextureSize(JNIEnv*, jobject) {
 #ifdef __ANDROID__ // Layoutlib does not support RenderProxy (RenderThread)
     return android::uirenderer::renderthread::RenderProxy::maxTextureSize();
 #else
@@ -175,14 +175,14 @@
 const char* const kClassPathName = "android/graphics/RecordingCanvas";
 
 static JNINativeMethod gMethods[] = {
+        {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
+        {"nGetMaximumTextureHeight", "()I",
+         (void*)android_view_DisplayListCanvas_getMaxTextureSize},
         // ------------ @CriticalNative --------------
         {"nCreateDisplayListCanvas", "(JII)J",
          (void*)android_view_DisplayListCanvas_createDisplayListCanvas},
         {"nResetDisplayListCanvas", "(JJII)V",
          (void*)android_view_DisplayListCanvas_resetDisplayListCanvas},
-        {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
-        {"nGetMaximumTextureHeight", "()I",
-         (void*)android_view_DisplayListCanvas_getMaxTextureSize},
         {"nEnableZ", "(JZ)V", (void*)android_view_DisplayListCanvas_enableZ},
         {"nFinishRecording", "(JJ)V", (void*)android_view_DisplayListCanvas_finishRecording},
         {"nDrawRenderNode", "(JJ)V", (void*)android_view_DisplayListCanvas_drawRenderNode},
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 58c14c1..e917f9a 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -236,6 +236,17 @@
     }
 }
 
+void SkiaRecordingCanvas::onFilterPaint(android::Paint& paint) {
+    INHERITED::onFilterPaint(paint);
+    SkShader* shader = paint.getShader();
+    // TODO(b/264559422): This only works for very specifically a BitmapShader.
+    //  It's better than nothing, though
+    SkImage* image = shader ? shader->isAImage(nullptr, nullptr) : nullptr;
+    if (image) {
+        mDisplayList->mMutableImages.push_back(image);
+    }
+}
+
 void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
     auto payload = DrawImagePayload(bitmap);
 
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index a8e4580..3bd091d 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -105,6 +105,8 @@
 
     void handleMutableImages(Bitmap& bitmap, DrawImagePayload& payload);
 
+    void onFilterPaint(Paint& paint) override;
+
     using INHERITED = SkiaCanvas;
 };
 
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 224c878..f949ddd 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -16,7 +16,13 @@
 
 #include "RenderProxy.h"
 
+#include <SkBitmap.h>
+#include <SkImage.h>
+#include <SkPicture.h>
 #include <gui/TraceUtils.h>
+#include <pthread.h>
+#include <ui/GraphicBufferAllocator.h>
+
 #include "DeferredLayerUpdater.h"
 #include "DisplayList.h"
 #include "Properties.h"
@@ -29,12 +35,6 @@
 #include "utils/Macros.h"
 #include "utils/TimeUtils.h"
 
-#include <SkBitmap.h>
-#include <SkImage.h>
-#include <SkPicture.h>
-
-#include <pthread.h>
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
@@ -323,6 +323,9 @@
             }
         });
     }
+    std::string grallocInfo;
+    GraphicBufferAllocator::getInstance().dump(grallocInfo);
+    dprintf(fd, "%s\n", grallocInfo.c_str());
 }
 
 void RenderProxy::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 7ef82a7..ffc664c 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -61,18 +61,10 @@
         ADD_FAILURE() << "ClipState not a rect";                                     \
     }
 
-#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \
-    TEST(test_case_name, test_name##_##pipeline) {                             \
-        RenderPipelineType oldType = Properties::getRenderPipelineType();      \
-        Properties::overrideRenderPipelineType(RenderPipelineType::pipeline);  \
-        functionCall;                                                          \
-        Properties::overrideRenderPipelineType(oldType);                       \
-    };
-
-#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \
-    INNER_PIPELINE_TEST(test_case_name, test_name, pipeline,                  \
-                        TestUtils::runOnRenderThread(                         \
-                                test_case_name##_##test_name##_RenderThreadTest::doTheThing))
+#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name)                                \
+    TEST(test_case_name, test_name) {                                                              \
+        TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+    }
 
 /**
  * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
@@ -83,21 +75,7 @@
     public:                                                                                 \
         static void doTheThing(renderthread::RenderThread& renderThread);                   \
     };                                                                                      \
-    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL);                    \
-    /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
-    /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */          \
-    void test_case_name##_##test_name##_RenderThreadTest::doTheThing(                       \
-            renderthread::RenderThread& renderThread)
-
-/**
- * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes
- */
-#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name)                          \
-    class test_case_name##_##test_name##_RenderThreadTest {                                 \
-    public:                                                                                 \
-        static void doTheThing(renderthread::RenderThread& renderThread);                   \
-    };                                                                                      \
-    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL);                    \
+    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name);                            \
     /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
     /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */          \
     void test_case_name##_##test_name##_RenderThreadTest::doTheThing(                       \
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index cc7d34b..89d00d3 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -35,7 +35,7 @@
 }
 
 // TOOD(258700630): fix this test and re-enable
-RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
+RENDERTHREAD_TEST(CacheManager, DISABLED_trimMemory) {
     int32_t width = DeviceInfo::get()->getWidth();
     int32_t height = DeviceInfo::get()->getHeight();
     GrDirectContext* grContext = renderThread.getGrContext();
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 1e055c2..073a835 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -355,7 +355,7 @@
     EXPECT_EQ(3, canvas.getIndex());
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) {
+RENDERTHREAD_TEST(RenderNodeDrawable, emptyReceiver) {
     class ProjectionTestCanvas : public SkCanvas {
     public:
         ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
@@ -419,7 +419,7 @@
     EXPECT_EQ(2, canvas.getDrawCounter());
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
     /* R is backward projected on B and C is a layer.
                 A
                / \
@@ -1052,7 +1052,7 @@
 }
 
 // Verify that layers are composed with linear filtering.
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) {
+RENDERTHREAD_TEST(RenderNodeDrawable, layerComposeQuality) {
     static const int CANVAS_WIDTH = 1;
     static const int CANVAS_HEIGHT = 1;
     static const int LAYER_WIDTH = 1;
@@ -1170,7 +1170,7 @@
 }
 
 // Draw a vector drawable twice but with different bounds and verify correct bounds are used.
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
+RENDERTHREAD_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
     static const int CANVAS_WIDTH = 100;
     static const int CANVAS_HEIGHT = 200;
     class VectorDrawableTestCanvas : public TestCanvasBase {
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 9aa2e1d..0f8bd13 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -370,9 +370,9 @@
 }
 
 using namespace android::uirenderer;
-RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
+RENDERTHREAD_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
     if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) {
-        // RENDERTHREAD_SKIA_PIPELINE_TEST declares both SkiaVK and SkiaGL variants.
+        // RENDERTHREAD_TEST declares both SkiaVK and SkiaGL variants.
         GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan";
     }
     if (!folderExist(getExternalStorageFolder())) {
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 1a1ce1e..064d42e 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -129,6 +129,33 @@
     EXPECT_EQ(counts.destroyed, 1);
 }
 
+TEST(SkiaDisplayList, recordMutableBitmap) {
+    SkiaRecordingCanvas canvas{nullptr, 100, 100};
+    auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+            10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType));
+    EXPECT_FALSE(bitmap->isImmutable());
+    canvas.drawBitmap(*bitmap, 0, 0, nullptr);
+    auto displayList = canvas.finishRecording();
+    ASSERT_EQ(1, displayList->mMutableImages.size());
+    EXPECT_EQ(10, displayList->mMutableImages[0]->width());
+    EXPECT_EQ(20, displayList->mMutableImages[0]->height());
+}
+
+TEST(SkiaDisplayList, recordMutableBitmapInShader) {
+    SkiaRecordingCanvas canvas{nullptr, 100, 100};
+    auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+            10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType));
+    EXPECT_FALSE(bitmap->isImmutable());
+    SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
+    Paint paint;
+    paint.setShader(bitmap->makeImage()->makeShader(sampling));
+    canvas.drawPaint(paint);
+    auto displayList = canvas.finishRecording();
+    ASSERT_EQ(1, displayList->mMutableImages.size());
+    EXPECT_EQ(10, displayList->mMutableImages[0]->width());
+    EXPECT_EQ(20, displayList->mMutableImages[0]->height());
+}
+
 class ContextFactory : public IContextFactory {
 public:
     virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
@@ -136,7 +163,7 @@
     }
 };
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
+RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren) {
     auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
@@ -195,7 +222,7 @@
     canvasContext->destroy();
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
+RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
     auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 6f180e7..3ded540 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -42,7 +42,7 @@
 using namespace android::uirenderer::renderthread;
 using namespace android::uirenderer::skiapipeline;
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrame) {
     auto redNode = TestUtils::createSkiaNode(
             0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
                 redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -62,7 +62,7 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) {
     auto halfGreenNode = TestUtils::createSkiaNode(
             0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
                 Paint greenPaint;
@@ -89,7 +89,7 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
     auto redNode = TestUtils::createSkiaNode(
             0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
                 redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -111,7 +111,7 @@
     ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) {
+RENDERTHREAD_TEST(SkiaPipeline, renderLayer) {
     auto redNode = TestUtils::createSkiaNode(
             0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
                 redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -154,7 +154,7 @@
     blueNode->setLayerSurface(sk_sp<SkSurface>());
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) {
+RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) {
     ScopedProperty<bool> prop(Properties::debugOverdraw, true);
 
     auto whiteNode = TestUtils::createSkiaNode(
@@ -227,7 +227,7 @@
 };
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) {
+RENDERTHREAD_TEST(SkiaPipeline, deferRenderNodeScene) {
     class DeferTestCanvas : public SkCanvas {
     public:
         DeferTestCanvas() : SkCanvas(800, 600) {}
@@ -297,7 +297,7 @@
     EXPECT_EQ(4, surface->canvas()->mDrawCounter);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) {
+RENDERTHREAD_TEST(SkiaPipeline, clipped) {
     static const int CANVAS_WIDTH = 200;
     static const int CANVAS_HEIGHT = 200;
     class ClippedTestCanvas : public SkCanvas {
@@ -330,7 +330,7 @@
 }
 
 // Test renderFrame with a dirty clip and a pre-transform matrix.
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) {
+RENDERTHREAD_TEST(SkiaPipeline, clipped_rotated) {
     static const int CANVAS_WIDTH = 200;
     static const int CANVAS_HEIGHT = 100;
     static const SkMatrix rotateMatrix = SkMatrix::MakeAll(0, -1, CANVAS_HEIGHT, 1, 0, 0, 0, 0, 1);
@@ -366,7 +366,7 @@
     EXPECT_EQ(1, surface->canvas()->mDrawCounter);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) {
+RENDERTHREAD_TEST(SkiaPipeline, clip_replace) {
     static const int CANVAS_WIDTH = 50;
     static const int CANVAS_HEIGHT = 50;
     class ClipReplaceTestCanvas : public SkCanvas {
@@ -396,7 +396,7 @@
     EXPECT_EQ(1, surface->canvas()->mDrawCounter);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) {
+RENDERTHREAD_TEST(SkiaPipeline, context_lost) {
     test::TestContext context;
     auto surface = context.surface();
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
@@ -410,7 +410,7 @@
     EXPECT_TRUE(pipeline->isSurfaceReady());
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) {
+RENDERTHREAD_TEST(SkiaPipeline, pictureCallback) {
     // create a pipeline and add a picture callback
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     int callbackCount = 0;
diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp
index 10c874e..76cbc8a 100644
--- a/libs/hwui/tests/unit/main.cpp
+++ b/libs/hwui/tests/unit/main.cpp
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
+#include <getopt.h>
+#include <signal.h>
 
 #include "Properties.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
 #include "hwui/Typeface.h"
 #include "tests/common/LeakChecker.h"
 
-#include <signal.h>
-
 using namespace std;
 using namespace android;
 using namespace android::uirenderer;
@@ -45,6 +45,57 @@
     raise(sig);
 }
 
+// For options that only exist in long-form. Anything in the
+// 0-255 range is reserved for short options (which just use their ASCII value)
+namespace LongOpts {
+enum {
+    Reserved = 255,
+    Renderer,
+};
+}
+
+static const struct option LONG_OPTIONS[] = {
+        {"renderer", required_argument, nullptr, LongOpts::Renderer}, {0, 0, 0, 0}};
+
+static RenderPipelineType parseRenderer(const char* renderer) {
+    // Anything that's not skiavk is skiagl
+    if (!strcmp(renderer, "skiavk")) {
+        return RenderPipelineType::SkiaVulkan;
+    }
+    return RenderPipelineType::SkiaGL;
+}
+
+struct Options {
+    RenderPipelineType renderer = RenderPipelineType::SkiaGL;
+};
+
+Options parseOptions(int argc, char* argv[]) {
+    int c;
+    opterr = 0;
+    Options opts;
+
+    while (true) {
+        /* getopt_long stores the option index here. */
+        int option_index = 0;
+
+        c = getopt_long(argc, argv, "", LONG_OPTIONS, &option_index);
+
+        if (c == -1) break;
+
+        switch (c) {
+            case 0:
+                // Option set a flag, don't need to do anything
+                // (although none of the current LONG_OPTIONS do this...)
+                break;
+
+            case LongOpts::Renderer:
+                opts.renderer = parseRenderer(optarg);
+                break;
+        }
+    }
+    return opts;
+}
+
 class TypefaceEnvironment : public testing::Environment {
 public:
     virtual void SetUp() { Typeface::setRobotoTypefaceForTest(); }
@@ -64,8 +115,9 @@
 
     // Avoid talking to SF
     Properties::isolatedProcess = true;
-    // Default to GLES (Vulkan-aware tests will override this)
-    Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL);
+
+    auto opts = parseOptions(argc, argv);
+    Properties::overrideRenderPipelineType(opts.renderer);
 
     // Run the tests
     testing::InitGoogleTest(&argc, argv);
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index fc33cef..8f1b9fe 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -25,6 +25,7 @@
 import android.system.OsConstants;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.midi.MidiDispatcher;
 
 import dalvik.system.CloseGuard;
@@ -34,6 +35,7 @@
 import java.io.Closeable;
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -83,6 +85,14 @@
     private AtomicInteger mTotalInputBytes = new AtomicInteger();
     private AtomicInteger mTotalOutputBytes = new AtomicInteger();
 
+    private static final int UNUSED_UID = -1;
+
+    private final Object mUmpUidLock = new Object();
+    @GuardedBy("mUmpUidLock")
+    private final int[] mUmpInputPortUids;
+    @GuardedBy("mUmpUidLock")
+    private final int[] mUmpOutputPortUids;
+
     public interface Callback {
         /**
          * Called to notify when an our device status has changed
@@ -137,6 +147,9 @@
                 int portNumber = mOutputPort.getPortNumber();
                 mInputPortOutputPorts[portNumber] = null;
                 mInputPortOpen[portNumber] = false;
+                synchronized (mUmpUidLock) {
+                    mUmpInputPortUids[portNumber] = UNUSED_UID;
+                }
                 mTotalOutputBytes.addAndGet(mOutputPort.pullTotalBytesCount());
                 updateTotalBytes();
                 updateDeviceStatus();
@@ -162,6 +175,9 @@
                 dispatcher.getSender().disconnect(mInputPort);
                 int openCount = dispatcher.getReceiverCount();
                 mOutputPortOpenCount[portNumber] = openCount;
+                synchronized (mUmpUidLock) {
+                    mUmpOutputPortUids[portNumber] = UNUSED_UID;
+                }
                 mTotalInputBytes.addAndGet(mInputPort.pullTotalBytesCount());
                 updateTotalBytes();
                 updateDeviceStatus();
@@ -210,6 +226,25 @@
                     return null;
                 }
 
+                if (isUmpDevice()) {
+                    if (portNumber >= mOutputPortCount) {
+                        Log.e(TAG, "out portNumber out of range in openInputPort: " + portNumber);
+                        return null;
+                    }
+                    synchronized (mUmpUidLock) {
+                        if (mUmpInputPortUids[portNumber] != UNUSED_UID) {
+                            Log.e(TAG, "input port already open in openInputPort: " + portNumber);
+                            return null;
+                        }
+                        if ((mUmpOutputPortUids[portNumber] != UNUSED_UID)
+                                && (Binder.getCallingUid() != mUmpOutputPortUids[portNumber])) {
+                            Log.e(TAG, "different uid for output in openInputPort: " + portNumber);
+                            return null;
+                        }
+                        mUmpInputPortUids[portNumber] = Binder.getCallingUid();
+                    }
+                }
+
                 try {
                     FileDescriptor[] pair = createSeqPacketSocketPair();
                     MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
@@ -242,6 +277,25 @@
                 return null;
             }
 
+            if (isUmpDevice()) {
+                if (portNumber >= mInputPortCount) {
+                    Log.e(TAG, "in portNumber out of range in openOutputPort: " + portNumber);
+                    return null;
+                }
+                synchronized (mUmpUidLock) {
+                    if (mUmpOutputPortUids[portNumber] != UNUSED_UID) {
+                        Log.e(TAG, "output port already open in openOutputPort: " + portNumber);
+                        return null;
+                    }
+                    if ((mUmpInputPortUids[portNumber] != UNUSED_UID)
+                            && (Binder.getCallingUid() != mUmpInputPortUids[portNumber])) {
+                        Log.e(TAG, "different uid for input in openOutputPort: " + portNumber);
+                        return null;
+                    }
+                    mUmpOutputPortUids[portNumber] = Binder.getCallingUid();
+                }
+            }
+
             try {
                 FileDescriptor[] pair = createSeqPacketSocketPair();
                 MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
@@ -358,6 +412,13 @@
         mInputPortOpen = new boolean[mInputPortCount];
         mOutputPortOpenCount = new int[numOutputPorts];
 
+        synchronized (mUmpUidLock) {
+            mUmpInputPortUids = new int[mInputPortCount];
+            mUmpOutputPortUids = new int[mOutputPortCount];
+            Arrays.fill(mUmpInputPortUids, UNUSED_UID);
+            Arrays.fill(mUmpOutputPortUids, UNUSED_UID);
+        }
+
         mGuard.open("close");
     }
 
@@ -467,4 +528,8 @@
             Log.e(TAG, "RemoteException in updateTotalBytes");
         }
     }
+
+    private boolean isUmpDevice() {
+        return mDeviceInfo.getDefaultProtocol() != MidiDeviceInfo.PROTOCOL_UNKNOWN;
+    }
 }
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
index 388d95b..96540a2 100644
--- a/media/java/android/media/midi/MidiDeviceService.java
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -34,15 +34,15 @@
  * <p>To extend this class, you must declare the service in your manifest file with
  * an intent filter with the {@link #SERVICE_INTERFACE} action
  * and meta-data to describe the virtual device.
- For example:</p>
+ * For example:</p>
  * <pre>
  * &lt;service android:name=".VirtualDeviceService"
- *          android:label="&#64;string/service_name">
+ *         android:label="&#64;string/service_name">
  *     &lt;intent-filter>
- *         &lt;action android:name="android.media.midi.MidiDeviceService" />
+ *             &lt;action android:name="android.media.midi.MidiDeviceService" />
  *     &lt;/intent-filter>
- *           &lt;meta-data android:name="android.media.midi.MidiDeviceService"
-                android:resource="@xml/device_info" />
+ *     &lt;meta-data android:name="android.media.midi.MidiDeviceService"
+ *             android:resource="@xml/device_info" />
  * &lt;/service></pre>
  */
 abstract public class MidiDeviceService extends Service {
@@ -114,8 +114,8 @@
     }
 
     /**
-     * returns the {@link MidiDeviceInfo} instance for this service
-     * @return our MidiDeviceInfo
+     * Returns the {@link MidiDeviceInfo} instance for this service
+     * @return the MidiDeviceInfo of the virtual MIDI device
      */
     public final MidiDeviceInfo getDeviceInfo() {
         return mDeviceInfo;
@@ -123,13 +123,14 @@
 
     /**
      * Called to notify when an our {@link MidiDeviceStatus} has changed
-     * @param status the number of the port that was opened
+     * @param status the current status of the MIDI device
      */
     public void onDeviceStatusChanged(MidiDeviceStatus status) {
     }
 
     /**
-     * Called to notify when our device has been closed by all its clients
+     * Called to notify when the virtual MIDI device running in this service has been closed by
+     * all its clients
      */
     public void onClose() {
     }
diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java
new file mode 100644
index 0000000..0c6096e
--- /dev/null
+++ b/media/java/android/media/midi/MidiUmpDeviceService.java
@@ -0,0 +1,161 @@
+/*
+ * 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 android.media.midi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A service that implements a virtual MIDI device for Universal MIDI Packets (UMP).
+ * Subclasses must implement the {@link #onGetInputPortReceivers} method to provide a
+ * list of {@link MidiReceiver}s to receive data sent to the device's input ports.
+ * Similarly, subclasses can call {@link #getOutputPortReceivers} to fetch a list
+ * of {@link MidiReceiver}s for sending data out the output ports.
+ *
+ * Unlike traditional MIDI byte streams, only complete UMPs should be sent.
+ * Unlike with {@link #MidiDeviceService}, the number of input and output ports must be equal.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * an intent filter with the {@link #SERVICE_INTERFACE} action
+ * and meta-data to describe the virtual device.
+ * For example:</p>
+ * <pre>
+ * &lt;service android:name=".VirtualDeviceService"
+ *         android:label="&#64;string/service_name">
+ *     &lt;intent-filter>
+ *             &lt;action android:name="android.media.midi.MidiUmpDeviceService" />
+ *     &lt;/intent-filter>
+ *     &lt;property android:name="android.media.midi.MidiUmpDeviceService"
+ *             android:resource="@xml/device_info" />
+ * &lt;/service></pre>
+ */
+public abstract class MidiUmpDeviceService extends Service {
+    private static final String TAG = "MidiUmpDeviceService";
+
+    public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
+
+    private IMidiManager mMidiManager;
+    private MidiDeviceServer mServer;
+    private MidiDeviceInfo mDeviceInfo;
+
+    private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
+        @Override
+        public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
+            MidiUmpDeviceService.this.onDeviceStatusChanged(status);
+        }
+
+        @Override
+        public void onClose() {
+            MidiUmpDeviceService.this.onClose();
+        }
+    };
+
+    @Override
+    public void onCreate() {
+        mMidiManager = IMidiManager.Stub.asInterface(
+                    ServiceManager.getService(Context.MIDI_SERVICE));
+        MidiDeviceServer server;
+        try {
+            MidiDeviceInfo deviceInfo = mMidiManager.getServiceDeviceInfo(getPackageName(),
+                    this.getClass().getName());
+            if (deviceInfo == null) {
+                Log.e(TAG, "Could not find MidiDeviceInfo for MidiUmpDeviceService " + this);
+                return;
+            }
+            mDeviceInfo = deviceInfo;
+
+            List<MidiReceiver> inputPortReceivers = onGetInputPortReceivers();
+            if (inputPortReceivers == null) {
+                Log.e(TAG, "Could not get input port receivers for MidiUmpDeviceService " + this);
+                return;
+            }
+            MidiReceiver[] inputPortReceiversArr = new MidiReceiver[inputPortReceivers.size()];
+            inputPortReceivers.toArray(inputPortReceiversArr);
+            server = new MidiDeviceServer(mMidiManager, inputPortReceiversArr, deviceInfo,
+                    mCallback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in IMidiManager.getServiceDeviceInfo");
+            server = null;
+        }
+        mServer = server;
+    }
+
+    /**
+     * Returns a list of {@link MidiReceiver} for the device's input ports.
+     * Subclasses must override this to provide the receivers which will receive
+     * data sent to the device's input ports.
+     * The number of input and output ports must be equal and non-zero.
+     * @return list of MidiReceivers
+     */
+    public abstract @NonNull List<MidiReceiver> onGetInputPortReceivers();
+
+    /**
+     * Returns a list of {@link MidiReceiver} for the device's output ports.
+     * These can be used to send data out the device's output ports.
+     * The number of input and output ports must be equal and non-zero.
+     * @return the list of MidiReceivers
+     */
+    public final @NonNull List<MidiReceiver> getOutputPortReceivers() {
+        if (mServer == null) {
+            return new ArrayList<MidiReceiver>();
+        } else {
+            return Arrays.asList(mServer.getOutputPortReceivers());
+        }
+    }
+
+    /**
+     * Returns the {@link MidiDeviceInfo} instance for this service
+     * @return the MidiDeviceInfo of the virtual MIDI device
+     */
+    public final @Nullable MidiDeviceInfo getDeviceInfo() {
+        return mDeviceInfo;
+    }
+
+    /**
+     * Called to notify when the {@link MidiDeviceStatus} has changed
+     * @param status the current status of the MIDI device
+     */
+    public void onDeviceStatusChanged(@Nullable MidiDeviceStatus status) {
+    }
+
+    /**
+     * Called to notify when the virtual MIDI device running in this service has been closed by
+     * all its clients
+     */
+    public void onClose() {
+    }
+
+    @Override
+    public @Nullable IBinder onBind(@NonNull Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) {
+            return mServer.getBinderInterface().asBinder();
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 67df1b2..d9e38e2 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -267,6 +267,56 @@
 contain System Real-Time messages, which can be interleaved inside other
 messages.</p>
 
+<h1 id=using_midi_btle>Using MIDI Over Bluetooth LE</h1>
+
+<p>MIDI devices can be connected to Android using Bluetooth LE.</p>
+
+<p>Before using the device, the app must scan for available BTLE devices and then allow
+the user to connect.
+See the Android developer website for an
+<a href="https://source.android.com/devices/audio/midi_test#apps" target="_blank">example
+program</a>.</p>
+
+<h2 id=btle_location_permissions>Request Location Permission for BTLE</h2>
+
+<p>Applications that scan for Bluetooth devices must request permission in the
+manifest file. This LOCATION permission is required because it may be possible to
+guess the location of an Android device by seeing which BTLE devices are nearby.</p>
+
+<pre class=prettyprint>
+&lt;uses-permission android:name="android.permission.BLUETOOTH"/>
+&lt;uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+&lt;uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+</pre>
+
+<p>Apps must also request location permission from the user at run-time.
+See the documentation for <code>Activity.requestPermissions()</code> for details and an example.
+</p>
+
+<h2 id=btle_scan_devices>Scan for MIDI Devices</h2>
+
+<p>The app will only want to see MIDI devices and not mice or other non-MIDI devices.
+So construct a ScanFilter using the UUID for standard MIDI over BTLE.</p>
+
+<pre class=prettyprint>
+MIDI over BTLE UUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"
+</pre>
+
+<h2 id=btle_open_device>Open a MIDI Bluetooth Device</h2>
+
+<p>See the documentation for <code>android.bluetooth.le.BluetoothLeScanner.startScan()</code>
+method for details. When the user selects a MIDI/BTLE device then you can open it
+using the MidiManager.</p>
+
+<pre class=prettyprint>
+m.openBluetoothDevice(bluetoothDevice, callback, handler);
+</pre>
+
+<p>Once the MIDI/BTLE device has been opened by one app then it will also become available to other
+apps using the
+<a href="#get_list_of_already_plugged_in_entities">MIDI device discovery calls described above</a>.
+</p>
+
 <h1 id=creating_a_midi_virtual_device_service>Creating a MIDI Virtual Device Service</h1>
 
 
@@ -355,62 +405,17 @@
 }
 </pre>
 
-<h1 id=using_midi_btle>Using MIDI Over Bluetooth LE</h1>
 
-<p>MIDI devices can be connected to Android using Bluetooth LE.</p>
+<h1 id=using_midi_2_0>Using MIDI 2.0</h1>
 
-<p>Before using the device, the app must scan for available BTLE devices and then allow
-the user to connect.
-See the Android developer website for an
-<a href="https://source.android.com/devices/audio/midi_test#apps" target="_blank">example
-program</a>.</p>
-
-<h2 id=btle_location_permissions>Request Location Permission for BTLE</h2>
-
-<p>Applications that scan for Bluetooth devices must request permission in the
-manifest file. This LOCATION permission is required because it may be possible to
-guess the location of an Android device by seeing which BTLE devices are nearby.</p>
-
-<pre class=prettyprint>
-&lt;uses-permission android:name="android.permission.BLUETOOTH"/>
-&lt;uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
-&lt;uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
-</pre>
-
-<p>Apps must also request location permission from the user at run-time.
-See the documentation for <code>Activity.requestPermissions()</code> for details and an example.
-</p>
-
-<h2 id=btle_scan_devices>Scan for MIDI Devices</h2>
-
-<p>The app will only want to see MIDI devices and not mice or other non-MIDI devices.
-So construct a ScanFilter using the UUID for standard MIDI over BTLE.</p>
-
-<pre class=prettyprint>
-MIDI over BTLE UUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"
-</pre>
-
-<h2 id=btle_open_device>Open a MIDI Bluetooth Device</h2>
-
-<p>See the documentation for <code>android.bluetooth.le.BluetoothLeScanner.startScan()</code>
-method for details. When the user selects a MIDI/BTLE device then you can open it
-using the MidiManager.</p>
-
-<pre class=prettyprint>
-m.openBluetoothDevice(bluetoothDevice, callback, handler);
-</pre>
-
-<p>Once the MIDI/BTLE device has been opened by one app then it will also become available to other
-apps using the
-<a href="#get_list_of_already_plugged_in_entities">MIDI device discovery calls described above</a>.
-</p>
-
-<h1 id=using_midi_2_0_over_usb>Using MIDI 2.0 over USB</h1>
-
-<p>An app can use MIDI 2.0 over USB starting in Android T. MIDI 2.0 packets are embedded in
+<p>An app can use <a href=
+"https://www.midi.org/midi-articles/details-about-midi-2-0-midi-ci-profiles-and-property-exchange"
+class="external">MIDI 2.0</a> over USB starting in Android T. MIDI 2.0 packets are embedded in
 Universal MIDI Packets, or UMP for short. A MIDI 2.0 USB device should create two interfaces,
 one endpoint that accepts only MIDI 1.0 packets and one that accepts only UMP packets.
-For more info about MIDI 2.0 and UMP, please read the MIDI 2.0 USB spec.</p>
+For more info about MIDI 2.0 and UMP, please read the MIDI 2.0 and UMP spec. Starting from Android
+V, apps can also open <a href="#creating_a_midi_2_0_virtual_device_service"> MIDI 2.0 virtual</a>
+devices.</p>
 
 <p>MidiManager.getDevices() would simply return the 1.0 interface. This interface should work
 exactly the same as before. In order to use the new UMP interface, retrieve the device with the
@@ -421,15 +426,15 @@
         MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS);
 </pre>
 
-<p>UMP Packets are always in multiple of 4 bytes. For each set of 4 bytes, they are sent in network
-order. Compare the following NoteOn code snippet with the NoteOn code snippet above. </p>
+<p>UMP packet sizes are always a multiple of 4 bytes. For each set of 4 bytes, they are sent in
+network order. Compare the following NoteOn code snippet with the NoteOn code snippet above. </p>
 
 <pre class=prettyprint>
 byte[] buffer = new byte[32];
 int numBytes = 0;
 int channel = 3; // MIDI channels 1-16 are encoded as 0-15.
 int group = 0;
-buffer[numBytes++] = (byte)(0x20 + group); // MIDI 1.0 voice message
+buffer[numBytes++] = (byte)(0x20 + group); // MIDI 1.0 Channel Voice Message
 buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on
 buffer[numBytes++] = (byte)60; // pitch is middle C
 buffer[numBytes++] = (byte)127; // max velocity
@@ -446,5 +451,108 @@
 int defaultProtocol = info.getDefaultProtocol();
 </pre>
 
+<h1 id=creating_a_midi_2_0_virtual_device_service>Creating a MIDI 2.0 Virtual Device Service</h1>
+
+
+<p>Starting in Android V, an app can provide a MIDI 2.0 Service that can be used by other apps.
+MIDI 2.0 packets are embedded in Universal MIDI Packets, or UMP for short. The service must be
+guarded with permission &quot;android.permission.BIND_MIDI_DEVICE_SERVICE&quot;.</p>
+
+<h2 id=manifest_files>Manifest Files</h2>
+
+
+<p>An app declares that it will function as a MIDI server in the AndroidManifest.xml file. Unlike
+MIDI 1.0 virtual devices, android.media.midi.MidiUmpDeviceService is used</p>
+
+<pre class=prettyprint>
+&lt;service android:name="<strong>MidiEchoDeviceService</strong>"
+  android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+  &lt;intent-filter>
+    &lt;action android:name="android.media.midi.MidiUmpDeviceService" />
+  &lt;/intent-filter>
+  &lt;meta-data android:name="android.media.midi.MidiUmpDeviceService"
+      android:resource="@xml/<strong>echo_device_info</strong>" />
+&lt;/service>
+</pre>
+
+
+<p>The details of the resource in this example is stored in &ldquo;res/xml/echo_device_info.xml
+&rdquo;. The port names that you declare in this file will be available from PortInfo.getName().
+Unlike MIDI 1.0, MIDI 2.0 ports are bidirectional. If you declare a port in this service, then it
+automatically creates an input port and an output port with the same name. Clients can use those
+two ports like the MIDI 1.0 ports.</p>
+
+<pre class=prettyprint>
+&lt;devices>
+    &lt;device manufacturer="MyCompany" product="MidiEcho">
+        &lt;port name="port1" />
+    &lt;/device>
+&lt;/devices>
+</pre>
+
+
+<h2 id=extend_midiumpdeviceservice>Extend MidiUmpDeviceService</h2>
+
+
+<p>You then define your server by extending android.media.midi.MidiUmpDeviceService.</p>
+
+<pre class=prettyprint>
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiReceiver;
+import android.media.midi.MidiUmpDeviceService;
+
+public class MidiEchoDeviceService extends MidiUmpDeviceService {
+    private static final String TAG = "MidiEchoDeviceService";
+    // Other apps will write to this port.
+    private MidiReceiver mInputReceiver = new MyReceiver();
+    // This app will copy the data to this port.
+    private MidiReceiver mOutputReceiver;
+
+    &#64;Override
+    public void onCreate() {
+        super.onCreate();
+    }
+
+    &#64;Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    &#64;Override
+    // Declare the receivers associated with your input ports.
+    public List<MidiReceiver> onGetInputPortReceivers() {
+        return new ArrayList<MidiReceiver>(Collections.singletonList(mInputReceiver));
+    }
+
+    /**
+     * Sample receiver to echo from the input port to the output port.
+     * In this example, we are just echoing the data and not parsing it.
+     * You will probably want to convert the bytes to a packet and then interpret the packet.
+     * See the MIDI 2.0 spec at the MMA site. Packets are either 4, 8, 12 or 16 bytes.
+     */
+    class MyReceiver extends MidiReceiver {
+        &#64;Override
+        public void onSend(byte[] data, int offset, int count, long timestamp)
+                throws IOException {
+            if (mOutputReceiver == null) {
+                mOutputReceiver = getOutputPortReceivers().get(0);
+            }
+            // Copy input to output.
+            mOutputReceiver.send(data, offset, count, timestamp);
+        }
+    }
+
+    /**
+     * This will get called when clients connect or disconnect.
+     * You can use it to figure out how many devices are connected.
+     */
+    &#64;Override
+    public void onDeviceStatusChanged(MidiDeviceStatus status) {
+        // inputOpened = status.isInputPortOpen(0);
+        // outputOpenCount = status.getOutputPortOpenCount(0);
+    }
+}
+</pre>
+
 </body>
 </html>
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index dea7f03..5ea98c0c 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2615,7 +2615,7 @@
             return;
         }
         auto cryptoInfo =
-                cryptoInfoObj ? NativeCryptoInfo{size} : NativeCryptoInfo{env, cryptoInfoObj};
+                cryptoInfoObj ? NativeCryptoInfo{env, cryptoInfoObj} : NativeCryptoInfo{size};
         if (env->ExceptionCheck()) {
             // Creation of cryptoInfo failed. Let the exception bubble up.
             return;
diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp
index b50514d..283445f 100644
--- a/native/android/configuration.cpp
+++ b/native/android/configuration.cpp
@@ -36,7 +36,7 @@
 
 void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am) {
     ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(am));
-    ResTable_config config = locked_mgr->GetConfiguration();
+    ResTable_config config = locked_mgr->GetConfigurations()[0];
 
     // AConfiguration is not a virtual subclass, so we can memcpy.
     memcpy(out, &config, sizeof(config));
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 28b9bc0..fe26dc3 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -44,7 +44,7 @@
     platform_apis: true,
     privileged: true,
 
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
 
     optimize: {
         proguard_compatibility: false,
diff --git a/packages/EasterEgg/Android.bp b/packages/EasterEgg/Android.bp
index 8699f59..0caf505 100644
--- a/packages/EasterEgg/Android.bp
+++ b/packages/EasterEgg/Android.bp
@@ -70,5 +70,5 @@
 
     manifest: "AndroidManifest.xml",
 
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
index 2278f7c..d29765e 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
@@ -19,6 +19,8 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.InstallSourceInfo;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.util.Log;
 
@@ -61,11 +63,19 @@
                 return;
             }
 
-            // TODO: Make sure the installer information here is accurate
-            String installer =
-                    context.getPackageManager().getInstallerPackageName(packageName);
-            new PackageInstalledNotificationUtils(context, installer,
-                    packageName).postAppInstalledNotification();
+            try {
+                InstallSourceInfo installerInfo =
+                        context.getPackageManager().getInstallSourceInfo(packageName);
+                String installer = installerInfo.getInstallingPackageName();
+                if (installer == null) {
+                    Log.e(TAG, "No installer package name for: " + packageName);
+                    return;
+                }
+                new PackageInstalledNotificationUtils(context, installer,
+                        packageName).postAppInstalledNotification();
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "Cannot get source info for: " + packageName);
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
index c75f41b..081e668 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
@@ -45,11 +45,10 @@
     BaseLayout(
         title = title,
         subTitle = {
-            if (singleLineSummary) {
-                SettingsBody(body = summary, maxLines = 1)
-            } else {
-                SettingsBody(body = summary)
-            }
+            SettingsBody(
+                body = summary.value,
+                maxLines = if (singleLineSummary) 1 else Int.MAX_VALUE,
+            )
         },
         modifier = modifier,
         icon = icon,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index 01ba8f8..57319e7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -24,7 +24,6 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.text.style.TextOverflow
@@ -35,11 +34,6 @@
 import com.android.settingslib.spa.framework.theme.toMediumWeight
 
 @Composable
-fun SettingsTitle(title: State<String>, useMediumWeight: Boolean = false) {
-    SettingsTitle(title.value, useMediumWeight)
-}
-
-@Composable
 fun SettingsTitle(title: String, useMediumWeight: Boolean = false) {
     Text(
         text = title,
@@ -55,14 +49,6 @@
 
 @Composable
 fun SettingsBody(
-    body: State<String>,
-    maxLines: Int = Int.MAX_VALUE,
-) {
-    SettingsBody(body = body.value, maxLines = maxLines)
-}
-
-@Composable
-fun SettingsBody(
     body: String,
     maxLines: Int = Int.MAX_VALUE,
 ) {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/TextTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/TextTest.kt
index 7e5b4f8..f9c1f94 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/TextTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/TextTest.kt
@@ -20,7 +20,6 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.compose.toState
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -31,14 +30,17 @@
     val composeTestRule = createComposeRule()
 
     @Test
-    fun testTitle() {
+    fun settingsTitle() {
         composeTestRule.setContent {
             SettingsTitle(title = "myTitleValue")
-            SettingsTitle(title = "myTitleState".toState())
+        }
+        composeTestRule.onNodeWithText("myTitleValue").assertIsDisplayed()
+    }
+
+    fun placeholderTitle() {
+        composeTestRule.setContent {
             PlaceholderTitle(title = "myTitlePlaceholder")
         }
-        composeTestRule.onNodeWithText("myTitleState").assertIsDisplayed()
-        composeTestRule.onNodeWithText("myTitleValue").assertIsDisplayed()
         composeTestRule.onNodeWithText("myTitlePlaceholder").assertIsDisplayed()
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index ddb92b1..b43210f 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -122,5 +122,5 @@
 @Composable
 internal fun AppLabel(app: ApplicationInfo, isClonedAppPage: Boolean = false) {
     val appRepository = rememberAppRepository()
-    SettingsTitle(title = appRepository.produceLabel(app, isClonedAppPage), useMediumWeight = true)
+    SettingsTitle(appRepository.produceLabel(app, isClonedAppPage).value, useMediumWeight = true)
 }
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_auto.xml b/packages/SettingsLib/res/drawable/ic_hotspot_auto.xml
new file mode 100644
index 0000000..ddd526a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_auto.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M240,760L240,800Q240,817 228.5,828.5Q217,840 200,840L160,840Q143,840 131.5,828.5Q120,817 120,800L120,480L204,240Q210,222 225.5,211Q241,200 260,200L700,200Q719,200 734.5,211Q750,222 756,240L840,480L840,800Q840,817 828.5,828.5Q817,840 800,840L760,840Q743,840 731.5,828.5Q720,817 720,800L720,760L240,760ZM232,400L728,400L686,280L274,280L232,400ZM200,480L200,480L200,680L200,680L200,480ZM300,640Q325,640 342.5,622.5Q360,605 360,580Q360,555 342.5,537.5Q325,520 300,520Q275,520 257.5,537.5Q240,555 240,580Q240,605 257.5,622.5Q275,640 300,640ZM660,640Q685,640 702.5,622.5Q720,605 720,580Q720,555 702.5,537.5Q685,520 660,520Q635,520 617.5,537.5Q600,555 600,580Q600,605 617.5,622.5Q635,640 660,640ZM200,680L760,680L760,480L200,480L200,680Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_laptop.xml b/packages/SettingsLib/res/drawable/ic_hotspot_laptop.xml
new file mode 100644
index 0000000..5e1b184
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_laptop.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M0,800L0,720L80,720L80,120L880,120L880,720L960,720L960,800L0,800ZM400,720L560,720L560,680L400,680L400,720ZM160,600L800,600L800,200L160,200L160,600ZM160,600L160,200L160,200L160,600L160,600Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_phone.xml b/packages/SettingsLib/res/drawable/ic_hotspot_phone.xml
new file mode 100644
index 0000000..baa793c
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_phone.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,800L280,840Q280,840 280,840Q280,840 280,840L680,840Q680,840 680,840Q680,840 680,840L680,800L280,800ZM280,720L680,720L680,240L280,240L280,720ZM280,160L680,160L680,120Q680,120 680,120Q680,120 680,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,160L280,120Q280,120 280,120Q280,120 280,120L280,120Q280,120 280,120Q280,120 280,120L280,160L280,160ZM280,800L280,800L280,840Q280,840 280,840Q280,840 280,840L280,840Q280,840 280,840Q280,840 280,840L280,800Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_tablet.xml b/packages/SettingsLib/res/drawable/ic_hotspot_tablet.xml
new file mode 100644
index 0000000..cf67cd9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_tablet.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M120,800Q87,800 63.5,776.5Q40,753 40,720L40,240Q40,207 63.5,183.5Q87,160 120,160L840,160Q873,160 896.5,183.5Q920,207 920,240L920,720Q920,753 896.5,776.5Q873,800 840,800L120,800ZM160,240L120,240Q120,240 120,240Q120,240 120,240L120,720Q120,720 120,720Q120,720 120,720L160,720L160,240ZM240,720L720,720L720,240L240,240L240,720ZM800,240L800,720L840,720Q840,720 840,720Q840,720 840,720L840,240Q840,240 840,240Q840,240 840,240L800,240ZM800,240L840,240Q840,240 840,240Q840,240 840,240L840,240Q840,240 840,240Q840,240 840,240L800,240L800,240ZM160,240L160,240L120,240Q120,240 120,240Q120,240 120,240L120,240Q120,240 120,240Q120,240 120,240L160,240Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_watch.xml b/packages/SettingsLib/res/drawable/ic_hotspot_watch.xml
new file mode 100644
index 0000000..252a0db
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_watch.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M360,880L306,698Q258,660 229,603Q200,546 200,480Q200,414 229,357Q258,300 306,262L360,80L600,80L654,262Q702,300 731,357Q760,414 760,480Q760,546 731,603Q702,660 654,698L600,880L360,880ZM480,680Q563,680 621.5,621.5Q680,563 680,480Q680,397 621.5,338.5Q563,280 480,280Q397,280 338.5,338.5Q280,397 280,480Q280,563 338.5,621.5Q397,680 480,680ZM404,210Q424,205 442.5,202Q461,199 480,199Q499,199 517.5,202Q536,205 556,210L540,160L420,160L404,210ZM420,800L540,800L556,750Q536,755 517.5,757.5Q499,760 480,760Q461,760 442.5,757.5Q424,755 404,750L420,800ZM404,160L420,160L540,160L556,160Q536,160 517.5,160Q499,160 480,160Q461,160 442.5,160Q424,160 404,160ZM420,800L404,800Q424,800 442.5,800Q461,800 480,800Q499,800 517.5,800Q536,800 556,800L540,800L420,800Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 3adb882..3252aad 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -660,4 +660,20 @@
         <item>2.0</item>
     </string-array>
 
+    <!-- Grammatical genders -->
+    <array name="grammatical_gender_entries">
+        <item>@string/not_specified</item>
+        <item>@string/neuter</item>
+        <item>@string/feminine</item>
+        <item>@string/masculine</item>
+    </array>
+
+    <!-- Values for grammatical genders -->
+    <string-array name="grammatical_gender_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+    </string-array>
+
 </resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 60bc226..dab3bcb 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1678,4 +1678,13 @@
 
     <!-- Formatting states for the scale of font size, in percent. Double "%" is required to represent the symbol "%". [CHAR LIMIT=20] -->
     <string name="font_scale_percentage"> <xliff:g id="percentage">%1$d</xliff:g> %%</string>
+
+    <!-- List entry in developer settings to set the grammatical gender to Not specified [CHAR LIMIT=30]-->
+    <string name="not_specified">Not specified</string>
+    <!-- List entry in developer settings to set the grammatical gender to Neuter [CHAR LIMIT=30]-->
+    <string name="neuter">Neuter</string>
+    <!-- List entry in developer settings to set the grammatical gender to Feminine [CHAR LIMIT=30]-->
+    <string name="feminine">Feminine</string>
+    <!-- List entry in developer settings to set the grammatical gender to Masculine [CHAR LIMIT=30]-->
+    <string name="masculine">Masculine</string>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 441d3a5..a6536a8c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -29,6 +29,7 @@
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -365,16 +366,17 @@
     public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
         device.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
         CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device);
-        final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
+        // Should iterate through the cloned set to avoid ConcurrentModificationException
+        final Set<CachedBluetoothDevice> memberDevices = new HashSet<>(device.getMemberDevice());
         if (!memberDevices.isEmpty()) {
-            // Main device is unpaired, to unpair the member device
+            // Main device is unpaired, also unpair the member devices
             for (CachedBluetoothDevice memberDevice : memberDevices) {
                 memberDevice.unpair();
                 memberDevice.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
                 device.removeMemberDevice(memberDevice);
             }
         } else if (mainDevice != null) {
-            // the member device unpaired, to unpair main device
+            // Member device is unpaired, also unpair the main device
             mainDevice.unpair();
         }
         mainDevice = mHearingAidDeviceManager.findMainDevice(device);
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index f911d35..a617bf3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -28,6 +28,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
@@ -35,6 +36,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -114,6 +116,26 @@
     private static final int SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT = 1;
     private static final int LOCKSCREEN_SHOW_CONTROLS_DEFAULT = 0;
 
+    private static final int DS_TYPE_ENABLED = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_ENABLED;
+    private static final int DS_TYPE_WHEN_TO_DREAM = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_WHEN_TO_DREAM;
+    private static final int DS_TYPE_DREAM_COMPONENT = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_DREAM_COMPONENT;
+    private static final int DS_TYPE_SHOW_ADDITIONAL_INFO = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_SHOW_ADDITIONAL_INFO;
+    private static final int DS_TYPE_SHOW_HOME_CONTROLS = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_SHOW_HOME_CONTROLS;
+
+    private static final int WHEN_TO_DREAM_UNSPECIFIED = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_UNSPECIFIED;
+    private static final int WHEN_TO_DREAM_CHARGING = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_WHILE_CHARGING_ONLY;
+    private static final int WHEN_TO_DREAM_DOCKED = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_WHILE_DOCKED_ONLY;
+    private static final int WHEN_TO_DREAM_CHARGING_OR_DOCKED = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_EITHER_CHARGING_OR_DOCKED;
+
     private final Context mContext;
     private final IDreamManager mDreamManager;
     private final DreamInfoComparator mComparator;
@@ -121,6 +143,7 @@
     private final boolean mDreamsActivatedOnSleepByDefault;
     private final boolean mDreamsActivatedOnDockByDefault;
     private final Set<ComponentName> mDisabledDreams;
+    private final List<String> mLoggableDreamPrefixes;
     private Set<Integer> mSupportedComplications;
     private static DreamBackend sInstance;
 
@@ -148,6 +171,8 @@
                         com.android.internal.R.array.config_disabledDreamComponents))
                 .map(ComponentName::unflattenFromString)
                 .collect(Collectors.toSet());
+        mLoggableDreamPrefixes = Arrays.stream(resources.getStringArray(
+                com.android.internal.R.array.config_loggable_dream_prefixes)).toList();
 
         mSupportedComplications = Arrays.stream(resources.getIntArray(
                         com.android.internal.R.array.config_supportedDreamComplications))
@@ -282,6 +307,8 @@
             default:
                 break;
         }
+
+        logDreamSettingChangeToStatsd(DS_TYPE_WHEN_TO_DREAM);
     }
 
     /** Gets all complications which have been enabled by the user. */
@@ -304,12 +331,14 @@
     public void setComplicationsEnabled(boolean enabled) {
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, enabled ? 1 : 0);
+        logDreamSettingChangeToStatsd(DS_TYPE_SHOW_ADDITIONAL_INFO);
     }
 
     /** Sets whether home controls are enabled by the user on the dream */
     public void setHomeControlsEnabled(boolean enabled) {
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, enabled ? 1 : 0);
+        logDreamSettingChangeToStatsd(DS_TYPE_SHOW_HOME_CONTROLS);
     }
 
     /** Gets whether home controls button is enabled on the dream */
@@ -353,6 +382,7 @@
     public void setEnabled(boolean value) {
         logd("setEnabled(%s)", value);
         setBoolean(Settings.Secure.SCREENSAVER_ENABLED, value);
+        logDreamSettingChangeToStatsd(DS_TYPE_ENABLED);
     }
 
     public boolean isActivatedOnDock() {
@@ -391,6 +421,7 @@
         try {
             ComponentName[] dreams = {dream};
             mDreamManager.setDreamComponents(dream == null ? null : dreams);
+            logDreamSettingChangeToStatsd(DS_TYPE_DREAM_COMPONENT);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to set active dream to " + dream, e);
         }
@@ -461,6 +492,68 @@
         }
     }
 
+    private void logDreamSettingChangeToStatsd(int dreamSettingType) {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.DREAM_SETTING_CHANGED, /*atom_tag*/
+                UserHandle.myUserId(), /*uid*/
+                isEnabled(), /*enabled*/
+                getActiveDreamComponentForStatsd(), /*dream_component*/
+                getWhenToDreamForStatsd(), /*when_to_dream*/
+                getComplicationsEnabled(), /*show_additional_info*/
+                getHomeControlsEnabled(), /*show_home_controls*/
+                dreamSettingType /*dream_setting_type*/
+        );
+    }
+
+    /**
+     * Returns the user selected dream component in string format for stats logging. If the dream
+     * component is not loggable, returns "other".
+     */
+    private String getActiveDreamComponentForStatsd() {
+        final ComponentName activeDream = getActiveDream();
+        if (activeDream == null) {
+            return "";
+        }
+
+        final String component = activeDream.flattenToShortString();
+        if (isLoggableDreamComponentForStatsd(component)) {
+            return component;
+        } else {
+            return "other";
+        }
+    }
+
+    /**
+     * Whether the dream component is loggable. Only components from the predefined packages are
+     * allowed to be logged for privacy.
+     */
+    private boolean isLoggableDreamComponentForStatsd(String component) {
+        for (int i = 0; i < mLoggableDreamPrefixes.size(); i++) {
+            if (component.startsWith(mLoggableDreamPrefixes.get(i))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the enum of "when to dream" setting for statsd logging.
+     */
+    private int getWhenToDreamForStatsd() {
+        switch (getWhenToDreamSetting()) {
+            case WHILE_CHARGING:
+                return WHEN_TO_DREAM_CHARGING;
+            case WHILE_DOCKED:
+                return WHEN_TO_DREAM_DOCKED;
+            case EITHER:
+                return WHEN_TO_DREAM_CHARGING_OR_DOCKED;
+            case NEVER:
+            default:
+                return WHEN_TO_DREAM_UNSPECIFIED;
+        }
+    }
+
     private static class DreamInfoComparator implements Comparator<DreamInfo> {
         private final ComponentName mDefaultDream;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index bff51e3..7e27560 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -154,15 +154,17 @@
     protected abstract RouteListingPreference getRouteListingPreference();
 
     /**
-     * Returns the list of currently active {@link RoutingSessionInfo routing sessions} known to the
-     * system.
+     * Returns the list of remote {@link RoutingSessionInfo routing sessions} known to the system.
      */
     @NonNull
-    protected abstract List<RoutingSessionInfo> getActiveRoutingSessions();
+    protected abstract List<RoutingSessionInfo> getRemoteSessions();
 
     @NonNull
     protected abstract List<RoutingSessionInfo> getRoutingSessionsForPackage();
 
+    @Nullable
+    protected abstract RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId);
+
     @NonNull
     protected abstract List<MediaRoute2Info> getAllRoutes();
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index fd5ca84..4fb0487 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -388,14 +388,12 @@
      * @param volume the value of volume
      */
     public void adjustSessionVolume(String sessionId, int volume) {
-        final List<RoutingSessionInfo> infos = getActiveMediaSession();
-        for (RoutingSessionInfo info : infos) {
-            if (TextUtils.equals(sessionId, info.getId())) {
-                mInfoMediaManager.adjustSessionVolume(info, volume);
-                return;
-            }
+        RoutingSessionInfo session = mInfoMediaManager.getRoutingSessionById(sessionId);
+        if (session != null) {
+            mInfoMediaManager.adjustSessionVolume(session, volume);
+        } else {
+            Log.w(TAG, "adjustSessionVolume: Unable to find session: " + sessionId);
         }
-        Log.w(TAG, "adjustSessionVolume: Unable to find session: " + sessionId);
     }
 
     /**
@@ -435,12 +433,12 @@
     }
 
     /**
-     * Gets the current active session.
+     * Gets the list of remote {@link RoutingSessionInfo routing sessions} known to the system.
      *
-     * @return current active session list{@link android.media.RoutingSessionInfo}
+     * <p>This list does not include any system routing sessions.
      */
-    public List<RoutingSessionInfo> getActiveMediaSession() {
-        return mInfoMediaManager.getActiveRoutingSessions();
+    public List<RoutingSessionInfo> getRemoteRoutingSessions() {
+        return mInfoMediaManager.getRemoteSessions();
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index b7ac1dce..0be2e0e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -30,7 +30,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -151,11 +150,22 @@
 
     @Override
     @NonNull
-    protected List<RoutingSessionInfo> getActiveRoutingSessions() {
-        List<RoutingSessionInfo> infos = new ArrayList<>();
-        infos.add(mRouterManager.getSystemRoutingSession(null));
-        infos.addAll(mRouterManager.getRemoteSessions());
-        return infos;
+    protected List<RoutingSessionInfo> getRemoteSessions() {
+        return mRouterManager.getRemoteSessions();
+    }
+
+    @Nullable
+    @Override
+    protected RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId) {
+        for (RoutingSessionInfo sessionInfo : getRemoteSessions()) {
+            if (TextUtils.equals(sessionInfo.getId(), sessionId)) {
+                return sessionInfo;
+            }
+        }
+
+        RoutingSessionInfo systemSession = mRouterManager.getSystemRoutingSession(null);
+
+        return TextUtils.equals(systemSession.getId(), sessionId) ? systemSession : null;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index afab046..b9a4647 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -27,6 +27,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.util.Log;
@@ -331,6 +332,22 @@
     }
 
     /**
+     * Returns the Hotspot network icon resource.
+     *
+     * @param deviceType The device type of Hotspot network
+     */
+    public static int getHotspotIconResource(int deviceType) {
+        return switch (deviceType) {
+            case NetworkProviderInfo.DEVICE_TYPE_PHONE -> R.drawable.ic_hotspot_phone;
+            case NetworkProviderInfo.DEVICE_TYPE_TABLET -> R.drawable.ic_hotspot_tablet;
+            case NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> R.drawable.ic_hotspot_laptop;
+            case NetworkProviderInfo.DEVICE_TYPE_WATCH -> R.drawable.ic_hotspot_watch;
+            case NetworkProviderInfo.DEVICE_TYPE_AUTO -> R.drawable.ic_hotspot_auto;
+            default -> R.drawable.ic_hotspot_phone;  // Return phone icon as default.
+        };
+    }
+
+    /**
      * Wrapper the {@link #getInternetIconResource} for testing compatibility.
      */
     public static class InternetIconInjector {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
index 2edf403..00ae96c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -75,6 +75,9 @@
         when(res.getStringArray(
                 com.android.internal.R.array.config_disabledDreamComponents)).thenReturn(
                 new String[]{});
+        when(res.getStringArray(
+                com.android.internal.R.array.config_loggable_dream_prefixes)).thenReturn(
+                new String[]{});
         mBackend = new DreamBackend(mContext);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 866ef9d..2252b69 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -832,19 +832,12 @@
     }
 
     @Test
-    public void getActiveMediaSession_returnActiveSession() {
-        RoutingSessionInfo sysSessionInfo = mock(RoutingSessionInfo.class);
+    public void getRemoteSessions_returnsRemoteSessions() {
         final List<RoutingSessionInfo> infos = new ArrayList<>();
         infos.add(mock(RoutingSessionInfo.class));
-        final List<RoutingSessionInfo> activeSessionInfos = new ArrayList<>();
-        activeSessionInfos.add(sysSessionInfo);
-        activeSessionInfos.addAll(infos);
-
-        mShadowRouter2Manager.setSystemRoutingSession(sysSessionInfo);
         mShadowRouter2Manager.setRemoteSessions(infos);
 
-        assertThat(mInfoMediaManager.getActiveRoutingSessions())
-                .containsExactlyElementsIn(activeSessionInfos);
+        assertThat(mInfoMediaManager.getRemoteSessions()).containsExactlyElementsIn(infos);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index d6c33ff..926b41a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -433,9 +433,9 @@
         final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
         when(info.getId()).thenReturn(TEST_SESSION_ID);
         routingSessionInfos.add(info);
-        when(mInfoMediaManager.getActiveRoutingSessions()).thenReturn(routingSessionInfos);
+        when(mInfoMediaManager.getRemoteSessions()).thenReturn(routingSessionInfos);
 
-        assertThat(mLocalMediaManager.getActiveMediaSession().get(0).getId())
+        assertThat(mLocalMediaManager.getRemoteRoutingSessions().get(0).getId())
                 .matches(TEST_SESSION_ID);
     }
 
@@ -544,7 +544,7 @@
         final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
         when(info.getId()).thenReturn(TEST_SESSION_ID);
         routingSessionInfos.add(info);
-        when(mInfoMediaManager.getActiveRoutingSessions()).thenReturn(routingSessionInfos);
+        when(mInfoMediaManager.getRoutingSessionById(TEST_SESSION_ID)).thenReturn(info);
 
         mLocalMediaManager.adjustSessionVolume(TEST_SESSION_ID, 10);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index b60dc6a..5293011 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -35,6 +35,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkScoreCache;
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -201,6 +202,25 @@
     }
 
     @Test
+    public void getHotspotIconResource_deviceTypeUnknown_shouldNotCrash() {
+        WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_UNKNOWN);
+    }
+
+    @Test
+    public void getHotspotIconResource_deviceTypeExists_shouldNotNull() {
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_PHONE))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_TABLET))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_LAPTOP))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_WATCH))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_AUTO))
+                .isNotNull();
+    }
+
+    @Test
     public void testInternetIconInjector_getIcon_returnsCorrectValues() {
         WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext);
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7be6043..78d9361 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -203,7 +203,7 @@
     manifest: "AndroidManifest.xml",
 
     javacflags: ["-Adagger.fastInit=enabled"],
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
 
     plugins: ["dagger2-compiler"],
 
@@ -228,6 +228,17 @@
 }
 
 filegroup {
+    name: "SystemUI-test-fakes",
+    srcs: [
+        /* Status bar fakes */
+        "tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt",
+        "tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt",
+        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt",
+    ],
+    path: "tests/src",
+}
+
+filegroup {
     name: "SystemUI-tests-robolectric-pilots",
     srcs: [
         /* Keyguard converted tests */
@@ -291,6 +302,11 @@
         "tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt",
         "tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt",
         "tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt",
+
+        /* Status bar wifi converted tests */
+        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt",
+        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt",
+        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt",
     ],
     path: "tests/src",
 }
@@ -394,7 +410,7 @@
         "android.test.base",
         "android.test.mock",
     ],
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
     aaptflags: [
         "--extra-packages",
         "com.android.systemui",
@@ -449,6 +465,7 @@
         "tests/robolectric/src/**/*.kt",
         "tests/robolectric/src/**/*.java",
         ":SystemUI-tests-utils",
+        ":SystemUI-test-fakes",
         ":SystemUI-tests-robolectric-pilots",
     ],
     static_libs: [
@@ -516,7 +533,7 @@
     certificate: "platform",
     privileged: true,
 
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
 
     dxflags: ["--multi-dex"],
     optimize: {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4fd4723..58c9f77 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -549,26 +549,6 @@
         </activity>
 
         <!-- started from UsbDeviceSettingsManager -->
-        <activity android:name=".usb.tv.TvUsbConfirmActivity"
-                  android:exported="true"
-                  android:launchMode="singleTop"
-                  android:permission="android.permission.MANAGE_USB"
-                  android:theme="@style/BottomSheet"
-                  android:finishOnCloseSystemDialogs="true"
-                  android:excludeFromRecents="true">
-        </activity>
-
-        <!-- started from UsbDeviceSettingsManager -->
-        <activity android:name=".usb.tv.TvUsbPermissionActivity"
-                  android:exported="true"
-                  android:launchMode="singleTop"
-                  android:permission="android.permission.MANAGE_USB"
-                  android:theme="@style/BottomSheet"
-                  android:finishOnCloseSystemDialogs="true"
-                  android:excludeFromRecents="true">
-        </activity>
-
-        <!-- started from UsbDeviceSettingsManager -->
         <activity android:name=".usb.UsbResolverActivity"
             android:exported="true"
             android:permission="android.permission.MANAGE_USB"
@@ -577,16 +557,6 @@
             android:excludeFromRecents="true">
         </activity>
 
-        <!-- started from HdmiCecLocalDevicePlayback -->
-        <activity android:name=".hdmi.HdmiCecSetMenuLanguageActivity"
-                  android:exported="true"
-                  android:launchMode="singleTop"
-                  android:permission="android.permission.CHANGE_CONFIGURATION"
-                  android:theme="@style/BottomSheet"
-                  android:finishOnCloseSystemDialogs="true"
-                  android:excludeFromRecents="true">
-        </activity>
-
         <!-- started from SensoryPrivacyService -->
         <activity android:name=".sensorprivacy.SensorUseStartedActivity"
                   android:exported="true"
@@ -597,27 +567,6 @@
                   android:showForAllUsers="true">
         </activity>
 
-        <!-- started from SensoryPrivacyService -->
-        <activity android:name=".sensorprivacy.television.TvUnblockSensorActivity"
-                  android:exported="true"
-                  android:launchMode="singleTop"
-                  android:permission="android.permission.MANAGE_SENSOR_PRIVACY"
-                  android:theme="@style/BottomSheet"
-                  android:finishOnCloseSystemDialogs="true"
-                  android:showForAllUsers="true">
-        </activity>
-
-        <!-- started from SensoryPrivacyService -->
-        <activity android:name=".sensorprivacy.television.TvSensorPrivacyChangedActivity"
-            android:exported="true"
-            android:launchMode="singleTop"
-            android:permission="android.permission.MANAGE_SENSOR_PRIVACY"
-            android:theme="@style/BottomSheet"
-            android:finishOnCloseSystemDialogs="true"
-            android:showForAllUsers="true">
-        </activity>
-
-
         <!-- started from UsbDeviceSettingsManager -->
         <activity android:name=".usb.UsbAccessoryUriActivity"
             android:exported="true"
@@ -703,14 +652,6 @@
             android:exported="false"
             android:permission="android.permission.MANAGE_MEDIA_PROJECTION"/>
 
-        <!-- started from TvNotificationPanel -->
-        <activity
-            android:name=".statusbar.tv.notifications.TvNotificationPanelActivity"
-            android:excludeFromRecents="true"
-            android:launchMode="singleTask"
-            android:noHistory="true"
-            android:theme="@style/TvSidePanelTheme" />
-
         <!-- started from SliceProvider -->
         <activity android:name=".SlicePermissionActivity"
             android:theme="@style/Theme.SystemUI.Dialog.Alert"
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 7a5a382..c59b0f9 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -72,7 +72,7 @@
           "exclude-annotation": "org.junit.Ignore"
         },
         {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
         },
         {
           "include-filter": "android.permissionui.cts.CameraMicIndicatorsPermissionTest"
@@ -110,6 +110,17 @@
       ]
     }
   ],
+  "postsubmit": [
+    {
+      // Permission indicators
+      "name": "CtsPermissionUiTestCases",
+      "options": [
+        {
+          "include-filter": "android.permissionui.cts.CameraMicIndicatorsPermissionTest"
+        }
+      ]
+    }
+  ],
   "silver-sysui": [
    {
       "name": "PlatformScenarioTests",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 764a855..8306620 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -302,10 +302,9 @@
 
     interface Callback {
         /** Whether we are currently on the keyguard or not. */
-        @JvmDefault fun isOnKeyguard(): Boolean = false
+        fun isOnKeyguard(): Boolean = false
 
         /** Hide the keyguard and animate using [runner]. */
-        @JvmDefault
         fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner) {
             throw UnsupportedOperationException()
         }
@@ -316,16 +315,16 @@
 
     interface Listener {
         /** Called when an activity launch animation started. */
-        @JvmDefault fun onLaunchAnimationStart() {}
+        fun onLaunchAnimationStart() {}
 
         /**
          * Called when an activity launch animation is finished. This will be called if and only if
          * [onLaunchAnimationStart] was called earlier.
          */
-        @JvmDefault fun onLaunchAnimationEnd() {}
+        fun onLaunchAnimationEnd() {}
 
         /** Called when an activity launch animation made progress. */
-        @JvmDefault fun onLaunchAnimationProgress(linearProgress: Float) {}
+        fun onLaunchAnimationProgress(linearProgress: Float) {}
     }
 
     /**
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 dbfa192..37b1ee5 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -32,11 +32,10 @@
 import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-import android.widget.FrameLayout
 import com.android.app.animation.Interpolators
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CujType
-import com.android.systemui.animation.view.LaunchableFrameLayout
+import com.android.systemui.util.maybeForceFullscreen
 import com.android.systemui.util.registerAnimationOnBackInvoked
 import kotlin.math.roundToInt
 
@@ -622,96 +621,12 @@
 
                 viewGroupWithBackground
             } else {
-                // We will make the dialog window (and therefore its DecorView) fullscreen to make
-                // it possible to animate outside its bounds.
-                //
-                // Before that, we add a new View as a child of the DecorView with the same size and
-                // gravity as that DecorView, then we add all original children of the DecorView to
-                // that new View. Finally we remove the background of the DecorView and add it to
-                // the new View, then we make the DecorView fullscreen. This new View now acts as a
-                // fake (non fullscreen) window.
-                //
-                // On top of that, we also add a fullscreen transparent background between the
-                // DecorView and the view that we added so that we can dismiss the dialog when this
-                // view is clicked. This is necessary because DecorView overrides onTouchEvent and
-                // therefore we can't set the click listener directly on the (now fullscreen)
-                // DecorView.
-                val fullscreenTransparentBackground = FrameLayout(dialog.context)
-                decorView.addView(
-                    fullscreenTransparentBackground,
-                    0 /* index */,
-                    FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
-                )
-
-                val dialogContentWithBackground = LaunchableFrameLayout(dialog.context)
-                dialogContentWithBackground.background = decorView.background
-
-                // Make the window background transparent. Note that setting the window (or
-                // DecorView) background drawable to null leads to issues with background color (not
-                // being transparent) or with insets that are not refreshed. Therefore we need to
-                // set it to something not null, hence we are using android.R.color.transparent
-                // here.
-                window.setBackgroundDrawableResource(android.R.color.transparent)
-
-                // Close the dialog when clicking outside of it.
-                fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() }
-                dialogContentWithBackground.isClickable = true
-
-                // Make sure the transparent and dialog backgrounds are not focusable by
-                // accessibility
-                // features.
-                fullscreenTransparentBackground.importantForAccessibility =
-                    View.IMPORTANT_FOR_ACCESSIBILITY_NO
-                dialogContentWithBackground.importantForAccessibility =
-                    View.IMPORTANT_FOR_ACCESSIBILITY_NO
-
-                fullscreenTransparentBackground.addView(
-                    dialogContentWithBackground,
-                    FrameLayout.LayoutParams(
-                        window.attributes.width,
-                        window.attributes.height,
-                        window.attributes.gravity
-                    )
-                )
-
-                // Move all original children of the DecorView to the new View we just added.
-                for (i in 1 until decorView.childCount) {
-                    val view = decorView.getChildAt(1)
-                    decorView.removeViewAt(1)
-                    dialogContentWithBackground.addView(view)
-                }
-
-                // Make the window fullscreen and add a layout listener to ensure it stays
-                // fullscreen.
-                window.setLayout(MATCH_PARENT, MATCH_PARENT)
-                decorViewLayoutListener =
-                    View.OnLayoutChangeListener {
-                        v,
-                        left,
-                        top,
-                        right,
-                        bottom,
-                        oldLeft,
-                        oldTop,
-                        oldRight,
-                        oldBottom ->
-                        if (
-                            window.attributes.width != MATCH_PARENT ||
-                                window.attributes.height != MATCH_PARENT
-                        ) {
-                            // The dialog size changed, copy its size to dialogContentWithBackground
-                            // and make the dialog window full screen again.
-                            val layoutParams = dialogContentWithBackground.layoutParams
-                            layoutParams.width = window.attributes.width
-                            layoutParams.height = window.attributes.height
-                            dialogContentWithBackground.layoutParams = layoutParams
-                            window.setLayout(MATCH_PARENT, MATCH_PARENT)
-                        }
-                    }
-                decorView.addOnLayoutChangeListener(decorViewLayoutListener)
-
+                val (dialogContentWithBackground, decorViewLayoutListener) =
+                    dialog.maybeForceFullscreen()!!
+                this.decorViewLayoutListener = decorViewLayoutListener
                 dialogContentWithBackground
             }
+
         this.dialogContentWithBackground = dialogContentWithBackground
         dialogContentWithBackground.setTag(R.id.tag_dialog_background, true)
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
index 428856d..0f63548 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
@@ -18,6 +18,9 @@
 
 import android.app.Dialog
 import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.FrameLayout
 import android.window.OnBackInvokedDispatcher
 import com.android.systemui.animation.back.BackAnimationSpec
 import com.android.systemui.animation.back.BackTransformation
@@ -25,6 +28,7 @@
 import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
 import com.android.systemui.animation.back.onBackAnimationCallbackFrom
 import com.android.systemui.animation.back.registerOnBackInvokedCallbackOnViewAttached
+import com.android.systemui.animation.view.LaunchableFrameLayout
 
 /**
  * Register on the Dialog's [OnBackInvokedDispatcher] an animation using the [BackAnimationSpec].
@@ -49,3 +53,110 @@
             ),
     )
 }
+
+/**
+ * Make the dialog window (and therefore its DecorView) fullscreen to make it possible to animate
+ * outside its bounds. No-op if the dialog is already fullscreen.
+ *
+ * <p>Returns null if the dialog is already fullscreen. Otherwise, returns a pair containing a view
+ * and a layout listener. The new view matches the original dialog DecorView in size, position, and
+ * background. This new view will be a child of the modified, transparent, fullscreen DecorView. The
+ * layout listener is listening to changes to the modified DecorView. It is the responsibility of
+ * the caller to deregister the listener when the dialog is dismissed.
+ */
+fun Dialog.maybeForceFullscreen(): Pair<LaunchableFrameLayout, View.OnLayoutChangeListener>? {
+    // Create the dialog so that its onCreate() method is called, which usually sets the dialog
+    // content.
+    create()
+
+    val window = window!!
+    val decorView = window.decorView as ViewGroup
+
+    val isWindowFullscreen =
+        window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT
+    if (isWindowFullscreen) {
+        return null
+    }
+
+    // We will make the dialog window (and therefore its DecorView) fullscreen to make it possible
+    // to animate outside its bounds.
+    //
+    // Before that, we add a new View as a child of the DecorView with the same size and gravity as
+    // that DecorView, then we add all original children of the DecorView to that new View. Finally
+    // we remove the background of the DecorView and add it to the new View, then we make the
+    // DecorView fullscreen. This new View now acts as a fake (non fullscreen) window.
+    //
+    // On top of that, we also add a fullscreen transparent background between the DecorView and the
+    // view that we added so that we can dismiss the dialog when this view is clicked. This is
+    // necessary because DecorView overrides onTouchEvent and therefore we can't set the click
+    // listener directly on the (now fullscreen) DecorView.
+    val fullscreenTransparentBackground = FrameLayout(context)
+    decorView.addView(
+        fullscreenTransparentBackground,
+        0 /* index */,
+        FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+    )
+
+    val dialogContentWithBackground = LaunchableFrameLayout(context)
+    dialogContentWithBackground.background = decorView.background
+
+    // Make the window background transparent. Note that setting the window (or DecorView)
+    // background drawable to null leads to issues with background color (not being transparent) or
+    // with insets that are not refreshed. Therefore we need to set it to something not null, hence
+    // we are using android.R.color.transparent here.
+    window.setBackgroundDrawableResource(android.R.color.transparent)
+
+    // Close the dialog when clicking outside of it.
+    fullscreenTransparentBackground.setOnClickListener { dismiss() }
+    dialogContentWithBackground.isClickable = true
+
+    // Make sure the transparent and dialog backgrounds are not focusable by accessibility
+    // features.
+    fullscreenTransparentBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+    dialogContentWithBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+
+    fullscreenTransparentBackground.addView(
+        dialogContentWithBackground,
+        FrameLayout.LayoutParams(
+            window.attributes.width,
+            window.attributes.height,
+            window.attributes.gravity
+        )
+    )
+
+    // Move all original children of the DecorView to the new View we just added.
+    for (i in 1 until decorView.childCount) {
+        val view = decorView.getChildAt(1)
+        decorView.removeViewAt(1)
+        dialogContentWithBackground.addView(view)
+    }
+
+    // Make the window fullscreen and add a layout listener to ensure it stays fullscreen.
+    window.setLayout(MATCH_PARENT, MATCH_PARENT)
+    val decorViewLayoutListener =
+        View.OnLayoutChangeListener {
+            v,
+            left,
+            top,
+            right,
+            bottom,
+            oldLeft,
+            oldTop,
+            oldRight,
+            oldBottom ->
+            if (
+                window.attributes.width != MATCH_PARENT || window.attributes.height != MATCH_PARENT
+            ) {
+                // The dialog size changed, copy its size to dialogContentWithBackground and make
+                // the dialog window full screen again.
+                val layoutParams = dialogContentWithBackground.layoutParams
+                layoutParams.width = window.attributes.width
+                layoutParams.height = window.attributes.height
+                dialogContentWithBackground.layoutParams = layoutParams
+                window.setLayout(MATCH_PARENT, MATCH_PARENT)
+            }
+        }
+    decorView.addOnLayoutChangeListener(decorViewLayoutListener)
+
+    return dialogContentWithBackground to decorViewLayoutListener
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
new file mode 100644
index 0000000..566967f
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.DisposableEffectResult
+import androidx.compose.runtime.DisposableEffectScope
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.lerp
+import com.android.compose.ui.util.lerp
+
+/**
+ * Animate a shared Int value.
+ *
+ * @see SceneScope.animateSharedValueAsState
+ */
+@Composable
+fun SceneScope.animateSharedIntAsState(
+    value: Int,
+    key: ValueKey,
+    element: ElementKey,
+    canOverflow: Boolean = true,
+): State<Int> {
+    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
+}
+
+/**
+ * Animate a shared Float value.
+ *
+ * @see SceneScope.animateSharedValueAsState
+ */
+@Composable
+fun SceneScope.animateSharedFloatAsState(
+    value: Float,
+    key: ValueKey,
+    element: ElementKey,
+    canOverflow: Boolean = true,
+): State<Float> {
+    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
+}
+
+/**
+ * Animate a shared Dp value.
+ *
+ * @see SceneScope.animateSharedValueAsState
+ */
+@Composable
+fun SceneScope.animateSharedDpAsState(
+    value: Dp,
+    key: ValueKey,
+    element: ElementKey,
+    canOverflow: Boolean = true,
+): State<Dp> {
+    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
+}
+
+/**
+ * Animate a shared Color value.
+ *
+ * @see SceneScope.animateSharedValueAsState
+ */
+@Composable
+fun SceneScope.animateSharedColorAsState(
+    value: Color,
+    key: ValueKey,
+    element: ElementKey,
+): State<Color> {
+    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false)
+}
+
+@Composable
+internal fun <T> animateSharedValueAsState(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+    key: ValueKey,
+    value: T,
+    lerp: (T, T, Float) -> T,
+    canOverflow: Boolean,
+): State<T> {
+    val sharedValue = remember(key) { Element.SharedValue(key, value) }
+    if (value != sharedValue.value) {
+        sharedValue.value = value
+    }
+
+    DisposableEffect(element, scene, sharedValue) {
+        addSharedValueToElement(element, scene, sharedValue)
+    }
+
+    return remember(layoutImpl, element, sharedValue, lerp, canOverflow) {
+        derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) }
+    }
+}
+
+private fun <T> DisposableEffectScope.addSharedValueToElement(
+    element: Element,
+    scene: Scene,
+    sharedValue: Element.SharedValue<T>,
+): DisposableEffectResult {
+    val sceneValues =
+        element.sceneValues[scene.key] ?: error("Element $element is not present in $scene")
+    val sharedValues = sceneValues.sharedValues
+
+    sharedValues[sharedValue.key] = sharedValue
+    return onDispose { sharedValues.remove(sharedValue.key) }
+}
+
+private fun <T> computeValue(
+    layoutImpl: SceneTransitionLayoutImpl,
+    element: Element,
+    sharedValue: Element.SharedValue<T>,
+    lerp: (T, T, Float) -> T,
+    canOverflow: Boolean,
+): T {
+    val state = layoutImpl.state.transitionState
+    if (
+        state !is TransitionState.Transition ||
+            state.fromScene == state.toScene ||
+            !layoutImpl.isTransitionReady(state)
+    ) {
+        return sharedValue.value
+    }
+
+    fun sceneValue(scene: SceneKey): Element.SharedValue<T>? {
+        val sceneValues = element.sceneValues[scene] ?: return null
+        val value = sceneValues.sharedValues[sharedValue.key] ?: return null
+        return value as Element.SharedValue<T>
+    }
+
+    val fromValue = sceneValue(state.fromScene)
+    val toValue = sceneValue(state.toScene)
+    return if (fromValue != null && toValue != null) {
+        val progress = if (canOverflow) state.progress else state.progress.coerceIn(0f, 1f)
+        lerp(fromValue.value, toValue.value, progress)
+    } else if (fromValue != null) {
+        fromValue.value
+    } else if (toValue != null) {
+        toValue.value
+    } else {
+        sharedValue.value
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt
new file mode 100644
index 0000000..7536728
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.SpringSpec
+import kotlin.math.absoluteValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Transition to [target] using a canned animation. This function will try to be smart and take over
+ * the currently running transition, if there is one.
+ */
+internal fun CoroutineScope.animateToScene(
+    layoutImpl: SceneTransitionLayoutImpl,
+    target: SceneKey,
+) {
+    val state = layoutImpl.state.transitionState
+    if (state.currentScene == target) {
+        // This can happen in 3 different situations, for which there isn't anything else to do:
+        //  1. There is no ongoing transition and [target] is already the current scene.
+        //  2. The user is swiping to [target] from another scene and released their pointer such
+        //     that the gesture was committed and the transition is animating to [scene] already.
+        //  3. The user is swiping from [target] to another scene and either:
+        //     a. didn't release their pointer yet.
+        //     b. released their pointer such that the swipe gesture was cancelled and the
+        //        transition is currently animating back to [target].
+        return
+    }
+
+    when (state) {
+        is TransitionState.Idle -> animate(layoutImpl, target)
+        is TransitionState.Transition -> {
+            if (state.toScene == state.fromScene) {
+                // Same as idle.
+                animate(layoutImpl, target)
+                return
+            }
+
+            // A transition is currently running: first check whether `transition.toScene` or
+            // `transition.fromScene` is the same as our target scene, in which case the transition
+            // can be accelerated or reversed to end up in the target state.
+
+            if (state.toScene == target) {
+                // The user is currently swiping to [target] but didn't release their pointer yet:
+                // animate the progress to `1`.
+
+                check(state.fromScene == state.currentScene)
+                val progress = state.progress
+                if ((1f - progress).absoluteValue < ProgressVisibilityThreshold) {
+                    // The transition is already finished (progress ~= 1): no need to animate.
+                    layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene)
+                } else {
+                    // The transition is in progress: start the canned animation at the same
+                    // progress as it was in.
+                    // TODO(b/290184746): Also take the current velocity into account.
+                    animate(layoutImpl, target, startProgress = progress)
+                }
+
+                return
+            }
+
+            if (state.fromScene == target) {
+                // There is a transition from [target] to another scene: simply animate the same
+                // transition progress to `0`.
+
+                check(state.toScene == state.currentScene)
+                val progress = state.progress
+                if (progress.absoluteValue < ProgressVisibilityThreshold) {
+                    // The transition is at progress ~= 0: no need to animate.
+                    layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene)
+                } else {
+                    // TODO(b/290184746): Also take the current velocity into account.
+                    animate(layoutImpl, target, startProgress = progress, reversed = true)
+                }
+
+                return
+            }
+
+            // Generic interruption; the current transition is neither from or to [target].
+            // TODO(b/290930950): Better handle interruptions here.
+            animate(layoutImpl, target)
+        }
+    }
+}
+
+private fun CoroutineScope.animate(
+    layoutImpl: SceneTransitionLayoutImpl,
+    target: SceneKey,
+    startProgress: Float = 0f,
+    reversed: Boolean = false,
+) {
+    val fromScene = layoutImpl.state.transitionState.currentScene
+
+    val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec
+    val visibilityThreshold =
+        (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
+    val animatable = Animatable(startProgress, visibilityThreshold = visibilityThreshold)
+
+    val targetProgress = if (reversed) 0f else 1f
+    val transition =
+        if (reversed) {
+            OneOffTransition(target, fromScene, currentScene = target, animatable)
+        } else {
+            OneOffTransition(fromScene, target, currentScene = target, animatable)
+        }
+
+    // Change the current layout state to use this new transition.
+    layoutImpl.state.transitionState = transition
+
+    // Animate the progress to its target value.
+    launch {
+        animatable.animateTo(targetProgress, animationSpec)
+
+        // Unless some other external state change happened, the state should now be idle.
+        if (layoutImpl.state.transitionState == transition) {
+            layoutImpl.state.transitionState = TransitionState.Idle(target)
+        }
+    }
+}
+
+private class OneOffTransition(
+    override val fromScene: SceneKey,
+    override val toScene: SceneKey,
+    override val currentScene: SceneKey,
+    private val animatable: Animatable<Float, AnimationVector1D>,
+) : TransitionState.Transition {
+    override val progress: Float
+        get() = animatable.value
+}
+
+// TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size
+// and screen density.
+private const val ProgressVisibilityThreshold = 1e-3f
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
new file mode 100644
index 0000000..0cc259a
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
@@ -0,0 +1,449 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.snapshots.SnapshotStateMap
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.geometry.lerp
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.IntermediateMeasureScope
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.round
+import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.modifiers.thenIf
+import com.android.compose.ui.util.lerp
+
+/** An element on screen, that can be composed in one or more scenes. */
+internal class Element(val key: ElementKey) {
+    /**
+     * The last offset assigned to this element, relative to the SceneTransitionLayout containing
+     * it.
+     */
+    var lastOffset = Offset.Unspecified
+
+    /** The last size assigned to this element. */
+    var lastSize = SizeUnspecified
+
+    /** The last alpha assigned to this element. */
+    var lastAlpha = 1f
+
+    /** The mapping between a scene and the values/state this element has in that scene, if any. */
+    val sceneValues = SnapshotStateMap<SceneKey, SceneValues>()
+
+    override fun toString(): String {
+        return "Element(key=$key)"
+    }
+
+    /** The target values of this element in a given scene. */
+    class SceneValues {
+        var size by mutableStateOf(SizeUnspecified)
+        var offset by mutableStateOf(Offset.Unspecified)
+        val sharedValues = SnapshotStateMap<ValueKey, SharedValue<*>>()
+    }
+
+    /** A shared value of this element. */
+    class SharedValue<T>(val key: ValueKey, initialValue: T) {
+        var value by mutableStateOf(initialValue)
+    }
+
+    companion object {
+        val SizeUnspecified = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
+    }
+}
+
+/** The implementation of [SceneScope.element]. */
+@Composable
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun Modifier.element(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    key: ElementKey,
+): Modifier {
+    val sceneValues = remember(scene, key) { Element.SceneValues() }
+    val element =
+        // Get the element associated to [key] if it was already composed in another scene,
+        // otherwise create it and add it to our Map<ElementKey, Element>. This is done inside a
+        // withoutReadObservation() because there is no need to recompose when that map is mutated.
+        Snapshot.withoutReadObservation {
+            val element =
+                layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it }
+            val previousValues = element.sceneValues[scene.key]
+            if (previousValues == null) {
+                element.sceneValues[scene.key] = sceneValues
+            } else if (previousValues != sceneValues) {
+                error("$key was composed multiple times in $scene")
+            }
+
+            element
+        }
+
+    DisposableEffect(scene, sceneValues, element) {
+        onDispose {
+            element.sceneValues.remove(scene.key)
+
+            // This was the last scene this element was in, so remove it from the map.
+            if (element.sceneValues.isEmpty()) {
+                layoutImpl.elements.remove(element.key)
+            }
+        }
+    }
+
+    val alpha =
+        remember(layoutImpl, element, scene) {
+            derivedStateOf { elementAlpha(layoutImpl, element, scene) }
+        }
+    val isOpaque by remember(alpha) { derivedStateOf { alpha.value == 1f } }
+    SideEffect {
+        if (isOpaque && element.lastAlpha != 1f) {
+            element.lastAlpha = 1f
+        }
+    }
+
+    return drawWithContent {
+            if (shouldDrawElement(layoutImpl, scene, element)) {
+                drawContent()
+            }
+        }
+        .modifierTransformations(layoutImpl, scene, element, sceneValues)
+        .intermediateLayout { measurable, constraints ->
+            val placeable =
+                measure(layoutImpl, scene, element, sceneValues, measurable, constraints)
+            layout(placeable.width, placeable.height) {
+                place(layoutImpl, scene, element, sceneValues, placeable, placementScope = this)
+            }
+        }
+        .thenIf(!isOpaque) {
+            Modifier.graphicsLayer {
+                val alpha = alpha.value
+                this.alpha = alpha
+                element.lastAlpha = alpha
+            }
+        }
+        .testTag(key.name)
+}
+
+private fun shouldDrawElement(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+): Boolean {
+    val state = layoutImpl.state.transitionState
+
+    // Always draw the element if there is no ongoing transition or if the element is not shared.
+    if (
+        state !is TransitionState.Transition ||
+            state.fromScene == state.toScene ||
+            !layoutImpl.isTransitionReady(state) ||
+            state.fromScene !in element.sceneValues ||
+            state.toScene !in element.sceneValues
+    ) {
+        return true
+    }
+
+    val otherScene =
+        layoutImpl.scenes.getValue(
+            if (scene.key == state.fromScene) {
+                state.toScene
+            } else {
+                state.fromScene
+            }
+        )
+
+    // When the element is shared, draw the one in the highest scene unless it is a background, i.e.
+    // it is usually drawn below everything else.
+    val isHighestScene = scene.zIndex > otherScene.zIndex
+    return if (element.key.isBackground) {
+        !isHighestScene
+    } else {
+        isHighestScene
+    }
+}
+
+/**
+ * Chain the [com.android.compose.animation.scene.transformation.ModifierTransformation] applied
+ * throughout the current transition, if any.
+ */
+private fun Modifier.modifierTransformations(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+    sceneValues: Element.SceneValues,
+): Modifier {
+    when (val state = layoutImpl.state.transitionState) {
+        is TransitionState.Idle -> return this
+        is TransitionState.Transition -> {
+            val fromScene = state.fromScene
+            val toScene = state.toScene
+            if (fromScene == toScene) {
+                // Same as idle.
+                return this
+            }
+
+            return layoutImpl.transitions
+                .transitionSpec(fromScene, state.toScene)
+                .transformations(element.key)
+                .modifier
+                .fold(this) { modifier, transformation ->
+                    with(transformation) {
+                        modifier.transform(layoutImpl, scene, element, sceneValues)
+                    }
+                }
+        }
+    }
+}
+
+private fun elementAlpha(
+    layoutImpl: SceneTransitionLayoutImpl,
+    element: Element,
+    scene: Scene
+): Float {
+    return computeValue(
+            layoutImpl,
+            scene,
+            element,
+            sceneValue = { 1f },
+            transformation = { it.alpha },
+            idleValue = 1f,
+            currentValue = { 1f },
+            lastValue = { element.lastAlpha },
+            ::lerp,
+        )
+        .coerceIn(0f, 1f)
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+private fun IntermediateMeasureScope.measure(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+    sceneValues: Element.SceneValues,
+    measurable: Measurable,
+    constraints: Constraints,
+): Placeable {
+    // Update the size this element has in this scene when idle.
+    val targetSizeInScene = lookaheadSize
+    if (targetSizeInScene != sceneValues.size) {
+        // TODO(b/290930950): Better handle when this changes to avoid instant size jumps.
+        sceneValues.size = targetSizeInScene
+    }
+
+    // Some lambdas called (max once) by computeValue() will need to measure [measurable], in which
+    // case we store the resulting placeable here to make sure the element is not measured more than
+    // once.
+    var maybePlaceable: Placeable? = null
+
+    fun Placeable.size() = IntSize(width, height)
+
+    val targetSize =
+        computeValue(
+            layoutImpl,
+            scene,
+            element,
+            sceneValue = { it.size },
+            transformation = { it.size },
+            idleValue = lookaheadSize,
+            currentValue = { measurable.measure(constraints).also { maybePlaceable = it }.size() },
+            lastValue = {
+                val lastSize = element.lastSize
+                if (lastSize != Element.SizeUnspecified) {
+                    lastSize
+                } else {
+                    measurable.measure(constraints).also { maybePlaceable = it }.size()
+                }
+            },
+            ::lerp,
+        )
+
+    val placeable =
+        maybePlaceable
+            ?: measurable.measure(
+                Constraints.fixed(
+                    targetSize.width.coerceAtLeast(0),
+                    targetSize.height.coerceAtLeast(0),
+                )
+            )
+
+    element.lastSize = placeable.size()
+    return placeable
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+private fun IntermediateMeasureScope.place(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+    sceneValues: Element.SceneValues,
+    placeable: Placeable,
+    placementScope: Placeable.PlacementScope,
+) {
+    with(placementScope) {
+        // Update the offset (relative to the SceneTransitionLayout) this element has in this scene
+        // when idle.
+        val coords = coordinates!!
+        val targetOffsetInScene = lookaheadScopeCoordinates.localLookaheadPositionOf(coords)
+        if (targetOffsetInScene != sceneValues.offset) {
+            // TODO(b/290930950): Better handle when this changes to avoid instant offset jumps.
+            sceneValues.offset = targetOffsetInScene
+        }
+
+        val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero)
+        val targetOffset =
+            computeValue(
+                layoutImpl,
+                scene,
+                element,
+                sceneValue = { it.offset },
+                transformation = { it.offset },
+                idleValue = targetOffsetInScene,
+                currentValue = { currentOffset },
+                lastValue = {
+                    val lastValue = element.lastOffset
+                    if (lastValue.isSpecified) {
+                        lastValue
+                    } else {
+                        currentOffset
+                    }
+                },
+                ::lerp,
+            )
+
+        element.lastOffset = targetOffset
+        placeable.place((targetOffset - currentOffset).round())
+    }
+}
+
+/**
+ * Return the value that should be used depending on the current layout state and transition.
+ *
+ * Important: This function must remain inline because of all the lambda parameters. These lambdas
+ * are necessary because getting some of them might require some computation, like measuring a
+ * Measurable.
+ *
+ * @param layoutImpl the [SceneTransitionLayoutImpl] associated to [element].
+ * @param scene the scene containing [element].
+ * @param element the element being animated.
+ * @param sceneValue the value being animated.
+ * @param transformation the transformation associated to the value being animated.
+ * @param idleValue the value when idle, i.e. when there is no transition happening.
+ * @param currentValue the value that would be used if it is not transformed. Note that this is
+ *   different than [idleValue] even if the value is not transformed directly because it could be
+ *   impacted by the transformations on other elements, like a parent that is being translated or
+ *   resized.
+ * @param lastValue the last value that was used. This should be equal to [currentValue] if this is
+ *   the first time the value is set.
+ * @param lerp the linear interpolation function used to interpolate between two values of this
+ *   value type.
+ */
+private inline fun <T> computeValue(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+    sceneValue: (Element.SceneValues) -> T,
+    transformation: (ElementTransformations) -> PropertyTransformation<T>?,
+    idleValue: T,
+    currentValue: () -> T,
+    lastValue: () -> T,
+    lerp: (T, T, Float) -> T,
+): T {
+    val state = layoutImpl.state.transitionState
+
+    // There is no ongoing transition.
+    if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+        return idleValue
+    }
+
+    // A transition was started but it's not ready yet (not all elements have been composed/laid
+    // out yet). Use the last value that was set, to make sure elements don't unexpectedly jump.
+    if (!layoutImpl.isTransitionReady(state)) {
+        return lastValue()
+    }
+
+    val fromScene = state.fromScene
+    val toScene = state.toScene
+    val fromValues = element.sceneValues[fromScene]
+    val toValues = element.sceneValues[toScene]
+
+    if (fromValues == null && toValues == null) {
+        error("This should not happen, element $element is neither in $fromScene or $toScene")
+    }
+
+    // TODO(b/291053278): Handle overscroll correctly. We should probably coerce between [0f, 1f]
+    // here and consume overflows at drawing time, somehow reusing Compose OverflowEffect or some
+    // similar mechanism.
+    val transitionProgress = state.progress
+
+    // The element is shared: interpolate between the value in fromScene and the value in toScene.
+    // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
+    // elements follow the finger direction.
+    if (fromValues != null && toValues != null) {
+        return lerp(
+            sceneValue(fromValues),
+            sceneValue(toValues),
+            transitionProgress,
+        )
+    }
+
+    val transformation =
+        transformation(
+            layoutImpl.transitions.transitionSpec(fromScene, toScene).transformations(element.key)
+        )
+        // If there is no transformation explicitly associated to this element value, let's use
+        // the value given by the system (like the current position and size given by the layout
+        // pass).
+        ?: return currentValue()
+
+    // Get the transformed value, i.e. the target value at the beginning (for entering elements) or
+    // end (for leaving elements) of the transition.
+    val targetValue =
+        transformation.transform(
+            layoutImpl,
+            scene,
+            element,
+            fromValues ?: toValues!!,
+            state,
+            idleValue,
+        )
+
+    // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range.
+    val rangeProgress = transformation.range?.progress(transitionProgress) ?: transitionProgress
+
+    // Interpolate between the value at rest and the value before entering/after leaving.
+    val isEntering = fromValues == null
+    return if (isEntering) {
+        lerp(targetValue, idleValue, rangeProgress)
+    } else {
+        lerp(idleValue, targetValue, rangeProgress)
+    }
+}
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
new file mode 100644
index 0000000..c3f44f8
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+/**
+ * A base class to create unique keys, associated to an [identity] that is used to check the
+ * equality of two key instances.
+ */
+sealed class Key(val name: String, val identity: Any) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (this.javaClass != other?.javaClass) return false
+        return identity == (other as? Key)?.identity
+    }
+
+    override fun hashCode(): Int {
+        return identity.hashCode()
+    }
+
+    override fun toString(): String {
+        return "Key(name=$name)"
+    }
+}
+
+/** Key for a scene. */
+class SceneKey(name: String, identity: Any = Object()) : Key(name, identity) {
+    override fun toString(): String {
+        return "SceneKey(name=$name)"
+    }
+}
+
+/** Key for an element. */
+class ElementKey(
+    name: String,
+    identity: Any = Object(),
+
+    /**
+     * Whether this element is a background and usually drawn below other elements. This should be
+     * set to true to make sure that shared backgrounds are drawn below elements of other scenes.
+     */
+    val isBackground: Boolean = false,
+) : Key(name, identity), ElementMatcher {
+    override fun matches(key: ElementKey): Boolean {
+        return key == this
+    }
+
+    override fun toString(): String {
+        return "ElementKey(name=$name)"
+    }
+
+    companion object {
+        /** Matches any element whose [key identity][ElementKey.identity] matches [predicate]. */
+        fun withIdentity(predicate: (Any) -> Boolean): ElementMatcher {
+            return object : ElementMatcher {
+                override fun matches(key: ElementKey): Boolean = predicate(key.identity)
+            }
+        }
+    }
+}
+
+/** Key for a shared value of an element. */
+class ValueKey(name: String, identity: Any = Object()) : Key(name, identity) {
+    override fun toString(): String {
+        return "ValueKey(name=$name)"
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt
new file mode 100644
index 0000000..a625250
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.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.compose.animation.scene
+
+import androidx.compose.runtime.snapshotFlow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+/**
+ * A scene transition state.
+ *
+ * This models the same thing as [TransitionState], with the following distinctions:
+ * 1. [TransitionState] values are backed by the Snapshot system (Compose State objects) and can be
+ *    used by callers tracking State reads, for instance in Compose code during the composition,
+ *    layout or Compose drawing phases.
+ * 2. [ObservableTransitionState] values are backed by Kotlin [Flow]s and can be collected by
+ *    non-Compose code to observe value changes.
+ * 3. [ObservableTransitionState.Transition.fromScene] and
+ *    [ObservableTransitionState.Transition.toScene] will never be equal, while
+ *    [TransitionState.Transition.fromScene] and [TransitionState.Transition.toScene] can be equal.
+ */
+sealed class ObservableTransitionState {
+    /** No transition/animation is currently running. */
+    data class Idle(val scene: SceneKey) : ObservableTransitionState()
+
+    /** There is a transition animating between two scenes. */
+    data class Transition(
+        val fromScene: SceneKey,
+        val toScene: SceneKey,
+        val progress: Flow<Float>,
+    ) : ObservableTransitionState()
+}
+
+/**
+ * The current [ObservableTransitionState]. This models the same thing as
+ * [SceneTransitionLayoutState.transitionState], except that it is backed by Flows and can be used
+ * by non-Compose code to observe state changes.
+ */
+fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTransitionState> {
+    return snapshotFlow {
+            when (val state = transitionState) {
+                is TransitionState.Idle -> ObservableTransitionState.Idle(state.currentScene)
+                is TransitionState.Transition -> {
+                    if (state.fromScene == state.toScene) {
+                        ObservableTransitionState.Idle(state.currentScene)
+                    } else {
+                        ObservableTransitionState.Transition(
+                            fromScene = state.fromScene,
+                            toScene = state.toScene,
+                            progress = snapshotFlow { state.progress },
+                        )
+                    }
+                }
+            }
+        }
+        .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
new file mode 100644
index 0000000..b44c8ef
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.zIndex
+
+/** A scene in a [SceneTransitionLayout]. */
+internal class Scene(
+    val key: SceneKey,
+    layoutImpl: SceneTransitionLayoutImpl,
+    content: @Composable SceneScope.() -> Unit,
+    actions: Map<UserAction, SceneKey>,
+    zIndex: Float,
+) {
+    private val scope = SceneScopeImpl(layoutImpl, this)
+
+    var content by mutableStateOf(content)
+    var userActions by mutableStateOf(actions)
+    var zIndex by mutableFloatStateOf(zIndex)
+    var size by mutableStateOf(IntSize.Zero)
+
+    @Composable
+    fun Content(modifier: Modifier = Modifier) {
+        Box(modifier.zIndex(zIndex).onPlaced { size = it.size }) { scope.content() }
+    }
+
+    override fun toString(): String {
+        return "Scene(key=$key)"
+    }
+}
+
+private class SceneScopeImpl(
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val scene: Scene,
+) : SceneScope {
+    @Composable
+    override fun Modifier.element(key: ElementKey): Modifier {
+        return element(layoutImpl, scene, key)
+    }
+
+    @Composable
+    override fun <T> animateSharedValueAsState(
+        value: T,
+        key: ValueKey,
+        element: ElementKey,
+        lerp: (T, T, Float) -> T,
+        canOverflow: Boolean
+    ): State<T> {
+        val element =
+            layoutImpl.elements[element]
+                ?: error(
+                    "Element $element is not composed. Make sure to call animateSharedXAsState " +
+                        "*after* Modifier.element(key)."
+                )
+
+        return animateSharedValueAsState(
+            layoutImpl,
+            scene,
+            element,
+            key,
+            value,
+            lerp,
+            canOverflow,
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
new file mode 100644
index 0000000..39173d9
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+
+/**
+ * [SceneTransitionLayout] is a container that automatically animates its content whenever
+ * [currentScene] changes, using the transitions defined in [transitions].
+ *
+ * Note: You should use [androidx.compose.animation.AnimatedContent] instead of
+ * [SceneTransitionLayout] if it fits your need. Use [SceneTransitionLayout] over AnimatedContent if
+ * you need support for swipe gestures, shared elements or transitions defined declaratively outside
+ * UI code.
+ *
+ * @param currentScene the current scene
+ * @param onChangeScene a mutator that should set [currentScene] to the given scene when called.
+ *   This is called when the user commits a transition to a new scene because of a [UserAction], for
+ *   instance by triggering back navigation or by swiping to a new scene.
+ * @param transitions the definition of the transitions used to animate a change of scene.
+ * @param state the observable state of this layout.
+ * @param scenes the configuration of the different scenes of this layout.
+ */
+@Composable
+fun SceneTransitionLayout(
+    currentScene: SceneKey,
+    onChangeScene: (SceneKey) -> Unit,
+    transitions: SceneTransitions,
+    modifier: Modifier = Modifier,
+    state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
+    scenes: SceneTransitionLayoutScope.() -> Unit,
+) {
+    val density = LocalDensity.current
+    val layoutImpl = remember {
+        SceneTransitionLayoutImpl(
+            onChangeScene,
+            scenes,
+            transitions,
+            state,
+            density,
+        )
+    }
+
+    layoutImpl.onChangeScene = onChangeScene
+    layoutImpl.transitions = transitions
+    layoutImpl.density = density
+    layoutImpl.setScenes(scenes)
+    layoutImpl.setCurrentScene(currentScene)
+
+    layoutImpl.Content(modifier)
+}
+
+interface SceneTransitionLayoutScope {
+    /**
+     * Add a scene to this layout, identified by [key].
+     *
+     * You can configure [userActions] so that swiping on this layout or navigating back will
+     * transition to a different scene.
+     *
+     * Important: scene order along the z-axis follows call order. Calling scene(A) followed by
+     * scene(B) will mean that scene B renders after/above scene A.
+     */
+    fun scene(
+        key: SceneKey,
+        userActions: Map<UserAction, SceneKey> = emptyMap(),
+        content: @Composable SceneScope.() -> Unit,
+    )
+}
+
+interface SceneScope {
+    /**
+     * Tag an element identified by [key].
+     *
+     * Tagging an element will allow you to reference that element when defining transitions, so
+     * that the element can be transformed and animated when the scene transitions in or out.
+     *
+     * Additionally, this [key] will be used to detect elements that are shared between scenes to
+     * automatically interpolate their size, offset and [shared values][animateSharedValueAsState].
+     *
+     * TODO(b/291566282): Migrate this to the new Modifier Node API and remove the @Composable
+     *   constraint.
+     */
+    @Composable fun Modifier.element(key: ElementKey): Modifier
+
+    /**
+     * Animate some value of a shared element.
+     *
+     * @param value the value of this shared value in the current scene.
+     * @param key the key of this shared value.
+     * @param element the element associated with this value.
+     * @param lerp the *linear* interpolation function that should be used to interpolate between
+     *   two different values. Note that it has to be linear because the [fraction] passed to this
+     *   interpolator is already interpolated.
+     * @param canOverflow whether this value can overflow past the values it is interpolated
+     *   between, for instance because the transition is animated using a bouncy spring.
+     * @see animateSharedIntAsState
+     * @see animateSharedFloatAsState
+     * @see animateSharedDpAsState
+     * @see animateSharedColorAsState
+     */
+    @Composable
+    fun <T> animateSharedValueAsState(
+        value: T,
+        key: ValueKey,
+        element: ElementKey,
+        lerp: (start: T, stop: T, fraction: Float) -> T,
+        canOverflow: Boolean,
+    ): State<T>
+}
+
+/** An action performed by the user. */
+sealed interface UserAction
+
+/** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
+object Back : UserAction
+
+/** The user swiped on the container. */
+enum class Swipe : UserAction {
+    Up,
+    Down,
+    Left,
+    Right,
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
new file mode 100644
index 0000000..350b9c2
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateMap
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.layout.LookaheadScope
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.ui.util.fastForEach
+import kotlinx.coroutines.channels.Channel
+
+internal class SceneTransitionLayoutImpl(
+    onChangeScene: (SceneKey) -> Unit,
+    builder: SceneTransitionLayoutScope.() -> Unit,
+    transitions: SceneTransitions,
+    internal val state: SceneTransitionLayoutState,
+    density: Density,
+) {
+    internal val scenes = SnapshotStateMap<SceneKey, Scene>()
+    internal val elements = SnapshotStateMap<ElementKey, Element>()
+
+    /** The scenes that are "ready", i.e. they were composed and fully laid-out at least once. */
+    private val readyScenes = SnapshotStateMap<SceneKey, Boolean>()
+
+    internal var onChangeScene by mutableStateOf(onChangeScene)
+    internal var transitions by mutableStateOf(transitions)
+    internal var density: Density by mutableStateOf(density)
+
+    /**
+     * The size of this layout. Note that this could be [IntSize.Zero] if this layour does not have
+     * any scene configured or right before the first measure pass of the layout.
+     */
+    internal var size by mutableStateOf(IntSize.Zero)
+
+    init {
+        setScenes(builder)
+    }
+
+    internal fun scene(key: SceneKey): Scene {
+        return scenes[key] ?: error("Scene $key is not configured")
+    }
+
+    internal fun setScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
+        // Keep a reference of the current scenes. After processing [builder], the scenes that were
+        // not configured will be removed.
+        val scenesToRemove = scenes.keys.toMutableSet()
+
+        // The incrementing zIndex of each scene.
+        var zIndex = 0f
+
+        object : SceneTransitionLayoutScope {
+                override fun scene(
+                    key: SceneKey,
+                    userActions: Map<UserAction, SceneKey>,
+                    content: @Composable SceneScope.() -> Unit,
+                ) {
+                    scenesToRemove.remove(key)
+
+                    val scene = scenes[key]
+                    if (scene != null) {
+                        // Update an existing scene.
+                        scene.content = content
+                        scene.userActions = userActions
+                        scene.zIndex = zIndex
+                    } else {
+                        // New scene.
+                        scenes[key] =
+                            Scene(
+                                key,
+                                this@SceneTransitionLayoutImpl,
+                                content,
+                                userActions,
+                                zIndex,
+                            )
+                    }
+
+                    zIndex++
+                }
+            }
+            .builder()
+
+        scenesToRemove.forEach { scenes.remove(it) }
+    }
+
+    @Composable
+    internal fun setCurrentScene(key: SceneKey) {
+        val channel = remember { Channel<SceneKey>(Channel.CONFLATED) }
+        SideEffect { channel.trySend(key) }
+        LaunchedEffect(channel) {
+            for (newKey in channel) {
+                // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
+                // late.
+                val newKey = channel.tryReceive().getOrNull() ?: newKey
+                animateToScene(this@SceneTransitionLayoutImpl, newKey)
+            }
+        }
+    }
+
+    @Composable
+    @OptIn(ExperimentalComposeUiApi::class)
+    internal fun Content(modifier: Modifier) {
+        Box(
+            modifier
+                // Handle horizontal and vertical swipes on this layout.
+                // Note: order here is important and will give a slight priority to the vertical
+                // swipes.
+                .swipeToScene(layoutImpl = this, Orientation.Horizontal)
+                .swipeToScene(layoutImpl = this, Orientation.Vertical)
+                .onSizeChanged { size = it }
+        ) {
+            LookaheadScope {
+                val scenesToCompose =
+                    when (val state = state.transitionState) {
+                        is TransitionState.Idle -> listOf(scene(state.currentScene))
+                        is TransitionState.Transition -> {
+                            if (state.toScene != state.fromScene) {
+                                listOf(scene(state.toScene), scene(state.fromScene))
+                            } else {
+                                listOf(scene(state.fromScene))
+                            }
+                        }
+                    }
+
+                // Handle back events.
+                // TODO(b/290184746): Make sure that this works with SystemUI once we use
+                // SceneTransitionLayout in Flexiglass.
+                scene(state.transitionState.currentScene).userActions[Back]?.let { backScene ->
+                    BackHandler { onChangeScene(backScene) }
+                }
+
+                Box(
+                    Modifier.drawWithContent {
+                        drawContent()
+
+                        // At this point, all scenes in scenesToCompose are fully laid out so they
+                        // are marked as ready. This is necessary because the animation code needs
+                        // to know the position and size of the elements in each scenes they are in,
+                        // so [readyScenes] will be used to decide whether the transition is ready
+                        // (see isTransitionReady() below).
+                        //
+                        // We can't do that in a DisposableEffect or SideEffect because those are
+                        // run between composition and layout. LaunchedEffect could work and might
+                        // be better, but it looks like launched effects run a frame later than this
+                        // code so doing this here seems better for performance.
+                        scenesToCompose.fastForEach { readyScenes[it.key] = true }
+                    }
+                ) {
+                    scenesToCompose.fastForEach { scene ->
+                        val key = scene.key
+                        key(key) {
+                            DisposableEffect(key) { onDispose { readyScenes.remove(key) } }
+
+                            scene.Content(
+                                Modifier.drawWithContent {
+                                    when (val state = state.transitionState) {
+                                        is TransitionState.Idle -> drawContent()
+                                        is TransitionState.Transition -> {
+                                            // Don't draw scenes that are not ready yet.
+                                            if (
+                                                readyScenes.containsKey(key) ||
+                                                    state.fromScene == state.toScene
+                                            ) {
+                                                drawContent()
+                                            }
+                                        }
+                                    }
+                                }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Return whether [transition] is ready, i.e. the elements of both scenes of the transition were
+     * laid out at least once.
+     */
+    internal fun isTransitionReady(transition: TransitionState.Transition): Boolean {
+        return readyScenes.containsKey(transition.fromScene) &&
+            readyScenes.containsKey(transition.toScene)
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
new file mode 100644
index 0000000..47e3d5a
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+
+/** The state of a [SceneTransitionLayout]. */
+class SceneTransitionLayoutState(initialScene: SceneKey) {
+    /**
+     * The current [TransitionState]. All values read here are backed by the Snapshot system.
+     *
+     * To observe those values outside of Compose/the Snapshot system, use
+     * [SceneTransitionLayoutState.observableTransitionState] instead.
+     */
+    var transitionState: TransitionState by mutableStateOf(TransitionState.Idle(initialScene))
+        internal set
+}
+
+sealed interface TransitionState {
+    /**
+     * The current effective scene. If a new transition was triggered, it would start from this
+     * scene.
+     *
+     * For instance, when swiping from scene A to scene B, the [currentScene] is A when the swipe
+     * gesture starts, but then if the user flings their finger and commits the transition to scene
+     * B, then [currentScene] becomes scene B even if the transition is not finished yet and is
+     * still animating to settle to scene B.
+     */
+    val currentScene: SceneKey
+
+    /** No transition/animation is currently running. */
+    data class Idle(override val currentScene: SceneKey) : TransitionState
+
+    /**
+     * There is a transition animating between two scenes.
+     *
+     * Important note: [fromScene] and [toScene] might be the same, in which case this [Transition]
+     * should be treated the same as [Idle]. This is designed on purpose so that a [Transition] can
+     * be started without knowing in advance where it is transitioning to, making the logic of
+     * [swipeToScene] easier to reason about.
+     */
+    interface Transition : TransitionState {
+        /** The scene this transition is starting from. */
+        val fromScene: SceneKey
+
+        /** The scene this transition is going to. */
+        val toScene: SceneKey
+
+        /**
+         * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
+         * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
+         * when flinging quickly during a swipe gesture.
+         */
+        val progress: Float
+    }
+}
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
new file mode 100644
index 0000000..9752f53
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.snap
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.transformation.AnchoredSize
+import com.android.compose.animation.scene.transformation.AnchoredTranslate
+import com.android.compose.animation.scene.transformation.EdgeTranslate
+import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.ModifierTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
+import com.android.compose.animation.scene.transformation.ScaleSize
+import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.Translate
+import com.android.compose.ui.util.fastForEach
+import com.android.compose.ui.util.fastMap
+
+/** The transitions configuration of a [SceneTransitionLayout]. */
+class SceneTransitions(
+    val transitionSpecs: List<TransitionSpec>,
+) {
+    private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
+
+    internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+        return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) }
+    }
+
+    private fun findSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+        val spec = transition(from, to) { it.from == from && it.to == to }
+        if (spec != null) {
+            return spec
+        }
+
+        val reversed = transition(from, to) { it.from == to && it.to == from }
+        if (reversed != null) {
+            return reversed.reverse()
+        }
+
+        val relaxedSpec =
+            transition(from, to) {
+                (it.from == from && it.to == null) || (it.to == to && it.from == null)
+            }
+        if (relaxedSpec != null) {
+            return relaxedSpec
+        }
+
+        return transition(from, to) {
+                (it.from == to && it.to == null) || (it.to == from && it.from == null)
+            }
+            ?.reverse()
+            ?: defaultTransition(from, to)
+    }
+
+    private fun transition(
+        from: SceneKey,
+        to: SceneKey,
+        filter: (TransitionSpec) -> Boolean,
+    ): TransitionSpec? {
+        var match: TransitionSpec? = null
+        transitionSpecs.fastForEach { spec ->
+            if (filter(spec)) {
+                if (match != null) {
+                    error("Found multiple transition specs for transition $from => $to")
+                }
+                match = spec
+            }
+        }
+        return match
+    }
+
+    private fun defaultTransition(from: SceneKey, to: SceneKey) =
+        TransitionSpec(from, to, emptyList(), snap())
+}
+
+/** The definition of a transition between [from] and [to]. */
+data class TransitionSpec(
+    val from: SceneKey?,
+    val to: SceneKey?,
+    val transformations: List<Transformation>,
+    val spec: AnimationSpec<Float>,
+) {
+    private val cache = mutableMapOf<ElementKey, ElementTransformations>()
+
+    internal fun reverse(): TransitionSpec {
+        return copy(
+            from = to,
+            to = from,
+            transformations = transformations.fastMap { it.reverse() },
+        )
+    }
+
+    internal fun transformations(element: ElementKey): ElementTransformations {
+        return cache.getOrPut(element) { computeTransformations(element) }
+    }
+
+    /** Filter [transformations] to compute the [ElementTransformations] of [element]. */
+    private fun computeTransformations(element: ElementKey): ElementTransformations {
+        val modifier = mutableListOf<ModifierTransformation>()
+        var offset: PropertyTransformation<Offset>? = null
+        var size: PropertyTransformation<IntSize>? = null
+        var alpha: PropertyTransformation<Float>? = null
+
+        fun <T> onPropertyTransformation(
+            root: PropertyTransformation<T>,
+            current: PropertyTransformation<T> = root,
+        ) {
+            when (current) {
+                is Translate,
+                is EdgeTranslate,
+                is AnchoredTranslate -> {
+                    throwIfNotNull(offset, element, property = "offset")
+                    offset = root as PropertyTransformation<Offset>
+                }
+                is ScaleSize,
+                is AnchoredSize -> {
+                    throwIfNotNull(size, element, property = "size")
+                    size = root as PropertyTransformation<IntSize>
+                }
+                is Fade -> {
+                    throwIfNotNull(alpha, element, property = "alpha")
+                    alpha = root as PropertyTransformation<Float>
+                }
+                is RangedPropertyTransformation -> onPropertyTransformation(root, current.delegate)
+            }
+        }
+
+        transformations.fastForEach { transformation ->
+            if (!transformation.matcher.matches(element)) {
+                return@fastForEach
+            }
+
+            when (transformation) {
+                is ModifierTransformation -> modifier.add(transformation)
+                is PropertyTransformation<*> -> onPropertyTransformation(transformation)
+            }
+        }
+
+        return ElementTransformations(modifier, offset, size, alpha)
+    }
+
+    private fun throwIfNotNull(
+        previous: PropertyTransformation<*>?,
+        element: ElementKey,
+        property: String,
+    ) {
+        if (previous != null) {
+            error("$element has multiple transformations for its $property property")
+        }
+    }
+}
+
+/** The transformations of an element during a transition. */
+internal class ElementTransformations(
+    val modifier: List<ModifierTransformation>,
+    val offset: PropertyTransformation<Offset>?,
+    val size: PropertyTransformation<IntSize>?,
+    val alpha: PropertyTransformation<Float>?,
+)
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
new file mode 100644
index 0000000..d9a45cd
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -0,0 +1,419 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.rememberDraggableState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import kotlin.math.absoluteValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
+ */
+@Composable
+internal fun Modifier.swipeToScene(
+    layoutImpl: SceneTransitionLayoutImpl,
+    orientation: Orientation,
+): Modifier {
+    val state = layoutImpl.state.transitionState
+    val currentScene = layoutImpl.scene(state.currentScene)
+    val transition = remember {
+        // Note that the currentScene here does not matter, it's only used for initializing the
+        // transition and will be replaced when a drag event starts.
+        SwipeTransition(initialScene = currentScene)
+    }
+
+    val enabled = state == transition || currentScene.shouldEnableSwipes(orientation)
+
+    // Immediately start the drag if this our [transition] is currently animating to a scene (i.e.
+    // the user released their input pointer after swiping in this orientation) and the user can't
+    // swipe in the other direction.
+    val startDragImmediately =
+        state == transition &&
+            transition.isAnimatingOffset &&
+            !currentScene.shouldEnableSwipes(orientation.opposite())
+
+    // The velocity threshold at which the intent of the user is to swipe up or down. It is the same
+    // as SwipeableV2Defaults.VelocityThreshold.
+    val velocityThreshold = with(LocalDensity.current) { 125.dp.toPx() }
+
+    // The positional threshold at which the intent of the user is to swipe to the next scene. It is
+    // the same as SwipeableV2Defaults.PositionalThreshold.
+    val positionalThreshold = with(LocalDensity.current) { 56.dp.toPx() }
+
+    return draggable(
+        orientation = orientation,
+        enabled = enabled,
+        startDragImmediately = startDragImmediately,
+        onDragStarted = { onDragStarted(layoutImpl, transition, orientation) },
+        state =
+            rememberDraggableState { delta -> onDrag(layoutImpl, transition, orientation, delta) },
+        onDragStopped = { velocity ->
+            onDragStopped(
+                layoutImpl,
+                transition,
+                velocity,
+                velocityThreshold,
+                positionalThreshold,
+            )
+        },
+    )
+}
+
+private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
+    var _currentScene by mutableStateOf(initialScene)
+    override val currentScene: SceneKey
+        get() = _currentScene.key
+
+    var _fromScene by mutableStateOf(initialScene)
+    override val fromScene: SceneKey
+        get() = _fromScene.key
+
+    var _toScene by mutableStateOf(initialScene)
+    override val toScene: SceneKey
+        get() = _toScene.key
+
+    override val progress: Float
+        get() {
+            val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+            if (distance == 0f) {
+                // This can happen only if fromScene == toScene.
+                error(
+                    "Transition.progress should be called only when Transition.fromScene != " +
+                        "Transition.toScene"
+                )
+            }
+            return offset / distance
+        }
+
+    /** The current offset caused by the drag gesture. */
+    var dragOffset by mutableFloatStateOf(0f)
+
+    /**
+     * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
+     */
+    var isAnimatingOffset by mutableStateOf(false)
+
+    /** The animatable used to animate the offset once the user lifted its finger. */
+    val offsetAnimatable = Animatable(0f, visibilityThreshold = OffsetVisibilityThreshold)
+
+    /**
+     * The job currently animating [offsetAnimatable], if it is animating. Note that setting this to
+     * a new job will automatically cancel the previous one.
+     */
+    var offsetAnimationJob: Job? = null
+        set(value) {
+            field?.cancel()
+            field = value
+        }
+
+    /** The absolute distance between [fromScene] and [toScene]. */
+    var absoluteDistance = 0f
+
+    /**
+     * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
+     * or to the left of [toScene].
+     */
+    var _distance by mutableFloatStateOf(0f)
+    val distance: Float
+        get() = _distance
+}
+
+/** The destination scene when swiping up or left from [this@upOrLeft]. */
+private fun Scene.upOrLeft(orientation: Orientation): SceneKey? {
+    return when (orientation) {
+        Orientation.Vertical -> userActions[Swipe.Up]
+        Orientation.Horizontal -> userActions[Swipe.Left]
+    }
+}
+
+/** The destination scene when swiping down or right from [this@downOrRight]. */
+private fun Scene.downOrRight(orientation: Orientation): SceneKey? {
+    return when (orientation) {
+        Orientation.Vertical -> userActions[Swipe.Down]
+        Orientation.Horizontal -> userActions[Swipe.Right]
+    }
+}
+
+/** Whether swipe should be enabled in the given [orientation]. */
+private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
+    return upOrLeft(orientation) != null || downOrRight(orientation) != null
+}
+
+private fun Orientation.opposite(): Orientation {
+    return when (this) {
+        Orientation.Vertical -> Orientation.Horizontal
+        Orientation.Horizontal -> Orientation.Vertical
+    }
+}
+
+private fun onDragStarted(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: SwipeTransition,
+    orientation: Orientation,
+) {
+    if (layoutImpl.state.transitionState == transition) {
+        // This [transition] was already driving the animation: simply take over it.
+        if (transition.isAnimatingOffset) {
+            // Stop animating and start from where the current offset. Setting the animation job to
+            // `null` will effectively cancel the animation.
+            transition.isAnimatingOffset = false
+            transition.offsetAnimationJob = null
+            transition.dragOffset = transition.offsetAnimatable.value
+        }
+
+        return
+    }
+
+    // TODO(b/290184746): Better handle interruptions here if state != idle.
+
+    val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+
+    transition._currentScene = fromScene
+    transition._fromScene = fromScene
+
+    // We don't know where we are transitioning to yet given that the drag just started, so set it
+    // to fromScene, which will effectively be treated the same as Idle(fromScene).
+    transition._toScene = fromScene
+
+    transition.dragOffset = 0f
+    transition.isAnimatingOffset = false
+    transition.offsetAnimationJob = null
+
+    // Use the layout size in the swipe orientation for swipe distance.
+    // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
+    // will also have to make sure that we correctly handle overscroll.
+    transition.absoluteDistance =
+        when (orientation) {
+            Orientation.Horizontal -> layoutImpl.size.width
+            Orientation.Vertical -> layoutImpl.size.height
+        }.toFloat()
+
+    if (transition.absoluteDistance > 0f) {
+        layoutImpl.state.transitionState = transition
+    }
+}
+
+private fun onDrag(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: SwipeTransition,
+    orientation: Orientation,
+    delta: Float,
+) {
+    transition.dragOffset += delta
+
+    // First check transition.fromScene should be changed for the case where the user quickly swiped
+    // twice in a row to accelerate the transition and go from A => B then B => C really fast.
+    maybeHandleAcceleratedSwipe(transition, orientation)
+
+    val fromScene = transition._fromScene
+    val upOrLeft = fromScene.upOrLeft(orientation)
+    val downOrRight = fromScene.downOrRight(orientation)
+    val offset = transition.dragOffset
+
+    // Compute the target scene depending on the current offset.
+    val targetSceneKey: SceneKey
+    val signedDistance: Float
+    when {
+        offset < 0f && upOrLeft != null -> {
+            targetSceneKey = upOrLeft
+            signedDistance = -transition.absoluteDistance
+        }
+        offset > 0f && downOrRight != null -> {
+            targetSceneKey = downOrRight
+            signedDistance = transition.absoluteDistance
+        }
+        else -> {
+            targetSceneKey = fromScene.key
+            signedDistance = 0f
+        }
+    }
+
+    if (transition._toScene.key != targetSceneKey) {
+        transition._toScene = layoutImpl.scenes.getValue(targetSceneKey)
+    }
+
+    if (transition._distance != signedDistance) {
+        transition._distance = signedDistance
+    }
+}
+
+/**
+ * Change fromScene in the case where the user quickly swiped multiple times in the same direction
+ * to accelerate the transition from A => B then B => C.
+ */
+private fun maybeHandleAcceleratedSwipe(
+    transition: SwipeTransition,
+    orientation: Orientation,
+) {
+    val toScene = transition._toScene
+    val fromScene = transition._fromScene
+
+    // If the swipe was not committed, don't do anything.
+    if (fromScene == toScene || transition._currentScene != toScene) {
+        return
+    }
+
+    // If the offset is past the distance then let's change fromScene so that the user can swipe to
+    // the next screen or go back to the previous one.
+    val offset = transition.dragOffset
+    val absoluteDistance = transition.absoluteDistance
+    if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) {
+        transition.dragOffset += absoluteDistance
+        transition._fromScene = toScene
+    } else if (offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key) {
+        transition.dragOffset -= absoluteDistance
+        transition._fromScene = toScene
+    }
+
+    // Important note: toScene and distance will be updated right after this function is called,
+    // using fromScene and dragOffset.
+}
+
+private fun CoroutineScope.onDragStopped(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: SwipeTransition,
+    velocity: Float,
+    velocityThreshold: Float,
+    positionalThreshold: Float,
+) {
+    // The state was changed since the drag started; don't do anything.
+    if (layoutImpl.state.transitionState != transition) {
+        return
+    }
+
+    // We were not animating.
+    if (transition._fromScene == transition._toScene) {
+        layoutImpl.state.transitionState = TransitionState.Idle(transition._fromScene.key)
+        return
+    }
+
+    // Compute the destination scene (and therefore offset) to settle in.
+    val targetScene: Scene
+    val targetOffset: Float
+    val offset = transition.dragOffset
+    val distance = transition.distance
+    if (
+        shouldCommitSwipe(
+            offset,
+            distance,
+            velocity,
+            velocityThreshold,
+            positionalThreshold,
+            wasCommitted = transition._currentScene == transition._toScene,
+        )
+    ) {
+        targetOffset = distance
+        targetScene = transition._toScene
+    } else {
+        targetOffset = 0f
+        targetScene = transition._fromScene
+    }
+
+    // If the effective current scene changed, it should be reflected right now in the current scene
+    // state, even before the settle animation is ongoing. That way all the swipeables and back
+    // handlers will be refreshed and the user can for instance quickly swipe vertically from A => B
+    // then horizontally from B => C, or swipe from A => B then immediately go back B => A.
+    if (targetScene != transition._currentScene) {
+        transition._currentScene = targetScene
+        layoutImpl.onChangeScene(targetScene.key)
+    }
+
+    // Animate the offset.
+    transition.offsetAnimationJob = launch {
+        transition.offsetAnimatable.snapTo(offset)
+        transition.isAnimatingOffset = true
+
+        transition.offsetAnimatable.animateTo(
+            targetOffset,
+            // TODO(b/290184746): Make this spring spec configurable.
+            spring(
+                stiffness = Spring.StiffnessMediumLow,
+                visibilityThreshold = OffsetVisibilityThreshold
+            ),
+            initialVelocity = velocity,
+        )
+
+        // Now that the animation is done, the state should be idle. Note that if the state was
+        // changed since this animation started, some external code changed it and we shouldn't do
+        // anything here. Note also that this job will be cancelled in the case where the user
+        // intercepts this swipe.
+        if (layoutImpl.state.transitionState == transition) {
+            layoutImpl.state.transitionState = TransitionState.Idle(targetScene.key)
+        }
+
+        transition.offsetAnimationJob = null
+    }
+}
+
+/**
+ * Whether the swipe to the target scene should be committed or not. This is inspired by
+ * SwipeableV2.computeTarget().
+ */
+private fun shouldCommitSwipe(
+    offset: Float,
+    distance: Float,
+    velocity: Float,
+    velocityThreshold: Float,
+    positionalThreshold: Float,
+    wasCommitted: Boolean,
+): Boolean {
+    fun isCloserToTarget(): Boolean {
+        return (offset - distance).absoluteValue < offset.absoluteValue
+    }
+
+    // Swiping up or left.
+    if (distance < 0f) {
+        return if (offset > 0f || velocity >= velocityThreshold) {
+            false
+        } else {
+            velocity <= -velocityThreshold ||
+                (offset <= -positionalThreshold && !wasCommitted) ||
+                isCloserToTarget()
+        }
+    }
+
+    // Swiping down or right.
+    return if (offset < 0f || velocity <= -velocityThreshold) {
+        false
+    } else {
+        velocity >= velocityThreshold ||
+            (offset >= positionalThreshold && !wasCommitted) ||
+            isCloserToTarget()
+    }
+}
+
+/**
+ * The number of pixels below which there won't be a visible difference in the transition and from
+ * which the animation can stop.
+ */
+private const val OffsetVisibilityThreshold = 0.5f
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
new file mode 100644
index 0000000..fb12b90
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -0,0 +1,194 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
+fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
+    return transitionsImpl(builder)
+}
+
+@DslMarker annotation class TransitionDsl
+
+@TransitionDsl
+interface SceneTransitionsBuilder {
+    /**
+     * Define the default animation to be played when transitioning [to] the specified scene, from
+     * any scene. For the animation specification to apply only when transitioning between two
+     * specific scenes, use [from] instead.
+     *
+     * @see from
+     */
+    fun to(
+        to: SceneKey,
+        builder: TransitionBuilder.() -> Unit = {},
+    ): TransitionSpec
+
+    /**
+     * Define the animation to be played when transitioning [from] the specified scene. For the
+     * animation specification to apply only when transitioning between two specific scenes, pass
+     * the destination scene via the [to] argument.
+     *
+     * When looking up which transition should be used when animating from scene A to scene B, we
+     * pick the single transition matching one of these predicates (in order of importance):
+     * 1. from == A && to == B
+     * 2. to == A && from == B, which is then treated in reverse.
+     * 3. (from == A && to == null) || (from == null && to == B)
+     * 4. (from == B && to == null) || (from == null && to == A), which is then treated in reverse.
+     */
+    fun from(
+        from: SceneKey,
+        to: SceneKey? = null,
+        builder: TransitionBuilder.() -> Unit = {},
+    ): TransitionSpec
+}
+
+@TransitionDsl
+interface TransitionBuilder : PropertyTransformationBuilder {
+    /**
+     * The [AnimationSpec] used to animate the progress of this transition from `0` to `1` when
+     * performing programmatic (not input pointer tracking) animations.
+     */
+    var spec: AnimationSpec<Float>
+
+    /**
+     * Define a progress-based range for the transformations inside [builder].
+     *
+     * For instance, the following will fade `Foo` during the first half of the transition then it
+     * will translate it by 100.dp during the second half.
+     *
+     * ```
+     * fractionRange(end = 0.5f) { fade(Foo) }
+     * fractionRange(start = 0.5f) { translate(Foo, x = 100.dp) }
+     * ```
+     *
+     * @param start the start of the range, in the [0; 1] range.
+     * @param end the end of the range, in the [0; 1] range.
+     */
+    fun fractionRange(
+        start: Float? = null,
+        end: Float? = null,
+        builder: PropertyTransformationBuilder.() -> Unit,
+    )
+
+    /**
+     * Define a timestamp-based range for the transformations inside [builder].
+     *
+     * For instance, the following will fade `Foo` during the first half of the transition then it
+     * will translate it by 100.dp during the second half.
+     *
+     * ```
+     * spec = tween(500)
+     * timestampRange(end = 250) { fade(Foo) }
+     * timestampRange(start = 250) { translate(Foo, x = 100.dp) }
+     * ```
+     *
+     * Important: [spec] must be a [androidx.compose.animation.core.DurationBasedAnimationSpec] if
+     * you call [timestampRange], otherwise this will throw. The spec duration will be used to
+     * transform this range into a [fractionRange].
+     *
+     * @param startMillis the start of the range, in the [0; spec.duration] range.
+     * @param endMillis the end of the range, in the [0; spec.duration] range.
+     */
+    fun timestampRange(
+        startMillis: Int? = null,
+        endMillis: Int? = null,
+        builder: PropertyTransformationBuilder.() -> Unit,
+    )
+
+    /**
+     * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and
+     * using the given [shape].
+     *
+     * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area.
+     * This can be used to make content drawn below an opaque element visible. For example, if we
+     * have [this lockscreen scene](http://shortn/_VYySFnJDhN) drawn below
+     * [this shade scene](http://shortn/_fpxGUk0Rg7) and punch a hole in the latter using the big
+     * clock time bounds and a RoundedCornerShape(10dp), [this](http://shortn/_qt80IvORFj) would be
+     * the result.
+     */
+    fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape = RectangleShape)
+}
+
+@TransitionDsl
+interface PropertyTransformationBuilder {
+    /**
+     * Fade the element(s) matching [matcher]. This will automatically fade in or fade out if the
+     * element is entering or leaving the scene, respectively.
+     */
+    fun fade(matcher: ElementMatcher)
+
+    /** Translate the element(s) matching [matcher] by ([x], [y]) dp. */
+    fun translate(matcher: ElementMatcher, x: Dp = 0.dp, y: Dp = 0.dp)
+
+    /**
+     * Translate the element(s) matching [matcher] from/to the [edge] of the [SceneTransitionLayout]
+     * animating it.
+     *
+     * If [startsOutsideLayoutBounds] is `true`, then the element will start completely outside of
+     * the layout bounds (i.e. none of it will be visible at progress = 0f if the layout clips its
+     * content). If it is `false`, then the element will start aligned with the edge of the layout
+     * (i.e. it will be completely visible at progress = 0f).
+     */
+    fun translate(matcher: ElementMatcher, edge: Edge, startsOutsideLayoutBounds: Boolean = true)
+
+    /**
+     * Translate the element(s) matching [matcher] by the same amount that [anchor] is translated
+     * during this transition.
+     *
+     * Note: This currently only works if [anchor] is a shared element of this transition.
+     *
+     * TODO(b/290184746): Also support anchors that are not shared but translated because of other
+     *   transformations, like an edge translation.
+     */
+    fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey)
+
+    /**
+     * Scale the [width] and [height] of the element(s) matching [matcher]. Note that this scaling
+     * is done during layout, so it will potentially impact the size and position of other elements.
+     *
+     * TODO(b/290184746): Also provide a scaleDrawing() to scale an element at drawing time.
+     */
+    fun scaleSize(matcher: ElementMatcher, width: Float = 1f, height: Float = 1f)
+
+    /**
+     * Scale the element(s) matching [matcher] so that it grows/shrinks to the same size as [anchor]
+     * .
+     *
+     * Note: This currently only works if [anchor] is a shared element of this transition.
+     */
+    fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey)
+}
+
+/** An interface to match one or more elements. */
+interface ElementMatcher {
+    /** Whether the element with key [key] matches this matcher. */
+    fun matches(key: ElementKey): Boolean
+}
+
+/** The edge of a [SceneTransitionLayout]. */
+enum class Edge {
+    Left,
+    Right,
+    Top,
+    Bottom,
+}
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
new file mode 100644
index 0000000..afd49b4
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.DurationBasedAnimationSpec
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.spring
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import com.android.compose.animation.scene.transformation.AnchoredSize
+import com.android.compose.animation.scene.transformation.AnchoredTranslate
+import com.android.compose.animation.scene.transformation.EdgeTranslate
+import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.PunchHole
+import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
+import com.android.compose.animation.scene.transformation.ScaleSize
+import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.TransformationRange
+import com.android.compose.animation.scene.transformation.Translate
+
+internal fun transitionsImpl(
+    builder: SceneTransitionsBuilder.() -> Unit,
+): SceneTransitions {
+    val impl = SceneTransitionsBuilderImpl().apply(builder)
+    return SceneTransitions(impl.transitionSpecs)
+}
+
+private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
+    val transitionSpecs = mutableListOf<TransitionSpec>()
+
+    override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec {
+        return transition(from = null, to = to, builder)
+    }
+
+    override fun from(
+        from: SceneKey,
+        to: SceneKey?,
+        builder: TransitionBuilder.() -> Unit
+    ): TransitionSpec {
+        return transition(from = from, to = to, builder)
+    }
+
+    private fun transition(
+        from: SceneKey?,
+        to: SceneKey?,
+        builder: TransitionBuilder.() -> Unit,
+    ): TransitionSpec {
+        val impl = TransitionBuilderImpl().apply(builder)
+        val spec =
+            TransitionSpec(
+                from,
+                to,
+                impl.transformations,
+                impl.spec,
+            )
+        transitionSpecs.add(spec)
+        return spec
+    }
+}
+
+private class TransitionBuilderImpl : TransitionBuilder {
+    val transformations = mutableListOf<Transformation>()
+    override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
+
+    private var range: TransformationRange? = null
+    private val durationMillis: Int by lazy {
+        val spec = spec
+        if (spec !is DurationBasedAnimationSpec) {
+            error("timestampRange {} can only be used with a DurationBasedAnimationSpec")
+        }
+
+        spec.vectorize(Float.VectorConverter).durationMillis
+    }
+
+    override fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape) {
+        transformations.add(PunchHole(matcher, bounds, shape))
+    }
+
+    override fun fractionRange(
+        start: Float?,
+        end: Float?,
+        builder: PropertyTransformationBuilder.() -> Unit
+    ) {
+        range = TransformationRange(start, end)
+        builder()
+        range = null
+    }
+
+    override fun timestampRange(
+        startMillis: Int?,
+        endMillis: Int?,
+        builder: PropertyTransformationBuilder.() -> Unit
+    ) {
+        if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) {
+            error("invalid start value: startMillis=$startMillis durationMillis=$durationMillis")
+        }
+
+        if (endMillis != null && (endMillis < 0 || endMillis > durationMillis)) {
+            error("invalid end value: endMillis=$startMillis durationMillis=$durationMillis")
+        }
+
+        val start = startMillis?.let { it.toFloat() / durationMillis }
+        val end = endMillis?.let { it.toFloat() / durationMillis }
+        fractionRange(start, end, builder)
+    }
+
+    private fun transformation(transformation: PropertyTransformation<*>) {
+        if (range != null) {
+            transformations.add(RangedPropertyTransformation(transformation, range!!))
+        } else {
+            transformations.add(transformation)
+        }
+    }
+
+    override fun fade(matcher: ElementMatcher) {
+        transformation(Fade(matcher))
+    }
+
+    override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) {
+        transformation(Translate(matcher, x, y))
+    }
+
+    override fun translate(
+        matcher: ElementMatcher,
+        edge: Edge,
+        startsOutsideLayoutBounds: Boolean
+    ) {
+        transformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds))
+    }
+
+    override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) {
+        transformation(AnchoredTranslate(matcher, anchor))
+    }
+
+    override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) {
+        transformation(ScaleSize(matcher, width, height))
+    }
+
+    override fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey) {
+        transformation(AnchoredSize(matcher, anchor))
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
new file mode 100644
index 0000000..d4ed697
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** Anchor the size of an element to the size of another element. */
+internal class AnchoredSize(
+    override val matcher: ElementMatcher,
+    private val anchor: ElementKey,
+) : PropertyTransformation<IntSize> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: IntSize,
+    ): IntSize {
+        fun anchorSizeIn(scene: SceneKey): IntSize {
+            val size = layoutImpl.elements[anchor]?.sceneValues?.get(scene)?.size
+            return if (size != null && size != Element.SizeUnspecified) {
+                size
+            } else {
+                value
+            }
+        }
+
+        // This simple implementation assumes that the size of [element] is the same as the size of
+        // the [anchor] in [scene], so simply transform to the size of the anchor in the other
+        // scene.
+        return if (scene.key == transition.fromScene) {
+            anchorSizeIn(transition.toScene)
+        } else {
+            anchorSizeIn(transition.fromScene)
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
new file mode 100644
index 0000000..8a5bd74
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isSpecified
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** Anchor the translation of an element to another element. */
+internal class AnchoredTranslate(
+    override val matcher: ElementMatcher,
+    private val anchor: ElementKey,
+) : PropertyTransformation<Offset> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: Offset,
+    ): Offset {
+        val anchor = layoutImpl.elements[anchor] ?: return value
+        fun anchorOffsetIn(scene: SceneKey): Offset? {
+            return anchor.sceneValues[scene]?.offset?.takeIf { it.isSpecified }
+        }
+
+        // [element] will move the same amount as [anchor] does.
+        // TODO(b/290184746): Also support anchors that are not shared but translated because of
+        // other transformations, like an edge translation.
+        val anchorFromOffset = anchorOffsetIn(transition.fromScene) ?: return value
+        val anchorToOffset = anchorOffsetIn(transition.toScene) ?: return value
+        val offset = anchorToOffset - anchorFromOffset
+
+        return if (scene.key == transition.toScene) {
+            Offset(
+                value.x - offset.x,
+                value.y - offset.y,
+            )
+        } else {
+            Offset(
+                value.x + offset.x,
+                value.y + offset.y,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
new file mode 100644
index 0000000..5cdce94
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.geometry.Offset
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** Translate an element from an edge of the layout. */
+internal class EdgeTranslate(
+    override val matcher: ElementMatcher,
+    private val edge: Edge,
+    private val startsOutsideLayoutBounds: Boolean = true,
+) : PropertyTransformation<Offset> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: Offset
+    ): Offset {
+        val sceneSize = scene.size
+        val elementSize = sceneValues.size
+        if (elementSize == Element.SizeUnspecified) {
+            return value
+        }
+
+        return when (edge) {
+            Edge.Top ->
+                if (startsOutsideLayoutBounds) {
+                    Offset(value.x, -elementSize.height.toFloat())
+                } else {
+                    Offset(value.x, 0f)
+                }
+            Edge.Left ->
+                if (startsOutsideLayoutBounds) {
+                    Offset(-elementSize.width.toFloat(), value.y)
+                } else {
+                    Offset(0f, value.y)
+                }
+            Edge.Bottom ->
+                if (startsOutsideLayoutBounds) {
+                    Offset(value.x, sceneSize.height.toFloat())
+                } else {
+                    Offset(value.x, (sceneSize.height - elementSize.height).toFloat())
+                }
+            Edge.Right ->
+                if (startsOutsideLayoutBounds) {
+                    Offset(sceneSize.width.toFloat(), value.y)
+                } else {
+                    Offset((sceneSize.width - elementSize.width).toFloat(), value.y)
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt
new file mode 100644
index 0000000..0a5ac54
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** Fade an element in or out. */
+internal class Fade(
+    override val matcher: ElementMatcher,
+) : PropertyTransformation<Float> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: Float
+    ): Float {
+        // Return the alpha value of [element] either when it starts fading in or when it finished
+        // fading out, which is `0` in both cases.
+        return 0f
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt
new file mode 100644
index 0000000..31e7d7c
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.toRect
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawOutline
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.withSaveLayer
+import androidx.compose.ui.unit.toSize
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+
+/** Punch a hole in an element using the bounds of another element and a given [shape]. */
+internal class PunchHole(
+    override val matcher: ElementMatcher,
+    private val bounds: ElementKey,
+    private val shape: Shape,
+) : ModifierTransformation {
+    override fun Modifier.transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+    ): Modifier {
+        return drawWithContent {
+            val bounds = layoutImpl.elements[bounds]
+            if (
+                bounds == null ||
+                    bounds.lastSize == Element.SizeUnspecified ||
+                    bounds.lastOffset == Offset.Unspecified
+            ) {
+                drawContent()
+                return@drawWithContent
+            }
+
+            drawIntoCanvas { canvas ->
+                canvas.withSaveLayer(size.toRect(), Paint()) {
+                    drawContent()
+
+                    val offset = bounds.lastOffset - element.lastOffset
+                    translate(offset.x, offset.y) { drawHole(bounds) }
+                }
+            }
+        }
+    }
+
+    private fun DrawScope.drawHole(bounds: Element) {
+        if (shape == RectangleShape) {
+            drawRect(Color.Black, blendMode = BlendMode.DstOut)
+            return
+        }
+
+        // TODO(b/290184746): Cache outline if the size of bounds does not change.
+        drawOutline(
+            shape.createOutline(
+                bounds.lastSize.toSize(),
+                layoutDirection,
+                this,
+            ),
+            Color.Black,
+            blendMode = BlendMode.DstOut,
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
new file mode 100644
index 0000000..ce754dc
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+import kotlin.math.roundToInt
+
+/**
+ * Scales the size of an element. Note that this makes the element resize every frame and will
+ * therefore impact the layout of other elements.
+ */
+internal class ScaleSize(
+    override val matcher: ElementMatcher,
+    private val width: Float = 1f,
+    private val height: Float = 1f,
+) : PropertyTransformation<IntSize> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: IntSize,
+    ): IntSize {
+        return IntSize(
+            width = (value.width * width).roundToInt(),
+            height = (value.height * height).roundToInt(),
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
new file mode 100644
index 0000000..ce6749d
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** A transformation applied to one or more elements during a transition. */
+sealed interface Transformation {
+    /**
+     * The matcher that should match the element(s) to which this transformation should be applied.
+     */
+    val matcher: ElementMatcher
+
+    /*
+     * Reverse this transformation. This is called when we use Transition(from = A, to = B) when
+     * animating from B to A and there is no Transition(from = B, to = A) defined.
+     */
+    fun reverse(): Transformation = this
+}
+
+/** A transformation that is applied on the element during the whole transition. */
+internal interface ModifierTransformation : Transformation {
+    /** Apply the transformation to [element]. */
+    // TODO(b/290184746): Figure out a public API for custom transformations that don't have access
+    // to these internal classes.
+    fun Modifier.transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+    ): Modifier
+}
+
+/** A transformation that changes the value of an element property, like its size or offset. */
+internal sealed interface PropertyTransformation<T> : Transformation {
+    /**
+     * The range during which the transformation is applied. If it is `null`, then the
+     * transformation will be applied throughout the whole scene transition.
+     */
+    val range: TransformationRange?
+        get() = null
+
+    /**
+     * Transform [value], i.e. the value of the transformed property without this transformation.
+     */
+    // TODO(b/290184746): Figure out a public API for custom transformations that don't have access
+    // to these internal classes.
+    fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: T,
+    ): T
+}
+
+/**
+ * A [PropertyTransformation] associated to a range. This is a helper class so that normal
+ * implementations of [PropertyTransformation] don't have to take care of reversing their range when
+ * they are reversed.
+ */
+internal class RangedPropertyTransformation<T>(
+    val delegate: PropertyTransformation<T>,
+    override val range: TransformationRange,
+) : PropertyTransformation<T> by delegate {
+    override fun reverse(): Transformation {
+        return RangedPropertyTransformation(
+            delegate.reverse() as PropertyTransformation<T>,
+            range.reverse()
+        )
+    }
+}
+
+/** The progress-based range of a [PropertyTransformation]. */
+data class TransformationRange
+private constructor(
+    val start: Float,
+    val end: Float,
+) {
+    constructor(
+        start: Float? = null,
+        end: Float? = null
+    ) : this(start ?: BoundUnspecified, end ?: BoundUnspecified)
+
+    init {
+        require(!start.isSpecified() || (start in 0f..1f))
+        require(!end.isSpecified() || (end in 0f..1f))
+        require(!start.isSpecified() || !end.isSpecified() || start <= end)
+    }
+
+    /** Reverse this range. */
+    fun reverse() = TransformationRange(start = reverseBound(end), end = reverseBound(start))
+
+    /** Get the progress of this range given the global [transitionProgress]. */
+    fun progress(transitionProgress: Float): Float {
+        return when {
+            start.isSpecified() && end.isSpecified() ->
+                ((transitionProgress - start) / (end - start)).coerceIn(0f, 1f)
+            !start.isSpecified() && !end.isSpecified() -> transitionProgress
+            end.isSpecified() -> (transitionProgress / end).coerceAtMost(1f)
+            else -> ((transitionProgress - start) / (1f - start)).coerceAtLeast(0f)
+        }
+    }
+
+    private fun Float.isSpecified() = this != BoundUnspecified
+
+    private fun reverseBound(bound: Float): Float {
+        return if (bound.isSpecified()) {
+            1f - bound
+        } else {
+            BoundUnspecified
+        }
+    }
+
+    companion object {
+        private const val BoundUnspecified = Float.MIN_VALUE
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt
new file mode 100644
index 0000000..8abca61
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** Translate an element by a fixed amount of density-independent pixels. */
+internal class Translate(
+    override val matcher: ElementMatcher,
+    private val x: Dp = 0.dp,
+    private val y: Dp = 0.dp,
+) : PropertyTransformation<Offset> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: Offset,
+    ): Offset {
+        return with(layoutImpl.density) {
+            Offset(
+                value.x + x.toPx(),
+                value.y + y.toPx(),
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
index 5224c51..27f0948 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
@@ -22,7 +22,6 @@
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.constrainWidth
 import androidx.compose.ui.unit.dp
 import kotlin.math.ceil
 import kotlin.math.max
@@ -126,18 +125,20 @@
             ((columns - 1) * horizontalSpacing.toPx()).roundToInt()
         val totalVerticalSpacingBetweenChildren = ((rows - 1) * verticalSpacing.toPx()).roundToInt()
         val childConstraints =
-            Constraints().apply {
-                if (constraints.maxWidth != Constraints.Infinity) {
-                    constrainWidth(
+            Constraints(
+                maxWidth =
+                    if (constraints.maxWidth != Constraints.Infinity) {
                         (constraints.maxWidth - totalHorizontalSpacingBetweenChildren) / columns
-                    )
-                }
-                if (constraints.maxHeight != Constraints.Infinity) {
-                    constrainWidth(
+                    } else {
+                        Constraints.Infinity
+                    },
+                maxHeight =
+                    if (constraints.maxHeight != Constraints.Infinity) {
                         (constraints.maxHeight - totalVerticalSpacingBetweenChildren) / rows
-                    )
-                }
-            }
+                    } else {
+                        Constraints.Infinity
+                    }
+            )
 
         val placeables = buildList {
             for (cellIndex in measurables.indices) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt
similarity index 96%
rename from packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt
rename to packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt
index 83071d7..135a6e4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.compose.modifiers
+package com.android.compose.modifiers
 
 import androidx.compose.ui.Modifier
 
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt b/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
deleted file mode 100644
index 946e779..0000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
+++ /dev/null
@@ -1,849 +0,0 @@
-/*
- * 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.compose.swipeable
-
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.SpringSpec
-import androidx.compose.foundation.gestures.DraggableState
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.draggable
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.Velocity
-import androidx.compose.ui.unit.dp
-import com.android.compose.swipeable.SwipeableDefaults.AnimationSpec
-import com.android.compose.swipeable.SwipeableDefaults.StandardResistanceFactor
-import com.android.compose.swipeable.SwipeableDefaults.VelocityThreshold
-import com.android.compose.swipeable.SwipeableDefaults.resistanceConfig
-import com.android.compose.ui.util.lerp
-import kotlin.math.PI
-import kotlin.math.abs
-import kotlin.math.sign
-import kotlin.math.sin
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.take
-import kotlinx.coroutines.launch
-
-/**
- * State of the [swipeable] modifier.
- *
- * This contains necessary information about any ongoing swipe or animation and provides methods to
- * change the state either immediately or by starting an animation. To create and remember a
- * [SwipeableState] with the default animation clock, use [rememberSwipeableState].
- *
- * @param initialValue The initial value of the state.
- * @param animationSpec The default animation that will be used to animate to a new state.
- * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
- *
- * TODO(b/272311106): this is a fork from material. Unfork it when Swipeable.kt reaches material3.
- */
-@Stable
-open class SwipeableState<T>(
-    initialValue: T,
-    internal val animationSpec: AnimationSpec<Float> = AnimationSpec,
-    internal val confirmStateChange: (newValue: T) -> Boolean = { true }
-) {
-    /**
-     * The current value of the state.
-     *
-     * If no swipe or animation is in progress, this corresponds to the anchor at which the
-     * [swipeable] is currently settled. If a swipe or animation is in progress, this corresponds
-     * the last anchor at which the [swipeable] was settled before the swipe or animation started.
-     */
-    var currentValue: T by mutableStateOf(initialValue)
-        private set
-
-    /** Whether the state is currently animating. */
-    var isAnimationRunning: Boolean by mutableStateOf(false)
-        private set
-
-    /**
-     * The current position (in pixels) of the [swipeable].
-     *
-     * You should use this state to offset your content accordingly. The recommended way is to use
-     * `Modifier.offsetPx`. This includes the resistance by default, if resistance is enabled.
-     */
-    val offset: State<Float>
-        get() = offsetState
-
-    /** The amount by which the [swipeable] has been swiped past its bounds. */
-    val overflow: State<Float>
-        get() = overflowState
-
-    // Use `Float.NaN` as a placeholder while the state is uninitialised.
-    private val offsetState = mutableStateOf(0f)
-    private val overflowState = mutableStateOf(0f)
-
-    // the source of truth for the "real"(non ui) position
-    // basically position in bounds + overflow
-    private val absoluteOffset = mutableStateOf(0f)
-
-    // current animation target, if animating, otherwise null
-    private val animationTarget = mutableStateOf<Float?>(null)
-
-    internal var anchors by mutableStateOf(emptyMap<Float, T>())
-
-    private val latestNonEmptyAnchorsFlow: Flow<Map<Float, T>> =
-        snapshotFlow { anchors }.filter { it.isNotEmpty() }.take(1)
-
-    internal var minBound = Float.NEGATIVE_INFINITY
-    internal var maxBound = Float.POSITIVE_INFINITY
-
-    internal fun ensureInit(newAnchors: Map<Float, T>) {
-        if (anchors.isEmpty()) {
-            // need to do initial synchronization synchronously :(
-            val initialOffset = newAnchors.getOffset(currentValue)
-            requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
-            offsetState.value = initialOffset
-            absoluteOffset.value = initialOffset
-        }
-    }
-
-    internal suspend fun processNewAnchors(oldAnchors: Map<Float, T>, newAnchors: Map<Float, T>) {
-        if (oldAnchors.isEmpty()) {
-            // If this is the first time that we receive anchors, then we need to initialise
-            // the state so we snap to the offset associated to the initial value.
-            minBound = newAnchors.keys.minOrNull()!!
-            maxBound = newAnchors.keys.maxOrNull()!!
-            val initialOffset = newAnchors.getOffset(currentValue)
-            requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
-            snapInternalToOffset(initialOffset)
-        } else if (newAnchors != oldAnchors) {
-            // If we have received new anchors, then the offset of the current value might
-            // have changed, so we need to animate to the new offset. If the current value
-            // has been removed from the anchors then we animate to the closest anchor
-            // instead. Note that this stops any ongoing animation.
-            minBound = Float.NEGATIVE_INFINITY
-            maxBound = Float.POSITIVE_INFINITY
-            val animationTargetValue = animationTarget.value
-            // if we're in the animation already, let's find it a new home
-            val targetOffset =
-                if (animationTargetValue != null) {
-                    // first, try to map old state to the new state
-                    val oldState = oldAnchors[animationTargetValue]
-                    val newState = newAnchors.getOffset(oldState)
-                    // return new state if exists, or find the closes one among new anchors
-                    newState ?: newAnchors.keys.minByOrNull { abs(it - animationTargetValue) }!!
-                } else {
-                    // we're not animating, proceed by finding the new anchors for an old value
-                    val actualOldValue = oldAnchors[offset.value]
-                    val value = if (actualOldValue == currentValue) currentValue else actualOldValue
-                    newAnchors.getOffset(value)
-                        ?: newAnchors.keys.minByOrNull { abs(it - offset.value) }!!
-                }
-            try {
-                animateInternalToOffset(targetOffset, animationSpec)
-            } catch (c: CancellationException) {
-                // If the animation was interrupted for any reason, snap as a last resort.
-                snapInternalToOffset(targetOffset)
-            } finally {
-                currentValue = newAnchors.getValue(targetOffset)
-                minBound = newAnchors.keys.minOrNull()!!
-                maxBound = newAnchors.keys.maxOrNull()!!
-            }
-        }
-    }
-
-    internal var thresholds: (Float, Float) -> Float by mutableStateOf({ _, _ -> 0f })
-
-    internal var velocityThreshold by mutableStateOf(0f)
-
-    internal var resistance: ResistanceConfig? by mutableStateOf(null)
-
-    internal val draggableState = DraggableState {
-        val newAbsolute = absoluteOffset.value + it
-        val clamped = newAbsolute.coerceIn(minBound, maxBound)
-        val overflow = newAbsolute - clamped
-        val resistanceDelta = resistance?.computeResistance(overflow) ?: 0f
-        offsetState.value = clamped + resistanceDelta
-        overflowState.value = overflow
-        absoluteOffset.value = newAbsolute
-    }
-
-    private suspend fun snapInternalToOffset(target: Float) {
-        draggableState.drag { dragBy(target - absoluteOffset.value) }
-    }
-
-    private suspend fun animateInternalToOffset(target: Float, spec: AnimationSpec<Float>) {
-        draggableState.drag {
-            var prevValue = absoluteOffset.value
-            animationTarget.value = target
-            isAnimationRunning = true
-            try {
-                Animatable(prevValue).animateTo(target, spec) {
-                    dragBy(this.value - prevValue)
-                    prevValue = this.value
-                }
-            } finally {
-                animationTarget.value = null
-                isAnimationRunning = false
-            }
-        }
-    }
-
-    /**
-     * The target value of the state.
-     *
-     * If a swipe is in progress, this is the value that the [swipeable] would animate to if the
-     * swipe finished. If an animation is running, this is the target value of that animation.
-     * Finally, if no swipe or animation is in progress, this is the same as the [currentValue].
-     */
-    val targetValue: T
-        get() {
-            // TODO(calintat): Track current velocity (b/149549482) and use that here.
-            val target =
-                animationTarget.value
-                    ?: computeTarget(
-                        offset = offset.value,
-                        lastValue = anchors.getOffset(currentValue) ?: offset.value,
-                        anchors = anchors.keys,
-                        thresholds = thresholds,
-                        velocity = 0f,
-                        velocityThreshold = Float.POSITIVE_INFINITY
-                    )
-            return anchors[target] ?: currentValue
-        }
-
-    /**
-     * Information about the ongoing swipe or animation, if any. See [SwipeProgress] for details.
-     *
-     * If no swipe or animation is in progress, this returns `SwipeProgress(value, value, 1f)`.
-     */
-    val progress: SwipeProgress<T>
-        get() {
-            val bounds = findBounds(offset.value, anchors.keys)
-            val from: T
-            val to: T
-            val fraction: Float
-            when (bounds.size) {
-                0 -> {
-                    from = currentValue
-                    to = currentValue
-                    fraction = 1f
-                }
-                1 -> {
-                    from = anchors.getValue(bounds[0])
-                    to = anchors.getValue(bounds[0])
-                    fraction = 1f
-                }
-                else -> {
-                    val (a, b) =
-                        if (direction > 0f) {
-                            bounds[0] to bounds[1]
-                        } else {
-                            bounds[1] to bounds[0]
-                        }
-                    from = anchors.getValue(a)
-                    to = anchors.getValue(b)
-                    fraction = (offset.value - a) / (b - a)
-                }
-            }
-            return SwipeProgress(from, to, fraction)
-        }
-
-    /**
-     * The direction in which the [swipeable] is moving, relative to the current [currentValue].
-     *
-     * This will be either 1f if it is is moving from left to right or top to bottom, -1f if it is
-     * moving from right to left or bottom to top, or 0f if no swipe or animation is in progress.
-     */
-    val direction: Float
-        get() = anchors.getOffset(currentValue)?.let { sign(offset.value - it) } ?: 0f
-
-    /**
-     * Set the state without any animation and suspend until it's set
-     *
-     * @param targetValue The new target value to set [currentValue] to.
-     */
-    suspend fun snapTo(targetValue: T) {
-        latestNonEmptyAnchorsFlow.collect { anchors ->
-            val targetOffset = anchors.getOffset(targetValue)
-            requireNotNull(targetOffset) { "The target value must have an associated anchor." }
-            snapInternalToOffset(targetOffset)
-            currentValue = targetValue
-        }
-    }
-
-    /**
-     * Set the state to the target value by starting an animation.
-     *
-     * @param targetValue The new value to animate to.
-     * @param anim The animation that will be used to animate to the new value.
-     */
-    suspend fun animateTo(targetValue: T, anim: AnimationSpec<Float> = animationSpec) {
-        latestNonEmptyAnchorsFlow.collect { anchors ->
-            try {
-                val targetOffset = anchors.getOffset(targetValue)
-                requireNotNull(targetOffset) { "The target value must have an associated anchor." }
-                animateInternalToOffset(targetOffset, anim)
-            } finally {
-                val endOffset = absoluteOffset.value
-                val endValue =
-                    anchors
-                        // fighting rounding error once again, anchor should be as close as 0.5
-                        // pixels
-                        .filterKeys { anchorOffset -> abs(anchorOffset - endOffset) < 0.5f }
-                        .values
-                        .firstOrNull()
-                        ?: currentValue
-                currentValue = endValue
-            }
-        }
-    }
-
-    /**
-     * Perform fling with settling to one of the anchors which is determined by the given
-     * [velocity]. Fling with settling [swipeable] will always consume all the velocity provided
-     * since it will settle at the anchor.
-     *
-     * In general cases, [swipeable] flings by itself when being swiped. This method is to be used
-     * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
-     * trigger settling fling when the child scroll container reaches the bound.
-     *
-     * @param velocity velocity to fling and settle with
-     * @return the reason fling ended
-     */
-    suspend fun performFling(velocity: Float) {
-        latestNonEmptyAnchorsFlow.collect { anchors ->
-            val lastAnchor = anchors.getOffset(currentValue)!!
-            val targetValue =
-                computeTarget(
-                    offset = offset.value,
-                    lastValue = lastAnchor,
-                    anchors = anchors.keys,
-                    thresholds = thresholds,
-                    velocity = velocity,
-                    velocityThreshold = velocityThreshold
-                )
-            val targetState = anchors[targetValue]
-            if (targetState != null && confirmStateChange(targetState)) animateTo(targetState)
-            // If the user vetoed the state change, rollback to the previous state.
-            else animateInternalToOffset(lastAnchor, animationSpec)
-        }
-    }
-
-    /**
-     * Force [swipeable] to consume drag delta provided from outside of the regular [swipeable]
-     * gesture flow.
-     *
-     * Note: This method performs generic drag and it won't settle to any particular anchor, *
-     * leaving swipeable in between anchors. When done dragging, [performFling] must be called as
-     * well to ensure swipeable will settle at the anchor.
-     *
-     * In general cases, [swipeable] drags by itself when being swiped. This method is to be used
-     * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
-     * force drag when the child scroll container reaches the bound.
-     *
-     * @param delta delta in pixels to drag by
-     * @return the amount of [delta] consumed
-     */
-    fun performDrag(delta: Float): Float {
-        val potentiallyConsumed = absoluteOffset.value + delta
-        val clamped = potentiallyConsumed.coerceIn(minBound, maxBound)
-        val deltaToConsume = clamped - absoluteOffset.value
-        if (abs(deltaToConsume) > 0) {
-            draggableState.dispatchRawDelta(deltaToConsume)
-        }
-        return deltaToConsume
-    }
-
-    companion object {
-        /** The default [Saver] implementation for [SwipeableState]. */
-        fun <T : Any> Saver(
-            animationSpec: AnimationSpec<Float>,
-            confirmStateChange: (T) -> Boolean
-        ) =
-            Saver<SwipeableState<T>, T>(
-                save = { it.currentValue },
-                restore = { SwipeableState(it, animationSpec, confirmStateChange) }
-            )
-    }
-}
-
-/**
- * Collects information about the ongoing swipe or animation in [swipeable].
- *
- * To access this information, use [SwipeableState.progress].
- *
- * @param from The state corresponding to the anchor we are moving away from.
- * @param to The state corresponding to the anchor we are moving towards.
- * @param fraction The fraction that the current position represents between [from] and [to]. Must
- *   be between `0` and `1`.
- */
-@Immutable
-class SwipeProgress<T>(
-    val from: T,
-    val to: T,
-    /*@FloatRange(from = 0.0, to = 1.0)*/
-    val fraction: Float
-) {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is SwipeProgress<*>) return false
-
-        if (from != other.from) return false
-        if (to != other.to) return false
-        if (fraction != other.fraction) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = from?.hashCode() ?: 0
-        result = 31 * result + (to?.hashCode() ?: 0)
-        result = 31 * result + fraction.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "SwipeProgress(from=$from, to=$to, fraction=$fraction)"
-    }
-}
-
-/**
- * Create and [remember] a [SwipeableState] with the default animation clock.
- *
- * @param initialValue The initial value of the state.
- * @param animationSpec The default animation that will be used to animate to a new state.
- * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
- */
-@Composable
-fun <T : Any> rememberSwipeableState(
-    initialValue: T,
-    animationSpec: AnimationSpec<Float> = AnimationSpec,
-    confirmStateChange: (newValue: T) -> Boolean = { true }
-): SwipeableState<T> {
-    return rememberSaveable(
-        saver =
-            SwipeableState.Saver(
-                animationSpec = animationSpec,
-                confirmStateChange = confirmStateChange
-            )
-    ) {
-        SwipeableState(
-            initialValue = initialValue,
-            animationSpec = animationSpec,
-            confirmStateChange = confirmStateChange
-        )
-    }
-}
-
-/**
- * Create and [remember] a [SwipeableState] which is kept in sync with another state, i.e.:
- * 1. Whenever the [value] changes, the [SwipeableState] will be animated to that new value.
- * 2. Whenever the value of the [SwipeableState] changes (e.g. after a swipe), the owner of the
- *    [value] will be notified to update their state to the new value of the [SwipeableState] by
- *    invoking [onValueChange]. If the owner does not update their state to the provided value for
- *    some reason, then the [SwipeableState] will perform a rollback to the previous, correct value.
- */
-@Composable
-internal fun <T : Any> rememberSwipeableStateFor(
-    value: T,
-    onValueChange: (T) -> Unit,
-    animationSpec: AnimationSpec<Float> = AnimationSpec
-): SwipeableState<T> {
-    val swipeableState = remember {
-        SwipeableState(
-            initialValue = value,
-            animationSpec = animationSpec,
-            confirmStateChange = { true }
-        )
-    }
-    val forceAnimationCheck = remember { mutableStateOf(false) }
-    LaunchedEffect(value, forceAnimationCheck.value) {
-        if (value != swipeableState.currentValue) {
-            swipeableState.animateTo(value)
-        }
-    }
-    DisposableEffect(swipeableState.currentValue) {
-        if (value != swipeableState.currentValue) {
-            onValueChange(swipeableState.currentValue)
-            forceAnimationCheck.value = !forceAnimationCheck.value
-        }
-        onDispose {}
-    }
-    return swipeableState
-}
-
-/**
- * Enable swipe gestures between a set of predefined states.
- *
- * To use this, you must provide a map of anchors (in pixels) to states (of type [T]). Note that
- * this map cannot be empty and cannot have two anchors mapped to the same state.
- *
- * When a swipe is detected, the offset of the [SwipeableState] will be updated with the swipe
- * delta. You should use this offset to move your content accordingly (see `Modifier.offsetPx`).
- * When the swipe ends, the offset will be animated to one of the anchors and when that anchor is
- * reached, the value of the [SwipeableState] will also be updated to the state corresponding to the
- * new anchor. The target anchor is calculated based on the provided positional [thresholds].
- *
- * Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe
- * past these bounds, a resistance effect will be applied by default. The amount of resistance at
- * each edge is specified by the [resistance] config. To disable all resistance, set it to `null`.
- *
- * For an example of a [swipeable] with three states, see:
- *
- * @param T The type of the state.
- * @param state The state of the [swipeable].
- * @param anchors Pairs of anchors and states, used to map anchors to states and vice versa.
- * @param thresholds Specifies where the thresholds between the states are. The thresholds will be
- *   used to determine which state to animate to when swiping stops. This is represented as a lambda
- *   that takes two states and returns the threshold between them in the form of a
- *   [ThresholdConfig]. Note that the order of the states corresponds to the swipe direction.
- * @param orientation The orientation in which the [swipeable] can be swiped.
- * @param enabled Whether this [swipeable] is enabled and should react to the user's input.
- * @param reverseDirection Whether to reverse the direction of the swipe, so a top to bottom swipe
- *   will behave like bottom to top, and a left to right swipe will behave like right to left.
- * @param interactionSource Optional [MutableInteractionSource] that will passed on to the internal
- *   [Modifier.draggable].
- * @param resistance Controls how much resistance will be applied when swiping past the bounds.
- * @param velocityThreshold The threshold (in dp per second) that the end velocity has to exceed in
- *   order to animate to the next state, even if the positional [thresholds] have not been reached.
- * @sample androidx.compose.material.samples.SwipeableSample
- */
-fun <T> Modifier.swipeable(
-    state: SwipeableState<T>,
-    anchors: Map<Float, T>,
-    orientation: Orientation,
-    enabled: Boolean = true,
-    reverseDirection: Boolean = false,
-    interactionSource: MutableInteractionSource? = null,
-    thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
-    resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
-    velocityThreshold: Dp = VelocityThreshold
-) =
-    composed(
-        inspectorInfo =
-            debugInspectorInfo {
-                name = "swipeable"
-                properties["state"] = state
-                properties["anchors"] = anchors
-                properties["orientation"] = orientation
-                properties["enabled"] = enabled
-                properties["reverseDirection"] = reverseDirection
-                properties["interactionSource"] = interactionSource
-                properties["thresholds"] = thresholds
-                properties["resistance"] = resistance
-                properties["velocityThreshold"] = velocityThreshold
-            }
-    ) {
-        require(anchors.isNotEmpty()) { "You must have at least one anchor." }
-        require(anchors.values.distinct().count() == anchors.size) {
-            "You cannot have two anchors mapped to the same state."
-        }
-        val density = LocalDensity.current
-        state.ensureInit(anchors)
-        LaunchedEffect(anchors, state) {
-            val oldAnchors = state.anchors
-            state.anchors = anchors
-            state.resistance = resistance
-            state.thresholds = { a, b ->
-                val from = anchors.getValue(a)
-                val to = anchors.getValue(b)
-                with(thresholds(from, to)) { density.computeThreshold(a, b) }
-            }
-            with(density) { state.velocityThreshold = velocityThreshold.toPx() }
-            state.processNewAnchors(oldAnchors, anchors)
-        }
-
-        Modifier.draggable(
-            orientation = orientation,
-            enabled = enabled,
-            reverseDirection = reverseDirection,
-            interactionSource = interactionSource,
-            startDragImmediately = state.isAnimationRunning,
-            onDragStopped = { velocity -> launch { state.performFling(velocity) } },
-            state = state.draggableState
-        )
-    }
-
-/**
- * Interface to compute a threshold between two anchors/states in a [swipeable].
- *
- * To define a [ThresholdConfig], consider using [FixedThreshold] and [FractionalThreshold].
- */
-@Stable
-interface ThresholdConfig {
-    /** Compute the value of the threshold (in pixels), once the values of the anchors are known. */
-    fun Density.computeThreshold(fromValue: Float, toValue: Float): Float
-}
-
-/**
- * A fixed threshold will be at an [offset] away from the first anchor.
- *
- * @param offset The offset (in dp) that the threshold will be at.
- */
-@Immutable
-data class FixedThreshold(private val offset: Dp) : ThresholdConfig {
-    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
-        return fromValue + offset.toPx() * sign(toValue - fromValue)
-    }
-}
-
-/**
- * A fractional threshold will be at a [fraction] of the way between the two anchors.
- *
- * @param fraction The fraction (between 0 and 1) that the threshold will be at.
- */
-@Immutable
-data class FractionalThreshold(
-    /*@FloatRange(from = 0.0, to = 1.0)*/
-    private val fraction: Float
-) : ThresholdConfig {
-    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
-        return lerp(fromValue, toValue, fraction)
-    }
-}
-
-/**
- * Specifies how resistance is calculated in [swipeable].
- *
- * There are two things needed to calculate resistance: the resistance basis determines how much
- * overflow will be consumed to achieve maximum resistance, and the resistance factor determines the
- * amount of resistance (the larger the resistance factor, the stronger the resistance).
- *
- * The resistance basis is usually either the size of the component which [swipeable] is applied to,
- * or the distance between the minimum and maximum anchors. For a constructor in which the
- * resistance basis defaults to the latter, consider using [resistanceConfig].
- *
- * You may specify different resistance factors for each bound. Consider using one of the default
- * resistance factors in [SwipeableDefaults]: `StandardResistanceFactor` to convey that the user has
- * run out of things to see, and `StiffResistanceFactor` to convey that the user cannot swipe this
- * right now. Also, you can set either factor to 0 to disable resistance at that bound.
- *
- * @param basis Specifies the maximum amount of overflow that will be consumed. Must be positive.
- * @param factorAtMin The factor by which to scale the resistance at the minimum bound. Must not be
- *   negative.
- * @param factorAtMax The factor by which to scale the resistance at the maximum bound. Must not be
- *   negative.
- */
-@Immutable
-class ResistanceConfig(
-    /*@FloatRange(from = 0.0, fromInclusive = false)*/
-    val basis: Float,
-    /*@FloatRange(from = 0.0)*/
-    val factorAtMin: Float = StandardResistanceFactor,
-    /*@FloatRange(from = 0.0)*/
-    val factorAtMax: Float = StandardResistanceFactor
-) {
-    fun computeResistance(overflow: Float): Float {
-        val factor = if (overflow < 0) factorAtMin else factorAtMax
-        if (factor == 0f) return 0f
-        val progress = (overflow / basis).coerceIn(-1f, 1f)
-        return basis / factor * sin(progress * PI.toFloat() / 2)
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ResistanceConfig) return false
-
-        if (basis != other.basis) return false
-        if (factorAtMin != other.factorAtMin) return false
-        if (factorAtMax != other.factorAtMax) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = basis.hashCode()
-        result = 31 * result + factorAtMin.hashCode()
-        result = 31 * result + factorAtMax.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "ResistanceConfig(basis=$basis, factorAtMin=$factorAtMin, factorAtMax=$factorAtMax)"
-    }
-}
-
-/**
- * Given an offset x and a set of anchors, return a list of anchors:
- * 1. [ ] if the set of anchors is empty,
- * 2. [ x' ] if x is equal to one of the anchors, accounting for a small rounding error, where x' is
- *    x rounded to the exact value of the matching anchor,
- * 3. [ min ] if min is the minimum anchor and x < min,
- * 4. [ max ] if max is the maximum anchor and x > max, or
- * 5. [ a , b ] if a and b are anchors such that a < x < b and b - a is minimal.
- */
-private fun findBounds(offset: Float, anchors: Set<Float>): List<Float> {
-    // Find the anchors the target lies between with a little bit of rounding error.
-    val a = anchors.filter { it <= offset + 0.001 }.maxOrNull()
-    val b = anchors.filter { it >= offset - 0.001 }.minOrNull()
-
-    return when {
-        a == null ->
-            // case 1 or 3
-            listOfNotNull(b)
-        b == null ->
-            // case 4
-            listOf(a)
-        a == b ->
-            // case 2
-            // Can't return offset itself here since it might not be exactly equal
-            // to the anchor, despite being considered an exact match.
-            listOf(a)
-        else ->
-            // case 5
-            listOf(a, b)
-    }
-}
-
-private fun computeTarget(
-    offset: Float,
-    lastValue: Float,
-    anchors: Set<Float>,
-    thresholds: (Float, Float) -> Float,
-    velocity: Float,
-    velocityThreshold: Float
-): Float {
-    val bounds = findBounds(offset, anchors)
-    return when (bounds.size) {
-        0 -> lastValue
-        1 -> bounds[0]
-        else -> {
-            val lower = bounds[0]
-            val upper = bounds[1]
-            if (lastValue <= offset) {
-                // Swiping from lower to upper (positive).
-                if (velocity >= velocityThreshold) {
-                    return upper
-                } else {
-                    val threshold = thresholds(lower, upper)
-                    if (offset < threshold) lower else upper
-                }
-            } else {
-                // Swiping from upper to lower (negative).
-                if (velocity <= -velocityThreshold) {
-                    return lower
-                } else {
-                    val threshold = thresholds(upper, lower)
-                    if (offset > threshold) upper else lower
-                }
-            }
-        }
-    }
-}
-
-private fun <T> Map<Float, T>.getOffset(state: T): Float? {
-    return entries.firstOrNull { it.value == state }?.key
-}
-
-/** Contains useful defaults for [swipeable] and [SwipeableState]. */
-object SwipeableDefaults {
-    /** The default animation used by [SwipeableState]. */
-    val AnimationSpec = SpringSpec<Float>()
-
-    /** The default velocity threshold (1.8 dp per millisecond) used by [swipeable]. */
-    val VelocityThreshold = 125.dp
-
-    /** A stiff resistance factor which indicates that swiping isn't available right now. */
-    const val StiffResistanceFactor = 20f
-
-    /** A standard resistance factor which indicates that the user has run out of things to see. */
-    const val StandardResistanceFactor = 10f
-
-    /**
-     * The default resistance config used by [swipeable].
-     *
-     * This returns `null` if there is one anchor. If there are at least two anchors, it returns a
-     * [ResistanceConfig] with the resistance basis equal to the distance between the two bounds.
-     */
-    fun resistanceConfig(
-        anchors: Set<Float>,
-        factorAtMin: Float = StandardResistanceFactor,
-        factorAtMax: Float = StandardResistanceFactor
-    ): ResistanceConfig? {
-        return if (anchors.size <= 1) {
-            null
-        } else {
-            val basis = anchors.maxOrNull()!! - anchors.minOrNull()!!
-            ResistanceConfig(basis, factorAtMin, factorAtMax)
-        }
-    }
-}
-
-// temp default nested scroll connection for swipeables which desire as an opt in
-// revisit in b/174756744 as all types will have their own specific connection probably
-internal val <T> SwipeableState<T>.PreUpPostDownNestedScrollConnection: NestedScrollConnection
-    get() =
-        object : NestedScrollConnection {
-            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
-                val delta = available.toFloat()
-                return if (delta < 0 && source == NestedScrollSource.Drag) {
-                    performDrag(delta).toOffset()
-                } else {
-                    Offset.Zero
-                }
-            }
-
-            override fun onPostScroll(
-                consumed: Offset,
-                available: Offset,
-                source: NestedScrollSource
-            ): Offset {
-                return if (source == NestedScrollSource.Drag) {
-                    performDrag(available.toFloat()).toOffset()
-                } else {
-                    Offset.Zero
-                }
-            }
-
-            override suspend fun onPreFling(available: Velocity): Velocity {
-                val toFling = Offset(available.x, available.y).toFloat()
-                return if (toFling < 0 && offset.value > minBound) {
-                    performFling(velocity = toFling)
-                    // since we go to the anchor with tween settling, consume all for the best UX
-                    available
-                } else {
-                    Velocity.Zero
-                }
-            }
-
-            override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
-                performFling(velocity = Offset(available.x, available.y).toFloat())
-                return available
-            }
-
-            private fun Float.toOffset(): Offset = Offset(0f, this)
-
-            private fun Offset.toFloat(): Float = this.y
-        }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt
new file mode 100644
index 0000000..741f00d
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package com.android.compose.ui.util
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+/**
+ * Iterates through a [List] using the index and calls [action] for each item. This does not
+ * allocate an iterator like [Iterable.forEach].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
+ */
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T> List<T>.fastForEach(action: (T) -> Unit) {
+    contract { callsInPlace(action) }
+    for (index in indices) {
+        val item = get(index)
+        action(item)
+    }
+}
+
+/**
+ * Returns a list containing the results of applying the given [transform] function to each element
+ * in the original collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
+ */
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R> List<T>.fastMap(transform: (T) -> R): List<R> {
+    contract { callsInPlace(transform) }
+    val target = ArrayList<R>(size)
+    fastForEach { target += transform(it) }
+    return target
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
index c1defb7..eb1a634 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
@@ -17,11 +17,10 @@
 
 package com.android.compose.ui.util
 
+import androidx.compose.ui.unit.IntSize
 import kotlin.math.roundToInt
 import kotlin.math.roundToLong
 
-// TODO(b/272311106): this is a fork from material. Unfork it when MathHelpers.kt reaches material3.
-
 /** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
 fun lerp(start: Float, stop: Float, fraction: Float): Float {
     return (1 - fraction) * start + fraction * stop
@@ -36,3 +35,11 @@
 fun lerp(start: Long, stop: Long, fraction: Float): Long {
     return start + ((stop - start) * fraction.toDouble()).roundToLong()
 }
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: IntSize, stop: IntSize, fraction: Float): IntSize {
+    return IntSize(
+        lerp(start.width, stop.width, fraction),
+        lerp(start.height, stop.height, fraction)
+    )
+}
diff --git a/packages/SystemUI/compose/core/tests/Android.bp b/packages/SystemUI/compose/core/tests/Android.bp
index 6119e96..5a8a374 100644
--- a/packages/SystemUI/compose/core/tests/Android.bp
+++ b/packages/SystemUI/compose/core/tests/Android.bp
@@ -42,7 +42,9 @@
         "androidx.compose.runtime_runtime",
         "androidx.compose.ui_ui-test-junit4",
         "androidx.compose.ui_ui-test-manifest",
+
+        "truth-prebuilt",
     ],
 
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
 }
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
new file mode 100644
index 0000000..04b3f8a
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ObservableTransitionStateTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testObservableTransitionState() = runTest {
+        val state = SceneTransitionLayoutState(TestScenes.SceneA)
+
+        // Collect the current observable state into [observableState].
+        // TODO(b/290184746): Use collectValues {} once it is extracted into a library that can be
+        // reused by non-SystemUI testing code.
+        var observableState: ObservableTransitionState? = null
+        backgroundScope.launch {
+            state.observableTransitionState().collect { observableState = it }
+        }
+
+        fun observableState(): ObservableTransitionState {
+            runCurrent()
+            return observableState!!
+        }
+
+        fun ObservableTransitionState.Transition.progress(): Float {
+            var lastProgress = -1f
+            backgroundScope.launch { progress.collect { lastProgress = it } }
+            runCurrent()
+            return lastProgress
+        }
+
+        rule.testTransition(
+            from = TestScenes.SceneA,
+            to = TestScenes.SceneB,
+            transitionLayout = { currentScene, onChangeScene ->
+                SceneTransitionLayout(
+                    currentScene,
+                    onChangeScene,
+                    EmptyTestTransitions,
+                    state = state,
+                ) {
+                    scene(TestScenes.SceneA) {}
+                    scene(TestScenes.SceneB) {}
+                }
+            }
+        ) {
+            before {
+                assertThat(observableState())
+                    .isEqualTo(ObservableTransitionState.Idle(TestScenes.SceneA))
+            }
+            at(0) {
+                val state = observableState()
+                assertThat(state).isInstanceOf(ObservableTransitionState.Transition::class.java)
+                assertThat((state as ObservableTransitionState.Transition).fromScene)
+                    .isEqualTo(TestScenes.SceneA)
+                assertThat(state.toScene).isEqualTo(TestScenes.SceneB)
+                assertThat(state.progress()).isEqualTo(0f)
+            }
+            at(TestTransitionDuration / 2) {
+                val state = observableState()
+                assertThat((state as ObservableTransitionState.Transition).fromScene)
+                    .isEqualTo(TestScenes.SceneA)
+                assertThat(state.toScene).isEqualTo(TestScenes.SceneB)
+                assertThat(state.progress()).isEqualTo(0.5f)
+            }
+            after {
+                assertThat(observableState())
+                    .isEqualTo(ObservableTransitionState.Idle(TestScenes.SceneB))
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
new file mode 100644
index 0000000..8bd6545
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -0,0 +1,323 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.activity.ComponentActivity
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onAllNodesWithTag
+import androidx.compose.ui.test.onChild
+import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.subjects.DpOffsetSubject
+import com.android.compose.test.subjects.assertThat
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SceneTransitionLayoutTest {
+    companion object {
+        private val LayoutSize = 300.dp
+    }
+
+    private var currentScene by mutableStateOf(TestScenes.SceneA)
+    private val layoutState = SceneTransitionLayoutState(currentScene)
+
+    // We use createAndroidComposeRule() here and not createComposeRule() because we need an
+    // activity for testBack().
+    @get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
+
+    /** The content under test. */
+    @Composable
+    private fun TestContent() {
+        SceneTransitionLayout(
+            currentScene,
+            { currentScene = it },
+            EmptyTestTransitions,
+            state = layoutState,
+            modifier = Modifier.size(LayoutSize),
+        ) {
+            scene(
+                TestScenes.SceneA,
+                userActions = mapOf(Back to TestScenes.SceneB),
+            ) {
+                Box(Modifier.fillMaxSize()) {
+                    SharedFoo(size = 50.dp, childOffset = 0.dp, Modifier.align(Alignment.TopEnd))
+                    Text("SceneA")
+                }
+            }
+            scene(TestScenes.SceneB) {
+                Box(Modifier.fillMaxSize()) {
+                    SharedFoo(
+                        size = 100.dp,
+                        childOffset = 50.dp,
+                        Modifier.align(Alignment.TopStart),
+                    )
+                    Text("SceneB")
+                }
+            }
+            scene(TestScenes.SceneC) {
+                Box(Modifier.fillMaxSize()) {
+                    SharedFoo(
+                        size = 150.dp,
+                        childOffset = 100.dp,
+                        Modifier.align(Alignment.BottomStart),
+                    )
+                    Text("SceneC")
+                }
+            }
+        }
+    }
+
+    @Composable
+    private fun SceneScope.SharedFoo(size: Dp, childOffset: Dp, modifier: Modifier = Modifier) {
+        Box(
+            modifier
+                .size(size)
+                .background(Color.Red)
+                .element(TestElements.Foo)
+                .testTag(TestElements.Foo.name)
+        ) {
+            // Offset the single child of Foo by some animated shared offset.
+            val offset by animateSharedDpAsState(childOffset, TestValues.Value1, TestElements.Foo)
+
+            Box(
+                Modifier.offset {
+                        val pxOffset = offset.roundToPx()
+                        IntOffset(pxOffset, pxOffset)
+                    }
+                    .size(30.dp)
+                    .background(Color.Blue)
+                    .testTag(TestElements.Bar.name)
+            )
+        }
+    }
+
+    @Test
+    fun testOnlyCurrentSceneIsDisplayed() {
+        rule.setContent { TestContent() }
+
+        // Only scene A is displayed.
+        rule.onNodeWithText("SceneA").assertIsDisplayed()
+        rule.onNodeWithText("SceneB").assertDoesNotExist()
+        rule.onNodeWithText("SceneC").assertDoesNotExist()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // Change to scene B. Only that scene is displayed.
+        currentScene = TestScenes.SceneB
+        rule.onNodeWithText("SceneA").assertDoesNotExist()
+        rule.onNodeWithText("SceneB").assertIsDisplayed()
+        rule.onNodeWithText("SceneC").assertDoesNotExist()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+    }
+
+    @Test
+    fun testBack() {
+        rule.setContent { TestContent() }
+
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        rule.activity.onBackPressed()
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+    }
+
+    @Test
+    fun testTransitionState() {
+        rule.setContent { TestContent() }
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // We will advance the clock manually.
+        rule.mainClock.autoAdvance = false
+
+        // Change the current scene. Until composition is triggered, this won't change the layout
+        // state.
+        currentScene = TestScenes.SceneB
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // On the next frame, we will recompose because currentScene changed, which will start the
+        // transition (i.e. it will change the transitionState to be a Transition) in a
+        // LaunchedEffect.
+        rule.mainClock.advanceTimeByFrame()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
+        val transition = layoutState.transitionState as TransitionState.Transition
+        assertThat(transition.fromScene).isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        assertThat(transition.progress).isEqualTo(0f)
+
+        // Then, on the next frame, the animator we started gets its initial value and clock
+        // starting time. We are now at progress = 0f.
+        rule.mainClock.advanceTimeByFrame()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
+            .isEqualTo(0f)
+
+        // The test transition lasts 480ms. 240ms after the start of the transition, we are at
+        // progress = 0.5f.
+        rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
+            .isEqualTo(0.5f)
+
+        // (240-16) ms later, i.e. one frame before the transition is finished, we are at
+        // progress=(480-16)/480.
+        rule.mainClock.advanceTimeBy(TestTransitionDuration / 2 - 16)
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
+            .isEqualTo((TestTransitionDuration - 16) / 480f)
+
+        // one frame (16ms) later, the transition is finished and we are in the idle state in scene
+        // B.
+        rule.mainClock.advanceTimeByFrame()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+    }
+
+    @Test
+    fun testSharedElement() {
+        rule.setContent { TestContent() }
+
+        // In scene A, the shared element SharedFoo() is at the top end of the layout and has a size
+        // of 50.dp.
+        var sharedFoo = rule.onNodeWithTag(TestElements.Foo.name, useUnmergedTree = true)
+        sharedFoo.assertWidthIsEqualTo(50.dp)
+        sharedFoo.assertHeightIsEqualTo(50.dp)
+        sharedFoo.assertPositionInRootIsEqualTo(
+            expectedTop = 0.dp,
+            expectedLeft = LayoutSize - 50.dp,
+        )
+
+        // The shared offset of the single child of SharedFoo() is 0dp in scene A.
+        assertThat(sharedFoo.onChild().offsetRelativeTo(sharedFoo)).isEqualTo(DpOffset(0.dp, 0.dp))
+
+        // Pause animations to test the state mid-transition.
+        rule.mainClock.autoAdvance = false
+
+        // Go to scene B and let the animation start. See [testLayoutState()] and
+        // [androidx.compose.ui.test.MainTestClock] to understand why we need to advance the clock
+        // by 2 frames to be at the start of the animation.
+        currentScene = TestScenes.SceneB
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+
+        // Advance to the middle of the animation.
+        rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
+
+        // We need to use onAllNodesWithTag().onFirst() here given that shared elements are
+        // composed and laid out in both scenes (but drawn only in one).
+        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.name).onFirst()
+
+        // In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
+        // 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
+        // use a linear interpolator. Foo was at (x = layoutSize - 50dp, y = 0) in SceneA and is
+        // going to (x = 0, y = 0), so the offset should now be half what it was.
+        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
+            .isEqualTo(0.5f)
+        sharedFoo.assertWidthIsEqualTo(75.dp)
+        sharedFoo.assertHeightIsEqualTo(75.dp)
+        sharedFoo.assertPositionInRootIsEqualTo(
+            expectedTop = 0.dp,
+            expectedLeft = (LayoutSize - 50.dp) / 2
+        )
+
+        // The shared offset of the single child of SharedFoo() is 50dp in scene B and 0dp in Scene
+        // A, so it should be 25dp now.
+        assertThat(sharedFoo.onChild().offsetRelativeTo(sharedFoo))
+            .isWithin(DpOffsetSubject.DefaultTolerance)
+            .of(DpOffset(25.dp, 25.dp))
+
+        // Animate to scene C, let the animation start then go to the middle of the transition.
+        currentScene = TestScenes.SceneC
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
+
+        // In Scene C, foo is at the bottom start of the layout and has a size of 150.dp. The
+        // transition scene B => scene C is using a FastOutSlowIn interpolator.
+        val interpolatedProgress = FastOutSlowInEasing.transform(0.5f)
+        val expectedTop = (LayoutSize - 150.dp) * interpolatedProgress
+        val expectedLeft = 0.dp
+        val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
+
+        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.name).onFirst()
+        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
+            .isEqualTo(interpolatedProgress)
+        sharedFoo.assertWidthIsEqualTo(expectedSize)
+        sharedFoo.assertHeightIsEqualTo(expectedSize)
+        sharedFoo.assertPositionInRootIsEqualTo(expectedLeft, expectedTop)
+
+        // The shared offset of the single child of SharedFoo() is 50dp in scene B and 100dp in
+        // Scene C.
+        val expectedOffset = 50.dp + (100.dp - 50.dp) * interpolatedProgress
+        assertThat(sharedFoo.onChild().offsetRelativeTo(sharedFoo))
+            .isWithin(DpOffsetSubject.DefaultTolerance)
+            .of(DpOffset(expectedOffset, expectedOffset))
+
+        // Go back to scene A. This should happen instantly (once the animation started, i.e. after
+        // 2 frames) given that we use a snap() animation spec.
+        currentScene = TestScenes.SceneA
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+    }
+
+    private fun SemanticsNodeInteraction.offsetRelativeTo(
+        other: SemanticsNodeInteraction,
+    ): DpOffset {
+        val node = fetchSemanticsNode()
+        val bounds = node.boundsInRoot
+        val otherBounds = other.fetchSemanticsNode().boundsInRoot
+        return with(node.layoutInfo.density) {
+            DpOffset(
+                x = (bounds.left - otherBounds.left).toDp(),
+                y = (bounds.top - otherBounds.top).toDp(),
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
new file mode 100644
index 0000000..cb2607a
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeWithVelocity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SwipeToSceneTest {
+    companion object {
+        private val LayoutWidth = 200.dp
+        private val LayoutHeight = 400.dp
+
+        /** The middle of the layout, in pixels. */
+        private val Density.middle: Offset
+            get() = Offset((LayoutWidth / 2).toPx(), (LayoutHeight / 2).toPx())
+    }
+
+    private var currentScene by mutableStateOf(TestScenes.SceneA)
+    private val layoutState = SceneTransitionLayoutState(currentScene)
+
+    @get:Rule val rule = createComposeRule()
+
+    /** The content under test. */
+    @Composable
+    private fun TestContent() {
+        SceneTransitionLayout(
+            currentScene,
+            { currentScene = it },
+            EmptyTestTransitions,
+            state = layoutState,
+            modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.name),
+        ) {
+            scene(
+                TestScenes.SceneA,
+                userActions =
+                    mapOf(
+                        Swipe.Left to TestScenes.SceneB,
+                        Swipe.Down to TestScenes.SceneC,
+                    ),
+            ) {
+                Box(Modifier.fillMaxSize())
+            }
+            scene(
+                TestScenes.SceneB,
+                userActions = mapOf(Swipe.Right to TestScenes.SceneA),
+            ) {
+                Box(Modifier.fillMaxSize())
+            }
+            scene(
+                TestScenes.SceneC,
+                userActions = mapOf(Swipe.Down to TestScenes.SceneA),
+            ) {
+                Box(Modifier.fillMaxSize())
+            }
+        }
+    }
+
+    @Test
+    fun testDragWithPositionalThreshold() {
+        // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+        // detected as a drag event.
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            TestContent()
+        }
+
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // Drag left (i.e. from right to left) by 55dp. We pick 55dp here because 56dp is the
+        // positional threshold from which we commit the gesture.
+        rule.onRoot().performTouchInput {
+            down(middle)
+
+            // We use a high delay so that the velocity of the gesture is slow (otherwise it would
+            // commit the gesture, even if we are below the positional threshold).
+            moveBy(Offset(-55.dp.toPx() - touchSlop, 0f), delayMillis = 1_000)
+        }
+
+        // We should be at a progress = 55dp / LayoutWidth given that we use the layout size in
+        // the gesture axis as swipe distance.
+        var transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
+
+        // Release the finger. We should now be animating back to A (currentScene = SceneA) given
+        // that 55dp < positional threshold.
+        rule.onRoot().performTouchInput { up() }
+        transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
+
+        // Wait for the animation to finish. We should now be in scene A.
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // Now we do the same but vertically and with a drag distance of 56dp, which is >=
+        // positional threshold.
+        rule.onRoot().performTouchInput {
+            down(middle)
+            moveBy(Offset(0f, 56.dp.toPx() + touchSlop), delayMillis = 1_000)
+        }
+
+        // Drag is in progress, so currentScene = SceneA and progress = 56dp / LayoutHeight
+        transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight)
+
+        // Release the finger. We should now be animating to C (currentScene = SceneC) given
+        // that 56dp >= positional threshold.
+        rule.onRoot().performTouchInput { up() }
+        transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight)
+
+        // Wait for the animation to finish. We should now be in scene C.
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+    }
+
+    @Test
+    fun testSwipeWithVelocityThreshold() {
+        // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+        // detected as a drag event.
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            TestContent()
+        }
+
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // Swipe left (i.e. from right to left) using a velocity of 124 dp/s. We pick 124 dp/s here
+        // because 125 dp/s is the velocity threshold from which we commit the gesture. We also use
+        // a swipe distance < 56dp, the positional threshold, to make sure that we don't commit
+        // the gesture because of a large enough swipe distance.
+        rule.onRoot().performTouchInput {
+            swipeWithVelocity(
+                start = middle,
+                end = middle - Offset(55.dp.toPx() + touchSlop, 0f),
+                endVelocity = 124.dp.toPx(),
+            )
+        }
+
+        // We should be animating back to A (currentScene = SceneA) given that 124 dp/s < velocity
+        // threshold.
+        var transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
+
+        // Wait for the animation to finish. We should now be in scene A.
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // Now we do the same but vertically and with a swipe velocity of 126dp, which is >
+        // velocity threshold. Note that in theory we could have used 125 dp (= velocity threshold)
+        // but it doesn't work reliably with how swipeWithVelocity() computes move events to get to
+        // the target velocity, probably because of float rounding errors.
+        rule.onRoot().performTouchInput {
+            swipeWithVelocity(
+                start = middle,
+                end = middle + Offset(0f, 55.dp.toPx() + touchSlop),
+                endVelocity = 126.dp.toPx(),
+            )
+        }
+
+        // We should be animating to C (currentScene = SceneC).
+        transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(transition.progress).isEqualTo(55.dp / LayoutHeight)
+
+        // Wait for the animation to finish. We should now be in scene C.
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
new file mode 100644
index 0000000..275149a0
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
@@ -0,0 +1,217 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.onNodeWithTag
+
+@DslMarker annotation class TransitionTestDsl
+
+@TransitionTestDsl
+interface TransitionTestBuilder {
+    /**
+     * Assert on the state of the layout before the transition starts.
+     *
+     * This should be called maximum once, before [at] or [after] is called.
+     */
+    fun before(builder: TransitionTestAssertionScope.() -> Unit)
+
+    /**
+     * Assert on the state of the layout during the transition at [timestamp].
+     *
+     * This should be called after [before] is called and before [after] is called. Successive calls
+     * to [at] must be called with increasing [timestamp].
+     *
+     * Important: [timestamp] must be a multiple of 16 (the duration of a frame on the JVM/Android).
+     * There is no intermediary state between `t` and `t + 16` , so testing transitions outside of
+     * `t = 0`, `t = 16`, `t = 32`, etc does not make sense.
+     */
+    fun at(timestamp: Long, builder: TransitionTestAssertionScope.() -> Unit)
+
+    /**
+     * Assert on the state of the layout after the transition finished.
+     *
+     * This should be called maximum once, after [before] or [at] is called.
+     */
+    fun after(builder: TransitionTestAssertionScope.() -> Unit)
+}
+
+@TransitionTestDsl
+interface TransitionTestAssertionScope {
+    /** Assert on [element]. */
+    fun onElement(element: ElementKey): SemanticsNodeInteraction
+}
+
+/**
+ * Test the transition between [fromSceneContent] and [toSceneContent] at different points in time.
+ *
+ * @sample com.android.compose.animation.scene.transformation.TranslateTest
+ */
+fun ComposeContentTestRule.testTransition(
+    fromSceneContent: @Composable SceneScope.() -> Unit,
+    toSceneContent: @Composable SceneScope.() -> Unit,
+    transition: TransitionBuilder.() -> Unit,
+    layoutModifier: Modifier = Modifier,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
+    testTransition(
+        from = TestScenes.SceneA,
+        to = TestScenes.SceneB,
+        transitionLayout = { currentScene, onChangeScene ->
+            SceneTransitionLayout(
+                currentScene,
+                onChangeScene,
+                transitions { from(TestScenes.SceneA, to = TestScenes.SceneB, transition) },
+                layoutModifier.fillMaxSize(),
+            ) {
+                scene(TestScenes.SceneA, content = fromSceneContent)
+                scene(TestScenes.SceneB, content = toSceneContent)
+            }
+        },
+        builder,
+    )
+}
+
+/**
+ * Test the transition between two scenes of [transitionLayout][SceneTransitionLayout] at different
+ * points in time.
+ */
+fun ComposeContentTestRule.testTransition(
+    from: SceneKey,
+    to: SceneKey,
+    transitionLayout:
+        @Composable
+        (
+            currentScene: SceneKey,
+            onChangeScene: (SceneKey) -> Unit,
+        ) -> Unit,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
+    val test = transitionTest(builder)
+    val assertionScope =
+        object : TransitionTestAssertionScope {
+            override fun onElement(element: ElementKey): SemanticsNodeInteraction {
+                return this@testTransition.onNodeWithTag(element.name)
+            }
+        }
+
+    var currentScene by mutableStateOf(from)
+    setContent { transitionLayout(currentScene, { currentScene = it }) }
+
+    // Wait for the UI to be idle then test the before state.
+    waitForIdle()
+    test.before(assertionScope)
+
+    // Manually advance the clock to the start of the animation.
+    mainClock.autoAdvance = false
+
+    // Change the current scene.
+    currentScene = to
+
+    // Advance by a frame to trigger recomposition, which will start the transition (i.e. it will
+    // change the transitionState to be a Transition) in a LaunchedEffect.
+    mainClock.advanceTimeByFrame()
+
+    // Advance by another frame so that the animator we started gets its initial value and clock
+    // starting time. We are now at progress = 0f.
+    mainClock.advanceTimeByFrame()
+    waitForIdle()
+
+    // Test the assertions at specific points in time.
+    test.timestamps.forEach { tsAssertion ->
+        if (tsAssertion.timestampDelta > 0L) {
+            mainClock.advanceTimeBy(tsAssertion.timestampDelta)
+            waitForIdle()
+        }
+
+        tsAssertion.assertion(assertionScope)
+    }
+
+    // Go to the end state and test it.
+    mainClock.autoAdvance = true
+    waitForIdle()
+    test.after(assertionScope)
+}
+
+private fun transitionTest(builder: TransitionTestBuilder.() -> Unit): TransitionTest {
+    // Collect the assertion lambdas in [TransitionTest]. Note that the ordering is forced by the
+    // builder, e.g. `before {}` must be called before everything else, then `at {}` (in increasing
+    // order of timestamp), then `after {}`. That way the test code is run with the same order as it
+    // is written, to avoid confusion.
+
+    val impl =
+        object : TransitionTestBuilder {
+                var before: (TransitionTestAssertionScope.() -> Unit)? = null
+                var after: (TransitionTestAssertionScope.() -> Unit)? = null
+                val timestamps = mutableListOf<TimestampAssertion>()
+
+                private var currentTimestamp = 0L
+
+                override fun before(builder: TransitionTestAssertionScope.() -> Unit) {
+                    check(before == null) { "before {} must be called maximum once" }
+                    check(after == null) { "before {} must be called before after {}" }
+                    check(timestamps.isEmpty()) { "before {} must be called before at(...) {}" }
+
+                    before = builder
+                }
+
+                override fun at(timestamp: Long, builder: TransitionTestAssertionScope.() -> Unit) {
+                    check(after == null) { "at(...) {} must be called before after {}" }
+                    check(timestamp >= currentTimestamp) {
+                        "at(...) must be called with timestamps in increasing order"
+                    }
+                    check(timestamp % 16 == 0L) {
+                        "timestamp must be a multiple of the frame time (16ms)"
+                    }
+
+                    val delta = timestamp - currentTimestamp
+                    currentTimestamp = timestamp
+
+                    timestamps.add(TimestampAssertion(delta, builder))
+                }
+
+                override fun after(builder: TransitionTestAssertionScope.() -> Unit) {
+                    check(after == null) { "after {} must be called maximum once" }
+                    after = builder
+                }
+            }
+            .apply(builder)
+
+    return TransitionTest(
+        before = impl.before ?: {},
+        timestamps = impl.timestamps,
+        after = impl.after ?: {},
+    )
+}
+
+private class TransitionTest(
+    val before: TransitionTestAssertionScope.() -> Unit,
+    val after: TransitionTestAssertionScope.() -> Unit,
+    val timestamps: List<TimestampAssertion>,
+)
+
+private class TimestampAssertion(
+    val timestampDelta: Long,
+    val assertion: TransitionTestAssertionScope.() -> Unit,
+)
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
new file mode 100644
index 0000000..8357262
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.snap
+import androidx.compose.animation.core.tween
+
+/** Scenes keys that can be reused by tests. */
+object TestScenes {
+    val SceneA = SceneKey("SceneA")
+    val SceneB = SceneKey("SceneB")
+    val SceneC = SceneKey("SceneC")
+}
+
+/** Element keys that can be reused by tests. */
+object TestElements {
+    val Foo = ElementKey("Foo")
+    val Bar = ElementKey("Bar")
+}
+
+/** Value keys that can be reused by tests. */
+object TestValues {
+    val Value1 = ValueKey("Value1")
+}
+
+// We use a transition duration of 480ms here because it is a multiple of 16, the time of a frame in
+// C JVM/Android. Doing so allows us for instance to test the state at progress = 0.5f given that t
+// = 240ms is also a multiple of 16.
+val TestTransitionDuration = 480L
+
+/** A definition of empty transitions between [TestScenes], using different animation specs. */
+val EmptyTestTransitions = transitions {
+    from(TestScenes.SceneA, to = TestScenes.SceneB) {
+        spec = tween(durationMillis = TestTransitionDuration.toInt(), easing = LinearEasing)
+    }
+
+    from(TestScenes.SceneB, to = TestScenes.SceneC) {
+        spec = tween(durationMillis = TestTransitionDuration.toInt(), easing = FastOutSlowInEasing)
+    }
+
+    from(TestScenes.SceneC, to = TestScenes.SceneA) { spec = snap() }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
new file mode 100644
index 0000000..8ef6757
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.test.assertSizeIsEqualTo
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AnchoredSizeTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testAnchoredSizeEnter() {
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo)) },
+            toSceneContent = {
+                Box(Modifier.size(50.dp, 50.dp).element(TestElements.Foo))
+                Box(Modifier.size(200.dp, 60.dp).element(TestElements.Bar))
+            },
+            transition = {
+                // Scale during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                anchoredSize(TestElements.Bar, TestElements.Foo)
+            },
+        ) {
+            // Bar is entering. It starts at the same size as Foo in scene A in and scales to its
+            // final size in scene B.
+            before { onElement(TestElements.Bar).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp) }
+            at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(125.dp, 90.dp) }
+            at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(150.dp, 80.dp) }
+            at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(175.dp, 70.dp) }
+            at(64) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) }
+            after { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) }
+        }
+    }
+
+    @Test
+    fun testAnchoredSizeExit() {
+        rule.testTransition(
+            fromSceneContent = {
+                Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo))
+                Box(Modifier.size(100.dp, 100.dp).element(TestElements.Bar))
+            },
+            toSceneContent = { Box(Modifier.size(200.dp, 60.dp).element(TestElements.Foo)) },
+            transition = {
+                // Scale during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                anchoredSize(TestElements.Bar, TestElements.Foo)
+            },
+        ) {
+            // Bar is leaving. It starts at 100dp x 100dp in scene A and is scaled to 200dp x 60dp,
+            // the size of Foo in scene B.
+            before { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp) }
+            at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp) }
+            at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(125.dp, 90.dp) }
+            at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(150.dp, 80.dp) }
+            at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(175.dp, 70.dp) }
+            after { onElement(TestElements.Bar).assertDoesNotExist() }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
new file mode 100644
index 0000000..d1205e7
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
@@ -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.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.offset
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.testTransition
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AnchoredTranslateTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testAnchoredTranslateExit() {
+        rule.testTransition(
+            fromSceneContent = {
+                Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo))
+                Box(Modifier.offset(20.dp, 40.dp).element(TestElements.Bar))
+            },
+            toSceneContent = { Box(Modifier.offset(30.dp, 10.dp).element(TestElements.Foo)) },
+            transition = {
+                // Anchor Bar to Foo, which is moving from (10dp, 50dp) to (30dp, 10dp).
+                spec = tween(16 * 4, easing = LinearEasing)
+                anchoredTranslate(TestElements.Bar, TestElements.Foo)
+            },
+        ) {
+            // Bar moves by (20dp, -40dp), like Foo.
+            before { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(20.dp, 40.dp) }
+            at(0) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(20.dp, 40.dp) }
+            at(16) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(25.dp, 30.dp) }
+            at(32) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(30.dp, 20.dp) }
+            at(48) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(35.dp, 10.dp) }
+            after { onElement(TestElements.Bar).assertDoesNotExist() }
+        }
+    }
+
+    @Test
+    fun testAnchoredTranslateEnter() {
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo)) },
+            toSceneContent = {
+                Box(Modifier.offset(30.dp, 10.dp).element(TestElements.Foo))
+                Box(Modifier.offset(20.dp, 40.dp).element(TestElements.Bar))
+            },
+            transition = {
+                // Anchor Bar to Foo, which is moving from (10dp, 50dp) to (30dp, 10dp).
+                spec = tween(16 * 4, easing = LinearEasing)
+                anchoredTranslate(TestElements.Bar, TestElements.Foo)
+            },
+        ) {
+            // Bar moves by (20dp, -40dp), like Foo.
+            before { onElement(TestElements.Bar).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(0.dp, 80.dp) }
+            at(16) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(5.dp, 70.dp) }
+            at(32) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(10.dp, 60.dp) }
+            at(48) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(15.dp, 50.dp) }
+            at(64) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(20.dp, 40.dp) }
+            after { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(20.dp, 40.dp) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
new file mode 100644
index 0000000..2a27763
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.TransitionTestBuilder
+import com.android.compose.animation.scene.testTransition
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class EdgeTranslateTest {
+
+    @get:Rule val rule = createComposeRule()
+
+    private fun testEdgeTranslate(
+        edge: Edge,
+        startsOutsideLayoutBounds: Boolean,
+        builder: TransitionTestBuilder.() -> Unit,
+    ) {
+        rule.testTransition(
+            // The layout under test is 300dp x 300dp.
+            layoutModifier = Modifier.size(300.dp),
+            fromSceneContent = {},
+            toSceneContent = {
+                // Foo is 100dp x 100dp in the center of the layout, so at offset = (100dp, 100dp)
+                Box(Modifier.fillMaxSize()) {
+                    Box(Modifier.size(100.dp).element(TestElements.Foo).align(Alignment.Center))
+                }
+            },
+            transition = {
+                spec = tween(16 * 4, easing = LinearEasing)
+                translate(TestElements.Foo, edge, startsOutsideLayoutBounds)
+            },
+            builder = builder,
+        )
+    }
+
+    @Test
+    fun testEntersFromTop_startsOutsideLayoutBounds() {
+        testEdgeTranslate(Edge.Top, startsOutsideLayoutBounds = true) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, (-100).dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 0.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromTop_startsInsideLayoutBounds() {
+        testEdgeTranslate(Edge.Top, startsOutsideLayoutBounds = false) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 0.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 50.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromBottom_startsOutsideLayoutBounds() {
+        testEdgeTranslate(Edge.Bottom, startsOutsideLayoutBounds = true) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 300.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 200.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromBottom_startsInsideLayoutBounds() {
+        testEdgeTranslate(Edge.Bottom, startsOutsideLayoutBounds = false) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 200.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 150.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromLeft_startsOutsideLayoutBounds() {
+        testEdgeTranslate(Edge.Left, startsOutsideLayoutBounds = true) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo((-100).dp, 100.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(0.dp, 100.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromLeft_startsInsideLayoutBounds() {
+        testEdgeTranslate(Edge.Left, startsOutsideLayoutBounds = false) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(0.dp, 100.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 100.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromRight_startsOutsideLayoutBounds() {
+        testEdgeTranslate(Edge.Right, startsOutsideLayoutBounds = true) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(300.dp, 100.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(200.dp, 100.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromRight_startsInsideLayoutBounds() {
+        testEdgeTranslate(Edge.Right, startsOutsideLayoutBounds = false) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(200.dp, 100.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(150.dp, 100.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
new file mode 100644
index 0000000..384355c
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.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.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.test.assertSizeIsEqualTo
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ScaleSizeTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testScaleSize() {
+        rule.testTransition(
+            fromSceneContent = {},
+            toSceneContent = { Box(Modifier.size(100.dp).element(TestElements.Foo)) },
+            transition = {
+                // Scale during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                scaleSize(TestElements.Foo, width = 2f, height = 0.5f)
+            },
+        ) {
+            // Foo is entering, is 100dp x 100dp at rest and is scaled by (2, 0.5) during the
+            // transition so it starts at 200dp x 50dp.
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertSizeIsEqualTo(200.dp, 50.dp) }
+            at(16) { onElement(TestElements.Foo).assertSizeIsEqualTo(175.dp, 62.5.dp) }
+            at(32) { onElement(TestElements.Foo).assertSizeIsEqualTo(150.dp, 75.dp) }
+            at(48) { onElement(TestElements.Foo).assertSizeIsEqualTo(125.dp, 87.5.dp) }
+            at(64) { onElement(TestElements.Foo).assertSizeIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertSizeIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
new file mode 100644
index 0000000..1d559fd
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.offset
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.testTransition
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TranslateTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testTranslateExit() {
+        rule.testTransition(
+            fromSceneContent = {
+                // Foo is at (10dp, 50dp) and is exiting.
+                Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo))
+            },
+            toSceneContent = {},
+            transition = {
+                // Foo is translated by (20dp, -40dp) during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                translate(TestElements.Foo, x = 20.dp, y = (-40).dp)
+            },
+        ) {
+            before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
+            at(16) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(15.dp, 40.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(20.dp, 30.dp) }
+            at(48) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(25.dp, 20.dp) }
+            after { onElement(TestElements.Foo).assertDoesNotExist() }
+        }
+    }
+
+    @Test
+    fun testTranslateEnter() {
+        rule.testTransition(
+            fromSceneContent = {},
+            toSceneContent = {
+                // Foo is entering to (10dp, 50dp)
+                Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo))
+            },
+            transition = {
+                // Foo is translated from (10dp, 50) + (20dp, -40dp) during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                translate(TestElements.Foo, x = 20.dp, y = (-40).dp)
+            },
+        ) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(30.dp, 10.dp) }
+            at(16) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(25.dp, 20.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(20.dp, 30.dp) }
+            at(48) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(15.dp, 40.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt
new file mode 100644
index 0000000..fbd1b51
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.compose.test
+
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.unit.Dp
+
+fun SemanticsNodeInteraction.assertSizeIsEqualTo(expectedWidth: Dp, expectedHeight: Dp) {
+    assertWidthIsEqualTo(expectedWidth)
+    assertHeightIsEqualTo(expectedHeight)
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
new file mode 100644
index 0000000..bf7bf98
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.test.subjects
+
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth.assertAbout
+
+/** Assert on a [DpOffset]. */
+fun assertThat(dpOffset: DpOffset): DpOffsetSubject {
+    return assertAbout(DpOffsetSubject.dpOffsets()).that(dpOffset)
+}
+
+/** A Truth subject to assert on [DpOffset] with some tolerance. Inspired by FloatSubject. */
+class DpOffsetSubject(
+    metadata: FailureMetadata,
+    private val actual: DpOffset,
+) : Subject(metadata, actual) {
+    fun isWithin(tolerance: Dp): TolerantDpOffsetComparison {
+        return object : TolerantDpOffsetComparison {
+            override fun of(expected: DpOffset) {
+                actual.x.assertIsEqualTo(expected.x, "offset.x", tolerance)
+                actual.y.assertIsEqualTo(expected.y, "offset.y", tolerance)
+            }
+        }
+    }
+
+    interface TolerantDpOffsetComparison {
+        fun of(expected: DpOffset)
+    }
+
+    companion object {
+        val DefaultTolerance = Dp(.5f)
+
+        fun dpOffsets() =
+            Factory<DpOffsetSubject, DpOffset> { metadata, actual ->
+                DpOffsetSubject(metadata, actual)
+            }
+    }
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 82fe3f2..609ea90 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -21,13 +21,11 @@
 import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.util.time.SystemClock
 
 /** The Compose facade, when Compose is *not* available. */
 object ComposeFacade : BaseComposeFacade {
@@ -53,14 +51,6 @@
         throwComposeUnavailableError()
     }
 
-    override fun createMultiShadeView(
-        context: Context,
-        viewModel: MultiShadeViewModel,
-        clock: SystemClock,
-    ): View {
-        throwComposeUnavailableError()
-    }
-
     override fun createSceneContainerView(
         context: Context,
         viewModel: SceneContainerViewModel,
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 7926f92..0ee88b9 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -23,8 +23,6 @@
 import androidx.compose.ui.platform.ComposeView
 import androidx.lifecycle.LifecycleOwner
 import com.android.compose.theme.PlatformTheme
-import com.android.systemui.multishade.ui.composable.MultiShade
-import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.compose.PeopleScreen
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -34,7 +32,6 @@
 import com.android.systemui.scene.ui.composable.ComposableScene
 import com.android.systemui.scene.ui.composable.SceneContainer
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.util.time.SystemClock
 
 /** The Compose facade, when Compose is available. */
 object ComposeFacade : BaseComposeFacade {
@@ -60,23 +57,6 @@
         }
     }
 
-    override fun createMultiShadeView(
-        context: Context,
-        viewModel: MultiShadeViewModel,
-        clock: SystemClock,
-    ): View {
-        return ComposeView(context).apply {
-            setContent {
-                PlatformTheme {
-                    MultiShade(
-                        viewModel = viewModel,
-                        clock = clock,
-                    )
-                }
-            }
-        }
-    }
-
     override fun createSceneContainerView(
         context: Context,
         viewModel: SceneContainerViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index b3d2e35..64227b8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -43,10 +43,10 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Easings
+import com.android.compose.modifiers.thenIf
 import com.android.internal.R
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
-import com.android.systemui.compose.modifiers.thenIf
 import kotlin.math.min
 import kotlin.math.pow
 import kotlin.math.sqrt
@@ -228,43 +228,45 @@
                 }
             }
     ) {
-        // Draw lines between dots.
-        selectedDots.forEachIndexed { index, dot ->
-            if (index > 0) {
-                val previousDot = selectedDots[index - 1]
-                val lineFadeOutAnimationProgress = lineFadeOutAnimatables[previousDot]!!.value
-                val startLerp = 1 - lineFadeOutAnimationProgress
-                val from = pixelOffset(previousDot, spacing, verticalOffset)
-                val to = pixelOffset(dot, spacing, verticalOffset)
-                val lerpedFrom =
-                    Offset(
-                        x = from.x + (to.x - from.x) * startLerp,
-                        y = from.y + (to.y - from.y) * startLerp,
+        if (isAnimationEnabled) {
+            // Draw lines between dots.
+            selectedDots.forEachIndexed { index, dot ->
+                if (index > 0) {
+                    val previousDot = selectedDots[index - 1]
+                    val lineFadeOutAnimationProgress = lineFadeOutAnimatables[previousDot]!!.value
+                    val startLerp = 1 - lineFadeOutAnimationProgress
+                    val from = pixelOffset(previousDot, spacing, verticalOffset)
+                    val to = pixelOffset(dot, spacing, verticalOffset)
+                    val lerpedFrom =
+                        Offset(
+                            x = from.x + (to.x - from.x) * startLerp,
+                            y = from.y + (to.y - from.y) * startLerp,
+                        )
+                    drawLine(
+                        start = lerpedFrom,
+                        end = to,
+                        cap = StrokeCap.Round,
+                        alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
+                        color = lineColor,
+                        strokeWidth = lineStrokeWidth,
                     )
-                drawLine(
-                    start = lerpedFrom,
-                    end = to,
-                    cap = StrokeCap.Round,
-                    alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
-                    color = lineColor,
-                    strokeWidth = lineStrokeWidth,
-                )
+                }
             }
-        }
 
-        // Draw the line between the most recently-selected dot and the input pointer position.
-        inputPosition?.let { lineEnd ->
-            currentDot?.let { dot ->
-                val from = pixelOffset(dot, spacing, verticalOffset)
-                val lineLength = sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
-                drawLine(
-                    start = from,
-                    end = lineEnd,
-                    cap = StrokeCap.Round,
-                    alpha = lineAlpha(spacing, lineLength),
-                    color = lineColor,
-                    strokeWidth = lineStrokeWidth,
-                )
+            // Draw the line between the most recently-selected dot and the input pointer position.
+            inputPosition?.let { lineEnd ->
+                currentDot?.let { dot ->
+                    val from = pixelOffset(dot, spacing, verticalOffset)
+                    val lineLength = sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
+                    drawLine(
+                        start = from,
+                        end = lineEnd,
+                        cap = StrokeCap.Round,
+                        alpha = lineAlpha(spacing, lineLength),
+                        color = lineColor,
+                        strokeWidth = lineStrokeWidth,
+                    )
+                }
             }
         }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 85178bc..ec6e5ed 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -14,76 +14,51 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalAnimationApi::class, ExperimentalAnimationGraphicsApi::class)
-
 package com.android.systemui.bouncer.ui.composable
 
 import android.view.HapticFeedbackConstants
-import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.MutableTransitionState
-import androidx.compose.animation.core.Transition
-import androidx.compose.animation.core.animateDp
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.snap
 import androidx.compose.animation.core.tween
-import androidx.compose.animation.core.updateTransition
-import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
-import androidx.compose.animation.graphics.res.animatedVectorResource
-import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
-import androidx.compose.animation.graphics.vector.AnimatedImageVector
-import androidx.compose.foundation.Image
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.key
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Easings
 import com.android.compose.grid.VerticalGrid
-import com.android.internal.R.id.image
+import com.android.compose.modifiers.thenIf
 import com.android.systemui.R
 import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
-import com.android.systemui.bouncer.ui.viewmodel.EnteredKey
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.compose.modifiers.thenIf
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.DurationUnit
 import kotlinx.coroutines.async
@@ -110,147 +85,6 @@
 }
 
 @Composable
-private fun PinInputDisplay(viewModel: PinBouncerViewModel) {
-    val currentPinEntries: List<EnteredKey> by viewModel.pinEntries.collectAsState()
-
-    // visiblePinEntries keeps pins removed from currentPinEntries in the composition until their
-    // disappear-animation completed. The list is sorted by the natural ordering of EnteredKey,
-    // which is guaranteed to produce the original edit order, since the model only modifies entries
-    // at the end.
-    val visiblePinEntries = remember { SnapshotStateList<EnteredKey>() }
-    currentPinEntries.forEach {
-        val index = visiblePinEntries.binarySearch(it)
-        if (index < 0) {
-            val insertionPoint = -(index + 1)
-            visiblePinEntries.add(insertionPoint, it)
-        }
-    }
-
-    Row(
-        modifier =
-            Modifier.heightIn(min = entryShapeSize)
-                // Pins overflowing horizontally should still be shown as scrolling.
-                .wrapContentSize(unbounded = true),
-    ) {
-        visiblePinEntries.forEachIndexed { index, entry ->
-            key(entry) {
-                val visibility = remember {
-                    MutableTransitionState<EntryVisibility>(EntryVisibility.Hidden)
-                }
-                visibility.targetState =
-                    when {
-                        currentPinEntries.isEmpty() && visiblePinEntries.size > 1 ->
-                            EntryVisibility.BulkHidden(index, visiblePinEntries.size)
-                        currentPinEntries.contains(entry) -> EntryVisibility.Shown
-                        else -> EntryVisibility.Hidden
-                    }
-
-                val shape = viewModel.pinShapes.getShape(entry.sequenceNumber)
-                PinInputEntry(shape, updateTransition(visibility, label = "Pin Entry $entry"))
-
-                LaunchedEffect(entry) {
-                    // Remove entry from visiblePinEntries once the hide transition completed.
-                    snapshotFlow {
-                            visibility.currentState == visibility.targetState &&
-                                visibility.targetState != EntryVisibility.Shown
-                        }
-                        .collect { isRemoved ->
-                            if (isRemoved) {
-                                visiblePinEntries.remove(entry)
-                            }
-                        }
-                }
-            }
-        }
-    }
-}
-
-private sealed class EntryVisibility {
-    object Shown : EntryVisibility()
-
-    object Hidden : EntryVisibility()
-
-    /**
-     * Same as [Hidden], but applies when multiple entries are hidden simultaneously, without
-     * collapsing during the hide.
-     */
-    data class BulkHidden(val staggerIndex: Int, val totalEntryCount: Int) : EntryVisibility()
-}
-
-@Composable
-private fun PinInputEntry(shapeResourceId: Int, transition: Transition<EntryVisibility>) {
-    // spec: http://shortn/_DEhE3Xl2bi
-    val dismissStaggerDelayMs = 33
-    val dismissDurationMs = 450
-    val expansionDurationMs = 250
-    val shapeCollapseDurationMs = 200
-
-    val animatedEntryWidth by
-        transition.animateDp(
-            transitionSpec = {
-                when (val target = targetState) {
-                    is EntryVisibility.BulkHidden ->
-                        // only collapse horizontal space once all entries are removed
-                        snap(dismissDurationMs + dismissStaggerDelayMs * target.totalEntryCount)
-                    else -> tween(expansionDurationMs, easing = Easings.Standard)
-                }
-            },
-            label = "entry space"
-        ) { state ->
-            if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
-        }
-
-    val animatedShapeSize by
-        transition.animateDp(
-            transitionSpec = {
-                when {
-                    EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown -> {
-                        // The AVD contains the entry transition.
-                        snap()
-                    }
-                    targetState is EntryVisibility.BulkHidden -> {
-                        val target = targetState as EntryVisibility.BulkHidden
-                        tween(
-                            dismissDurationMs,
-                            delayMillis = target.staggerIndex * dismissStaggerDelayMs,
-                            easing = Easings.Legacy,
-                        )
-                    }
-                    else -> tween(shapeCollapseDurationMs, easing = Easings.StandardDecelerate)
-                }
-            },
-            label = "shape size"
-        ) { state ->
-            if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
-        }
-
-    val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
-    Layout(
-        content = {
-            val image = AnimatedImageVector.animatedVectorResource(shapeResourceId)
-            var atEnd by remember { mutableStateOf(false) }
-            Image(
-                painter = rememberAnimatedVectorPainter(image, atEnd),
-                contentDescription = null,
-                contentScale = ContentScale.Crop,
-                colorFilter = ColorFilter.tint(dotColor),
-            )
-            LaunchedEffect(Unit) { atEnd = true }
-        }
-    ) { measurables, _ ->
-        val shapeSizePx = animatedShapeSize.roundToPx()
-        val placeable = measurables.single().measure(Constraints.fixed(shapeSizePx, shapeSizePx))
-
-        layout(animatedEntryWidth.roundToPx(), entryShapeSize.roundToPx()) {
-            placeable.place(
-                ((animatedEntryWidth - animatedShapeSize) / 2f).roundToPx(),
-                ((entryShapeSize - animatedShapeSize) / 2f).roundToPx()
-            )
-        }
-    }
-}
-
-@Composable
 private fun PinPad(viewModel: PinBouncerViewModel) {
     val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
     val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState()
@@ -512,8 +346,6 @@
     }
 }
 
-private val entryShapeSize = 30.dp
-
 private val pinButtonSize = 84.dp
 private val pinButtonErrorShrinkFactor = 67.dp / pinButtonSize
 private const val pinButtonErrorShrinkMs = 50
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
new file mode 100644
index 0000000..77065cf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
@@ -0,0 +1,437 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalAnimationGraphicsApi::class)
+
+package com.android.systemui.bouncer.ui.composable
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.runtime.toMutableStateList
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.Easings
+import com.android.keyguard.PinShapeAdapter
+import com.android.systemui.R
+import com.android.systemui.bouncer.ui.viewmodel.EntryToken.Digit
+import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.PinInputViewModel
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.joinAll
+import kotlinx.coroutines.launch
+
+@Composable
+fun PinInputDisplay(viewModel: PinBouncerViewModel) {
+    val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsState()
+    val shapeAnimations = rememberShapeAnimations(viewModel.pinShapes)
+
+    // The display comes in two different flavors:
+    // 1) hinting: shows a circle (◦) per expected pin input, and dot (●) per entered digit.
+    //    This has a fixed width, and uses two distinct types of AVDs to animate the addition and
+    //    removal of digits.
+    // 2) regular, shows a dot (●) per entered digit.
+    //    This grows/shrinks as digits are added deleted. Uses the same type of AVDs to animate the
+    //    addition of digits, but simply center-shrinks the dot (●) shape to zero to animate the
+    //    removal.
+    // Because of all these differences, there are two separate implementations, rather than
+    // unifying into a single, more complex implementation.
+
+    when (val length = hintedPinLength) {
+        null -> RegularPinInputDisplay(viewModel, shapeAnimations)
+        else -> HintingPinInputDisplay(viewModel, shapeAnimations, length)
+    }
+}
+
+/**
+ * A pin input display that shows a placeholder circle (◦) for every digit in the pin not yet
+ * entered.
+ *
+ * Used for auto-confirmed pins of a specific length, see design: http://shortn/_jS8kPzQ7QV
+ */
+@Composable
+private fun HintingPinInputDisplay(
+    viewModel: PinBouncerViewModel,
+    shapeAnimations: ShapeAnimations,
+    hintedPinLength: Int,
+) {
+    val pinInput: PinInputViewModel by viewModel.pinInput.collectAsState()
+    // [ClearAll] marker pointing at the beginning of the current pin input.
+    // When a new [ClearAll] token is added to the [pinInput], the clear-all animation is played
+    // and the marker is advanced manually to the most recent marker. See LaunchedEffect below.
+    var currentClearAll by remember { mutableStateOf(pinInput.mostRecentClearAll()) }
+    // The length of the pin currently entered by the user.
+    val currentPinLength = pinInput.getDigits(currentClearAll).size
+
+    // The animated vector drawables for each of the [hintedPinLength] slots.
+    // The first [currentPinLength] drawables end in a dot (●) shape, the remaining drawables up to
+    // [hintedPinLength] end in the circle (◦) shape.
+    // This list is re-generated upon each pin entry, it is modelled as a  [MutableStateList] to
+    // allow the clear-all animation to replace the shapes asynchronously, see LaunchedEffect below.
+    // Note that when a [ClearAll] token is added to the input (and the clear-all animation plays)
+    // the [currentPinLength] does not change; the [pinEntryDrawable] is remembered until the
+    // clear-all animation finishes and the [currentClearAll] state is manually advanced.
+    val pinEntryDrawable =
+        remember(currentPinLength) {
+            buildList {
+                    repeat(currentPinLength) { add(shapeAnimations.getShapeToDot(it)) }
+                    repeat(hintedPinLength - currentPinLength) { add(shapeAnimations.dotToCircle) }
+                }
+                .toMutableStateList()
+        }
+
+    val mostRecentClearAll = pinInput.mostRecentClearAll()
+    // Whenever a new [ClearAll] marker is added to the input, the clear-all animation needs to
+    // be played.
+    LaunchedEffect(mostRecentClearAll) {
+        if (currentClearAll == mostRecentClearAll) {
+            // Except during the initial composition.
+            return@LaunchedEffect
+        }
+
+        // Staggered replace of all dot (●) shapes with an animation from dot (●) to circle (◦).
+        for (index in 0 until hintedPinLength) {
+            if (!shapeAnimations.isDotShape(pinEntryDrawable[index])) break
+
+            pinEntryDrawable[index] = shapeAnimations.dotToCircle
+            delay(shapeAnimations.dismissStaggerDelay)
+        }
+
+        // Once the animation is done, start processing the next pin input again.
+        currentClearAll = mostRecentClearAll
+    }
+
+    // During the initial composition, do not play the [pinEntryDrawable] animations. This prevents
+    // the dot (●) to circle (◦) animation when the empty display becomes first visible, and a
+    // superfluous shape to dot (●)  animation after for example device rotation.
+    var playAnimation by remember { mutableStateOf(false) }
+    LaunchedEffect(Unit) { playAnimation = true }
+
+    val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
+    Row(modifier = Modifier.heightIn(min = shapeAnimations.shapeSize)) {
+        pinEntryDrawable.forEachIndexed { index, drawable ->
+            // Key the loop by [index] and [drawable], so that updating a shape drawable at the same
+            // index will play the new animation (by remembering a new [atEnd]).
+            key(index, drawable) {
+                // [rememberAnimatedVectorPainter] requires a `atEnd` boolean to switch from `false`
+                // to `true` for the animation to play. This animation is suppressed when
+                // playAnimation is false, always rendering the end-state of the animation.
+                var atEnd by remember { mutableStateOf(!playAnimation) }
+                LaunchedEffect(Unit) { atEnd = true }
+
+                Image(
+                    painter = rememberAnimatedVectorPainter(drawable, atEnd),
+                    contentDescription = null,
+                    contentScale = ContentScale.Crop,
+                    colorFilter = ColorFilter.tint(dotColor),
+                )
+            }
+        }
+    }
+}
+
+/**
+ * A pin input that shows a dot (●) for each entered pin, horizontally centered and growing /
+ * shrinking as more digits are entered and deleted.
+ *
+ * Used for pin input when the pin length is not hinted, see design http://shortn/_wNP7SrBD78
+ */
+@Composable
+private fun RegularPinInputDisplay(
+    viewModel: PinBouncerViewModel,
+    shapeAnimations: ShapeAnimations,
+) {
+    // Holds all currently [VisiblePinEntry] composables. This cannot be simply derived from
+    // `viewModel.pinInput` at composition, since deleting a pin entry needs to play a remove
+    // animation, thus the composable to be removed has to remain in the composition until fully
+    // disappeared (see `prune` launched effect below)
+    val pinInputRow = remember(shapeAnimations) { PinInputRow(shapeAnimations) }
+
+    // Processed `viewModel.pinInput` updates and applies them to [pinDigitShapes]
+    LaunchedEffect(viewModel.pinInput, pinInputRow) {
+        // Initial setup: capture the most recent [ClearAll] marker and create the visuals for the
+        // existing digits (if any) without animation..
+        var currentClearAll =
+            with(viewModel.pinInput.value) {
+                val initialClearAll = mostRecentClearAll()
+                pinInputRow.setDigits(getDigits(initialClearAll))
+                initialClearAll
+            }
+
+        viewModel.pinInput.collect { input ->
+            // Process additions and removals of pins within the current input block.
+            pinInputRow.updateDigits(input.getDigits(currentClearAll), scope = this@LaunchedEffect)
+
+            val mostRecentClearAll = input.mostRecentClearAll()
+            if (currentClearAll != mostRecentClearAll) {
+                // A new [ClearAll] token is added to the [input], play the clear-all animation
+                pinInputRow.playClearAllAnimation()
+
+                // Animation finished, advance manually to the next marker.
+                currentClearAll = mostRecentClearAll
+            }
+        }
+    }
+
+    LaunchedEffect(pinInputRow) {
+        // Prunes unused VisiblePinEntries once they are no longer visible.
+        snapshotFlow { pinInputRow.hasUnusedEntries() }
+            .collect { hasUnusedEntries ->
+                if (hasUnusedEntries) {
+                    pinInputRow.prune()
+                }
+            }
+    }
+
+    pinInputRow.Content()
+}
+
+private class PinInputRow(
+    val shapeAnimations: ShapeAnimations,
+) {
+    private val entries = mutableStateListOf<PinInputEntry>()
+
+    @Composable
+    fun Content() {
+        Row(
+            modifier =
+                Modifier.heightIn(min = shapeAnimations.shapeSize)
+                    // Pins overflowing horizontally should still be shown as scrolling.
+                    .wrapContentSize(unbounded = true),
+        ) {
+            entries.forEach { entry -> key(entry.digit) { entry.Content() } }
+        }
+    }
+
+    /**
+     * Replaces all current [PinInputEntry] composables with new instances for each digit.
+     *
+     * Does not play the entry expansion animation.
+     */
+    fun setDigits(digits: List<Digit>) {
+        entries.clear()
+        entries.addAll(digits.map { PinInputEntry(it, shapeAnimations) })
+    }
+
+    /**
+     * Adds [PinInputEntry] composables for new digits and plays an entry animation, and starts the
+     * exit animation for digits not in [updated] anymore.
+     *
+     * The function return immediately, playing the animations in the background.
+     *
+     * Removed entries have to be [prune]d once the exit animation completes, [hasUnusedEntries] can
+     * be used in a [SnapshotFlow] to discover when its time to do so.
+     */
+    fun updateDigits(updated: List<Digit>, scope: CoroutineScope) {
+        val incoming = updated.minus(entries.map { it.digit }.toSet()).toList()
+        val outgoing = entries.filterNot { entry -> updated.any { entry.digit == it } }.toList()
+
+        entries.addAll(
+            incoming.map {
+                PinInputEntry(it, shapeAnimations).apply { scope.launch { animateAppearance() } }
+            }
+        )
+
+        outgoing.forEach { entry -> scope.launch { entry.animateRemoval() } }
+
+        entries.sortWith(compareBy { it.digit })
+    }
+
+    /**
+     * Plays a staggered remove animation, and upon completion removes the [PinInputEntry]
+     * composables.
+     *
+     * This function returns once the animation finished playing and the entries are removed.
+     */
+    suspend fun playClearAllAnimation() = coroutineScope {
+        val entriesToRemove = entries.toList()
+        entriesToRemove
+            .mapIndexed { index, entry ->
+                launch {
+                    delay(shapeAnimations.dismissStaggerDelay * index)
+                    entry.animateClearAllCollapse()
+                }
+            }
+            .joinAll()
+
+        // Remove all [PinInputEntry] composables for which the staggered remove animation was
+        // played. Note that up to now, each PinInputEntry still occupied the full width.
+        entries.removeAll(entriesToRemove)
+    }
+
+    /**
+     * Whether there are [PinInputEntry] that can be removed from the composition since they were
+     * fully animated out.
+     */
+    fun hasUnusedEntries(): Boolean {
+        return entries.any { it.isUnused }
+    }
+
+    /** Remove all no longer visible [PinInputEntry]s from the composition. */
+    fun prune() {
+        entries.removeAll { it.isUnused }
+    }
+}
+
+private class PinInputEntry(
+    val digit: Digit,
+    val shapeAnimations: ShapeAnimations,
+) {
+    private val shape = shapeAnimations.getShapeToDot(digit.sequenceNumber)
+    // horizontal space occupied, used to shift contents as individual digits are animated in/out
+    private val entryWidth =
+        Animatable(shapeAnimations.shapeSize, Dp.VectorConverter, label = "Width of pin ($digit)")
+    // intrinsic width and height of the shape, used to collapse the shape during exit animations.
+    private val shapeSize =
+        Animatable(shapeAnimations.shapeSize, Dp.VectorConverter, label = "Size of pin ($digit)")
+
+    /**
+     * Whether the is fully animated out. When `true`, removing this from the composable won't have
+     * visual effects.
+     */
+    val isUnused: Boolean
+        get() {
+            return entryWidth.targetValue == 0.dp && !entryWidth.isRunning
+        }
+
+    /** Animate the shape appearance by growing the entry width from 0.dp to the intrinsic width. */
+    suspend fun animateAppearance() = coroutineScope {
+        entryWidth.snapTo(0.dp)
+        entryWidth.animateTo(shapeAnimations.shapeSize, shapeAnimations.inputShiftAnimationSpec)
+    }
+
+    /**
+     * Animates shape disappearance by collapsing the shape and occupied horizontal space.
+     *
+     * Once complete, [isUnused] will return `true`.
+     */
+    suspend fun animateRemoval() = coroutineScope {
+        awaitAll(
+            async { entryWidth.animateTo(0.dp, shapeAnimations.inputShiftAnimationSpec) },
+            async { shapeSize.animateTo(0.dp, shapeAnimations.deleteShapeSizeAnimationSpec) }
+        )
+    }
+
+    /** Collapses the shape in place, while still holding on to the horizontal space. */
+    suspend fun animateClearAllCollapse() = coroutineScope {
+        shapeSize.animateTo(0.dp, shapeAnimations.clearAllShapeSizeAnimationSpec)
+    }
+
+    @Composable
+    fun Content() {
+        val animatedShapeSize by shapeSize.asState()
+        val animatedEntryWidth by entryWidth.asState()
+
+        val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
+        val shapeHeight = shapeAnimations.shapeSize
+        var atEnd by remember { mutableStateOf(false) }
+        LaunchedEffect(Unit) { atEnd = true }
+        Image(
+            painter = rememberAnimatedVectorPainter(shape, atEnd),
+            contentDescription = null,
+            contentScale = ContentScale.Crop,
+            colorFilter = ColorFilter.tint(dotColor),
+            modifier =
+                Modifier.layout { measurable, _ ->
+                    val shapeSizePx = animatedShapeSize.roundToPx()
+                    val placeable = measurable.measure(Constraints.fixed(shapeSizePx, shapeSizePx))
+
+                    layout(animatedEntryWidth.roundToPx(), shapeHeight.roundToPx()) {
+                        placeable.place(
+                            ((animatedEntryWidth - animatedShapeSize) / 2f).roundToPx(),
+                            ((shapeHeight - animatedShapeSize) / 2f).roundToPx()
+                        )
+                    }
+                },
+        )
+    }
+}
+
+/** Animated Vector Drawables used to render the pin input. */
+private class ShapeAnimations(
+    /** Width and height for all the animation images listed here. */
+    val shapeSize: Dp,
+    /** Transitions from the dot (●) to the circle (◦). Used for the hinting pin input only. */
+    val dotToCircle: AnimatedImageVector,
+    /** Each of the animations transition from nothing via a shape to the dot (●). */
+    private val shapesToDot: List<AnimatedImageVector>,
+) {
+    /**
+     * Returns a transition from nothing via shape to the dot (●)., specific to the input position.
+     */
+    fun getShapeToDot(position: Int): AnimatedImageVector {
+        return shapesToDot[position.mod(shapesToDot.size)]
+    }
+
+    /**
+     * Whether the [shapeAnimation] is a image returned by [getShapeToDot], and thus is ending in
+     * the dot (●) shape.
+     *
+     * `false` if the shape's end state is the circle (◦).
+     */
+    fun isDotShape(shapeAnimation: AnimatedImageVector): Boolean {
+        return shapeAnimation != dotToCircle
+    }
+
+    // spec: http://shortn/_DEhE3Xl2bi
+    val dismissStaggerDelay = 33.milliseconds
+    val inputShiftAnimationSpec = tween<Dp>(durationMillis = 250, easing = Easings.Standard)
+    val deleteShapeSizeAnimationSpec =
+        tween<Dp>(durationMillis = 200, easing = Easings.StandardDecelerate)
+    val clearAllShapeSizeAnimationSpec = tween<Dp>(durationMillis = 450, easing = Easings.Legacy)
+}
+
+@Composable
+private fun rememberShapeAnimations(pinShapes: PinShapeAdapter): ShapeAnimations {
+    // NOTE: `animatedVectorResource` does remember the returned AnimatedImageVector.
+    val dotToCircle = AnimatedImageVector.animatedVectorResource(R.drawable.pin_dot_delete_avd)
+    val shapesToDot = pinShapes.shapes.map { AnimatedImageVector.animatedVectorResource(it) }
+    val shapeSize = dimensionResource(R.dimen.password_shape_size)
+
+    return remember(dotToCircle, shapesToDot, shapeSize) {
+        ShapeAnimations(shapeSize, dotToCircle, shapesToDot)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
deleted file mode 100644
index 99fe26c..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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.multishade.ui.composable
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.gestures.detectVerticalDragGestures
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxWithConstraints
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.unit.IntSize
-import com.android.systemui.R
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
-import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.footer.ui.compose.QuickSettings
-import com.android.systemui.statusbar.ui.composable.StatusBar
-import com.android.systemui.util.time.SystemClock
-
-@Composable
-fun MultiShade(
-    viewModel: MultiShadeViewModel,
-    clock: SystemClock,
-    modifier: Modifier = Modifier,
-) {
-    val isScrimEnabled: Boolean by viewModel.isScrimEnabled.collectAsState()
-    val scrimAlpha: Float by viewModel.scrimAlpha.collectAsState()
-
-    // TODO(b/273298030): find a different way to get the height constraint from its parent.
-    BoxWithConstraints(modifier = modifier) {
-        val maxHeightPx = with(LocalDensity.current) { maxHeight.toPx() }
-
-        Scrim(
-            modifier = Modifier.fillMaxSize(),
-            remoteTouch = viewModel::onScrimTouched,
-            alpha = { scrimAlpha },
-            isScrimEnabled = isScrimEnabled,
-        )
-        Shade(
-            viewModel = viewModel.leftShade,
-            currentTimeMillis = clock::elapsedRealtime,
-            containerHeightPx = maxHeightPx,
-            modifier = Modifier.align(Alignment.TopStart),
-        ) {
-            Column {
-                StatusBar()
-                Notifications()
-            }
-        }
-        Shade(
-            viewModel = viewModel.rightShade,
-            currentTimeMillis = clock::elapsedRealtime,
-            containerHeightPx = maxHeightPx,
-            modifier = Modifier.align(Alignment.TopEnd),
-        ) {
-            Column {
-                StatusBar()
-                QuickSettings()
-            }
-        }
-        Shade(
-            viewModel = viewModel.singleShade,
-            currentTimeMillis = clock::elapsedRealtime,
-            containerHeightPx = maxHeightPx,
-            modifier = Modifier,
-        ) {
-            Column {
-                StatusBar()
-                Notifications()
-                QuickSettings()
-            }
-        }
-    }
-}
-
-@Composable
-private fun Scrim(
-    remoteTouch: (ProxiedInputModel) -> Unit,
-    alpha: () -> Float,
-    isScrimEnabled: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    var size by remember { mutableStateOf(IntSize.Zero) }
-
-    Box(
-        modifier =
-            modifier
-                .graphicsLayer { this.alpha = alpha() }
-                .background(colorResource(R.color.opaque_scrim))
-                .fillMaxSize()
-                .onSizeChanged { size = it }
-                .then(
-                    if (isScrimEnabled) {
-                        Modifier.pointerInput(Unit) {
-                                detectTapGestures(onTap = { remoteTouch(ProxiedInputModel.OnTap) })
-                            }
-                            .pointerInput(Unit) {
-                                detectVerticalDragGestures(
-                                    onVerticalDrag = { change, dragAmount ->
-                                        remoteTouch(
-                                            ProxiedInputModel.OnDrag(
-                                                xFraction = change.position.x / size.width,
-                                                yDragAmountPx = dragAmount,
-                                            )
-                                        )
-                                    },
-                                    onDragEnd = { remoteTouch(ProxiedInputModel.OnDragEnd) },
-                                    onDragCancel = { remoteTouch(ProxiedInputModel.OnDragCancel) }
-                                )
-                            }
-                    } else {
-                        Modifier
-                    }
-                )
-    )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
deleted file mode 100644
index cfcc2fb..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * 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.multishade.ui.composable
-
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.interaction.DragInteraction
-import androidx.compose.foundation.interaction.InteractionSource
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.pointer.util.VelocityTracker
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.padding
-import com.android.compose.swipeable.FixedThreshold
-import com.android.compose.swipeable.SwipeableState
-import com.android.compose.swipeable.ThresholdConfig
-import com.android.compose.swipeable.rememberSwipeableState
-import com.android.compose.swipeable.swipeable
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.ui.viewmodel.ShadeViewModel
-import kotlin.math.min
-import kotlin.math.roundToInt
-import kotlinx.coroutines.launch
-
-/**
- * Renders a shade (container and content).
- *
- * This should be allowed to grow to fill the width and height of its container.
- *
- * @param viewModel The view-model for this shade.
- * @param currentTimeMillis A provider for the current time, in milliseconds.
- * @param containerHeightPx The height of the container that this shade is being shown in, in
- *   pixels.
- * @param modifier The Modifier.
- * @param content The content of the shade.
- */
-@Composable
-fun Shade(
-    viewModel: ShadeViewModel,
-    currentTimeMillis: () -> Long,
-    containerHeightPx: Float,
-    modifier: Modifier = Modifier,
-    content: @Composable () -> Unit = {},
-) {
-    val isVisible: Boolean by viewModel.isVisible.collectAsState()
-    if (!isVisible) {
-        return
-    }
-
-    val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
-    ReportNonProxiedInput(viewModel, interactionSource)
-
-    val swipeableState = rememberSwipeableState(initialValue = ShadeState.FullyCollapsed)
-    HandleForcedCollapse(viewModel, swipeableState)
-    HandleProxiedInput(viewModel, swipeableState, currentTimeMillis)
-    ReportShadeExpansion(viewModel, swipeableState, containerHeightPx)
-
-    val isSwipingEnabled: Boolean by viewModel.isSwipingEnabled.collectAsState()
-    val collapseThreshold: Float by viewModel.swipeCollapseThreshold.collectAsState()
-    val expandThreshold: Float by viewModel.swipeExpandThreshold.collectAsState()
-
-    val width: ShadeViewModel.Size by viewModel.width.collectAsState()
-    val density = LocalDensity.current
-
-    val anchors: Map<Float, ShadeState> =
-        remember(containerHeightPx) { swipeableAnchors(containerHeightPx) }
-
-    ShadeContent(
-        shadeHeightPx = { swipeableState.offset.value },
-        overstretch = { swipeableState.overflow.value / containerHeightPx },
-        isSwipingEnabled = isSwipingEnabled,
-        swipeableState = swipeableState,
-        interactionSource = interactionSource,
-        anchors = anchors,
-        thresholds = { _, to ->
-            swipeableThresholds(
-                to = to,
-                swipeCollapseThreshold = collapseThreshold.fractionToDp(density, containerHeightPx),
-                swipeExpandThreshold = expandThreshold.fractionToDp(density, containerHeightPx),
-            )
-        },
-        modifier = modifier.shadeWidth(width, density),
-        content = content,
-    )
-}
-
-/**
- * Draws the content of the shade.
- *
- * @param shadeHeightPx Provider for the current expansion of the shade, in pixels, where `0` is
- *   fully collapsed.
- * @param overstretch Provider for the current amount of vertical "overstretch" that the shade
- *   should be rendered with. This is `0` or a positive number that is a percentage of the total
- *   height of the shade when fully expanded. A value of `0` means that the shade is not stretched
- *   at all.
- * @param isSwipingEnabled Whether swiping inside the shade is enabled or not.
- * @param swipeableState The state to use for the [swipeable] modifier, allowing external control in
- *   addition to direct control (proxied user input in addition to non-proxied/direct user input).
- * @param anchors A map of [ShadeState] keyed by the vertical position, in pixels, where that state
- *   occurs; this is used to configure the [swipeable] modifier.
- * @param thresholds Function that returns the [ThresholdConfig] for going from one [ShadeState] to
- *   another. This controls how the [swipeable] decides which [ShadeState] to animate to once the
- *   user lets go of the shade; e.g. does it animate to fully collapsed or fully expanded.
- * @param content The content to render inside the shade.
- * @param modifier The [Modifier].
- */
-@Composable
-private fun ShadeContent(
-    shadeHeightPx: () -> Float,
-    overstretch: () -> Float,
-    isSwipingEnabled: Boolean,
-    swipeableState: SwipeableState<ShadeState>,
-    interactionSource: MutableInteractionSource,
-    anchors: Map<Float, ShadeState>,
-    thresholds: (from: ShadeState, to: ShadeState) -> ThresholdConfig,
-    modifier: Modifier = Modifier,
-    content: @Composable () -> Unit = {},
-) {
-    /**
-     * Returns a function that takes in [Density] and returns the current padding around the shade
-     * content.
-     */
-    fun padding(
-        shadeHeightPx: () -> Float,
-    ): Density.() -> Int {
-        return {
-            min(
-                12.dp.toPx().roundToInt(),
-                shadeHeightPx().roundToInt(),
-            )
-        }
-    }
-
-    Surface(
-        shape = RoundedCornerShape(32.dp),
-        modifier =
-            modifier
-                .fillMaxWidth()
-                .height { shadeHeightPx().roundToInt() }
-                .padding(
-                    horizontal = padding(shadeHeightPx),
-                    vertical = padding(shadeHeightPx),
-                )
-                .graphicsLayer {
-                    // Applies the vertical over-stretching of the shade content that may happen if
-                    // the user keep dragging down when the shade is already fully-expanded.
-                    transformOrigin = transformOrigin.copy(pivotFractionY = 0f)
-                    this.scaleY = 1 + overstretch().coerceAtLeast(0f)
-                }
-                .swipeable(
-                    enabled = isSwipingEnabled,
-                    state = swipeableState,
-                    interactionSource = interactionSource,
-                    anchors = anchors,
-                    thresholds = thresholds,
-                    orientation = Orientation.Vertical,
-                ),
-        content = content,
-    )
-}
-
-/** Funnels current shade expansion values into the view-model. */
-@Composable
-private fun ReportShadeExpansion(
-    viewModel: ShadeViewModel,
-    swipeableState: SwipeableState<ShadeState>,
-    containerHeightPx: Float,
-) {
-    LaunchedEffect(swipeableState.offset, containerHeightPx) {
-        snapshotFlow { swipeableState.offset.value / containerHeightPx }
-            .collect { expansion -> viewModel.onExpansionChanged(expansion) }
-    }
-}
-
-/** Funnels drag gesture start and end events into the view-model. */
-@Composable
-private fun ReportNonProxiedInput(
-    viewModel: ShadeViewModel,
-    interactionSource: InteractionSource,
-) {
-    LaunchedEffect(interactionSource) {
-        interactionSource.interactions.collect {
-            when (it) {
-                is DragInteraction.Start -> {
-                    viewModel.onDragStarted()
-                }
-                is DragInteraction.Stop -> {
-                    viewModel.onDragEnded()
-                }
-            }
-        }
-    }
-}
-
-/** When told to force collapse, collapses the shade. */
-@Composable
-private fun HandleForcedCollapse(
-    viewModel: ShadeViewModel,
-    swipeableState: SwipeableState<ShadeState>,
-) {
-    LaunchedEffect(viewModel) {
-        viewModel.isForceCollapsed.collect {
-            launch { swipeableState.animateTo(ShadeState.FullyCollapsed) }
-        }
-    }
-}
-
-/**
- * Handles proxied input (input originating outside of the UI of the shade) by driving the
- * [SwipeableState] accordingly.
- */
-@Composable
-private fun HandleProxiedInput(
-    viewModel: ShadeViewModel,
-    swipeableState: SwipeableState<ShadeState>,
-    currentTimeMillis: () -> Long,
-) {
-    val velocityTracker: VelocityTracker = remember { VelocityTracker() }
-    LaunchedEffect(viewModel) {
-        viewModel.proxiedInput.collect {
-            when (it) {
-                is ProxiedInputModel.OnDrag -> {
-                    velocityTracker.addPosition(
-                        timeMillis = currentTimeMillis.invoke(),
-                        position = Offset(0f, it.yDragAmountPx),
-                    )
-                    swipeableState.performDrag(it.yDragAmountPx)
-                }
-                is ProxiedInputModel.OnDragEnd -> {
-                    launch {
-                        val velocity = velocityTracker.calculateVelocity().y
-                        velocityTracker.resetTracking()
-                        // We use a VelocityTracker to keep a record of how fast the pointer was
-                        // moving such that we know how far to fling the shade when the gesture
-                        // ends. Flinging the SwipeableState using performFling is required after
-                        // one or more calls to performDrag such that the swipeable settles into one
-                        // of the states. Without doing that, the shade would remain unmoving in an
-                        // in-between state on the screen.
-                        swipeableState.performFling(velocity)
-                    }
-                }
-                is ProxiedInputModel.OnDragCancel -> {
-                    launch {
-                        velocityTracker.resetTracking()
-                        swipeableState.animateTo(swipeableState.progress.from)
-                    }
-                }
-                else -> Unit
-            }
-        }
-    }
-}
-
-/**
- * Converts the [Float] (which is assumed to be a fraction between `0` and `1`) to a value in dp.
- *
- * @param density The [Density] of the display.
- * @param wholePx The whole amount that the given [Float] is a fraction of.
- * @return The dp size that's a fraction of the whole amount.
- */
-private fun Float.fractionToDp(density: Density, wholePx: Float): Dp {
-    return with(density) { (this@fractionToDp * wholePx).toDp() }
-}
-
-private fun Modifier.shadeWidth(
-    size: ShadeViewModel.Size,
-    density: Density,
-): Modifier {
-    return then(
-        when (size) {
-            is ShadeViewModel.Size.Fraction -> Modifier.fillMaxWidth(size.fraction)
-            is ShadeViewModel.Size.Pixels -> Modifier.width(with(density) { size.pixels.toDp() })
-        }
-    )
-}
-
-/** Returns the pixel positions for each of the supported shade states. */
-private fun swipeableAnchors(containerHeightPx: Float): Map<Float, ShadeState> {
-    return mapOf(
-        0f to ShadeState.FullyCollapsed,
-        containerHeightPx to ShadeState.FullyExpanded,
-    )
-}
-
-/**
- * Returns the [ThresholdConfig] for how far the shade should be expanded or collapsed such that it
- * actually completes the expansion or collapse after the user lifts their pointer.
- */
-private fun swipeableThresholds(
-    to: ShadeState,
-    swipeExpandThreshold: Dp,
-    swipeCollapseThreshold: Dp,
-): ThresholdConfig {
-    return FixedThreshold(
-        when (to) {
-            ShadeState.FullyExpanded -> swipeExpandThreshold
-            ShadeState.FullyCollapsed -> swipeCollapseThreshold
-        }
-    )
-}
-
-/** Enumerates the shade UI states for [SwipeableState]. */
-private enum class ShadeState {
-    FullyCollapsed,
-    FullyExpanded,
-}
diff --git a/packages/SystemUI/compose/features/tests/Android.bp b/packages/SystemUI/compose/features/tests/Android.bp
index ff534bd..c7c9140 100644
--- a/packages/SystemUI/compose/features/tests/Android.bp
+++ b/packages/SystemUI/compose/features/tests/Android.bp
@@ -44,5 +44,5 @@
         "androidx.compose.ui_ui-test-manifest",
     ],
 
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
 }
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
index dc450bb..fc37b355 100644
--- a/packages/SystemUI/customization/Android.bp
+++ b/packages/SystemUI/customization/Android.bp
@@ -48,5 +48,5 @@
     ],
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
 }
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 95a9ce9..d43276c 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
@@ -187,6 +187,9 @@
         /** Flag denoting transit clock are enabled in wallpaper picker. */
         const val FLAG_NAME_TRANSIT_CLOCK = "lockscreen_custom_transit_clock"
 
+        /** Flag denoting transit clock are enabled in wallpaper picker. */
+        const val FLAG_NAME_PAGE_TRANSITIONS = "wallpaper_picker_page_transitions"
+
         object Columns {
             /** String. Unique ID for the flag. */
             const val NAME = "name"
diff --git a/packages/SystemUI/res/anim/tv_bottom_sheet_button_state_list_animator.xml b/packages/SystemUI/res/anim/tv_bottom_sheet_button_state_list_animator.xml
deleted file mode 100644
index fc3b4ed..0000000
--- a/packages/SystemUI/res/anim/tv_bottom_sheet_button_state_list_animator.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<selector
-    xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true">
-        <set>
-            <objectAnimator
-                android:duration="200"
-                android:propertyName="scaleX"
-                android:valueFrom="1.0"
-                android:valueTo="@dimen/bottom_sheet_button_selection_scaled"
-                android:valueType="floatType"/>
-            <objectAnimator
-                android:duration="200"
-                android:propertyName="scaleY"
-                android:valueFrom="1.0"
-                android:valueTo="@dimen/bottom_sheet_button_selection_scaled"
-                android:valueType="floatType"/>
-        </set>
-    </item>
-    <item android:state_focused="false">
-        <set>
-            <objectAnimator
-                android:duration="200"
-                android:propertyName="scaleX"
-                android:valueFrom="@dimen/bottom_sheet_button_selection_scaled"
-                android:valueTo="1.0"
-                android:valueType="floatType"/>
-            <objectAnimator
-                android:duration="200"
-                android:propertyName="scaleY"
-                android:valueFrom="@dimen/bottom_sheet_button_selection_scaled"
-                android:valueTo="1.0"
-                android:valueType="floatType"/>
-        </set>
-    </item>
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/anim/tv_bottom_sheet_enter.xml b/packages/SystemUI/res/anim/tv_bottom_sheet_enter.xml
deleted file mode 100644
index cace36d..0000000
--- a/packages/SystemUI/res/anim/tv_bottom_sheet_enter.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:interpolator="@android:interpolator/decelerate_quint">
-    <translate android:fromYDelta="100%"
-               android:toYDelta="0"
-               android:duration="900"/>
-</set>
\ No newline at end of file
diff --git a/packages/SystemUI/res/anim/tv_bottom_sheet_exit.xml b/packages/SystemUI/res/anim/tv_bottom_sheet_exit.xml
deleted file mode 100644
index f7efe7cd..0000000
--- a/packages/SystemUI/res/anim/tv_bottom_sheet_exit.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:interpolator="@android:interpolator/decelerate_quint">
-    <translate android:fromYDelta="0"
-               android:toYDelta="100%"
-               android:duration="500"/>
-</set>
\ No newline at end of file
diff --git a/packages/SystemUI/res/anim/tv_privacy_chip_collapse.xml b/packages/SystemUI/res/anim/tv_privacy_chip_collapse.xml
deleted file mode 100644
index 94deced..0000000
--- a/packages/SystemUI/res/anim/tv_privacy_chip_collapse.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:propertyName="collapseProgress"
-    android:interpolator="@interpolator/tv_privacy_chip_collapse_interpolator"
-    android:valueTo="1"
-    android:valueType="floatType"
-    android:duration="@integer/privacy_chip_animation_millis" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/anim/tv_privacy_chip_expand.xml b/packages/SystemUI/res/anim/tv_privacy_chip_expand.xml
deleted file mode 100644
index 81c83e2..0000000
--- a/packages/SystemUI/res/anim/tv_privacy_chip_expand.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:propertyName="collapseProgress"
-    android:interpolator="@interpolator/tv_privacy_chip_expand_interpolator"
-    android:valueTo="0"
-    android:valueType="floatType"
-    android:duration="@integer/privacy_chip_animation_millis" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/bottom_sheet_button_text_color.xml b/packages/SystemUI/res/color/bottom_sheet_button_text_color.xml
deleted file mode 100644
index 05248f1..0000000
--- a/packages/SystemUI/res/color/bottom_sheet_button_text_color.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/bottom_sheet_button_text_color_focused"/>
-    <item android:color="@color/bottom_sheet_button_text_color_unfocused"/>
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml b/packages/SystemUI/res/color/qs_dialog_btn_filled_background.xml
similarity index 67%
rename from packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
rename to packages/SystemUI/res/color/qs_dialog_btn_filled_background.xml
index 9b0bae0..40bab5e 100644
--- a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
+++ b/packages/SystemUI/res/color/qs_dialog_btn_filled_background.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2021 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,8 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/bottom_sheet_button_background_color_focused"/>
-    <item android:color="@color/bottom_sheet_button_background_color_unfocused"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+          android:color="?androidprv:attr/materialColorPrimary"
+          android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorPrimary"/>
 </selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_background.xml
similarity index 62%
copy from packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
copy to packages/SystemUI/res/color/qs_dialog_btn_filled_large_background.xml
index 9b0bae0..f8d4af5 100644
--- a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
+++ b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_background.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
+<?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.
@@ -14,8 +13,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/bottom_sheet_button_background_color_focused"/>
-    <item android:color="@color/bottom_sheet_button_background_color_unfocused"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorPrimaryFixed"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorPrimaryFixed"/>
 </selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_text.xml
similarity index 62%
copy from packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
copy to packages/SystemUI/res/color/qs_dialog_btn_filled_large_text.xml
index 9b0bae0..faba8fc 100644
--- a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
+++ b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_text.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
+<?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.
@@ -14,8 +13,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/bottom_sheet_button_background_color_focused"/>
-    <item android:color="@color/bottom_sheet_button_background_color_unfocused"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorOnPrimaryFixed"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorOnPrimaryFixed"/>
 </selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml b/packages/SystemUI/res/color/qs_dialog_btn_filled_text_color.xml
similarity index 66%
copy from packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
copy to packages/SystemUI/res/color/qs_dialog_btn_filled_text_color.xml
index 9b0bae0..e76ad99 100644
--- a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
+++ b/packages/SystemUI/res/color/qs_dialog_btn_filled_text_color.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2021 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,8 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/bottom_sheet_button_background_color_focused"/>
-    <item android:color="@color/bottom_sheet_button_background_color_unfocused"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+          android:color="?androidprv:attr/materialColorOnPrimary"
+          android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorOnPrimary"/>
 </selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml b/packages/SystemUI/res/color/qs_dialog_btn_outline.xml
similarity index 63%
copy from packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
copy to packages/SystemUI/res/color/qs_dialog_btn_outline.xml
index 9b0bae0..1adfe5b 100644
--- a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
+++ b/packages/SystemUI/res/color/qs_dialog_btn_outline.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
+<?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.
@@ -14,8 +13,11 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/bottom_sheet_button_background_color_focused"/>
-    <item android:color="@color/bottom_sheet_button_background_color_unfocused"/>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorPrimary"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorPrimary"/>
 </selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml b/packages/SystemUI/res/color/qs_dialog_btn_outline_text.xml
similarity index 63%
copy from packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
copy to packages/SystemUI/res/color/qs_dialog_btn_outline_text.xml
index 9b0bae0..5dc994f23 100644
--- a/packages/SystemUI/res/color/bottom_sheet_button_background_color.xml
+++ b/packages/SystemUI/res/color/qs_dialog_btn_outline_text.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
+<?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.
@@ -14,8 +13,11 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/bottom_sheet_button_background_color_focused"/>
-    <item android:color="@color/bottom_sheet_button_background_color_unfocused"/>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorOnSurface"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorOnSurface"/>
 </selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_down.xml b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_down.xml
new file mode 100644
index 0000000..16076b1
--- /dev/null
+++ b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_down.xml
@@ -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.
+  -->
+
+<!-- TODO(b/273761935): This drawable night variant is identical to the standard drawable. Delete once the drawable cache correctly invalidates for attributes that reference colors that change when the UI mode changes. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,12C0,5.373 5.373,0 12,0C18.627,0 24,5.373 24,12C24,18.627 18.627,24 12,24C5.373,24 0,18.627 0,12Z"
+      android:fillColor="?androidprv:attr/materialColorSurfaceContainerHigh"/>
+  <path
+      android:pathData="M7.607,9.059L6.667,9.999L12,15.332L17.333,9.999L16.393,9.059L12,13.445"
+      android:fillColor="?androidprv:attr/materialColorOnSurface"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_up.xml b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_up.xml
new file mode 100644
index 0000000..309770d
--- /dev/null
+++ b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_up.xml
@@ -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.
+  -->
+
+<!-- TODO(b/273761935): This drawable night variant is identical to the standard drawable. Delete once the drawable cache correctly invalidates for attributes that reference colors that change when the UI mode changes. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,12C0,5.3726 5.3726,0 12,0C18.6274,0 24,5.3726 24,12C24,18.6274 18.6274,24 12,24C5.3726,24 0,18.6274 0,12Z"
+      android:fillColor="?androidprv:attr/materialColorSurfaceContainerHigh"/>
+  <path
+      android:pathData="M16.3934,14.9393L17.3334,13.9993L12.0001,8.666L6.6667,13.9993L7.6068,14.9393L12.0001,10.5527"
+      android:fillColor="?androidprv:attr/materialColorOnSurface"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/bottom_sheet_background.xml b/packages/SystemUI/res/drawable/bottom_sheet_background.xml
deleted file mode 100644
index 87850a0..0000000
--- a/packages/SystemUI/res/drawable/bottom_sheet_background.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
-    <solid android:color="@color/bottom_sheet_background_color"/>
-    <corners android:radius="@dimen/bottom_sheet_corner_radius"/>
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/bottom_sheet_background_with_blur.xml b/packages/SystemUI/res/drawable/bottom_sheet_background_with_blur.xml
deleted file mode 100644
index cd2aa9c..0000000
--- a/packages/SystemUI/res/drawable/bottom_sheet_background_with_blur.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
-    <solid android:color="@color/bottom_sheet_background_color_with_blur"/>
-    <corners android:radius="@dimen/bottom_sheet_corner_radius"/>
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/bottom_sheet_button_background.xml b/packages/SystemUI/res/drawable/bottom_sheet_button_background.xml
deleted file mode 100644
index 585a6bc..0000000
--- a/packages/SystemUI/res/drawable/bottom_sheet_button_background.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
-    <solid android:color="@color/bottom_sheet_button_background_color"/>
-    <corners android:radius="@dimen/bottom_sheet_button_corner_radius"/>
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/media_output_icon_volume.xml b/packages/SystemUI/res/drawable/media_output_icon_volume.xml
index fce4e00..85d608f 100644
--- a/packages/SystemUI/res/drawable/media_output_icon_volume.xml
+++ b/packages/SystemUI/res/drawable/media_output_icon_volume.xml
@@ -3,7 +3,8 @@
     android:height="24dp"
     android:viewportWidth="24"
     android:viewportHeight="24"
-    android:tint="?attr/colorControlNormal">
+    android:tint="?attr/colorControlNormal"
+    android:autoMirrored="true">
   <path
       android:fillColor="@color/media_dialog_item_main_content"
       android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.125,8.475 15.812,9.575Q16.5,10.675 16.5,12Q16.5,13.325 15.812,14.4Q15.125,15.475 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
diff --git a/packages/SystemUI/res/drawable/media_output_title_icon_area.xml b/packages/SystemUI/res/drawable/media_output_title_icon_area.xml
index b937937..a877900 100644
--- a/packages/SystemUI/res/drawable/media_output_title_icon_area.xml
+++ b/packages/SystemUI/res/drawable/media_output_title_icon_area.xml
@@ -17,9 +17,9 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
     <corners
-        android:bottomLeftRadius="28dp"
-        android:topLeftRadius="28dp"
-        android:bottomRightRadius="0dp"
-        android:topRightRadius="0dp"/>
+        android:bottomLeftRadius="@dimen/media_output_dialog_icon_left_radius"
+        android:topLeftRadius="@dimen/media_output_dialog_icon_left_radius"
+        android:bottomRightRadius="@dimen/media_output_dialog_icon_right_radius"
+        android:topRightRadius="@dimen/media_output_dialog_icon_right_radius"/>
     <solid android:color="@color/media_dialog_item_background" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_circle.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_circle.xml
new file mode 100644
index 0000000..f63c2ff
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_circle.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:colorControlHighlight">
+    <item android:id="@android:id/background">
+        <shape
+            android:shape="oval"
+            android:id="@id/background"
+            android:gravity="center">
+            <size
+                android:height="24dp"
+                android:width="24dp"/>
+            <solid android:color="@android:color/white"/>
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_large_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_large_bottom.xml
new file mode 100644
index 0000000..5d5529f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_large_bottom.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_large" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_large" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_small_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_small_bottom.xml
new file mode 100644
index 0000000..310b0be
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_small_bottom.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_large" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_large" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_large_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_large_bottom.xml
new file mode 100644
index 0000000..e89bdd3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_large_bottom.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_small" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_small" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_small_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_small_bottom.xml
new file mode 100644
index 0000000..fcf0b1c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_small_bottom.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_small" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_small" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_check_icon.xml b/packages/SystemUI/res/drawable/privacy_dialog_check_icon.xml
new file mode 100644
index 0000000..b9f5d60
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_check_icon.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M9.55,18l-5.7,-5.7 1.425,-1.425L9.55,15.15l9.175,-9.175L20.15,7.4z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_default_permission_icon.xml b/packages/SystemUI/res/drawable/privacy_dialog_default_permission_icon.xml
new file mode 100644
index 0000000..ea8f9c2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_default_permission_icon.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M26.0,14.0l-4.0,0.0l0.0,4.0l4.0,0.0l0.0,-4.0zm0.0,8.0l-4.0,0.0l0.0,12.0l4.0,0.0L26.0,22.0zm8.0,-19.98L14.0,2.0c-2.21,0.0 -4.0,1.79 -4.0,4.0l0.0,36.0c0.0,2.21 1.79,4.0 4.0,4.0l20.0,0.0c2.21,0.0 4.0,-1.79 4.0,-4.0L38.0,6.0c0.0,-2.21 -1.79,-3.98 -4.0,-3.98zM34.0,38.0L14.0,38.0L14.0,10.0l20.0,0.0l0.0,28.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_down.xml b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_down.xml
new file mode 100644
index 0000000..f8b99f4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_down.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,12C0,5.373 5.373,0 12,0C18.627,0 24,5.373 24,12C24,18.627 18.627,24 12,24C5.373,24 0,18.627 0,12Z"
+      android:fillColor="?androidprv:attr/materialColorSurfaceContainerHigh"/>
+  <path
+      android:pathData="M7.607,9.059L6.667,9.999L12,15.332L17.333,9.999L16.393,9.059L12,13.445"
+      android:fillColor="?androidprv:attr/materialColorOnSurface"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_up.xml b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_up.xml
new file mode 100644
index 0000000..ae60d51
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_up.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,12C0,5.3726 5.3726,0 12,0C18.6274,0 24,5.3726 24,12C24,18.6274 18.6274,24 12,24C5.3726,24 0,18.6274 0,12Z"
+      android:fillColor="?androidprv:attr/materialColorSurfaceContainerHigh"/>
+  <path
+      android:pathData="M16.3934,14.9393L17.3334,13.9993L12.0001,8.666L6.6667,13.9993L7.6068,14.9393L12.0001,10.5527"
+      android:fillColor="?androidprv:attr/materialColorOnSurface"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
index c4e45bf..9bc8b53 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
@@ -15,7 +15,6 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
        android:insetTop="@dimen/dialog_button_vertical_inset"
        android:insetBottom="@dimen/dialog_button_vertical_inset">
     <ripple android:color="?android:attr/colorControlHighlight">
@@ -28,7 +27,7 @@
         <item>
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
-                <solid android:color="?androidprv:attr/materialColorPrimary"/>
+                <solid android:color="@color/qs_dialog_btn_filled_background"/>
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
                          android:top="@dimen/dialog_button_vertical_padding"
                          android:right="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
index 1590daa..50405ca 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
@@ -26,7 +26,7 @@
     <item>
         <shape android:shape="rectangle">
             <corners android:radius="18dp"/>
-            <solid android:color="?androidprv:attr/materialColorPrimaryFixed"/>
+            <solid android:color="@color/qs_dialog_btn_filled_large_background"/>
         </shape>
     </item>
 </ripple>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
index b0dc652..9e9533a 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -15,7 +15,6 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
        android:insetTop="@dimen/dialog_button_vertical_inset"
        android:insetBottom="@dimen/dialog_button_vertical_inset">
     <ripple android:color="?android:attr/colorControlHighlight">
@@ -29,7 +28,7 @@
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
                 <solid android:color="@android:color/transparent"/>
-                <stroke android:color="?androidprv:attr/materialColorPrimary"
+                <stroke android:color="@color/qs_dialog_btn_outline"
                         android:width="1dp"
                 />
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/drawable/tv_volume_dialog_background.xml b/packages/SystemUI/res/drawable/tv_volume_dialog_background.xml
index 3705d918..264e10a 100644
--- a/packages/SystemUI/res/drawable/tv_volume_dialog_background.xml
+++ b/packages/SystemUI/res/drawable/tv_volume_dialog_background.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
+<!-- TODO(b/289498394) move this to the TvSystemUI target -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
 
diff --git a/packages/SystemUI/res/drawable/tv_volume_dialog_circle.xml b/packages/SystemUI/res/drawable/tv_volume_dialog_circle.xml
index 3c4fc05..bc73aab 100644
--- a/packages/SystemUI/res/drawable/tv_volume_dialog_circle.xml
+++ b/packages/SystemUI/res/drawable/tv_volume_dialog_circle.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
+<!-- TODO(b/289498394) move this to the TvSystemUI target -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="oval">
   <solid android:color="@color/tv_volume_dialog_circle" />
diff --git a/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml b/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml
index 588782d..3c31861 100644
--- a/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml
+++ b/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml
@@ -14,6 +14,7 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
+<!-- TODO(b/289498394) move this to the TvSystemUI target -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
     <solid android:color="@color/tv_volume_dialog_accent" />
     <size android:width="@dimen/tv_volume_seek_bar_thumb_diameter"
diff --git a/packages/SystemUI/res/interpolator/tv_privacy_chip_collapse_interpolator.xml b/packages/SystemUI/res/interpolator/tv_privacy_chip_collapse_interpolator.xml
deleted file mode 100644
index 1a40173..0000000
--- a/packages/SystemUI/res/interpolator/tv_privacy_chip_collapse_interpolator.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:controlX1="0.4"
-    android:controlY1="1.00"
-    android:controlX2="0.12"
-    android:controlY2="1.00"/>
diff --git a/packages/SystemUI/res/interpolator/tv_privacy_chip_expand_interpolator.xml b/packages/SystemUI/res/interpolator/tv_privacy_chip_expand_interpolator.xml
deleted file mode 100644
index ed44715..0000000
--- a/packages/SystemUI/res/interpolator/tv_privacy_chip_expand_interpolator.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
-                  android:controlX1="0.12"
-                  android:controlY1="1.00"
-                  android:controlX2="0.4"
-                  android:controlY2="1.00"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
index 991dc63e..bddcb6a 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
@@ -13,6 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<!-- TODO(b/289498394) move this to the TvSystemUI target -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:tag="row"
diff --git a/packages/SystemUI/res/layout-television/inattentive_sleep_warning.xml b/packages/SystemUI/res/layout-television/inattentive_sleep_warning.xml
deleted file mode 100644
index eb21c43..0000000
--- a/packages/SystemUI/res/layout-television/inattentive_sleep_warning.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/sleep_warning_dialog_container"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:theme="@android:style/Theme.DeviceDefault.Dialog"
-    android:focusable="true">
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="@android:color/black"
-        android:alpha="?android:backgroundDimAmount" />
-    <LinearLayout
-        android:layout_width="380dp"
-        android:layout_height="wrap_content"
-        android:background="@drawable/rounded_bg_full"
-        android:padding="16dp"
-        android:layout_margin="32dp"
-        android:layout_gravity="bottom|right"
-        android:orientation="vertical">
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/inattentive_sleep_warning_title"
-            android:layout_marginBottom="8dp"
-            android:textColor="?android:attr/textColorPrimary"
-            android:textAppearance="@android:style/TextAppearance.DeviceDefault.Large"/>
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/inattentive_sleep_warning_message"
-            android:textColor="?android:attr/textColorSecondary"
-            android:textAppearance="@android:style/TextAppearance.DeviceDefault"/>
-    </LinearLayout>
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/privacy_dialog_card_button.xml b/packages/SystemUI/res/layout/privacy_dialog_card_button.xml
new file mode 100644
index 0000000..e297b93
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dialog_card_button.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+<Button
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="56dp"
+    android:layout_marginBottom="4dp"
+    android:ellipsize="end"
+    android:maxLines="1"
+    android:clickable="true"
+    android:focusable="true"
+    android:gravity="center"
+    style="@style/Widget.Dialog.Button.BorderButton"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_item_v2.xml b/packages/SystemUI/res/layout/privacy_dialog_item_v2.xml
new file mode 100644
index 0000000..b84f3a9
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dialog_item_v2.xml
@@ -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.
+  -->
+<androidx.cardview.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/privacy_dialog_item_card"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="4dp"
+    android:layout_marginBottom="4dp"
+    android:foreground="?android:attr/selectableItemBackground"
+    app:cardCornerRadius="28dp"
+    app:cardElevation="0dp"
+    app:cardBackgroundColor="?androidprv:attr/materialColorSurfaceBright">
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <LinearLayout
+            android:id="@+id/privacy_dialog_item_header"
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="20dp"
+            android:paddingBottom="20dp"
+            android:paddingStart="24dp"
+            android:paddingEnd="24dp">
+            <ImageView
+                android:id="@+id/privacy_dialog_item_header_icon"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_centerVertical="true"
+                android:importantForAccessibility="no" />
+            <LinearLayout
+                android:orientation="vertical"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:layout_height="match_parent"
+                android:layout_marginStart="16dp"
+                android:layout_marginEnd="16dp"
+                android:layout_centerVertical="true">
+                <TextView
+                    android:id="@+id/privacy_dialog_item_header_title"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="2dp"
+                    android:hyphenationFrequency="normalFast"
+                    android:textAlignment="viewStart"
+                    android:textAppearance="@style/TextAppearance.PrivacyDialog.Item.Title" />
+                <TextView
+                    android:id="@+id/privacy_dialog_item_header_summary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:textAlignment="viewStart"
+                    android:textAppearance="@style/TextAppearance.PrivacyDialog.Item.Summary" />
+            </LinearLayout>
+            <ImageView
+                android:id="@+id/privacy_dialog_item_header_expand_toggle"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_centerVertical="true"
+                android:visibility="gone" />
+        </LinearLayout>
+        <LinearLayout
+            android:id="@+id/privacy_dialog_item_header_expanded_layout"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="16dp"
+            android:paddingStart="24dp"
+            android:paddingEnd="24dp"
+            android:visibility="gone">
+        </LinearLayout>
+    </LinearLayout>
+</androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_v2.xml b/packages/SystemUI/res/layout/privacy_dialog_v2.xml
new file mode 100644
index 0000000..843dad0
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dialog_v2.xml
@@ -0,0 +1,109 @@
+<!--
+  ~ 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.
+  -->
+<androidx.core.widget.NestedScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="@dimen/large_dialog_width"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp"
+        android:orientation="vertical">
+
+        <!-- Header -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical"
+            android:gravity="center">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                android:fontFamily="@*android:string/config_headlineFontFamily"
+                android:text="@string/privacy_dialog_title"
+                android:layout_marginBottom="12dp"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/privacy_dialog_summary"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+                android:gravity="center"
+                android:layout_marginBottom="20dp"/>
+        </LinearLayout>
+
+        <!-- Items -->
+        <LinearLayout
+            android:id="@+id/privacy_dialog_items_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="20dp"
+            android:orientation="vertical"
+        />
+
+        <!-- Buttons -->
+        <LinearLayout
+            android:id="@+id/button_layout"
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="18dp"
+            android:clickable="false"
+            android:focusable="false">
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:layout_gravity="start|center_vertical"
+                android:orientation="vertical">
+                <Button
+                    android:id="@+id/privacy_dialog_more_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/privacy_dialog_more_button"
+                    android:ellipsize="end"
+                    android:maxLines="1"
+                    style="@style/Widget.Dialog.Button.BorderButton"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:gravity="center"/>
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="end|center_vertical">
+                <Button
+                    android:id="@+id/privacy_dialog_close_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/privacy_dialog_done_button"
+                    android:ellipsize="end"
+                    android:maxLines="1"
+                    style="@style/Widget.Dialog.Button.BorderButton"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:gravity="center"/>
+            </LinearLayout>
+        </LinearLayout>
+    </LinearLayout>
+</androidx.core.widget.NestedScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_tile_side_icon.xml b/packages/SystemUI/res/layout/qs_tile_side_icon.xml
index f1b7259..fbcead1 100644
--- a/packages/SystemUI/res/layout/qs_tile_side_icon.xml
+++ b/packages/SystemUI/res/layout/qs_tile_side_icon.xml
@@ -30,12 +30,11 @@
         android:visibility="gone"
     />
 
-    <ImageView
+    <com.android.systemui.qs.tileimpl.ChevronImageView
         android:id="@+id/chevron"
         android:layout_width="@dimen/qs_icon_size"
         android:layout_height="@dimen/qs_icon_size"
         android:src="@*android:drawable/ic_chevron_end"
-        android:autoMirrored="true"
         android:visibility="gone"
         android:importantForAccessibility="no"
     />
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
deleted file mode 100644
index d6c63eb..0000000
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2018, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<com.android.systemui.statusbar.StatusBarMobileView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/mobile_combo"
-    android:layout_width="wrap_content"
-    android:layout_height="match_parent"
-    android:gravity="center_vertical" >
-
-    <include layout="@layout/status_bar_mobile_signal_group_inner" />
-
-</com.android.systemui.statusbar.StatusBarMobileView>
-
diff --git a/packages/SystemUI/res/layout/tv_bottom_sheet.xml b/packages/SystemUI/res/layout/tv_bottom_sheet.xml
deleted file mode 100644
index b69cdc7..0000000
--- a/packages/SystemUI/res/layout/tv_bottom_sheet.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/bottom_sheet"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center_vertical"
-    android:orientation="horizontal"
-    android:minHeight="@dimen/bottom_sheet_min_height"
-    android:paddingHorizontal="@dimen/bottom_sheet_padding_horizontal"
-    android:paddingVertical="@dimen/bottom_sheet_padding_vertical">
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:minWidth="80dp"
-        android:gravity="center"
-        android:orientation="horizontal">
-        <ImageView
-            android:id="@+id/bottom_sheet_icon"
-            android:layout_width="@dimen/bottom_sheet_icon_size"
-            android:layout_height="@dimen/bottom_sheet_icon_size"
-            android:layout_gravity="center_vertical"
-            android:tint="@color/bottom_sheet_icon_color"/>
-        <ImageView
-            android:id="@+id/bottom_sheet_second_icon"
-            android:layout_width="@dimen/bottom_sheet_icon_size"
-            android:layout_height="@dimen/bottom_sheet_icon_size"
-            android:layout_marginStart="@dimen/bottom_sheet_icon_margin"
-            android:layout_gravity="center_vertical"
-            android:tint="@color/bottom_sheet_icon_color"/>
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/bottom_sheet_padding_horizontal"
-        android:layout_weight="1"
-        android:orientation="vertical">
-        <TextView
-            android:id="@+id/bottom_sheet_title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="@dimen/bottom_sheet_title_margin_bottom"
-            android:textAppearance="@style/BottomSheet.TitleText"/>
-
-        <TextView
-            android:id="@+id/bottom_sheet_body"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="@dimen/bottom_sheet_details_margin_bottom"
-            android:textAppearance="@style/BottomSheet.BodyText" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:orientation="vertical"
-        android:layout_width="@dimen/bottom_sheet_actions_width"
-        android:layout_height="match_parent"
-        android:gravity="center">
-        <Button
-            android:id="@+id/bottom_sheet_positive_button"
-            style="@style/BottomSheet.ActionItem" />
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="@dimen/bottom_sheet_actions_spacing" />
-        <Button
-            android:id="@+id/bottom_sheet_negative_button"
-            style="@style/BottomSheet.ActionItem" />
-    </LinearLayout>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/tv_notification_item.xml b/packages/SystemUI/res/layout/tv_notification_item.xml
deleted file mode 100644
index 711cd4e..0000000
--- a/packages/SystemUI/res/layout/tv_notification_item.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/tv_notification_panel_width"
-    android:layout_height="wrap_content"
-    android:background="?android:attr/selectableItemBackground"
-    android:orientation="vertical"
-    android:padding="12dp">
-
-    <TextView
-        android:id="@+id/tv_notification_title"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingBottom="12dp"
-        android:textColor="@color/tv_notification_text_color"
-        android:textSize="18sp" />
-
-    <TextView
-        android:id="@+id/tv_notification_details"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:textColor="@color/tv_notification_text_color" />
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/tv_notification_panel.xml b/packages/SystemUI/res/layout/tv_notification_panel.xml
deleted file mode 100644
index eae44c8..0000000
--- a/packages/SystemUI/res/layout/tv_notification_panel.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/tv_notification_panel"
-    android:layout_width="@dimen/tv_notification_panel_width"
-    android:layout_height="match_parent"
-    android:layout_gravity="end"
-    android:background="@android:color/transparent"
-    android:orientation="vertical">
-
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:padding="12dp"
-        android:paddingTop="24dp"
-        android:text="@string/tv_notification_panel_title"
-        android:textColor="@color/tv_notification_text_color"
-        android:textSize="24sp"
-        android:textStyle="bold" />
-
-    <TextView
-        android:id="@+id/no_tv_notifications"
-        style="?android:attr/titleTextStyle"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
-        android:gravity="top|center"
-        android:paddingTop="24dp"
-        android:text="@string/tv_notification_panel_no_notifications"
-        android:textColor="@color/tv_notification_text_color"
-        android:visibility="gone" />
-
-    <androidx.leanback.widget.VerticalGridView
-        android:id="@+id/notifications_list"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1" />
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/tv_ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/tv_ongoing_privacy_chip.xml
deleted file mode 100644
index 9069b8f..0000000
--- a/packages/SystemUI/res/layout/tv_ongoing_privacy_chip.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="@dimen/privacy_chip_height"
-    android:minWidth="@dimen/privacy_chip_dot_bg_width"
-    android:gravity="center">
-    <LinearLayout
-        android:id="@+id/icons_container"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/privacy_chip_height"
-        android:minWidth="@dimen/privacy_chip_dot_bg_width"
-        android:orientation="horizontal"
-        android:gravity="center"
-        android:paddingHorizontal="@dimen/privacy_chip_padding_horizontal"
-        android:clipToPadding="false" />
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/tv_privacy_chip_container.xml b/packages/SystemUI/res/layout/tv_privacy_chip_container.xml
deleted file mode 100644
index 20ffe2b9..0000000
--- a/packages/SystemUI/res/layout/tv_privacy_chip_container.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:minWidth="@dimen/privacy_chips_max_width"
-    android:orientation="horizontal"
-    android:clipToPadding="false"
-    android:gravity="end"
-    android:padding="@dimen/privacy_chips_bar_padding">
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="@dimen/privacy_chip_height" />
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings_tv.xml b/packages/SystemUI/res/values-af/strings_tv.xml
deleted file mode 100644
index 4fec3b2..0000000
--- a/packages/SystemUI/res/values-af/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN is gekoppel"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN is ontkoppel"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Kennisgewings"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Geen kennisgewings nie"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofoon neem tans op"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera neem tans op"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera en mikrofoon neem tans op"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofoon het opname gestop"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera het opname gestop"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera en mikrofoon het opname gestop"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Skermopname het begin"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Skermopname het gestop"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-am/strings_tv.xml b/packages/SystemUI/res/values-am/strings_tv.xml
deleted file mode 100644
index d3d1433..0000000
--- a/packages/SystemUI/res/values-am/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN ተያይዟል"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ተቋርቷል"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"በ<xliff:g id="VPN_APP">%1$s</xliff:g> በኩል"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"ማሳወቂያዎች"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"ምንም ማሳወቂያዎች የሉም"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"ማይክሮፎን እየቀዳ ነው"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"ካሜራ እየቀረጸ ነው"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"ካሜራ እየቀረጸ እና ማይክሮፎን እየቀዳ ነው"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"ማይክሮፎን መቅዳት አቁሟል"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"ካሜራ መቅረጽ አቁሟል"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"ካሜራ መቅረጽ እና ማይክሮፎን መቅዳት አቁመዋል"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"የማያ ገፅ ቀረጻ ተጀምሯል"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"የማያ ገፅ ቀረጻ ቆሟል"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ar/strings_tv.xml b/packages/SystemUI/res/values-ar/strings_tv.xml
deleted file mode 100644
index 0a96069..0000000
--- a/packages/SystemUI/res/values-ar/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"‏شبكة VPN متصلة."</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"‏شبكة VPN غير متصلة."</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"عبر <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"الإشعارات"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"ما من إشعارات"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"جارٍ التسجيل بالميكرفون"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"جارٍ التسجيل بالكاميرا"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"جارٍ التسجيل بالكاميرا والميكروفون"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"توقف التسجيل بالميكرفون."</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"توقف التسجيل بالكاميرا."</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"توقف التسجيل بالكاميرا والميكروفون."</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"بدأ تسجيل الشاشة."</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"توقَّف تسجيل الشاشة."</string>
-</resources>
diff --git a/packages/SystemUI/res/values-as/strings_tv.xml b/packages/SystemUI/res/values-as/strings_tv.xml
deleted file mode 100644
index ec37d53..0000000
--- a/packages/SystemUI/res/values-as/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"ভিপিএন সংযোগ হৈ আছে"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"ভিপিএনৰ সংযোগ বিচ্ছিন্ন হৈছে"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g>ৰ জৰিয়তে"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"জাননী"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"কোনো জাননী নাই"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"মাইক্ৰ’ফ’নটোৱে ৰেক’ৰ্ড কৰি আছে"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"কেমেৰাটোৱে ৰেক’ৰ্ড কৰি আছে"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"কেমেৰা আৰু মাইক্ৰ’ফ’নটোৱে ৰেক’ৰ্ড কৰি আছে"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"মাইক্ৰ’ফ’নটোৱে ৰেক’ৰ্ড কৰাটো বন্ধ কৰিছে"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"কেমেৰাটোৱে ৰেক’ৰ্ড কৰাটো বন্ধ কৰিছে"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"কেমেৰা আৰু মাইক্ৰ’ফ’নটোৱে ৰেক’ৰ্ড কৰাটো বন্ধ কৰিছে"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"স্ক্ৰীন ৰেকৰ্ডিং আৰম্ভ কৰা হৈছে"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"স্ক্ৰীন ৰেকৰ্ডিং বন্ধ হৈছে"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-az/strings_tv.xml b/packages/SystemUI/res/values-az/strings_tv.xml
deleted file mode 100644
index 055b2fa..0000000
--- a/packages/SystemUI/res/values-az/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN qoşulub"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ayrılıb"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> vasitəsilə"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Bildirişlər"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Bildiriş yoxdur"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon yazır"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera yazır"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera və mikrofon yazır"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofon yazmağı dayandırıb"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera yazmağı dayandırıb"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera və mikrofon yazmağı dayandırıb"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Ekranı videoya çəkməyə başlandı"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Ekranı videoya çəkməyi dayandırdınız"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings_tv.xml b/packages/SystemUI/res/values-b+sr+Latn/strings_tv.xml
deleted file mode 100644
index f72890bd..0000000
--- a/packages/SystemUI/res/values-b+sr+Latn/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN je povezan"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Veza sa VPN-om je prekinuta"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Preko: <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Obaveštenja"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Nema obaveštenja"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon snima"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera snima"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera i mikrofon snimaju"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Snimanje mikrofonom je zaustavljeno"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Snimanje kamerom je zaustavljeno"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Snimanje kamerom i mikrofonom je zaustavljeno"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Snimanje ekrana je započeto"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Snimanje ekrana je zaustavljeno"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-be/strings_tv.xml b/packages/SystemUI/res/values-be/strings_tv.xml
deleted file mode 100644
index aee04c2..0000000
--- a/packages/SystemUI/res/values-be/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN падключаны"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN адключаны"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Праз <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Апавяшчэнні"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Апавяшчэнняў няма"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Ідзе запіс з выкарыстаннем мікрафона"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Ідзе запіс з выкарыстаннем камеры"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Ідзе запіс з выкарыстаннем камеры і мікрафона"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Запіс з выкарыстаннем мікрафона спынены"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Запіс з выкарыстаннем камеры спынены"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Запіс з выкарыстаннем камеры і мікрафона спынены"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Пачаўся запіс экрана"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Запіс экрана спынены"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-bg/strings_tv.xml b/packages/SystemUI/res/values-bg/strings_tv.xml
deleted file mode 100644
index 6d578ad..0000000
--- a/packages/SystemUI/res/values-bg/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN е свързана"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Връзката с VPN е прекратена"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Чрез <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Известия"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Няма известия"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Микрофонът записва"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Камерата записва"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Камерата и микрофонът записват"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Микрофонът спря да записва"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Камерата спря да записва"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Камерата и микрофонът спряха да записват"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Записването на екрана започна"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Записването на екрана бе спряно"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-bn/strings_tv.xml b/packages/SystemUI/res/values-bn/strings_tv.xml
deleted file mode 100644
index dcaefd97..0000000
--- a/packages/SystemUI/res/values-bn/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN কানেক্ট করা হয়েছে"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ডিসকানেক্ট করা হয়েছে"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g>-এর মাধ্যমে"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"বিজ্ঞপ্তি"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"কোনও বিজ্ঞপ্তি নেই"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"মাইক্রোফোনে রেকর্ড করা হচ্ছে"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"ক্যামেরায় রেকর্ড করা হচ্ছে"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"ক্যামেরা ও মাইক্রোফোনে রেকর্ড করা হচ্ছে"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"মাইক্রোফোনে রেকর্ড করা বন্ধ হয়ে গেছে"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"ক্যামেরায় রেকর্ড করা বন্ধ হয়ে গেছে"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"ক্যামেরা ও মাইক্রোফোনে রেকর্ড করা বন্ধ হয়ে গেছে"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"স্ক্রিন রেকর্ড করা শুরু হয়েছে"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"স্ক্রিন রেকর্ড করা বন্ধ হয়েছে"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-bs/strings_tv.xml b/packages/SystemUI/res/values-bs/strings_tv.xml
deleted file mode 100644
index db8c5b3..0000000
--- a/packages/SystemUI/res/values-bs/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN je povezan"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Veza s VPN-om je prekinuta"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Putem: <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Obavještenja"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Nema obavještenja"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon snima"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera snima"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera i mikrofon snimaju"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofon je prestao snimati"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera je prestala snimati"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera i mikrofon su prestali snimati"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Snimanje ekrana je pokrenuto"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Snimanje ekrana je zaustavljeno"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ca/strings_tv.xml b/packages/SystemUI/res/values-ca/strings_tv.xml
deleted file mode 100644
index 972d38b..0000000
--- a/packages/SystemUI/res/values-ca/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"La VPN està connectada"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"La VPN està desconnectada"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Mitjançant <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notificacions"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Cap notificació"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"El micròfon està gravant"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"La càmera està gravant"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"La càmera i el micròfon estan gravant"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"El micròfon ha deixat de gravar"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"La càmera ha deixat de gravar"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"La càmera i el micròfon han deixat de gravar"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"La gravació de la pantalla ha començat"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"La gravació de la pantalla s\'ha aturat"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-cs/strings_tv.xml b/packages/SystemUI/res/values-cs/strings_tv.xml
deleted file mode 100644
index ab391f4..0000000
--- a/packages/SystemUI/res/values-cs/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"Síť VPN je připojena"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Síť VPN je odpojena"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Přes <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Oznámení"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Žádná oznámení"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon nahrává"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera nahrává"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera a mikrofon nahrávají"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofon přestal nahrávat"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera přestala nahrávat"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera a mikrofon přestaly nahrávat"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Zahájeno nahrávání obrazovky"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Záznam obrazovky ukončen"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-da/strings_tv.xml b/packages/SystemUI/res/values-da/strings_tv.xml
deleted file mode 100644
index 66b7964..0000000
--- a/packages/SystemUI/res/values-da/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN er tilsluttet"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN er afbrudt"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifikationer"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Ingen notifikationer"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofonen optager"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kameraet optager"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kameraet og mikrofonen optager"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofonen er stoppet med at optage"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kameraet er stoppet med at optage"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kameraet og mikrofonen er stoppet med at optage"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Skærmoptagelsen er startet"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Skærmoptagelsen er stoppet"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-de/strings_tv.xml b/packages/SystemUI/res/values-de/strings_tv.xml
deleted file mode 100644
index 5df9d32..0000000
--- a/packages/SystemUI/res/values-de/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN ist verbunden"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ist nicht verbunden"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Über <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Benachrichtigungen"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Keine Benachrichtigungen"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon nimmt auf"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera nimmt auf"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera und Mikrofon nehmen auf"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Aufnahme des Mikrofons gestoppt"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Aufnahme der Kamera gestoppt"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Aufnahme von Kamera und Mikrofon gestoppt"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Bildschirmaufzeichnung gestartet"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Bildschirmaufzeichnung beendet"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-el/strings_tv.xml b/packages/SystemUI/res/values-el/strings_tv.xml
deleted file mode 100644
index 9fb126d..0000000
--- a/packages/SystemUI/res/values-el/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"Το VPN συνδέθηκε"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Το VPN αποσυνδέθηκε"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Μέσω <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Ειδοποιήσεις"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Δεν υπάρχουν ειδοποιήσεις."</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Πραγματοποιείται εγγραφή από το μικρόφωνο"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Πραγματοποιείται εγγραφή από την κάμερα"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Πραγματοποιείται εγγραφή από την κάμερα και το μικρόφωνο"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Η εγγραφή από το μικρόφωνο διακόπηκε"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Η εγγραφή από την κάμερα διακόπηκε"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Η εγγραφή από την κάμερα και το μικρόφωνο διακόπηκε"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Η εγγραφή οθόνης ξεκίνησε"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Η εγγραφή οθόνης σταμάτησε"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings_tv.xml b/packages/SystemUI/res/values-en-rAU/strings_tv.xml
deleted file mode 100644
index e97dbe4..0000000
--- a/packages/SystemUI/res/values-en-rAU/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN is connected"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN is disconnected"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifications"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No notifications"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Microphone is recording"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Camera is recording"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and microphone are recording"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Microphone stopped recording"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Camera stopped recording"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and microphone stopped recording"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Screen recording started"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Screen recording stopped"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings_tv.xml b/packages/SystemUI/res/values-en-rCA/strings_tv.xml
deleted file mode 100644
index a628846..0000000
--- a/packages/SystemUI/res/values-en-rCA/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN is connected"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN is disconnected"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifications"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No Notifications"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Microphone is recording"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Camera is recording"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and Microphone are recording"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Microphone stopped recording"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Camera stopped recording"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and Microphone stopped recording"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Screen recording started"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Screen recording stopped"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings_tv.xml b/packages/SystemUI/res/values-en-rGB/strings_tv.xml
deleted file mode 100644
index e97dbe4..0000000
--- a/packages/SystemUI/res/values-en-rGB/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN is connected"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN is disconnected"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifications"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No notifications"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Microphone is recording"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Camera is recording"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and microphone are recording"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Microphone stopped recording"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Camera stopped recording"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and microphone stopped recording"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Screen recording started"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Screen recording stopped"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings_tv.xml b/packages/SystemUI/res/values-en-rIN/strings_tv.xml
deleted file mode 100644
index e97dbe4..0000000
--- a/packages/SystemUI/res/values-en-rIN/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN is connected"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN is disconnected"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifications"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No notifications"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Microphone is recording"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Camera is recording"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and microphone are recording"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Microphone stopped recording"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Camera stopped recording"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and microphone stopped recording"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Screen recording started"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Screen recording stopped"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings_tv.xml b/packages/SystemUI/res/values-en-rXC/strings_tv.xml
deleted file mode 100644
index 06116ed..0000000
--- a/packages/SystemUI/res/values-en-rXC/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎VPN is connected‎‏‎‎‏‎"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‏‎‏‏‎VPN is disconnected‎‏‎‎‏‎"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‎‎‎‎‏‏‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎Via ‎‏‎‎‏‏‎<xliff:g id="VPN_APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‎‏‎‏‏‎‏‎‏‎‎‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎Notifications‎‏‎‎‏‎"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‎‎No Notifications‎‏‎‎‏‎"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‏‏‏‏‏‎Microphone is recording‎‏‎‎‏‎"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‎‎‎‎‏‎‎‎‎Camera is recording‎‏‎‎‏‎"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎Camera and Microphone are recording‎‏‎‎‏‎"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‎‏‎‎‏‎‏‏‎‏‎‎Microphone stopped recording‎‏‎‎‏‎"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎Camera stopped recording‎‏‎‎‏‎"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‎‎‎‏‎‎‎Camera and Microphone stopped recording‎‏‎‎‏‎"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‎‎‏‏‎‎‎‏‏‎‎‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎Screen recording started‎‏‎‎‏‎"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎Screen recording stopped‎‏‎‎‏‎"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings_tv.xml b/packages/SystemUI/res/values-es-rUS/strings_tv.xml
deleted file mode 100644
index 513e0c7..0000000
--- a/packages/SystemUI/res/values-es-rUS/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"La VPN está conectada."</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"La VPN está desconectada"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"A través de <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notificaciones"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No hay notificaciones"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"El micrófono está grabando"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"La cámara está grabando"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"La cámara y el micrófono están grabando"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"El micrófono dejó de grabar"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"La cámara dejó de grabar"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"La cámara y el micrófono dejaron de grabar"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Se inició la grabación de pantalla"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Se detuvo la grabación de pantalla"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-es/strings_tv.xml b/packages/SystemUI/res/values-es/strings_tv.xml
deleted file mode 100644
index 1482fd0..0000000
--- a/packages/SystemUI/res/values-es/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"La VPN está conectada"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"La VPN está desconectada"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"A través de <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notificaciones"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Sin notificaciones"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"El micrófono está grabando"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"La cámara está grabando"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"La cámara y el micrófono están grabando"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"El micrófono ha dejado de grabar"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"La cámara ha dejado de grabar"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"La cámara y el micrófono han dejado de grabar"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Grabación de pantalla iniciada"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Grabación de pantalla detenida"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-et/strings_tv.xml b/packages/SystemUI/res/values-et/strings_tv.xml
deleted file mode 100644
index ee7bba8..0000000
--- a/packages/SystemUI/res/values-et/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN on ühendatud"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN-i ühendus on katkestatud"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Teenuse <xliff:g id="VPN_APP">%1$s</xliff:g> kaudu"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Märguanded"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Märguandeid pole"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon salvestab"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kaamera salvestab"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kaamera ja mikrofon salvestavad"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofon peatas salvestamise"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kaamera peatas salvestamise"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kaamera ja mikrofon peatasid salvestamise"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Ekraanikuva salvestamine algas"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Ekraanikuva salvestamine on peatatud"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-eu/strings_tv.xml b/packages/SystemUI/res/values-eu/strings_tv.xml
deleted file mode 100644
index de5249b..0000000
--- a/packages/SystemUI/res/values-eu/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN sarera konektatuta dago"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ez dago sarera konektatuta"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> bidez"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Jakinarazpenak"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Ez dago jakinarazpenik"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofonoa grabatzen ari da"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera grabatzen ari da"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera eta mikrofonoa grabatzen ari dira"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofonoak grabatzeari utzi dio"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamerak grabatzeari utzi dio"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamerak eta mikrofonoak grabatzeari utzi diote"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Hasi da pantailaren grabaketa"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Gelditu da pantailaren grabaketa"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-fa/strings_tv.xml b/packages/SystemUI/res/values-fa/strings_tv.xml
deleted file mode 100644
index 89f57af..0000000
--- a/packages/SystemUI/res/values-fa/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"‏VPN متصل است"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"‏VPN قطع است"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"ازطریق <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"اعلان‌ها"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"اعلانی ندارید"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"میکروفون درحال ضبط کردن است"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"دوربین درحال ضبط کردن است"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"دوربین و میکروفون درحال ضبط کردن هستند"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"ضبط میکروفون متوقف شد"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"ضبط دوربین متوقف شد"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"ضبط دوربین و میکروفون متوقف شد"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"ضبط صفحه شروع شد"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"ضبط صفحه متوقف شد"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-fi/strings_tv.xml b/packages/SystemUI/res/values-fi/strings_tv.xml
deleted file mode 100644
index 61198d4..0000000
--- a/packages/SystemUI/res/values-fi/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN on yhdistetty"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ei ole yhdistettynä"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Palvelun <xliff:g id="VPN_APP">%1$s</xliff:g> kautta"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Ilmoitukset"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Ei ilmoituksia"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofoni tallentaa"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera kuvaa"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera ja mikrofoni tallentavat"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofoni lopetti tallentamisen"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera lopetti kuvaamisen"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera ja mikrofoni lopettivat tallentamisen"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Näytön tallennus aloitettu"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Näytön tallennus lopetettu"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings_tv.xml b/packages/SystemUI/res/values-fr-rCA/strings_tv.xml
deleted file mode 100644
index 7ab63af..0000000
--- a/packages/SystemUI/res/values-fr-rCA/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"RPV connecté"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"RPV déconnecté"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Par <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifications"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Aucune notification"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Le microphone enregistre"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"L\'appareil photo enregistre"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"L\'appareil photo et le microphone enregistrent"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Le microphone a arrêté l\'enregistrement"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"L\'appareil photo a arrêté l\'enregistrement"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"L\'appareil photo et le microphone ont arrêté l\'enregistrement"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"L\'enregistrement d\'écran a commencé"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"L\'enregistrement d\'écran s\'est arrêté"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-fr/strings_tv.xml b/packages/SystemUI/res/values-fr/strings_tv.xml
deleted file mode 100644
index d1ac554..0000000
--- a/packages/SystemUI/res/values-fr/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN connecté"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN déconnecté"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifications"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Aucune notification"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Le micro enregistre…"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"La caméra enregistre…"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"La caméra et le micro enregistrent…"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Le micro a arrêté l\'enregistrement"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"La caméra a arrêté l\'enregistrement"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"La caméra et le micro ont arrêté l\'enregistrement"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Enregistrement de l\'écran démarré"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Enregistrement de l\'écran arrêté"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-gl/strings_tv.xml b/packages/SystemUI/res/values-gl/strings_tv.xml
deleted file mode 100644
index 58279ee..0000000
--- a/packages/SystemUI/res/values-gl/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"A VPN está conectada"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"A VPN está desconectada"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"A través de <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notificacións"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Non hai notificacións"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"O micrófono está gravando"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"A cámara está gravando"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"A cámara e o micrófono están gravando"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"O micrófono deixou de gravar"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"A cámara deixou de gravar"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"A cámara e o micrófono deixaron de gravar"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Iniciouse a gravación da pantalla"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Parouse a gravación da pantalla"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-gu/strings_tv.xml b/packages/SystemUI/res/values-gu/strings_tv.xml
deleted file mode 100644
index 096a93a..0000000
--- a/packages/SystemUI/res/values-gu/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN કનેક્ટ કરેલું છે"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ડિસ્કનેક્ટ કરેલું છે"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> મારફતે"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"નોટિફિકેશન"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"કોઈ નોટિફિકેશન નથી"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"માઇક્રોફોનનું રેકોર્ડિંગ ચાલુ છે"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"કૅમેરાનું રેકોર્ડિંગ ચાલુ છે"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"કૅમેરા અને માઇક્રોફોનનું રેકોર્ડિંગ ચાલુ છે"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"માઇક્રોફોનનું રેકોર્ડિંગ બંધ થઈ ગયું"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"કૅમેરાનું રેકોર્ડિંગ બંધ થઈ ગયું"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"કૅમેરા અને માઇક્રોફોનનું રેકોર્ડિંગ બંધ થઈ ગયું"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"સ્ક્રીન રેકોર્ડિંગ શરૂ થયું"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"સ્ક્રીન રેકોર્ડિંગ બંધ કર્યું"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-hi/strings_tv.xml b/packages/SystemUI/res/values-hi/strings_tv.xml
deleted file mode 100644
index 0e5e1225..0000000
--- a/packages/SystemUI/res/values-hi/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"वीपीएन कनेक्ट हो गया है"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"वीपीएन डिसकनेक्ट हो गया है"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> के ज़रिए"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"सूचनाएं"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"कोई सूचना नहीं है"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"माइक्रोफ़ोन रिकॉर्ड कर रहा है"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"कैमरा रिकॉर्ड कर रहा है"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"कैमरा और माइक्रोफ़ोन रिकॉर्ड कर रहे हैं"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"माइक्रोफ़ोन ने रिकॉर्ड करना बंद कर दिया है"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"कैमरे ने रिकॉर्ड करना बंद कर दिया है"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"कैमरे और माइक्रोफ़ोन ने रिकॉर्ड करना बंद कर दिया है"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"स्क्रीन रिकॉर्डिंग शुरू की गई"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"स्क्रीन रिकॉर्डिंग बंद की गई"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-hr/strings_tv.xml b/packages/SystemUI/res/values-hr/strings_tv.xml
deleted file mode 100644
index 61cf646..0000000
--- a/packages/SystemUI/res/values-hr/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN je spojen"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN je isključen"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Putem mreže <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Obavijesti"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Nema obavijesti"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon snima"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera snima"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera i mikrofon snimaju"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofon je prestao snimati"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera je prestala snimati"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera i mikrofon prestali su snimati"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Snimanje zaslona je počelo"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Snimanje zaslona je zaustavljeno"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-hu/strings_tv.xml b/packages/SystemUI/res/values-hu/strings_tv.xml
deleted file mode 100644
index f251c38..0000000
--- a/packages/SystemUI/res/values-hu/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"A VPN-kapcsolat létrejött"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"A VPN-kapcsolat megszakadt"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Ezzel: <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Értesítések"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Nincs értesítés"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"A mikrofon felvételt készít…"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"A kamera felvételt készít…"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"A kamera és a mikrofon felvételt készít…"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"A mikrofon befejezte a felvételkészítést"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"A kamera befejezte a felvételkészítést"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"A kamera és a mikrofon befejezte a felvételkészítést"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"A képernyő rögzítése elindult"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"A képernyő rögzítése leállt"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-hy/strings_tv.xml b/packages/SystemUI/res/values-hy/strings_tv.xml
deleted file mode 100644
index b6a2e13..0000000
--- a/packages/SystemUI/res/values-hy/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN-ը միացված է"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN-ն անջատված է"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g>-ի միջոցով"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Ծանուցումներ"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Ծանուցումներ չկան"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Խոսափողը ձայնագրում է"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Տեսախցիկը տեսագրում է"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Տեսախցիկն ու խոսափողը տեսաձայնագրում են"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Խոսափողն այլևս չի ձայնագրում"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Տեսախցիկն այլևս չի տեսագրում"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Տեսախցիկն ու խոսափողը այլևս չեն տեսաձայնագրում"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Էկրանի տեսագրումը սկսվեց"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Էկրանի տեսագրումը կանգնեցվեց"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-in/strings_tv.xml b/packages/SystemUI/res/values-in/strings_tv.xml
deleted file mode 100644
index 1b8261e7..0000000
--- a/packages/SystemUI/res/values-in/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN terhubung"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN terputus"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Melalui <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifikasi"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Tidak Ada Notifikasi"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon sedang merekam"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera sedang merekam"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera dan Mikrofon sedang merekam"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofon berhenti merekam"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera berhenti merekam"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera dan Mikrofon berhenti merekam"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Perekaman layar dimulai"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Perekaman layar dihentikan"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-is/strings_tv.xml b/packages/SystemUI/res/values-is/strings_tv.xml
deleted file mode 100644
index 7150fc8..0000000
--- a/packages/SystemUI/res/values-is/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN er tengt"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN er ekki tengt"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Í gegnum <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Tilkynningar"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Engar tilkynningar"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Hljóðnemi er að taka upp"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Myndavél er að taka upp"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Myndavél og hljóðnemi eru að taka upp"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Hljóðnemi er hættur að taka upp"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Myndavél er hætt að taka upp"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Myndavél og hljóðnemi eru hætt að taka upp"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Skjáupptaka er hafin"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Skjáupptöku er lokið"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-it/strings_tv.xml b/packages/SystemUI/res/values-it/strings_tv.xml
deleted file mode 100644
index ab9ee78..0000000
--- a/packages/SystemUI/res/values-it/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN connessa"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN disconnessa"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Tramite <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifiche"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Nessuna notifica"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Registrazione in corso con il microfono"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Registrazione in corso con la fotocamera"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Registrazione in corso con fotocamera e microfono"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Registrazione con il microfono interrotta"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Registrazione con la fotocamera interrotta"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Registrazione con fotocamera e microfono interrotta"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Registrazione dello schermo avviata"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Registrazione dello schermo interrotta"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-iw/strings_tv.xml b/packages/SystemUI/res/values-iw/strings_tv.xml
deleted file mode 100644
index 1085505..0000000
--- a/packages/SystemUI/res/values-iw/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"‏ה-VPN מחובר"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"‏ה-VPN מנותק"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"דרך <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"התראות"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"אין התראות"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"המיקרופון מקליט"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"המצלמה מקליטה"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"המצלמה והמיקרופון מקליטים"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"המיקרופון הפסיק להקליט"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"המצלמה הפסיקה להקליט"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"המצלמה והמיקרופון הפסיקו להקליט"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"הקלטת המסך החלה"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"הקלטת המסך הופסקה"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ja/strings_tv.xml b/packages/SystemUI/res/values-ja/strings_tv.xml
deleted file mode 100644
index 6b68c73..0000000
--- a/packages/SystemUI/res/values-ja/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN に接続しました"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN に接続していません"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> 経由"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"通知"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"通知はありません"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"マイクで録音しています"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"カメラで録画しています"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"カメラとマイクで録画、録音しています"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"マイクが録音を停止しました"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"カメラが録画を停止しました"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"カメラとマイクが録画、録音を停止しました"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"画面の録画を開始しました"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"画面の録画を停止しました"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ka/strings_tv.xml b/packages/SystemUI/res/values-ka/strings_tv.xml
deleted file mode 100644
index dcaa138..0000000
--- a/packages/SystemUI/res/values-ka/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN დაკავშირებულია"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN გათიშულია"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g>-ის მიერ"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"შეტყობინებები"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"შეტყობინებები არ არის"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"მიკროფონი იწერს"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"კამერა იწერს"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"კამერა და მიკროფონი იწერს"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"მიკროფონმა ჩაწერა შეწყვიტა"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"კამერამ ჩაწერა შეწყვიტა"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"კამერამ და მიკროფონმა ჩაწერა შეწყვიტა"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"ეკრანის ჩაწერა დაიწყო"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"ეკრანის ჩაწერა დასრულდა"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-kk/strings_tv.xml b/packages/SystemUI/res/values-kk/strings_tv.xml
deleted file mode 100644
index bb0e06b..0000000
--- a/packages/SystemUI/res/values-kk/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN қосылған"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ажыратылған"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> арқылы жалғанған"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Хабарландырулар"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Хабарландырулар жоқ"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Микрофон жазып жатыр."</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Камера жазып жатыр."</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Камера мен микрофон жазып жатыр."</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Микрофон жазуды тоқтатты."</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Камера жазуды тоқтатты."</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Камера мен микрофон жазуды тоқтатты."</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Экранды бейнеге жазу басталды."</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Экранды бейнеге жазу тоқтатылды."</string>
-</resources>
diff --git a/packages/SystemUI/res/values-km/strings_tv.xml b/packages/SystemUI/res/values-km/strings_tv.xml
deleted file mode 100644
index 1be9d48..0000000
--- a/packages/SystemUI/res/values-km/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN ត្រូវបានភ្ជាប់"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ត្រូវបានផ្ដាច់"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"តាម​រយៈ <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"ការ​ជូនដំណឹង"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"គ្មាន​ការជូនដំណឹងទេ"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"មីក្រូហ្វូនកំពុងថត"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"កាមេរ៉ាកំពុងថត"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"កាមេរ៉ា និងមីក្រូហ្វូនកំពុងថត"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"មីក្រូហ្វូនបានឈប់ថត"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"កាមេរ៉ាបានឈប់ថត"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"កាមេរ៉ា និងមីក្រូហ្វូនបានឈប់ថត"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"មុខងារថតវីដេអូអេក្រង់​បាន​ចាប់ផ្ដើម"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"មុខងារថតវីដេអូអេក្រង់​​បាន​បញ្ឈប់"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-kn/strings_tv.xml b/packages/SystemUI/res/values-kn/strings_tv.xml
deleted file mode 100644
index cfcc896..0000000
--- a/packages/SystemUI/res/values-kn/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ಕನೆಕ್ಷನ್ ಕಡಿತಗೊಂಡಿದೆ"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> ಮೂಲಕ"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"ಅಧಿಸೂಚನೆಗಳು"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"ಯಾವುದೇ ಅಧಿಸೂಚನೆಗಳಿಲ್ಲ"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"ಮೈಕ್ರೊಫೋನ್ ರೆಕಾರ್ಡಿಂಗ್ ಆಗುತ್ತಿದೆ"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"ಕ್ಯಾಮರಾ ರೆಕಾರ್ಡಿಂಗ್ ಆಗುತ್ತಿದೆ"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"ಕ್ಯಾಮರಾ ಮತ್ತು ಮೈಕ್ರೊಫೋನ್ ರೆಕಾರ್ಡಿಂಗ್ ಆಗುತ್ತಿವೆ"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"ಮೈಕ್ರೊಫೋನ್ ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸಿದೆ"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"ಕ್ಯಾಮರಾ ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸಲಾಗಿದೆ"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"ಕ್ಯಾಮರಾ ಮತ್ತು ಮೈಕ್ರೊಫೋನ್ ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸಿವೆ"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭವಾಗಿದೆ"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸಲಾಗಿದೆ"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ko/strings_tv.xml b/packages/SystemUI/res/values-ko/strings_tv.xml
deleted file mode 100644
index 2412ae8..0000000
--- a/packages/SystemUI/res/values-ko/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN에 연결됨"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN 연결이 해제됨"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g>에 연결됨"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"알림"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"알림 없음"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"마이크 녹음 중"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"카메라 녹화 중"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"카메라 녹화 및 마이크 녹음 중"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"마이크 녹음 중단됨"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"카메라 녹화 중단됨"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"카메라 녹화 및 마이크 녹음 중단됨"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"화면 녹화가 시작되었습니다."</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"화면 녹화가 중지되었습니다."</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ky/strings_tv.xml b/packages/SystemUI/res/values-ky/strings_tv.xml
deleted file mode 100644
index 7ad134a..0000000
--- a/packages/SystemUI/res/values-ky/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN туташтырылды"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ажыратылды"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> аркылуу"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Билдирмелер"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Билдирме жок"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Микрофон жаздырууда"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Камера жаздырууда"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Камера менен микрофон жаздырууда"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Микрофон жаздырууну токтотту"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Камера жаздырууну токтотту"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Камера менен микрофон жаздырууну токтотту"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Экрандан видео жаздырылып башталды"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Экрандан видео жаздыруу токтотулду"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-land-television/dimens.xml b/packages/SystemUI/res/values-land-television/dimens.xml
index 8fc4612..52f591f 100644
--- a/packages/SystemUI/res/values-land-television/dimens.xml
+++ b/packages/SystemUI/res/values-land-television/dimens.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
+<!-- TODO(b/289498394) move this to the TvSystemUI target -->
 <resources>
   <!-- Height of volume bar -->
   <dimen name="volume_dialog_panel_height">190dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens_tv.xml b/packages/SystemUI/res/values-ldrtl/dimens.xml
similarity index 76%
rename from packages/SystemUI/res/values/dimens_tv.xml
rename to packages/SystemUI/res/values-ldrtl/dimens.xml
index 3dbd990..0d99b61 100644
--- a/packages/SystemUI/res/values/dimens_tv.xml
+++ b/packages/SystemUI/res/values-ldrtl/dimens.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 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.
@@ -15,6 +15,6 @@
   ~ limitations under the License.
   -->
 <resources>
-    <dimen name="tv_notification_panel_width">360dp</dimen>
-    <dimen name="tv_notification_blur_radius">31dp</dimen>
-</resources>
+    <dimen name="media_output_dialog_icon_left_radius">0dp</dimen>
+    <dimen name="media_output_dialog_icon_right_radius">28dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-lo/strings_tv.xml b/packages/SystemUI/res/values-lo/strings_tv.xml
deleted file mode 100644
index 759931d..0000000
--- a/packages/SystemUI/res/values-lo/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"ເຊື່ອມຕໍ່ VPN ແລ້ວ"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"ຕັດການເຊື່ອມຕໍ່ VPN ແລ້ວ"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"ຜ່ານ <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"ການແຈ້ງເຕືອນ"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"ບໍ່ມີການແຈ້ງເຕືອນ"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"ໄມໂຄຣໂຟນກຳລັງບັນທຶກ"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"ກ້ອງຖ່າຍຮູບກຳລັງບັນທຶກ"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"ກ້ອງຖ່າຍຮູບ ແລະ ໄມໂຄຣໂຟນກຳລັງບັນທຶກ"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"ໄມໂຄຣໂຟນຢຸດການບັນທຶກແລ້ວ"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"ກ້ອງຖ່າຍຮູບຢຸດການບັນທຶກແລ້ວ"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"ກ້ອງຖ່າຍຮູບ ແລະ ໄມໂຄຣໂຟນຢຸດການບັນທຶກແລ້ວ"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"ເລີ່ມການບັນທຶກໜ້າຈໍແລ້ວ"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"ຢຸດການບັນທຶກໜ້າຈໍແລ້ວ"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-lt/strings_tv.xml b/packages/SystemUI/res/values-lt/strings_tv.xml
deleted file mode 100644
index 93c77f5..0000000
--- a/packages/SystemUI/res/values-lt/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN prijungtas"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN atjungtas"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Per „<xliff:g id="VPN_APP">%1$s</xliff:g>“"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Pranešimai"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Nėra jokių pranešimų"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofonas įrašo"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera įrašo"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera ir mikrofonas įrašo"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofonas nebeįrašo"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera nebeįrašo"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera ir mikrofonas nebeįrašo"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Ekrano vaizdo įrašymas pradėtas"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Ekrano vaizdo įrašymas sustabdytas"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-lv/strings_tv.xml b/packages/SystemUI/res/values-lv/strings_tv.xml
deleted file mode 100644
index da64f32..0000000
--- a/packages/SystemUI/res/values-lv/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"Savienojums ar VPN ir izveidots."</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Savienojums ar VPN ir pārtraukts."</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Izmantojot: <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Paziņojumi"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Nav paziņojumu"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Notiek ierakstīšana ar mikrofonu"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Notiek ierakstīšana ar kameru"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Notiek ierakstīšana ar kameru un mikrofonu"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Ierakstīšana ar mikrofonu apturēta"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Ierakstīšana ar kameru apturēta"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Ierakstīšana ar kameru un mikrofonu apturēta"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Ekrāna ierakstīšana ir sākta."</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Ekrāna ierakstīšana ir apturēta."</string>
-</resources>
diff --git a/packages/SystemUI/res/values-mk/strings_tv.xml b/packages/SystemUI/res/values-mk/strings_tv.xml
deleted file mode 100644
index 529d19d..0000000
--- a/packages/SystemUI/res/values-mk/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN е поврзана"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN е исклучена"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Преку <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Известувања"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Нема известувања"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Микрофонот снима"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Камерата снима"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Камерата и микрофонот снимаат"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Микрофонот прекина со снимање"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Камерата прекина со снимање"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Камерата и микрофонот прекинаа со снимање"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Снимањето екран започна"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Снимањето екран сопре"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ml/strings_tv.xml b/packages/SystemUI/res/values-ml/strings_tv.xml
deleted file mode 100644
index 2faccb8..0000000
--- a/packages/SystemUI/res/values-ml/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN കണക്റ്റ് ചെയ്‌തു"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN വിച്ഛേദിച്ചു"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> വഴി"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"അറിയിപ്പുകൾ"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"അറിയിപ്പുകൾ ഒന്നുമില്ല"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"മൈക്രോഫോൺ റെക്കോർഡ് ചെയ്യുകയാണ്"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"ക്യാമറ റെക്കോർഡ് ചെയ്യുകയാണ്"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"ക്യാമറയും മൈക്രോഫോണും റെക്കോർഡ് ചെയ്യുകയാണ്"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"മൈക്രോഫോൺ റെക്കോർഡ് ചെയ്യുന്നത് നിർത്തി"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"ക്യാമറ റെക്കോർഡ് ചെയ്യുന്നത് നിർത്തി"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"ക്യാമറയും മൈക്രോഫോണും റെക്കോർഡ് ചെയ്യുന്നത് നിർത്തി"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"സ്ക്രീൻ റെക്കോർഡിംഗ് ആരംഭിച്ചു"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"സ്‌ക്രീൻ റെക്കോർഡിംഗ് നിർത്തി"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-mn/strings_tv.xml b/packages/SystemUI/res/values-mn/strings_tv.xml
deleted file mode 100644
index c9b667c..0000000
--- a/packages/SystemUI/res/values-mn/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN холбогдсон"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN салсан"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g>-р"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Мэдэгдэл"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Мэдэгдэл байхгүй байна"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Микрофон бичиж байна"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Камер бичиж байна"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Камер болон микрофон бичиж байна"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Микрофон бичихээ зогсоосон"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Камер бичихээ зогсоосон"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Камер болон микрофон бичихээ зогсоосон"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Дэлгэцийн бичлэгийг эхлүүлсэн"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Дэлгэцийн бичлэгийг зогсоосон"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-mr/strings_tv.xml b/packages/SystemUI/res/values-mr/strings_tv.xml
deleted file mode 100644
index 74d60cd..0000000
--- a/packages/SystemUI/res/values-mr/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN कनेक्‍ट केले"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN डिस्कनेक्ट केले"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> द्वारे"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"सूचना"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"सूचना नाहीत"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"मायक्रोफोन रेकॉर्ड करत आहे"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"कॅमेरा रेकॉर्ड करत आहे"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"कॅमेरा आणि मायक्रोफोन रेकॉर्ड करत आहेत"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"मायक्रोफोनने रेकॉर्ड करणे थांबवले"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"कॅमेराने रेकॉर्ड करणे थांबवले"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"कॅमेरा आणि मायक्रोफोनने रेकॉर्ड करणे थांबवले"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"स्क्रीन रेकॉर्डिंग सुरू झाले आहे"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"स्क्रीन रेकॉर्डिंग थांबले आहे"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ms/strings_tv.xml b/packages/SystemUI/res/values-ms/strings_tv.xml
deleted file mode 100644
index 08b9de0..0000000
--- a/packages/SystemUI/res/values-ms/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN telah disambungkan"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN diputuskan sambungan"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Melalui <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Pemberitahuan"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Tiada Pemberitahuan"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon sedang merakam"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera sedang merakam"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera dan Mikrofon sedang merakam"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofon berhenti merakam"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera berhenti merakam"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera dan Mikrofon berhenti merakam"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Rakaman skrin dimulakan"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Rakaman skrin dihentikan"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-my/strings_tv.xml b/packages/SystemUI/res/values-my/strings_tv.xml
deleted file mode 100644
index 0ac3950..0000000
--- a/packages/SystemUI/res/values-my/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN ချိတ်ဆက်ထားသည်"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ချိတ်ဆက်မှုမရှိပါ"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> မှတစ်ဆင့်"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"အကြောင်းကြားချက်များ"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"အကြောင်းကြားချက်များ မရှိပါ"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"မိုက်ခရိုဖုန်း မှတ်တမ်းတင်နေသည်"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"ကင်မရာ မှတ်တမ်းတင်နေသည်"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"ကင်မရာနှင့် မိုက်ခရိုဖုန်းက မှတ်တမ်းတင်နေသည်"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"မိုက်ခရိုဖုန်း မှတ်တမ်းတင်ခြင်းကို ရပ်ထားသည်"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"ကင်မရာ မှတ်တမ်းတင်ခြင်းကို ရပ်ထားသည်"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"ကင်မရာနှင့် မိုက်ခရိုဖုန်းက မှတ်တမ်းတင်ခြင်းကို ရပ်ထားသည်"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"မျက်နှာပြင် ရိုက်ကူးမှု စတင်လိုက်ပါပြီ"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"မျက်နှာပြင် ရိုက်ကူးမှုကို ရပ်လိုက်ပါပြီ"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-nb/strings_tv.xml b/packages/SystemUI/res/values-nb/strings_tv.xml
deleted file mode 100644
index 5848f58..0000000
--- a/packages/SystemUI/res/values-nb/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN er tilkoblet"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN er frakoblet"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Varsler"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Ingen varsler"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofonen tar opp"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kameraet tar opp"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kameraet og mikrofonen tar opp"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofonen stoppet opptaket"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kameraet stoppet opptaket"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kameraet og mikrofonen stoppet opptaket"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Skjermopptaket er startet"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Skjermopptaket er stoppet"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ne/strings_tv.xml b/packages/SystemUI/res/values-ne/strings_tv.xml
deleted file mode 100644
index ffc315b..0000000
--- a/packages/SystemUI/res/values-ne/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN कनेक्ट गरिएको छ"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN डिस्कनेक्ट गरिएको छ"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> मार्फत"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"सूचनाहरू"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"कुनै पनि सूचना छैन"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"माइक्रोफोनले रेकर्ड गर्दै छ"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"क्यामेराले रेकर्ड गर्दै छ"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"क्यामेरा र माइक्रोफोनले रेकर्ड गर्दै छन्"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"माइक्रोफोनले रेकर्ड गर्न छाड्यो"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"क्यामेराले रेकर्ड गर्न छाड्यो"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"क्यामेरा र माइक्रोफोनले रेकर्ड गर्न छाडे"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"स्क्रिन रेकर्ड गर्न थालियो"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"स्क्रिन रेकर्ड गर्न छाडियो"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-nl/strings_tv.xml b/packages/SystemUI/res/values-nl/strings_tv.xml
deleted file mode 100644
index e018d97..0000000
--- a/packages/SystemUI/res/values-nl/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"Verbinding met VPN"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Geen verbinding met VPN"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Meldingen"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Geen meldingen"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Microfoon neemt op"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Camera neemt op"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera en microfoon nemen op"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Microfoon neemt niet meer op"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Camera neemt niet meer op"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera en microfoon nemen niet meer op"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Schermopname gestart"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Schermopname gestopt"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-or/strings_tv.xml b/packages/SystemUI/res/values-or/strings_tv.xml
deleted file mode 100644
index 178bd4b..0000000
--- a/packages/SystemUI/res/values-or/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN ସଂଯୋଗ କରାଯାଇଛି"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ବିଚ୍ଛିନ୍ନ କରାଯାଇଛି"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> ମାଧ୍ୟମରେ"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"କୌଣସି ବିଜ୍ଞପ୍ତି ନାହିଁ"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"ମାଇକ୍ରୋଫୋନ୍ ରେକର୍ଡିଂ କରୁଛି"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"କ୍ୟାମେରା ରେକର୍ଡିଂ କରୁଛି"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"କ୍ୟାମେରା ଏବଂ ମାଇକ୍ରୋଫୋନ୍ ରେକର୍ଡିଂ କରୁଛି"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"ମାଇକ୍ରୋଫୋନ୍ ରେକର୍ଡିଂ ବନ୍ଦ କରିଛି"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"କ୍ୟାମେରା ରେକର୍ଡିଂ ବନ୍ଦ କରିଛି"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"କ୍ୟାମେରା ଏବଂ ମାଇକ୍ରୋଫୋନ୍ ରେକର୍ଡିଂ ବନ୍ଦ କରିଛି"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"ସ୍କ୍ରିନ ରେକର୍ଡିଂ ଆରମ୍ଭ କରାଯାଇଛି"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"ସ୍କ୍ରିନ ରେକର୍ଡିଂ ବନ୍ଦ କରାଯାଇଛି"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-pa/strings_tv.xml b/packages/SystemUI/res/values-pa/strings_tv.xml
deleted file mode 100644
index 4939a73..0000000
--- a/packages/SystemUI/res/values-pa/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN ਕਨੈਕਟ ਹੈ"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN ਡਿਸਕਨੈਕਟ ਹੈ"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> ਰਾਹੀਂ"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"ਸੂਚਨਾਵਾਂ"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"ਕੋਈ ਸੂਚਨਾ ਨਹੀਂ"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਰਿਕਾਰਡ ਕਰ ਰਿਹਾ ਹੈ"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"ਕੈਮਰਾ ਰਿਕਾਰਡ ਕਰ ਰਿਹਾ ਹੈ"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"ਕੈਮਰਾ ਅਤੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਰਿਕਾਰਡ ਕਰ ਰਹੇ ਹਨ"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੇ ਰਿਕਾਰਡ ਕਰਨਾ ਬੰਦ ਕਰ ਦਿੱਤਾ ਹੈ"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"ਕੈਮਰੇ ਨੇ ਰਿਕਾਰਡ ਕਰਨਾ ਬੰਦ ਕਰ ਦਿੱਤਾ ਹੈ"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"ਕੈਮਰੇ ਅਤੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੇ ਰਿਕਾਰਡ ਕਰਨਾ ਬੰਦ ਕਰ ਦਿੱਤਾ ਹੈ"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਹੋਈ"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਬੰਦ ਹੋਈ"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-pl/strings_tv.xml b/packages/SystemUI/res/values-pl/strings_tv.xml
deleted file mode 100644
index 98db830..0000000
--- a/packages/SystemUI/res/values-pl/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"Połączono z VPN"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Rozłączono z VPN"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Przez: <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Powiadomienia"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Brak powiadomień"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon rejestruje"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Aparat rejestruje"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Aparat i mikrofon rejestrują"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofon przestał rejestrować"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Aparat przestał rejestrować"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Aparat i mikrofon przestały rejestrować"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Rozpoczęto nagrywanie ekranu"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Zatrzymano nagrywanie ekranu"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings_tv.xml b/packages/SystemUI/res/values-pt-rBR/strings_tv.xml
deleted file mode 100644
index 6c5ff0c..0000000
--- a/packages/SystemUI/res/values-pt-rBR/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"A VPN está conectada"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"A VPN está desconectada"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notificações"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Nenhuma notificação"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"O microfone está gravando"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"A câmera está gravando"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"A câmera e o microfone estão gravando"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"O microfone parou de gravar"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"A câmera parou de gravar"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"A câmera e o microfone pararam de gravar"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Gravação de tela iniciada"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Gravação de tela interrompida"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings_tv.xml b/packages/SystemUI/res/values-pt-rPT/strings_tv.xml
deleted file mode 100644
index c3e6c43..0000000
--- a/packages/SystemUI/res/values-pt-rPT/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"A VPN está ligada"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"A VPN está desligada"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Através de <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notificações"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Sem notificações"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"O microfone está a gravar"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"A câmara está a gravar"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"A câmara e o microfone estão a gravar"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"O microfone parou a gravação"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"A câmara parou a gravação"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"A câmara e o microfone pararam a gravação"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Gravação de ecrã iniciada"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Gravação de ecrã parada"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-pt/strings_tv.xml b/packages/SystemUI/res/values-pt/strings_tv.xml
deleted file mode 100644
index 6c5ff0c..0000000
--- a/packages/SystemUI/res/values-pt/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"A VPN está conectada"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"A VPN está desconectada"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notificações"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Nenhuma notificação"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"O microfone está gravando"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"A câmera está gravando"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"A câmera e o microfone estão gravando"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"O microfone parou de gravar"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"A câmera parou de gravar"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"A câmera e o microfone pararam de gravar"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Gravação de tela iniciada"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Gravação de tela interrompida"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ro/strings_tv.xml b/packages/SystemUI/res/values-ro/strings_tv.xml
deleted file mode 100644
index 991eef6..0000000
--- a/packages/SystemUI/res/values-ro/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN este conectat"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN este deconectat"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Prin <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notificări"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Nicio notificare"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Microfonul înregistrează"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Camera foto înregistrează"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera foto și microfonul înregistrează"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Microfonul nu mai înregistrează"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Camera foto a oprit înregistrarea"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera foto și microfonul nu mai înregistrează"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Înregistrarea ecranului a început"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Înregistrarea ecranului s-a oprit"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ru/strings_tv.xml b/packages/SystemUI/res/values-ru/strings_tv.xml
deleted file mode 100644
index e23558c..0000000
--- a/packages/SystemUI/res/values-ru/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN-подключение установлено"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN-подключение отключено"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Через приложение <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Уведомления"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Уведомлений нет."</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Выполняется запись с микрофона"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Выполняется запись с камеры"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Выполняется запись с камеры и микрофона"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Запись с микрофона остановлена"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Запись с камеры остановлена"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Запись с камеры и микрофона остановлена"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Запись видео с экрана началась."</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Запись видео с экрана остановлена."</string>
-</resources>
diff --git a/packages/SystemUI/res/values-si/strings_tv.xml b/packages/SystemUI/res/values-si/strings_tv.xml
deleted file mode 100644
index 1eaa24a..0000000
--- a/packages/SystemUI/res/values-si/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN සම්බන්ධිතයි"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN විසන්ධි කර ඇත"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> හරහා"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"දැනුම්දීම්"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"දැනුම්දීම් නැත"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"මයික්‍රෆෝනය පටිගත කරයි"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"කැමරාව පටිගත කරයි"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"කැමරාව සහ මයික්‍රෆෝනය පටිගත කරයි"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"මයික්‍රෆෝනය පටිගත කිරීම නැවැත්වීය"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"කැමරාව පටිගත කිරීම නැවැත්වීය"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"කැමරාව සහ මයික්‍රෆෝනය පටිගත කිරීම නැවැත්වීය"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"තිර පටිගත කිරීම ආරම්භ විය"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"තිර පටිගත කිරීම නතර විය"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-sk/strings_tv.xml b/packages/SystemUI/res/values-sk/strings_tv.xml
deleted file mode 100644
index a7479aa..0000000
--- a/packages/SystemUI/res/values-sk/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"Sieť VPN je pripojená"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Sieť VPN je odpojená"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Cez: <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Upozornenia"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Žiadne upozornenia"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofón nahráva"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera nahráva"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera a mikrofón nahrávajú"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofón prestal nahrávať"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera prestala nahrávať"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera a mikrofón prestali nahrávať"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Nahrávanie obrazovky bolo spustené"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Nahrávanie obrazovky bolo zastavené"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-sl/strings_tv.xml b/packages/SystemUI/res/values-sl/strings_tv.xml
deleted file mode 100644
index 765b590..0000000
--- a/packages/SystemUI/res/values-sl/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"Povezava z omrežjem VPN je vzpostavljena"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Povezava z omrežjem VPN je prekinjena"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Prek storitve <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Obvestila"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Ni obvestil"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Snemanje z mikrofonom"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Snemanje s fotoaparatom"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Snemanje s fotoaparatom in mikrofonom"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Ustavljeno snemanje z mikrofonom"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Ustavljeno snemanje s fotoaparatom"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Ustavljeno snemanje s fotoaparatom in mikrofonom"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Snemanje zaslona se je začelo."</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Snemanje zaslona je ustavljeno."</string>
-</resources>
diff --git a/packages/SystemUI/res/values-sq/strings_tv.xml b/packages/SystemUI/res/values-sq/strings_tv.xml
deleted file mode 100644
index 12b42b8..0000000
--- a/packages/SystemUI/res/values-sq/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN-ja është e lidhur"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN-ja është shkëputur"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Nëpërmjet <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Njoftimet"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Asnjë njoftim"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"\"Mikrofoni\" po regjistron"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"\"Kamera\" po regjistron"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"\"Kamera\" dhe \"Mikrofoni\" po regjistrojnë"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"\"Mikrofoni\" ndaloi së regjistruari"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"\"Kamera\" ndaloi së regjistruari"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"\"Kamera\" dhe \"Mikrofoni\" ndaluan së regjistruari"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Regjistrimi i ekranit filloi"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Regjistrimi i ekranit ndaloi"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-sr/strings_tv.xml b/packages/SystemUI/res/values-sr/strings_tv.xml
deleted file mode 100644
index 85c38ca..0000000
--- a/packages/SystemUI/res/values-sr/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN је повезан"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Веза са VPN-ом је прекинута"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Преко: <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Обавештења"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Нема обавештења"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Микрофон снима"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Камера снима"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Камера и микрофон снимају"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Снимање микрофоном је заустављено"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Снимање камером је заустављено"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Снимање камером и микрофоном је заустављено"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Снимање екрана је започето"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Снимање екрана је заустављено"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-sv/strings_tv.xml b/packages/SystemUI/res/values-sv/strings_tv.xml
deleted file mode 100644
index 6830436..0000000
--- a/packages/SystemUI/res/values-sv/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN är anslutet"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN är frånkopplat"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Aviseringar"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Inga aviseringar"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofonen spelar in"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kameran spelar in"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kameran och mikrofonen spelar in"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofonen slutade spela in"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kameran slutade spela in"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kameran och mikrofonen slutade spelade in"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Skärminspelningen har startats"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Skärminspelningen har stoppats"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-sw/strings_tv.xml b/packages/SystemUI/res/values-sw/strings_tv.xml
deleted file mode 100644
index f3e2ca2..0000000
--- a/packages/SystemUI/res/values-sw/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN imeunganishwa"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN imeondolewa"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Kupitia <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Arifa"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Hakuna Arifa"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Maikrofoni inarekodi"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera inarekodi"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera na Maikrofoni zinarekodi"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Maikrofoni imeacha kurekodi"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera imeacha kurekodi"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera na Maikrofoni zimeacha kurekodi"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Imeanza kurekodi skrini"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Imeacha kurekodi skrini"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ta/strings_tv.xml b/packages/SystemUI/res/values-ta/strings_tv.xml
deleted file mode 100644
index 3e0baf6..0000000
--- a/packages/SystemUI/res/values-ta/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN இணைக்கப்பட்டது"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN துண்டிக்கப்பட்டது"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> வழியாக"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"அறிவிப்புகள்"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"அறிவிப்புகள் எதுவுமில்லை"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"மைக்ரோஃபோன் ரெக்கார்டு செய்கிறது"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"கேமரா ரெக்கார்டு செய்கிறது"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"கேமராவும் மைக்ரோஃபோனும் ரெக்கார்டு செய்கின்றன"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"மைக்ரோஃபோன் ரெக்கார்டு செய்வதை நிறுத்திவிட்டது"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"கேமரா ரெக்கார்டு செய்வதை நிறுத்திவிட்டது"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"கேமராவும் மைக்ரோஃபோனும் ரெக்கார்டு செய்வதை நிறுத்திவிட்டன"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"ஸ்கிரீன் ரெக்கார்டிங் தொடங்கியது"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"ஸ்கிரீன் ரெக்கார்டிங் நிறுத்தப்பட்டது"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-te/strings_tv.xml b/packages/SystemUI/res/values-te/strings_tv.xml
deleted file mode 100644
index 2131064..0000000
--- a/packages/SystemUI/res/values-te/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN కనెక్ట్ అయింది"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN డిస్‌కనెక్ట్ అయింది"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> ద్వారా"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"నోటిఫికేషన్‌లు"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"నోటిఫికేషన్‌లు లేవు"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"మైక్రోఫోన్ రికార్డింగ్ చేస్తోంది"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"కెమెరా రికార్డింగ్ చేస్తోంది"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"కెమెరా, మైక్రోఫోన్‌లు రికార్డింగ్ చేస్తున్నాయి"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"మైక్రోఫోన్ రికార్డింగ్ చేయడం ఆపివేసింది"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"రికార్డింగ్ చేయడాన్ని కెమెరా ఆపివేసింది"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"కెమెరా, మైక్రోఫోన్‌లు రికార్డింగ్ చేయడం ఆపివేశాయి"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"స్క్రీన్ రికార్డింగ్ ప్రారంభమైంది"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"స్క్రీన్ రికార్డింగ్ ఆపివేయబడింది"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-television/colors.xml b/packages/SystemUI/res/values-television/colors.xml
deleted file mode 100644
index 3e9e182..0000000
--- a/packages/SystemUI/res/values-television/colors.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright 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.
- */
--->
-<resources>
-    <color name="volume_dialog_background_color">#E61F232B</color>
-    <color name="volume_dialog_background_color_above_blur">#C71F232B</color>
-
-    <color name="bottom_sheet_icon_color">#D2E3FC</color>
-
-    <color name="bottom_sheet_title_color">#E8F0FE</color>
-    <color name="bottom_sheet_body_color">#D2E3FC</color>
-
-    <color name="bottom_sheet_background_color">#1F232C</color>
-    <color name="bottom_sheet_background_color_with_blur">#AA1A2734</color>
-
-    <color name="bottom_sheet_button_background_color_focused">#E8F0FE</color>
-    <color name="bottom_sheet_button_background_color_unfocused">#0FE8EAED</color>
-
-    <color name="bottom_sheet_button_text_color_focused">#DB202124</color>
-    <color name="bottom_sheet_button_text_color_unfocused">#B5E8EAED</color>
-
-    <color name="privacy_mic_cam_chip">#5BB974</color> <!-- g400 -->
-    <color name="privacy_media_projection_chip">#C9CCD0</color>
-    <color name="privacy_icon_tint">#30302A</color>
-    <color name="privacy_chip_dot_bg_tint">#66000000</color>
-    <color name="cast_connected_fill">#FF0000</color>
-</resources>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
deleted file mode 100644
index 2ace86f..0000000
--- a/packages/SystemUI/res/values-television/config.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2019, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
-     for different hardware and product builds. -->
-<resources>
-    <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
-    <integer name="recents_svelte_level">3</integer>
-
-    <!-- Show a separate icon for low and high volume on the volume dialog -->
-    <bool name="config_showLowMediaVolumeIcon">true</bool>
-
-    <!-- Change the volume row tint when it is inactive, i.e. when it is being dismissed -->
-    <bool name="config_changeVolumeRowTintWhenInactive">false</bool>
-
-    <!-- The duraction of the show animation for the volume dialog in milliseconds -->
-    <integer name="config_dialogShowAnimationDurationMs">600</integer>
-
-    <!-- The duraction of the hide animation for the volume dialog in milliseconds -->
-    <integer name="config_dialogHideAnimationDurationMs">400</integer>
-
-    <!-- Whether to use window background blur for the volume dialog. -->
-    <bool name="config_volumeDialogUseBackgroundBlur">true</bool>
-
-    <!-- Whether to tint the icon of the sensor hardware privacy toggle unblock dialog.
-        Set to false if using a custom icon. -->
-    <bool name="config_unblockHwSensorIconEnableTint">true</bool>
-
-    <!-- Configuration to set Learn more in device logs as URL link -->
-    <bool name="log_access_confirmation_learn_more_as_link">false</bool>
-</resources>
diff --git a/packages/SystemUI/res/values-television/dimens.xml b/packages/SystemUI/res/values-television/dimens.xml
deleted file mode 100644
index ee615d9..0000000
--- a/packages/SystemUI/res/values-television/dimens.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<resources>
-    <!-- Opacity at which the background for the shutdown UI will be drawn. -->
-    <item name="shutdown_scrim_behind_alpha" format="float" type="dimen">1.0</item>
-
-    <dimen name="bottom_sheet_padding_horizontal">32dp</dimen>
-    <dimen name="bottom_sheet_padding_vertical">24dp</dimen>
-
-    <dimen name="bottom_sheet_icon_size">42dp</dimen>
-    <dimen name="bottom_sheet_icon_margin">8dp</dimen>
-    <dimen name="bottom_sheet_title_margin_bottom">18dp</dimen>
-    <dimen name="bottom_sheet_details_margin_bottom">8dp</dimen>
-
-    <dimen name="bottom_sheet_actions_width">296dp</dimen>
-    <dimen name="bottom_sheet_actions_spacing">12dp</dimen>
-    <item name="bottom_sheet_button_selection_scaled" format="float" type="dimen">1.1</item>
-    <dimen name="bottom_sheet_button_width">232dp</dimen>
-    <dimen name="bottom_sheet_button_padding_horizontal">20dp</dimen>
-    <dimen name="bottom_sheet_button_padding_vertical">16dp</dimen>
-
-    <dimen name="bottom_sheet_corner_radius">24dp</dimen>
-    <dimen name="bottom_sheet_button_corner_radius">10dp</dimen>
-
-    <dimen name="bottom_sheet_min_height">208dp</dimen>
-    <dimen name="bottom_sheet_margin">24dp</dimen>
-    <dimen name="bottom_sheet_background_blur_radius">37dp</dimen>
-
-    <dimen name="privacy_chips_max_width">110dp</dimen>
-    <dimen name="privacy_chips_bar_padding">9dp</dimen>
-    <dimen name="privacy_chip_margin">3dp</dimen>
-    <dimen name="privacy_chip_icon_margin_in_between">4dp</dimen>
-    <dimen name="privacy_chip_padding_horizontal">5dp</dimen>
-    <dimen name="privacy_chip_icon_size">12dp</dimen>
-    <dimen name="privacy_chip_collapsed_icon_size">10dp</dimen>
-    <dimen name="privacy_chip_height">24dp</dimen>
-    <dimen name="privacy_chip_radius">12dp</dimen>
-
-    <dimen name="privacy_chip_dot_size">8dp</dimen>
-    <dimen name="privacy_chip_dot_radius">4dp</dimen>
-
-    <dimen name="privacy_chip_dot_bg_width">24dp</dimen>
-    <dimen name="privacy_chip_dot_bg_height">18dp</dimen>
-    <dimen name="privacy_chip_dot_bg_radius">9dp</dimen>
-
-    <dimen name="unblock_hw_sensor_icon_width">@dimen/bottom_sheet_icon_size</dimen>
-    <dimen name="unblock_hw_sensor_icon_height">@dimen/bottom_sheet_icon_size</dimen>
-
-</resources>
diff --git a/packages/SystemUI/res/values-television/integers.xml b/packages/SystemUI/res/values-television/integers.xml
deleted file mode 100644
index 02f5d0d..0000000
--- a/packages/SystemUI/res/values-television/integers.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<resources>
-    <!-- The position of the volume dialog on the screen.
-         See com.android.systemui.volume.VolumeDialogImpl.
-         Value 81 corresponds to BOTTOM|CENTER_HORIZONTAL.
-         Value 21 corresponds to RIGHT|CENTER_VERTICAL.
-         Value 8388629 corresponds to END|CENTER_VERTICAL -->
-    <integer name="volume_dialog_gravity">8388629</integer>
-
-    <integer name="privacy_chip_animation_millis">300</integer>
-</resources>
diff --git a/packages/SystemUI/res/values-television/styles.xml b/packages/SystemUI/res/values-television/styles.xml
index c517845..4a4fac2 100644
--- a/packages/SystemUI/res/values-television/styles.xml
+++ b/packages/SystemUI/res/values-television/styles.xml
@@ -14,15 +14,9 @@
      limitations under the License.
 -->
 
+<!-- TODO(b/289498394) move this to the TvSystemUI target -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog" />
-    <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert" />
-
-    <style name="Animation.ShutdownUi">
-        <item name="android:windowEnterAnimation">@null</item>
-        <item name="android:windowExitAnimation">@null</item>
-    </style>
 
     <style name="volume_dialog_theme" parent="Theme.SystemUI">
         <item name="android:colorAccent">@color/tv_volume_dialog_accent</item>
@@ -30,43 +24,4 @@
         <item name="android:dialogCornerRadius">@dimen/volume_dialog_panel_width_half</item>
     </style>
 
-    <style name="PrivacyChip">
-        <item name="android:colorError">@color/cast_connected_fill</item>
-    </style>
-
-    <style name="BottomSheet" parent="Theme.Leanback">
-        <item name="android:windowIsFloating">true</item>
-        <item name="android:windowActivityTransitions">true</item>
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowIsTranslucent">true</item>
-        <item name="android:backgroundDimAmount">0.2</item>
-    </style>
-
-    <style name="BottomSheet.TitleText">
-        <item name="android:textSize">28sp</item>
-        <item name="android:textColor">@color/bottom_sheet_title_color</item>
-    </style>
-
-    <style name="BottomSheet.BodyText">
-        <item name="android:textSize">16sp</item>
-        <item name="android:textColor">@color/bottom_sheet_body_color</item>
-    </style>
-
-    <style name="BottomSheet.ActionItem">
-        <item name="android:layout_width">@dimen/bottom_sheet_button_width</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:gravity">left|center_vertical</item>
-        <item name="android:textSize">16sp</item>
-        <item name="android:textColor">@color/bottom_sheet_button_text_color</item>
-        <item name="android:background">@drawable/bottom_sheet_button_background</item>
-        <item name="android:paddingHorizontal">@dimen/bottom_sheet_button_padding_horizontal</item>
-        <item name="android:paddingVertical">@dimen/bottom_sheet_button_padding_vertical</item>
-        <item name="android:stateListAnimator">@anim/tv_bottom_sheet_button_state_list_animator</item>
-    </style>
-
-    <!-- The style for log access consent button -->
-    <style name="LogAccessDialogTheme" parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
-        <item name="permissionGrantButtonTopStyle">?android:buttonBarButtonStyle</item>
-        <item name="permissionGrantButtonBottomStyle">?android:buttonBarButtonStyle</item>
-    </style>
 </resources>
diff --git a/packages/SystemUI/res/values-th/strings_tv.xml b/packages/SystemUI/res/values-th/strings_tv.xml
deleted file mode 100644
index 1df2612..0000000
--- a/packages/SystemUI/res/values-th/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"เชื่อมต่อ VPN แล้ว"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"ยกเลิกการเชื่อมต่อ VPN แล้ว"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"ผ่าน <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"การแจ้งเตือน"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"ไม่มีการแจ้งเตือน"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"ไมโครโฟนกำลังบันทึก"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"กล้องกำลังบันทึก"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"กล้องและไมโครโฟนกำลังบันทึก"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"ไมโครโฟนหยุดบันทึกแล้ว"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"กล้องหยุดบันทึกแล้ว"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"กล้องและไมโครโฟนหยุดบันทึกแล้ว"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"เริ่มบันทึกหน้าจอแล้ว"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"หยุดการบันทึกหน้าจอแล้ว"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-tl/strings_tv.xml b/packages/SystemUI/res/values-tl/strings_tv.xml
deleted file mode 100644
index 890ccd4..0000000
--- a/packages/SystemUI/res/values-tl/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"Nakakonekta ang VPN"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Nakadiskonekta ang VPN"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Sa pamamagitan ng <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Mga Notification"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Walang Notification"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Nagre-record ang Mikropono"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Nagre-record ang Camera"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Nagre-record ang Camera at Mikropono"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Huminto sa pag-record ang Mikropono"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Huminto sa pag-record ang Camera"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Huminto sa pag-record ang Camera at Mikropono"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Sinimulan ang pag-record ng screen"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Itinigil ang pag-record ng screen"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-tr/strings_tv.xml b/packages/SystemUI/res/values-tr/strings_tv.xml
deleted file mode 100644
index f981aa2..0000000
--- a/packages/SystemUI/res/values-tr/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN bağlandı"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN bağlantısı kesildi"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> üzerinden"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Bildirimler"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Bildirim Yok"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon kaydediyor"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera kaydediyor"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera ve Mikrofon kaydediyor"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofon kaydı durdu"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera kaydı durdu"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera ve Mikrofon kaydı durdu"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Ekran kaydı başladı"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Ekran kaydı durduruldu"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-uk/strings_tv.xml b/packages/SystemUI/res/values-uk/strings_tv.xml
deleted file mode 100644
index 8b96aac..0000000
--- a/packages/SystemUI/res/values-uk/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"Мережу VPN під\'єднано"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Мережу VPN від\'єднано"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Через <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Сповіщення"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Немає сповіщень"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Мікрофон записує"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Камера записує"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Камера й мікрофон записують"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Мікрофон припинив запис"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Камера припинила запис"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Камера й мікрофон припинили запис"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Запис відео з екрана розпочато"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Запис відео з екрана зупинено"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-ur/strings_tv.xml b/packages/SystemUI/res/values-ur/strings_tv.xml
deleted file mode 100644
index ed186ac..0000000
--- a/packages/SystemUI/res/values-ur/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"‏VPN منسلک ہے"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"‏VPN غیر منسلک ہے"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"بذریعہ <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"اطلاعات"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"کوئی اطلاع نہیں ہے"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"مائیکروفون ریکارڈ کر رہا ہے"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"کیمرا ریکارڈ کر رہا ہے"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"کیمرا اور مائیکروفون ریکارڈ کر رہا ہے"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"مائیکروفون نے ریکارڈ کرنا بند کر دیا"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"کیمرے نے ریکارڈ کرنا بند کر دیا"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"کیمرے اور مائیکروفون نے ریکارڈ کرنا بند کر دیا"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"اسکرین ریکارڈنگ شروع ہو گئی"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"اسکرین ریکارڈنگ بند کر دی گئی"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-uz/strings_tv.xml b/packages/SystemUI/res/values-uz/strings_tv.xml
deleted file mode 100644
index c34ee4f..0000000
--- a/packages/SystemUI/res/values-uz/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN ulandi"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN uzildi"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> orqali"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Bildirishnomalar"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Bildirishnomalar yoʻq"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Mikrofon yozib olmoqda"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Kamera yozib olmoqda"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Kamera va mikrofon yozib olmoqda"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Mikrofon yozib olishni toʻxtatdi"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Kamera yozib olishni toʻxtatdi"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Kamera va mikrofon yozib olishni toʻxtatdi"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Ekrandan yozib olish boshlandi"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Ekrandan yozib olish toʻxtatildi"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-vi/strings_tv.xml b/packages/SystemUI/res/values-vi/strings_tv.xml
deleted file mode 100644
index b140fc0..0000000
--- a/packages/SystemUI/res/values-vi/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN đã được kết nối"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN đã bị ngắt kết nối"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Thông qua <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Thông báo"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Không có thông báo"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Micrô đang ghi"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Máy ảnh đang ghi"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Máy ảnh và micrô đang ghi"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Micrô đã dừng ghi"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Máy ảnh đã dừng ghi"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Máy ảnh và micrô đã dừng ghi"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Đã bắt đầu ghi màn hình"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Đã dừng ghi màn hình"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings_tv.xml b/packages/SystemUI/res/values-zh-rCN/strings_tv.xml
deleted file mode 100644
index ad4d94b0..0000000
--- a/packages/SystemUI/res/values-zh-rCN/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN 已连接"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN 已断开连接"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"通过“<xliff:g id="VPN_APP">%1$s</xliff:g>”"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"通知"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"没有通知"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"麦克风正在录制"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"相机正在录制"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"相机和麦克风正在录制"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"麦克风已停止录制"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"相机已停止录制"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"相机和麦克风已停止录制"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"已开始录制屏幕"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"已停止录制屏幕"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings_tv.xml b/packages/SystemUI/res/values-zh-rHK/strings_tv.xml
deleted file mode 100644
index 88ec5e0..0000000
--- a/packages/SystemUI/res/values-zh-rHK/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN 已連線"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN 已中斷連線"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"透過 <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"通知"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"沒有通知"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"麥克風正在錄音"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"相機正在錄影"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"相機和麥克風正在錄影及錄音"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"麥克風已停止錄音"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"相機已停止錄影"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"相機和麥克風已停止錄影及錄音"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"已開始錄製螢幕畫面"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"已停止錄製螢幕畫面"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings_tv.xml b/packages/SystemUI/res/values-zh-rTW/strings_tv.xml
deleted file mode 100644
index b6b1b1e..0000000
--- a/packages/SystemUI/res/values-zh-rTW/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN 已連線"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN 連線已中斷"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"透過「<xliff:g id="VPN_APP">%1$s</xliff:g>」"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"通知"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"沒有通知"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"麥克風正在錄音"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"相機正在錄影"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"相機和麥克風正在錄影及錄音"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"麥克風已停止錄音"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"相機已停止錄影"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"相機和麥克風已停止錄影及錄音"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"已開始錄製螢幕畫面"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"已停止錄製螢幕畫面"</string>
-</resources>
diff --git a/packages/SystemUI/res/values-zu/strings_tv.xml b/packages/SystemUI/res/values-zu/strings_tv.xml
deleted file mode 100644
index 59fcb8d..0000000
--- a/packages/SystemUI/res/values-zu/strings_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_vpn_connected" msgid="3891023882833274730">"I-VPN ixhunyiwe"</string>
-    <string name="notification_vpn_disconnected" msgid="7150747626448044843">"I-VPN inqanyuliwe"</string>
-    <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Nge-<xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="tv_notification_panel_title" msgid="5311050946506276154">"Izaziso"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Azikho Izaziso"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"Imakrofoni iyarekhoda"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"Ikhamera iyarekhoda"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Ikhamera nemakrofoni kuyarekhoda"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Imakrofoni iyekile ukurekhoda"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Ikhamera iyeke ukurekhoda"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Ikhemera nemakrofoni kuyekile ukurekhoda"</string>
-    <string name="screen_recording_announcement" msgid="2996750593472241520">"Ukurekhoda isikrini kuqalile"</string>
-    <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Ukurekhoda isikrini kumisiwe"</string>
-</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index db7eb7a..ab75498 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -16,7 +16,8 @@
  * limitations under the License.
  */
 -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <drawable name="notification_number_text_color">#ffffffff</drawable>
     <drawable name="system_bar_background">@color/system_bar_background_opaque</drawable>
     <color name="system_bar_background_opaque">#ff000000</color>
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index 51b57bd..2bab3cb 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -16,6 +16,7 @@
  * limitations under the License.
  */
 -->
+<!-- TODO(b/289498394) move this to the TvSystemUI target -->
 <resources>
     <color name="recents_tv_card_background_color">#FF263238</color>
     <color name="recents_tv_card_title_text_color">#CCEEEEEE</color>
@@ -32,8 +33,4 @@
     <color name="tv_volume_dialog_seek_bar_background">#A03C4043</color>
     <color name="tv_volume_dialog_seek_bar_fill">#FFF8F9FA</color>
     <color name="tv_volume_dialog_accent">#FFDADCE0</color>
-
-    <color name="tv_notification_default_background_color">#383838</color>
-    <color name="tv_notification_blur_background_color">#a0383838</color>
-    <color name="tv_notification_text_color">#FFFFFF</color>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9c864ab..a38c629 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -366,10 +366,16 @@
 
     <!-- Whether or not notifications that can be expanded will always be in their expanded state.
          This value only affects notifications that are not a group of notifications from the same
-         applications. If this value is false, then only the first notification will be expanded;
-         the other notifications need to be manually expanded by the user. -->
+         applications. If this value is false, then only the first notification will be expanded
+         when config_autoExpandFirstNotification is true; the other notifications need to be
+         manually expanded by the user. -->
     <bool name="config_alwaysExpandNonGroupedNotifications">false</bool>
 
+    <!-- Whether or not the first expandable notification will be expanded automatically by the
+         system. This value only affects notifications that are not a group of notifications from
+         the same applications and when config_alwaysExpandNonGroupedNotifications is false. -->
+    <bool name="config_autoExpandFirstNotification">true</bool>
+
     <!-- Whether or not an expandable notification can be manually expanded or collapsed by the
          user. Grouped notifications are still expandable even if this value is false. -->
     <bool name="config_enableNonGroupedNotificationExpand">true</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3366f4f..5a15dce 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1097,6 +1097,9 @@
 
     <dimen name="ongoing_appops_dialog_side_padding">16dp</dimen>
 
+    <dimen name="privacy_dialog_background_radius_large">12dp</dimen>
+    <dimen name="privacy_dialog_background_radius_small">4dp</dimen>
+
     <!-- Size of media cards in the QSPanel carousel -->
     <dimen name="qs_media_padding">16dp</dimen>
     <dimen name="qs_media_album_radius">14dp</dimen>
@@ -1346,6 +1349,8 @@
     <dimen name="media_output_dialog_default_margin_end">16dp</dimen>
     <dimen name="media_output_dialog_selectable_margin_end">80dp</dimen>
     <dimen name="media_output_dialog_list_padding_top">8dp</dimen>
+    <dimen name="media_output_dialog_icon_left_radius">28dp</dimen>
+    <dimen name="media_output_dialog_icon_right_radius">0dp</dimen>
 
     <!-- Distance that the full shade transition takes in order to complete by tapping on a button
          like "expand". -->
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 3a2177a..15ca9d4 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -214,4 +214,8 @@
     <item type="id" name="nssl_guideline" />
     <item type="id" name="lock_icon" />
     <item type="id" name="lock_icon_bg" />
+
+    <!-- Privacy dialog -->
+    <item type="id" name="privacy_dialog_close_app_button" />
+    <item type="id" name="privacy_dialog_manage_app_button" />
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f8c13b0..c11e7ba2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2582,6 +2582,9 @@
     <!-- Tooltip to show in management screen when there are multiple structures [CHAR_LIMIT=50] -->
     <string name="controls_structure_tooltip">Swipe to see more</string>
 
+    <!-- Accessibility action informing the user how they can retry face authentication [CHAR LIMIT=NONE] -->
+    <string name="retry_face">Retry face authentication</string>
+
     <!-- Message to tell the user to wait while systemui attempts to load a set of
          recommended controls [CHAR_LIMIT=60] -->
     <string name="controls_seeding_in_progress">Loading recommendations</string>
@@ -3167,10 +3170,43 @@
     <!--- Content of toast triggered when the notes app entry point is triggered without setting a default notes app. [CHAR LIMIT=NONE] -->
     <string name="set_default_notes_app_toast_content">Set default notes app in Settings</string>
 
-    <!--
-    Label for a button that, when clicked, sends the user to the app store to install an app.
-
-    [CHAR LIMIT=64].
-    -->
+    <!-- Label for a button that, when clicked, sends the user to the app store to install an app. [CHAR LIMIT=64]. -->
     <string name="install_app">Install app</string>
+
+    <!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
+    <string name="privacy_dialog_title">Microphone &amp; Camera</string>
+    <!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_summary">Recent app use</string>
+    <!-- Label of the secondary button of the privacy dialog, used to check recent app usage of phone sensors [CHAR LIMIT=30] -->
+    <string name="privacy_dialog_more_button">See recent access</string>
+    <!-- Label of the primary button to dismiss the privacy dialog [CHAR LIMIT=20] -->
+    <string name="privacy_dialog_done_button">Done</string>
+    <!-- Description for expanding a collapsible widget in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_expand_action">Expand and show options</string>
+    <!-- Description for collapsing a collapsible widget in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_collapse_action">Collapse</string>
+    <!-- Label of a button of the privacy dialog to close an app actively using a phone sensor [CHAR LIMIT=50] -->
+    <string name="privacy_dialog_close_app_button">Close this app</string>
+    <!-- Message shown in the privacy dialog when an app actively using a phone sensor is closed [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_close_app_message"><xliff:g id="app_name" example="Gmail">%1$s</xliff:g> closed</string>
+    <!-- Label of a button of the privacy dialog to learn more of a service actively or recently using a phone sensor [CHAR LIMIT=50] -->
+    <string name="privacy_dialog_manage_service">Manage service</string>
+    <!-- Label of a button of the privacy dialog to manage permissions of an app actively or recently using a phone sensor [CHAR LIMIT=50] -->
+    <string name="privacy_dialog_manage_permissions">Manage access</string>
+    <!-- Label for active usage of a phone sensor by phone call in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_active_call_usage">In use by phone call</string>
+    <!-- Label for recent usage of a phone sensor by phone call in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_recent_call_usage">Recently used in phone call</string>
+    <!-- Label for active app usage of a phone sensor in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_active_app_usage">In use by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g></string>
+    <!-- Label for recent app usage of a phone sensor in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_recent_app_usage">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g></string>
+    <!-- Label for active app usage of a phone sensor with sub-attribution or proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_active_app_usage_1">In use by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g>)</string>
+    <!-- Label for recent app usage of a phone sensor with sub-attribution or proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_recent_app_usage_1">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g>)</string>
+    <!-- Label for active app usage of a phone sensor with sub-attribution and proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_active_app_usage_2">In use by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
+    <!-- Label for recent app usage of a phone sensor with sub-attribution and proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_recent_app_usage_2">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
 </resources>
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
deleted file mode 100644
index 8e372e4..0000000
--- a/packages/SystemUI/res/values/strings_tv.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Title and subtitle for AudioRecordingIndicator -->
-
-    <string name="notification_vpn_connected">VPN is connected</string>
-    <string name="notification_vpn_disconnected">VPN is disconnected</string>
-    <!-- Disclosure text in the connected notification that indicates that the device is connected to a VPN. The placeholder is the VPN name. [CHAR LIMIT=40] -->
-    <string name="notification_disclosure_vpn_text">Via <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
-
-    <string name="tv_notification_panel_title">Notifications</string>
-    <string name="tv_notification_panel_no_notifications">No Notifications</string>
-
-    <string name="mic_recording_announcement">Microphone is recording</string>
-    <string name="camera_recording_announcement">Camera is recording</string>
-    <string name="mic_and_camera_recording_announcement">Camera and Microphone are recording</string>
-    <string name="mic_stopped_recording_announcement">Microphone stopped recording</string>
-    <string name="camera_stopped_recording_announcement">Camera stopped recording</string>
-    <string name="mic_camera_stopped_recording_announcement">Camera and Microphone stopped recording</string>
-    <string name="screen_recording_announcement">Screen recording started</string>
-    <string name="screen_stopped_recording_announcement">Screen recording stopped</string>
-
-</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6b85621..d520670 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1116,7 +1116,7 @@
     <style name="Widget.Dialog.Button">
         <item name="android:buttonCornerRadius">28dp</item>
         <item name="android:background">@drawable/qs_dialog_btn_filled</item>
-        <item name="android:textColor">?androidprv:attr/materialColorOnPrimary</item>
+        <item name="android:textColor">@color/qs_dialog_btn_filled_text_color</item>
         <item name="android:textSize">14sp</item>
         <item name="android:lineHeight">20sp</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
@@ -1126,13 +1126,13 @@
 
     <style name="Widget.Dialog.Button.BorderButton">
         <item name="android:background">@drawable/qs_dialog_btn_outline</item>
-        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+        <item name="android:textColor">@color/qs_dialog_btn_outline_text</item>
     </style>
 
     <style name="Widget.Dialog.Button.Large">
         <item name="android:background">@drawable/qs_dialog_btn_filled_large</item>
         <item name="android:minHeight">56dp</item>
-        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryFixed</item>
+        <item name="android:textColor">@color/qs_dialog_btn_filled_large_text</item>
     </style>
 
     <style name="Widget.Dialog.Button.QuickSettings">
@@ -1439,4 +1439,22 @@
         <item name="android:windowEnterAnimation">@anim/long_press_lock_screen_popup_enter</item>
         <item name="android:windowExitAnimation">@anim/long_press_lock_screen_popup_exit</item>
     </style>
+
+    <style name="TextAppearance.PrivacyDialog.Item.Title"
+           parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">14sp</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+    </style>
+
+    <style name="TextAppearance.PrivacyDialog.Item.Summary"
+           parent="@android:style/TextAppearance.DeviceDefault.Small">
+        <item name="android:textSize">14sp</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
+    </style>
+
+    <style name="Theme.PrivacyDialog" parent="@style/Theme.SystemUI.Dialog">
+        <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainer</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/values/styles_tv.xml b/packages/SystemUI/res/values/styles_tv.xml
deleted file mode 100644
index 3e09026..0000000
--- a/packages/SystemUI/res/values/styles_tv.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <style name="PipTheme" parent="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
-        <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:backgroundDimEnabled">false</item>
-        <item name="android:windowDisablePreview">true</item>
-     </style>
-
-    <style name="TvSidePanelTheme">
-        <item name="android:windowIsTranslucent">true</item>
-        <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:backgroundDimEnabled">false</item>
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowContentOverlay">@null</item>
-        <item name="android:windowIsFloating">true</item>
-    </style>
-</resources>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 28e786b..ca30e15 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -65,7 +65,7 @@
     ],
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
 }
 
 java_library {
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
index d8085b9..22cdb30 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -20,6 +20,7 @@
 import android.os.PowerManager
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.FaceAuthApiRequestReason.Companion.ACCESSIBILITY_ACTION
 import com.android.keyguard.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED
 import com.android.keyguard.FaceAuthApiRequestReason.Companion.PICK_UP_GESTURE_TRIGGERED
 import com.android.keyguard.FaceAuthApiRequestReason.Companion.QS_EXPANDED
@@ -71,6 +72,7 @@
     NOTIFICATION_PANEL_CLICKED,
     QS_EXPANDED,
     PICK_UP_GESTURE_TRIGGERED,
+    ACCESSIBILITY_ACTION,
 )
 annotation class FaceAuthApiRequestReason {
     companion object {
@@ -80,6 +82,7 @@
         const val QS_EXPANDED = "Face auth due to QS expansion."
         const val PICK_UP_GESTURE_TRIGGERED =
             "Face auth due to pickup gesture triggered when the device is awake and not from AOD."
+        const val ACCESSIBILITY_ACTION = "Face auth due to an accessibility action."
     }
 }
 
@@ -217,7 +220,8 @@
     @UiEvent(doc = STRONG_AUTH_ALLOWED_CHANGED)
     FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED),
     @UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED)
-    FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED);
+    FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED),
+    @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION);
 
     override fun getId(): Int = this.id
 
@@ -233,6 +237,8 @@
             FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED,
         QS_EXPANDED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED,
         PICK_UP_GESTURE_TRIGGERED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED,
+        PICK_UP_GESTURE_TRIGGERED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED,
+        ACCESSIBILITY_ACTION to FaceAuthUiEvent.FACE_AUTH_ACCESSIBILITY_ACTION,
     )
 
 /** Converts the [reason] to the corresponding [FaceAuthUiEvent]. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index f952337..bc24249 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -68,6 +68,7 @@
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
+import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.biometrics.SideFpsUiRequestSource;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
@@ -417,9 +418,11 @@
             BouncerMessageInteractor bouncerMessageInteractor,
             Provider<JavaAdapter> javaAdapter,
             UserInteractor userInteractor,
+            FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
             Provider<SceneInteractor> sceneInteractor
     ) {
         super(view);
+        view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
         mLockPatternUtils = lockPatternUtils;
         mUpdateMonitor = keyguardUpdateMonitor;
         mSecurityModel = keyguardSecurityModel;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index a6252a3..7585279 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -113,6 +113,7 @@
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusView:");
         pw.println("  mDarkAmount: " + mDarkAmount);
+        pw.println("  visibility: " + getVisibility());
         if (mClockView != null) {
             mClockView.dump(pw, args);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 6854c97..a04d13b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -37,15 +37,19 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
+import androidx.viewpager.widget.ViewPager;
 
 import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.logging.KeyguardLogger;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -58,14 +62,17 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.ViewController;
 
+import java.io.PrintWriter;
+
 import javax.inject.Inject;
 
 /**
  * Injectable controller for {@link KeyguardStatusView}.
  */
-public class KeyguardStatusViewController extends ViewController<KeyguardStatusView> {
+public class KeyguardStatusViewController extends ViewController<KeyguardStatusView> implements
+        Dumpable {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
-    private static final String TAG = "KeyguardStatusViewController";
+    @VisibleForTesting static final String TAG = "KeyguardStatusViewController";
 
     /**
      * Duration to use for the animator when the keyguard status view alignment changes, and a
@@ -87,6 +94,8 @@
 
     private Boolean mStatusViewCentered = true;
 
+    private DumpManager mDumpManager;
+
     private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
             new TransitionListenerAdapter() {
                 @Override
@@ -112,7 +121,8 @@
             ScreenOffAnimationController screenOffAnimationController,
             KeyguardLogger logger,
             FeatureFlags featureFlags,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            DumpManager dumpManager) {
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
         mKeyguardClockSwitchController = keyguardClockSwitchController;
@@ -123,11 +133,13 @@
                 logger.getBuffer());
         mInteractionJankMonitor = interactionJankMonitor;
         mFeatureFlags = featureFlags;
+        mDumpManager = dumpManager;
     }
 
     @Override
     public void onInit() {
         mKeyguardClockSwitchController.init();
+        mDumpManager.registerDumpable(this);
     }
 
     @Override
@@ -143,6 +155,13 @@
     }
 
     /**
+     * Called in notificationPanelViewController to avoid leak
+     */
+    public void onDestroy() {
+        mDumpManager.unregisterDumpable(TAG);
+    }
+
+    /**
      * Updates views on doze time tick.
      */
     public void dozeTimeTick() {
@@ -365,6 +384,19 @@
             // Excluding media from the transition on split-shade, as it doesn't transition
             // horizontally properly.
             transition.excludeTarget(R.id.status_view_media_container, true);
+
+            // Exclude smartspace viewpager and its children from the transition.
+            //     - Each step of the transition causes the ViewPager to invoke resize,
+            //     which invokes scrolling to the recalculated position. The scrolling
+            //     actions are congested, resulting in kinky translation, and
+            //     delay in settling to the final position. (http://b/281620564#comment1)
+            //     - Also, the scrolling is unnecessary in the transition. We just want
+            //     the viewpager to stay on the same page.
+            //     - Exclude by Class type instead of resource id, since the resource id
+            //     isn't available for all devices, and probably better to exclude all
+            //     ViewPagers any way.
+            transition.excludeTarget(ViewPager.class, true);
+            transition.excludeChildren(ViewPager.class, true);
         }
 
         transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -397,6 +429,24 @@
                 adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
                 adapter.addTarget(clockView);
                 set.addTransition(adapter);
+
+                if (splitShadeEnabled) {
+                    // Exclude smartspace viewpager and its children from the transition set.
+                    //     - This is necessary in addition to excluding them from the
+                    //     ChangeBounds child transition.
+                    //     - Without this, the viewpager is scrolled to the new position
+                    //     (corresponding to its end size) before the size change is realized.
+                    //     Note that the size change is realized at the end of the ChangeBounds
+                    //     transition. With the "prescrolling", the viewpager ends up in a weird
+                    //     position, then recovers smoothly during the transition, and ends at
+                    //     the position for the current page.
+                    //     - Exclude by Class type instead of resource id, since the resource id
+                    //     isn't available for all devices, and probably better to exclude all
+                    //     ViewPagers any way.
+                    set.excludeTarget(ViewPager.class, true);
+                    set.excludeChildren(ViewPager.class, true);
+                }
+
                 set.addListener(mKeyguardStatusAlignmentTransitionListener);
                 TransitionManager.beginDelayedTransition(notifContainerParent, set);
             }
@@ -408,6 +458,11 @@
         constraintSet.applyTo(notifContainerParent);
     }
 
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        mView.dump(pw, args);
+    }
+
     @VisibleForTesting
     static class SplitShadeTransitionAdapter extends Transition {
         private static final String PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft";
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 35198de..5833d98 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1496,7 +1496,7 @@
 
                 @Override
                 public void onDetectionStatusChanged(@NonNull FaceDetectionStatus status) {
-                    handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
+                    handleBiometricDetected(status.getUserId(), FACE, status.isStrongBiometric());
                 }
             };
 
@@ -3158,6 +3158,10 @@
             return false;
         }
 
+        if (isFaceAuthInteractorEnabled()) {
+            return mFaceAuthInteractor.canFaceAuthRun();
+        }
+
         final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
         final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
                 && !statusBarShadeLocked;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 71f78c3..3990b10 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.Assert;
 
 import com.google.errorprone.annotations.CompileTimeConstant;
 
@@ -85,6 +86,7 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
+        Assert.isMainThread();
         PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
         boolean isOccluded = mKeyguardStateController.isOccluded();
         mKeyguardViewVisibilityAnimating = false;
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 04acd0b..b01e136 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -49,7 +49,6 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentService;
-import com.android.systemui.hdmi.HdmiCecSetMenuLanguageHelper;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -242,7 +241,6 @@
     @Inject Lazy<LocationController> mLocationController;
     @Inject Lazy<RotationLockController> mRotationLockController;
     @Inject Lazy<ZenModeController> mZenModeController;
-    @Inject Lazy<HdmiCecSetMenuLanguageHelper> mHdmiCecSetMenuLanguageHelper;
     @Inject Lazy<HotspotController> mHotspotController;
     @Inject Lazy<CastController> mCastController;
     @Inject Lazy<FlashlightController> mFlashlightController;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 2f6a68c..03ad132 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -54,6 +54,7 @@
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -188,6 +189,7 @@
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final NotificationShadeWindowController mNotificationShadeController;
     private final ShadeController mShadeController;
+    private final Lazy<ShadeViewController> mShadeViewController;
     private final StatusBarWindowCallback mNotificationShadeCallback;
     private boolean mDismissNotificationShadeActionRegistered;
 
@@ -196,12 +198,14 @@
             UserTracker userTracker,
             NotificationShadeWindowController notificationShadeController,
             ShadeController shadeController,
+            Lazy<ShadeViewController> shadeViewController,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             Optional<Recents> recentsOptional,
             DisplayTracker displayTracker) {
         mContext = context;
         mUserTracker = userTracker;
         mShadeController = shadeController;
+        mShadeViewController = shadeViewController;
         mRecentsOptional = recentsOptional;
         mDisplayTracker = displayTracker;
         mReceiver = new SystemActionsBroadcastReceiver();
@@ -330,8 +334,7 @@
         final Optional<CentralSurfaces> centralSurfacesOptional =
                 mCentralSurfacesOptionalLazy.get();
         if (centralSurfacesOptional.isPresent()
-                && centralSurfacesOptional.get().getShadeViewController() != null
-                && centralSurfacesOptional.get().getShadeViewController().isPanelExpanded()
+                && mShadeViewController.get().isPanelExpanded()
                 && !centralSurfacesOptional.get().isKeyguardShowing()) {
             if (!mDismissNotificationShadeActionRegistered) {
                 mA11yManager.registerSystemAction(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
index 783460c3..0ef256d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -145,6 +145,8 @@
      */
     @MainThread
     fun updateFontScaleDelayed(delayMsFromSource: Long) {
+        doneButton.isEnabled = false
+
         var delayMs = delayMsFromSource
         if (systemClock.elapsedRealtime() - lastUpdateTime < MIN_UPDATE_INTERVAL_MS) {
             delayMs += MIN_UPDATE_INTERVAL_MS
@@ -197,17 +199,22 @@
             title.post {
                 title.setTextAppearance(R.style.TextAppearance_Dialog_Title)
                 doneButton.setTextAppearance(R.style.Widget_Dialog_Button)
+                doneButton.isEnabled = true
             }
         }
     }
 
     @WorkerThread
     fun updateFontScale() {
-        systemSettings.putStringForUser(
-            Settings.System.FONT_SCALE,
-            strEntryValues[lastProgress.get()],
-            userTracker.userId
-        )
+        if (
+            !systemSettings.putStringForUser(
+                Settings.System.FONT_SCALE,
+                strEntryValues[lastProgress.get()],
+                userTracker.userId
+            )
+        ) {
+            title.post { doneButton.isEnabled = true }
+        }
     }
 
     @WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index a977966..deb3d03 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 package com.android.systemui.authentication.data.repository
 
 import com.android.internal.widget.LockPatternChecker
@@ -29,6 +27,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.time.SystemClock
 import dagger.Binds
 import dagger.Module
@@ -38,16 +37,14 @@
 import kotlin.coroutines.suspendCoroutine
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
 /** Defines interface for classes that can access authentication-related application state. */
@@ -156,32 +153,18 @@
     }
 
     override val isAutoConfirmEnabled: StateFlow<Boolean> =
-        userRepository.selectedUserInfo
-            .map { it.id }
-            .flatMapLatest { userId ->
-                flow { emit(lockPatternUtils.isAutoPinConfirmEnabled(userId)) }
-                    .flowOn(backgroundDispatcher)
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
+        refreshingFlow(
+            initialValue = false,
+            getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled,
+        )
 
     override val hintedPinLength: Int = 6
 
     override val isPatternVisible: StateFlow<Boolean> =
-        userRepository.selectedUserInfo
-            .map { it.id }
-            .flatMapLatest { userId ->
-                flow { emit(lockPatternUtils.isVisiblePatternEnabled(userId)) }
-                    .flowOn(backgroundDispatcher)
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = true,
-            )
+        refreshingFlow(
+            initialValue = true,
+            getFreshValue = lockPatternUtils::isVisiblePatternEnabled,
+        )
 
     private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
     override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()
@@ -276,6 +259,48 @@
             )
         }
     }
+
+    /**
+     * Returns a [StateFlow] that's automatically kept fresh. The passed-in [getFreshValue] is
+     * invoked on a background thread every time the selected user is changed and every time a new
+     * downstream subscriber is added to the flow.
+     *
+     * Initially, the flow will emit [initialValue] while it refreshes itself in the background by
+     * invoking the [getFreshValue] function and emitting the fresh value when that's done.
+     *
+     * Every time the selected user is changed, the flow will re-invoke [getFreshValue] and emit the
+     * new value.
+     *
+     * Every time a new downstream subscriber is added to the flow it first receives the latest
+     * cached value that's either the [initialValue] or the latest previously fetched value. In
+     * addition, adding a new downstream subscriber also triggers another [getFreshValue] call and a
+     * subsequent emission of that newest value.
+     */
+    private fun <T> refreshingFlow(
+        initialValue: T,
+        getFreshValue: suspend (selectedUserId: Int) -> T,
+    ): StateFlow<T> {
+        val flow = MutableStateFlow(initialValue)
+        applicationScope.launch {
+            combine(
+                    // Emits a value initially and every time the selected user is changed.
+                    userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged(),
+                    // Emits a value only when the number of downstream subscribers of this flow
+                    // increases.
+                    flow.subscriptionCount.pairwise(initialValue = 0).filter { (previous, current)
+                        ->
+                        current > previous
+                    },
+                ) { selectedUserId, _ ->
+                    selectedUserId
+                }
+                .collect { selectedUserId ->
+                    flow.value = withContext(backgroundDispatcher) { getFreshValue(selectedUserId) }
+                }
+        }
+
+        return flow.asStateFlow()
+    }
 }
 
 @Module
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 b482977..d4371bf 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
@@ -104,7 +104,9 @@
             }
             .stateIn(
                 scope = applicationScope,
-                started = SharingStarted.Eagerly,
+                // Make sure this is kept as WhileSubscribed or we can run into a bug where the
+                // downstream continues to receive old/stale/cached values.
+                started = SharingStarted.WhileSubscribed(),
                 initialValue = null,
             )
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
new file mode 100644
index 0000000..b9fa240
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
@@ -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.systemui.biometrics
+
+import android.content.res.Resources
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import com.android.keyguard.FaceAuthApiRequestReason
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import javax.inject.Inject
+
+/**
+ * Accessibility delegate that will add a click accessibility action to a view when face auth can
+ * run. When the click a11y action is triggered, face auth will retry.
+ */
+@SysUISingleton
+class FaceAuthAccessibilityDelegate
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val faceAuthInteractor: KeyguardFaceAuthInteractor,
+) : View.AccessibilityDelegate() {
+    override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+        super.onInitializeAccessibilityNodeInfo(host, info)
+        if (keyguardUpdateMonitor.shouldListenForFace()) {
+            val clickActionToRetryFace =
+                AccessibilityNodeInfo.AccessibilityAction(
+                    AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+                    resources.getString(R.string.retry_face)
+                )
+            info.addAction(clickActionToRetryFace)
+        }
+    }
+
+    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()
+            true
+        } else super.performAccessibilityAction(host, action, args)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 083e21f..37ce444 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -17,7 +17,12 @@
 
 import android.app.ActivityTaskManager
 import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Color
 import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.Rect
 import android.hardware.biometrics.BiometricOverlayConstants
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
@@ -28,23 +33,27 @@
 import android.hardware.fingerprint.ISidefpsController
 import android.os.Handler
 import android.util.Log
+import android.util.RotationUtils
 import android.view.Display
 import android.view.DisplayInfo
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.Surface
 import android.view.View
+import android.view.View.AccessibilityDelegate
 import android.view.ViewPropertyAnimator
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+import android.view.accessibility.AccessibilityEvent
+import androidx.annotation.RawRes
 import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
-import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -55,7 +64,6 @@
 import com.android.systemui.util.traceSection
 import java.io.PrintWriter
 import javax.inject.Inject
-import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
@@ -78,7 +86,6 @@
     @Main private val mainExecutor: DelayableExecutor,
     @Main private val handler: Handler,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
-    private val sideFpsOverlayViewModelFactory: Provider<SideFpsOverlayViewModel>,
     @Application private val scope: CoroutineScope,
     dumpManager: DumpManager
 ) : Dumpable {
@@ -243,15 +250,105 @@
     private fun createOverlayForDisplay(@BiometricOverlayConstants.ShowReason reason: Int) {
         val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
         overlayView = view
-        SideFpsOverlayViewBinder.bind(
-            view = view,
-            viewModel = sideFpsOverlayViewModelFactory.get(),
-            overlayViewParams = overlayViewParams,
-            reason = reason,
-            context = context,
+        val display = context.display!!
+        // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation
+        display.getDisplayInfo(displayInfo)
+        val offsets =
+            sensorProps.getLocation(display.uniqueId).let { location ->
+                if (location == null) {
+                    Log.w(TAG, "No location specified for display: ${display.uniqueId}")
+                }
+                location ?: sensorProps.location
+            }
+        overlayOffsets = offsets
+
+        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+        view.rotation =
+            display.asSideFpsAnimationRotation(
+                offsets.isYAligned(),
+                getRotationFromDefault(displayInfo.rotation)
+            )
+        lottie.setAnimation(
+            display.asSideFpsAnimation(
+                offsets.isYAligned(),
+                getRotationFromDefault(displayInfo.rotation)
+            )
         )
+        lottie.addLottieOnCompositionLoadedListener {
+            // Check that view is not stale, and that overlayView has not been hidden/removed
+            if (overlayView != null && overlayView == view) {
+                updateOverlayParams(display, it.bounds)
+            }
+        }
         orientationReasonListener.reason = reason
+        lottie.addOverlayDynamicColor(context, reason)
+
+        /**
+         * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
+         * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
+         * in focus
+         */
+        view.setAccessibilityDelegate(
+            object : AccessibilityDelegate() {
+                override fun dispatchPopulateAccessibilityEvent(
+                    host: View,
+                    event: AccessibilityEvent
+                ): Boolean {
+                    return if (
+                        event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+                    ) {
+                        true
+                    } else {
+                        super.dispatchPopulateAccessibilityEvent(host, event)
+                    }
+                }
+            }
+        )
     }
+
+    @VisibleForTesting
+    fun updateOverlayParams(display: Display, bounds: Rect) {
+        val isNaturalOrientation = display.isNaturalOrientation()
+        val isDefaultOrientation =
+            if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
+        val size = windowManager.maximumWindowMetrics.bounds
+
+        val displayWidth = if (isDefaultOrientation) size.width() else size.height()
+        val displayHeight = if (isDefaultOrientation) size.height() else size.width()
+        val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
+        val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
+
+        val sensorBounds =
+            if (overlayOffsets.isYAligned()) {
+                Rect(
+                    displayWidth - boundsWidth,
+                    overlayOffsets.sensorLocationY,
+                    displayWidth,
+                    overlayOffsets.sensorLocationY + boundsHeight
+                )
+            } else {
+                Rect(
+                    overlayOffsets.sensorLocationX,
+                    0,
+                    overlayOffsets.sensorLocationX + boundsWidth,
+                    boundsHeight
+                )
+            }
+
+        RotationUtils.rotateBounds(
+            sensorBounds,
+            Rect(0, 0, displayWidth, displayHeight),
+            getRotationFromDefault(display.rotation)
+        )
+
+        overlayViewParams.x = sensorBounds.left
+        overlayViewParams.y = sensorBounds.top
+
+        windowManager.updateViewLayout(overlayView, overlayViewParams)
+    }
+
+    private fun getRotationFromDefault(rotation: Int): Int =
+        if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
 }
 
 private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
@@ -276,12 +373,89 @@
 private fun ActivityTaskManager.topClass(): String =
     getTasks(1).firstOrNull()?.topActivity?.className ?: ""
 
+@RawRes
+private fun Display.asSideFpsAnimation(yAligned: Boolean, rotationFromDefault: Int): Int =
+    when (rotationFromDefault) {
+        Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+        Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+        else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
+    }
+
+private fun Display.asSideFpsAnimationRotation(yAligned: Boolean, rotationFromDefault: Int): Float =
+    when (rotationFromDefault) {
+        Surface.ROTATION_90 -> if (yAligned) 0f else 180f
+        Surface.ROTATION_180 -> 180f
+        Surface.ROTATION_270 -> if (yAligned) 180f else 0f
+        else -> 0f
+    }
+
 private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
 
 private fun Display.isNaturalOrientation(): Boolean =
     rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
 
-public class OrientationReasonListener(
+private fun LottieAnimationView.addOverlayDynamicColor(
+    context: Context,
+    @BiometricOverlayConstants.ShowReason reason: Int
+) {
+    fun update() {
+        val isKeyguard = reason == REASON_AUTH_KEYGUARD
+        if (isKeyguard) {
+            val color =
+                com.android.settingslib.Utils.getColorAttrDefaultColor(
+                    context,
+                    com.android.internal.R.attr.materialColorPrimaryFixed
+                )
+            val outerRimColor =
+                com.android.settingslib.Utils.getColorAttrDefaultColor(
+                    context,
+                    com.android.internal.R.attr.materialColorPrimaryFixedDim
+                )
+            val chevronFill =
+                com.android.settingslib.Utils.getColorAttrDefaultColor(
+                    context,
+                    com.android.internal.R.attr.materialColorOnPrimaryFixed
+                )
+            addValueCallback(KeyPath(".blue600", "**"), LottieProperty.COLOR_FILTER) {
+                PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
+            }
+            addValueCallback(KeyPath(".blue400", "**"), LottieProperty.COLOR_FILTER) {
+                PorterDuffColorFilter(outerRimColor, PorterDuff.Mode.SRC_ATOP)
+            }
+            addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+                PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
+            }
+        } else {
+            if (!isDarkMode(context)) {
+                addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+                    PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
+                }
+            }
+            for (key in listOf(".blue600", ".blue400")) {
+                addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+                    PorterDuffColorFilter(
+                        context.getColor(R.color.settingslib_color_blue400),
+                        PorterDuff.Mode.SRC_ATOP
+                    )
+                }
+            }
+        }
+    }
+
+    if (composition != null) {
+        update()
+    } else {
+        addLottieOnCompositionLoadedListener { update() }
+    }
+}
+
+private fun isDarkMode(context: Context): Boolean {
+    val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+    return darkMode == Configuration.UI_MODE_NIGHT_YES
+}
+
+@VisibleForTesting
+class OrientationReasonListener(
     context: Context,
     displayManager: DisplayManager,
     handler: Handler,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index efbde4c..efc92ad 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -16,11 +16,8 @@
 
 package com.android.systemui.biometrics.data.repository
 
-import android.hardware.biometrics.ComponentInfoInternal
 import android.hardware.biometrics.SensorLocationInternal
-import android.hardware.biometrics.SensorProperties
 import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -33,8 +30,10 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.shareIn
 
 /**
@@ -44,17 +43,22 @@
  */
 interface FingerprintPropertyRepository {
 
+    /**
+     * If the repository is initialized or not. Other properties are defaults until this is true.
+     */
+    val isInitialized: Flow<Boolean>
+
     /** The id of fingerprint sensor. */
-    val sensorId: Flow<Int>
+    val sensorId: StateFlow<Int>
 
     /** The security strength of sensor (convenience, weak, strong). */
-    val strength: Flow<SensorStrength>
+    val strength: StateFlow<SensorStrength>
 
     /** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
-    val sensorType: Flow<FingerprintSensorType>
+    val sensorType: StateFlow<FingerprintSensorType>
 
     /** The sensor location relative to each physical display. */
-    val sensorLocations: Flow<Map<String, SensorLocationInternal>>
+    val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
 }
 
 @SysUISingleton
@@ -62,10 +66,10 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val fingerprintManager: FingerprintManager?
+    private val fingerprintManager: FingerprintManager?,
 ) : FingerprintPropertyRepository {
 
-    private val props: Flow<FingerprintSensorPropertiesInternal> =
+    override val isInitialized: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
                     object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -73,47 +77,45 @@
                             sensors: List<FingerprintSensorPropertiesInternal>
                         ) {
                             if (sensors.isNotEmpty()) {
-                                trySendWithFailureLogging(sensors[0], TAG, "initialize properties")
-                            } else {
-                                trySendWithFailureLogging(
-                                    DEFAULT_PROPS,
-                                    TAG,
-                                    "initialize with default properties"
-                                )
+                                setProperties(sensors[0])
+                                trySendWithFailureLogging(true, TAG, "initialize properties")
                             }
                         }
                     }
                 fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
-                trySendWithFailureLogging(DEFAULT_PROPS, TAG, "initialize with default properties")
+                trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
                 awaitClose {}
             }
             .shareIn(scope = applicationScope, started = SharingStarted.Eagerly, replay = 1)
 
-    override val sensorId: Flow<Int> = props.map { it.sensorId }
-    override val strength: Flow<SensorStrength> =
-        props.map { sensorStrengthIntToObject(it.sensorStrength) }
-    override val sensorType: Flow<FingerprintSensorType> =
-        props.map { sensorTypeIntToObject(it.sensorType) }
-    override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
-        props.map {
-            it.allLocations.associateBy { sensorLocationInternal ->
+    private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
+    override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+
+    private val _strength: MutableStateFlow<SensorStrength> =
+        MutableStateFlow(SensorStrength.CONVENIENCE)
+    override val strength = _strength.asStateFlow()
+
+    private val _sensorType: MutableStateFlow<FingerprintSensorType> =
+        MutableStateFlow(FingerprintSensorType.UNKNOWN)
+    override val sensorType = _sensorType.asStateFlow()
+
+    private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
+        MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
+    override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
+        _sensorLocations.asStateFlow()
+
+    private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
+        _sensorId.value = prop.sensorId
+        _strength.value = sensorStrengthIntToObject(prop.sensorStrength)
+        _sensorType.value = sensorTypeIntToObject(prop.sensorType)
+        _sensorLocations.value =
+            prop.allLocations.associateBy { sensorLocationInternal ->
                 sensorLocationInternal.displayId
             }
-        }
+    }
 
     companion object {
         private const val TAG = "FingerprintPropertyRepositoryImpl"
-        private val DEFAULT_PROPS =
-            FingerprintSensorPropertiesInternal(
-                -1 /* sensorId */,
-                SensorProperties.STRENGTH_CONVENIENCE,
-                0 /* maxEnrollmentsPerUser */,
-                listOf<ComponentInfoInternal>(),
-                FingerprintSensorProperties.TYPE_UNKNOWN,
-                false /* halControlsIllumination */,
-                true /* resetLockoutRequiresHardwareAuthToken */,
-                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
-            )
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
index 37f39cb..aa85e5f3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
@@ -17,24 +17,16 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.hardware.biometrics.SensorLocationInternal
+import android.util.Log
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
 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
-import kotlinx.coroutines.flow.combine
 
 /** Business logic for SideFps overlay offsets. */
 interface SideFpsOverlayInteractor {
-    /** The displayId of the current display. */
-    val displayId: Flow<String>
 
-    /** The corresponding offsets based on different displayId. */
-    val overlayOffsets: Flow<SensorLocationInternal>
-
-    /** Update the displayId. */
-    fun changeDisplay(displayId: String?)
+    /** Get the corresponding offsets based on different displayId. */
+    fun getOverlayOffsets(displayId: String): SensorLocationInternal
 }
 
 @SysUISingleton
@@ -43,16 +35,14 @@
 constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) :
     SideFpsOverlayInteractor {
 
-    private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
-    override val displayId: Flow<String> = _displayId.asStateFlow()
-
-    override val overlayOffsets: Flow<SensorLocationInternal> =
-        combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
-            offsets[displayId] ?: SensorLocationInternal.DEFAULT
+    override fun getOverlayOffsets(displayId: String): SensorLocationInternal {
+        val offsets = fingerprintPropertyRepository.sensorLocations.value
+        return if (offsets.containsKey(displayId)) {
+            offsets[displayId]!!
+        } else {
+            Log.w(TAG, "No location specified for display: $displayId")
+            offsets[""]!!
         }
-
-    override fun changeDisplay(displayId: String?) {
-        _displayId.value = displayId ?: ""
     }
 
     companion object {
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 e5a4d1a..7ae1443 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
@@ -479,10 +479,10 @@
                 failureReason,
                 messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
                 authenticateAfterError = modalities.hasFingerprint,
-                suppressIf = { currentMessage ->
+                suppressIf = { currentMessage, history ->
                     modalities.hasFaceAndFingerprint &&
                         failedModality == BiometricModality.Face &&
-                        currentMessage.isError
+                        (currentMessage.isError || history.faceFailed)
                 },
                 failedModality = failedModality,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
deleted file mode 100644
index 0409519..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * 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.biometrics.ui.binder
-
-import android.content.Context
-import android.content.res.Configuration
-import android.graphics.Color
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import android.hardware.biometrics.BiometricOverlayConstants
-import android.view.View
-import android.view.WindowManager
-import android.view.accessibility.AccessibilityEvent
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.model.KeyPath
-import com.android.systemui.R
-import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.launch
-
-/** Sub-binder for SideFpsOverlayView. */
-object SideFpsOverlayViewBinder {
-
-    /** Bind the view. */
-    @JvmStatic
-    fun bind(
-        view: View,
-        viewModel: SideFpsOverlayViewModel,
-        overlayViewParams: WindowManager.LayoutParams,
-        @BiometricOverlayConstants.ShowReason reason: Int,
-        @Application context: Context
-    ) {
-        val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
-
-        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
-
-        viewModel.changeDisplay()
-
-        view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.STARTED) {
-                launch {
-                    viewModel.sideFpsAnimationRotation.collect { rotation ->
-                        view.rotation = rotation
-                    }
-                }
-
-                launch {
-                    // TODO(b/221037350, wenhuiy): Create a separate ViewBinder for sideFpsAnimation
-                    // in order to add scuba tests in the future.
-                    viewModel.sideFpsAnimation.collect { animation ->
-                        lottie.setAnimation(animation)
-                    }
-                }
-
-                launch {
-                    viewModel.sensorBounds.collect { sensorBounds ->
-                        overlayViewParams.x = sensorBounds.left
-                        overlayViewParams.y = sensorBounds.top
-
-                        windowManager.updateViewLayout(view, overlayViewParams)
-                    }
-                }
-
-                launch {
-                    viewModel.overlayOffsets.collect { overlayOffsets ->
-                        lottie.addLottieOnCompositionLoadedListener {
-                            viewModel.updateSensorBounds(
-                                it.bounds,
-                                windowManager.maximumWindowMetrics.bounds,
-                                overlayOffsets
-                            )
-                        }
-                    }
-                }
-            }
-        }
-
-        lottie.addOverlayDynamicColor(context, reason)
-
-        /**
-         * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
-         * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
-         * in focus
-         */
-        view.accessibilityDelegate =
-            object : View.AccessibilityDelegate() {
-                override fun dispatchPopulateAccessibilityEvent(
-                    host: View,
-                    event: AccessibilityEvent
-                ): Boolean {
-                    return if (
-                        event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
-                    ) {
-                        true
-                    } else {
-                        super.dispatchPopulateAccessibilityEvent(host, event)
-                    }
-                }
-            }
-    }
-}
-
-private fun LottieAnimationView.addOverlayDynamicColor(
-    context: Context,
-    @BiometricOverlayConstants.ShowReason reason: Int
-) {
-    fun update() {
-        val isKeyguard = reason == BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-        if (isKeyguard) {
-            val color = context.getColor(R.color.numpad_key_color_secondary) // match bouncer color
-            val chevronFill =
-                com.android.settingslib.Utils.getColorAttrDefaultColor(
-                    context,
-                    android.R.attr.textColorPrimaryInverse
-                )
-            for (key in listOf(".blue600", ".blue400")) {
-                addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
-                    PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
-                }
-            }
-            addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
-                PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
-            }
-        } else if (!isDarkMode(context)) {
-            addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
-                PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
-            }
-        } else if (isDarkMode(context)) {
-            for (key in listOf(".blue600", ".blue400")) {
-                addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
-                    PorterDuffColorFilter(
-                        context.getColor(R.color.settingslib_color_blue400),
-                        PorterDuff.Mode.SRC_ATOP
-                    )
-                }
-            }
-        }
-    }
-
-    if (composition != null) {
-        update()
-    } else {
-        addLottieOnCompositionLoadedListener { update() }
-    }
-}
-
-private fun isDarkMode(context: Context): Boolean {
-    val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
-    return darkMode == Configuration.UI_MODE_NIGHT_YES
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt
new file mode 100644
index 0000000..d002bf0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.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.biometrics.ui.viewmodel
+
+import com.android.systemui.biometrics.shared.model.BiometricModality
+
+/** Contains metadata about key events that have occurred while biometric prompt is showing. */
+interface PromptHistory {
+
+    /** If face authentication has failed at least once. */
+    val faceFailed: Boolean
+
+    /** If fingerprint authentication has failed at least once. */
+    val fingerprintFailed: Boolean
+}
+
+class PromptHistoryImpl : PromptHistory {
+    private var failures = mutableSetOf<BiometricModality>()
+
+    override val faceFailed
+        get() = failures.contains(BiometricModality.Face)
+
+    override val fingerprintFailed
+        get() = failures.contains(BiometricModality.Fingerprint)
+
+    /** Record a failure event. */
+    fun failure(modality: BiometricModality) {
+        if (modality != BiometricModality.None) {
+            failures.add(modality)
+        }
+    }
+}
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 8a2e405..dca19c5 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
@@ -204,6 +204,7 @@
             }
             .distinctUntilChanged()
 
+    private val history = PromptHistoryImpl()
     private var messageJob: Job? = null
 
     /**
@@ -214,13 +215,13 @@
      * is set (or via [showHelp] when not set) after the error is dismissed.
      *
      * The error is ignored if the user has already authenticated or if [suppressIf] is true given
-     * the currently showing [PromptMessage].
+     * the currently showing [PromptMessage] and [PromptHistory].
      */
     suspend fun showTemporaryError(
         message: String,
         messageAfterError: String,
         authenticateAfterError: Boolean,
-        suppressIf: (PromptMessage) -> Boolean = { false },
+        suppressIf: (PromptMessage, PromptHistory) -> Boolean = { _, _ -> false },
         hapticFeedback: Boolean = true,
         failedModality: BiometricModality = BiometricModality.None,
     ) = coroutineScope {
@@ -230,7 +231,9 @@
 
         _canTryAgainNow.value = supportsRetry(failedModality)
 
-        if (suppressIf(_message.value)) {
+        val suppress = suppressIf(_message.value, history)
+        history.failure(failedModality)
+        if (suppress) {
             return@coroutineScope
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
deleted file mode 100644
index e938b4e..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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.biometrics.ui.viewmodel
-
-import android.content.Context
-import android.graphics.Rect
-import android.hardware.biometrics.SensorLocationInternal
-import android.util.RotationUtils
-import android.view.Display
-import android.view.DisplayInfo
-import android.view.Surface
-import androidx.annotation.RawRes
-import com.android.systemui.R
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
-import com.android.systemui.dagger.qualifiers.Application
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.map
-
-/** View-model for SideFpsOverlayView. */
-class SideFpsOverlayViewModel
-@Inject
-constructor(
-    @Application private val context: Context,
-    private val sideFpsOverlayInteractor: SideFpsOverlayInteractor,
-) {
-
-    private val isReverseDefaultRotation =
-        context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
-
-    private val _sensorBounds: MutableStateFlow<Rect> = MutableStateFlow(Rect())
-    val sensorBounds = _sensorBounds.asStateFlow()
-
-    val overlayOffsets: Flow<SensorLocationInternal> = sideFpsOverlayInteractor.overlayOffsets
-
-    /** Update the displayId. */
-    fun changeDisplay() {
-        sideFpsOverlayInteractor.changeDisplay(context.display!!.uniqueId)
-    }
-
-    /** Determine the rotation of the sideFps animation given the overlay offsets. */
-    val sideFpsAnimationRotation: Flow<Float> =
-        overlayOffsets.map { overlayOffsets ->
-            val display = context.display!!
-            val displayInfo: DisplayInfo = DisplayInfo()
-            // b/284098873 `context.display.rotation` may not up-to-date, we use
-            // displayInfo.rotation
-            display.getDisplayInfo(displayInfo)
-            val yAligned: Boolean = overlayOffsets.isYAligned()
-            when (getRotationFromDefault(displayInfo.rotation)) {
-                Surface.ROTATION_90 -> if (yAligned) 0f else 180f
-                Surface.ROTATION_180 -> 180f
-                Surface.ROTATION_270 -> if (yAligned) 180f else 0f
-                else -> 0f
-            }
-        }
-
-    /** Populate the sideFps animation from the overlay offsets. */
-    @RawRes
-    val sideFpsAnimation: Flow<Int> =
-        overlayOffsets.map { overlayOffsets ->
-            val display = context.display!!
-            val displayInfo: DisplayInfo = DisplayInfo()
-            // b/284098873 `context.display.rotation` may not up-to-date, we use
-            // displayInfo.rotation
-            display.getDisplayInfo(displayInfo)
-            val yAligned: Boolean = overlayOffsets.isYAligned()
-            when (getRotationFromDefault(displayInfo.rotation)) {
-                Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
-                Surface.ROTATION_180 ->
-                    if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
-                else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
-            }
-        }
-
-    /**
-     * Calculate and update the bounds of the sensor based on the bounds of the overlay view, the
-     * maximum bounds of the window, and the offsets of the sensor location.
-     */
-    fun updateSensorBounds(
-        bounds: Rect,
-        maximumWindowBounds: Rect,
-        offsets: SensorLocationInternal
-    ) {
-        val isNaturalOrientation = context.display!!.isNaturalOrientation()
-        val isDefaultOrientation =
-            if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
-
-        val displayWidth =
-            if (isDefaultOrientation) maximumWindowBounds.width() else maximumWindowBounds.height()
-        val displayHeight =
-            if (isDefaultOrientation) maximumWindowBounds.height() else maximumWindowBounds.width()
-        val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
-        val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
-
-        val sensorBounds =
-            if (offsets.isYAligned()) {
-                Rect(
-                    displayWidth - boundsWidth,
-                    offsets.sensorLocationY,
-                    displayWidth,
-                    offsets.sensorLocationY + boundsHeight
-                )
-            } else {
-                Rect(
-                    offsets.sensorLocationX,
-                    0,
-                    offsets.sensorLocationX + boundsWidth,
-                    boundsHeight
-                )
-            }
-
-        val displayInfo: DisplayInfo = DisplayInfo()
-        context.display!!.getDisplayInfo(displayInfo)
-
-        RotationUtils.rotateBounds(
-            sensorBounds,
-            Rect(0, 0, displayWidth, displayHeight),
-            getRotationFromDefault(displayInfo.rotation)
-        )
-
-        _sensorBounds.value = sensorBounds
-    }
-
-    private fun getRotationFromDefault(rotation: Int): Int =
-        if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
-}
-
-private fun Display.isNaturalOrientation(): Boolean =
-    rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-
-private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 1b14acc..844cf02 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -40,9 +40,10 @@
     ) {
 
     val pinShapes = PinShapeAdapter(applicationContext)
+    private val mutablePinInput = MutableStateFlow(PinInputViewModel.empty())
 
-    private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
-    val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
+    /** Currently entered pin keys. */
+    val pinInput: StateFlow<PinInputViewModel> = mutablePinInput
 
     /** The length of the PIN for which we should show a hint. */
     val hintedPinLength: StateFlow<Int?> = interactor.hintedPinLength
@@ -50,17 +51,19 @@
     /** Appearance of the backspace button. */
     val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
         combine(
-                mutablePinEntries,
+                mutablePinInput,
                 interactor.isAutoConfirmEnabled,
             ) { mutablePinEntries, isAutoConfirmEnabled ->
                 computeBackspaceButtonAppearance(
-                    enteredPin = mutablePinEntries,
+                    pinInput = mutablePinEntries,
                     isAutoConfirmEnabled = isAutoConfirmEnabled,
                 )
             }
             .stateIn(
                 scope = applicationScope,
-                started = SharingStarted.Eagerly,
+                // Make sure this is kept as WhileSubscribed or we can run into a bug where the
+                // downstream continues to receive old/stale/cached values.
+                started = SharingStarted.WhileSubscribed(),
                 initialValue = ActionButtonAppearance.Hidden,
             )
 
@@ -87,26 +90,23 @@
 
     /** Notifies that the user clicked on a PIN button with the given digit value. */
     fun onPinButtonClicked(input: Int) {
-        if (mutablePinEntries.value.isEmpty()) {
+        val pinInput = mutablePinInput.value
+        if (pinInput.isEmpty()) {
             interactor.clearMessage()
         }
 
-        mutablePinEntries.value += EnteredKey(input)
-
+        mutablePinInput.value = pinInput.append(input)
         tryAuthenticate(useAutoConfirm = true)
     }
 
     /** Notifies that the user clicked the backspace button. */
     fun onBackspaceButtonClicked() {
-        if (mutablePinEntries.value.isEmpty()) {
-            return
-        }
-        mutablePinEntries.value = mutablePinEntries.value.toMutableList().apply { removeLast() }
+        mutablePinInput.value = mutablePinInput.value.deleteLast()
     }
 
     /** Notifies that the user long-pressed the backspace button. */
     fun onBackspaceButtonLongPressed() {
-        mutablePinEntries.value = emptyList()
+        mutablePinInput.value = mutablePinInput.value.clearAll()
     }
 
     /** Notifies that the user clicked the "enter" button. */
@@ -115,7 +115,7 @@
     }
 
     private fun tryAuthenticate(useAutoConfirm: Boolean) {
-        val pinCode = mutablePinEntries.value.map { it.input }
+        val pinCode = mutablePinInput.value.getPin()
 
         applicationScope.launch {
             val isSuccess = interactor.authenticate(pinCode, useAutoConfirm) ?: return@launch
@@ -124,15 +124,17 @@
                 showFailureAnimation()
             }
 
-            mutablePinEntries.value = emptyList()
+            // TODO(b/291528545): this should not be cleared on success (at least until the view
+            // is animated away).
+            mutablePinInput.value = mutablePinInput.value.clearAll()
         }
     }
 
     private fun computeBackspaceButtonAppearance(
-        enteredPin: List<EnteredKey>,
+        pinInput: PinInputViewModel,
         isAutoConfirmEnabled: Boolean,
     ): ActionButtonAppearance {
-        val isEmpty = enteredPin.isEmpty()
+        val isEmpty = pinInput.isEmpty()
 
         return when {
             isAutoConfirmEnabled && isEmpty -> ActionButtonAppearance.Hidden
@@ -151,19 +153,3 @@
     /** Button is shown. */
     Shown,
 }
-
-private var nextSequenceNumber = 1
-
-/**
- * The pin bouncer [input] as digits 0-9, together with a [sequenceNumber] to indicate the ordering.
- *
- * Since the model only allows appending/removing [EnteredKey]s from the end, the [sequenceNumber]
- * is strictly increasing in input order of the pin, but not guaranteed to be monotonic or start at
- * a specific number.
- */
-data class EnteredKey
-internal constructor(val input: Int, val sequenceNumber: Int = nextSequenceNumber++) :
-    Comparable<EnteredKey> {
-    override fun compareTo(other: EnteredKey): Int =
-        compareValuesBy(this, other, EnteredKey::sequenceNumber)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModel.kt
new file mode 100644
index 0000000..4efc21b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModel.kt
@@ -0,0 +1,177 @@
+/*
+ * 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.bouncer.ui.viewmodel
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.bouncer.ui.viewmodel.EntryToken.ClearAll
+import com.android.systemui.bouncer.ui.viewmodel.EntryToken.Digit
+
+/**
+ * Immutable pin input state.
+ *
+ * The input is a hybrid of state ([Digit]) and event ([ClearAll]) tokens. The [ClearAll] token can
+ * be interpreted as a watermark, indicating that the current input up to that point is deleted
+ * (after a auth failure or when long-pressing the delete button). Therefore, [Digit]s following a
+ * [ClearAll] make up the next pin input entry. Up to two complete pin inputs are memoized.
+ *
+ * This is required when auto-confirm rejects the input, and the last digit will be animated-in at
+ * the end of the input, concurrently with the staggered clear-all animation starting to play at the
+ * beginning of the input.
+ *
+ * The input is guaranteed to always contain a initial [ClearAll] token as a sentinel, thus clients
+ * can always assume there is a 'ClearAll' watermark available.
+ */
+data class PinInputViewModel
+internal constructor(
+    @get:VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    internal val input: List<EntryToken>
+) {
+    init {
+        require(input.firstOrNull() is ClearAll) { "input does not begin with a ClearAll token" }
+        require(input.zipWithNext().all { it.first < it.second }) {
+            "EntryTokens are not sorted by their sequenceNumber"
+        }
+    }
+    /**
+     * [PinInputViewModel] with [previousInput] and appended [newToken].
+     *
+     * [previousInput] is trimmed so that the new [PinBouncerViewModel] contains at most two pin
+     * inputs.
+     */
+    private constructor(
+        previousInput: List<EntryToken>,
+        newToken: EntryToken
+    ) : this(
+        buildList {
+            addAll(
+                previousInput.subList(previousInput.indexOfLastClearAllToKeep(), previousInput.size)
+            )
+            add(newToken)
+        }
+    )
+
+    fun append(digit: Int): PinInputViewModel {
+        return PinInputViewModel(input, Digit(digit))
+    }
+
+    /**
+     * Delete last digit.
+     *
+     * This removes the last digit from the input. Returns `this` if the last token is [ClearAll].
+     */
+    fun deleteLast(): PinInputViewModel {
+        if (isEmpty()) return this
+        return PinInputViewModel(input.take(input.size - 1))
+    }
+
+    /**
+     * Appends a [ClearAll] watermark, completing the current pin.
+     *
+     * Returns `this` if the last token is [ClearAll].
+     */
+    fun clearAll(): PinInputViewModel {
+        if (isEmpty()) return this
+        return PinInputViewModel(input, ClearAll())
+    }
+
+    /** Whether the current pin is empty. */
+    fun isEmpty(): Boolean {
+        return input.last() is ClearAll
+    }
+
+    /** The current pin, or an empty list if [isEmpty]. */
+    fun getPin(): List<Int> {
+        return getDigits(mostRecentClearAll()).map { it.input }
+    }
+
+    /**
+     * The digits following the specified [ClearAll] marker, up to the next marker or the end of the
+     * input.
+     *
+     * Returns an empty list if the [ClearAll] is not in the input.
+     */
+    fun getDigits(clearAllMarker: ClearAll): List<Digit> {
+        val startIndex = input.indexOf(clearAllMarker) + 1
+        if (startIndex == 0 || startIndex == input.size) return emptyList()
+
+        return input.subList(startIndex, input.size).takeWhile { it is Digit }.map { it as Digit }
+    }
+
+    /** The most recent [ClearAll] marker. */
+    fun mostRecentClearAll(): ClearAll {
+        return input.last { it is ClearAll } as ClearAll
+    }
+
+    companion object {
+        fun empty() = PinInputViewModel(listOf(ClearAll()))
+    }
+}
+
+/**
+ * Pin bouncer entry token with a [sequenceNumber] to indicate input event ordering.
+ *
+ * Since the model only allows appending/removing [Digit]s from the end, the [sequenceNumber] is
+ * strictly increasing in input order of the pin, but not guaranteed to be monotonic or start at a
+ * specific number.
+ */
+sealed interface EntryToken : Comparable<EntryToken> {
+    val sequenceNumber: Int
+
+    /** The pin bouncer [input] as digits 0-9. */
+    data class Digit
+    internal constructor(val input: Int, override val sequenceNumber: Int = nextSequenceNumber++) :
+        EntryToken {
+        init {
+            check(input in 0..9)
+        }
+    }
+
+    /**
+     * Marker to indicate the input is completely cleared, and subsequent [EntryToken]s mark a new
+     * pin entry.
+     */
+    data class ClearAll
+    internal constructor(override val sequenceNumber: Int = nextSequenceNumber++) : EntryToken
+
+    override fun compareTo(other: EntryToken): Int =
+        compareValuesBy(this, other, EntryToken::sequenceNumber)
+
+    companion object {
+        private var nextSequenceNumber = 1
+    }
+}
+
+/**
+ * Index of the last [ClearAll] token to keep for a new [PinInputViewModel], so that after appending
+ * another [EntryToken], there are at most two pin inputs in the [PinInputViewModel].
+ */
+private fun List<EntryToken>.indexOfLastClearAllToKeep(): Int {
+    require(isNotEmpty() && first() is ClearAll)
+
+    var seenClearAll = 0
+    for (i in size - 1 downTo 0) {
+        if (get(i) is ClearAll) {
+            seenClearAll++
+            if (seenClearAll == 2) {
+                return i
+            }
+        }
+    }
+
+    // The first element is guaranteed to be a ClearAll marker.
+    return 0
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
index e342ac2..566a74a 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
@@ -17,6 +17,7 @@
 package com.android.systemui.clipboardoverlay;
 
 import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -41,10 +42,16 @@
         // From the ACTION_SEND docs:
         //   "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the
         //    MIME type of the data in EXTRA_STREAM"
-        if (clipData.getItemAt(0).getUri() != null) {
-            shareIntent.setDataAndType(
-                    clipData.getItemAt(0).getUri(), clipData.getDescription().getMimeType(0));
-            shareIntent.putExtra(Intent.EXTRA_STREAM, clipData.getItemAt(0).getUri());
+        Uri uri = clipData.getItemAt(0).getUri();
+        if (uri != null) {
+            // We don't use setData here because some apps interpret this as "to:".
+            shareIntent.setType(clipData.getDescription().getMimeType(0));
+            // Include URI in ClipData also, so that grantPermission picks it up.
+            shareIntent.setClipData(new ClipData(
+                    new ClipDescription(
+                            "content", new String[]{clipData.getDescription().getMimeType(0)}),
+                    new ClipData.Item(uri)));
+            shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
             shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         } else {
             shareIntent.putExtra(
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index b15c60e..85f31e5 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -21,13 +21,11 @@
 import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.util.time.SystemClock
 
 /**
  * A facade to interact with Compose, when it is available.
@@ -64,13 +62,6 @@
     ): View
 
     /** Create a [View] to represent [viewModel] on screen. */
-    fun createMultiShadeView(
-        context: Context,
-        viewModel: MultiShadeViewModel,
-        clock: SystemClock,
-    ): View
-
-    /** Create a [View] to represent [viewModel] on screen. */
     fun createSceneContainerView(
         context: Context,
         viewModel: SceneContainerViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 8d5a2dd..32e40c9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,7 +20,6 @@
 
 import com.android.systemui.ForegroundServicesDialog;
 import com.android.systemui.contrast.ContrastDialogActivity;
-import com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity;
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.people.PeopleSpaceActivity;
 import com.android.systemui.people.widget.LaunchConversationActivity;
@@ -28,10 +27,7 @@
 import com.android.systemui.screenshot.appclips.AppClipsActivity;
 import com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity;
 import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
-import com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity;
-import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
 import com.android.systemui.settings.brightness.BrightnessDialog;
-import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
 import com.android.systemui.telephony.ui.activity.SwitchToManagedProfileForCallActivity;
 import com.android.systemui.tuner.TunerActivity;
 import com.android.systemui.usb.UsbAccessoryUriActivity;
@@ -118,12 +114,6 @@
     @ClassKey(CreateUserActivity.class)
     public abstract Activity bindCreateUserActivity(CreateUserActivity activity);
 
-    /** Inject into TvNotificationPanelActivity. */
-    @Binds
-    @IntoMap
-    @ClassKey(TvNotificationPanelActivity.class)
-    public abstract Activity bindTvNotificationPanelActivity(TvNotificationPanelActivity activity);
-
     /** Inject into PeopleSpaceActivity. */
     @Binds
     @IntoMap
@@ -160,25 +150,7 @@
     @ClassKey(SensorUseStartedActivity.class)
     public abstract Activity bindSensorUseStartedActivity(SensorUseStartedActivity activity);
 
-    /** Inject into TvUnblockSensorActivity. */
-    @Binds
-    @IntoMap
-    @ClassKey(TvUnblockSensorActivity.class)
-    public abstract Activity bindTvUnblockSensorActivity(TvUnblockSensorActivity activity);
 
-    /** Inject into HdmiCecSetMenuLanguageActivity. */
-    @Binds
-    @IntoMap
-    @ClassKey(HdmiCecSetMenuLanguageActivity.class)
-    public abstract Activity bindHdmiCecSetMenuLanguageActivity(
-            HdmiCecSetMenuLanguageActivity activity);
-
-    /** Inject into TvSensorPrivacyChangedActivity. */
-    @Binds
-    @IntoMap
-    @ClassKey(TvSensorPrivacyChangedActivity.class)
-    public abstract Activity bindTvSensorPrivacyChangedActivity(
-            TvSensorPrivacyChangedActivity activity);
 
     /** Inject into SwitchToManagedProfileForCallActivity. */
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 35cf4a1..3562477 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -43,8 +43,6 @@
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
 import com.android.systemui.settings.dagger.MultiUserUtilsModule;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyboardShortcutsModule;
@@ -150,9 +148,6 @@
     @Binds
     abstract DockManager bindDockManager(DockManagerImpl dockManager);
 
-    @Binds
-    abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
-
     @SysUISingleton
     @Provides
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index d82bf58..6fdb4ca 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -223,10 +223,10 @@
     Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider();
 
     /** */
-    Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli();
+    MediaMuteAwaitConnectionCli getMediaMuteAwaitConnectionCli();
 
     /** */
-    Optional<NearbyMediaDevicesManager> getNearbyMediaDevicesManager();
+    NearbyMediaDevicesManager getNearbyMediaDevicesManager();
 
     /**
      * Returns {@link CoreStartable}s that should be started with the application.
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index d70c57f..dfec771 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -21,11 +21,9 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.SystemUIInitializer;
-import com.android.systemui.tv.TvWMComponent;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.dagger.TvWMShellModule;
 import com.android.wm.shell.dagger.WMShellModule;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.desktopmode.DesktopMode;
@@ -52,7 +50,7 @@
  * provided by its particular device/form-factor SystemUI implementation.
  *
  * ie. {@link WMComponent} includes {@link WMShellModule}
- *     and {@link TvWMComponent} includes {@link TvWMShellModule}
+ * and {@code TvWMComponent} includes {@link com.android.wm.shell.dagger.TvWMShellModule}
  */
 @WMSingleton
 @Subcomponent(modules = {WMShellModule.class})
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
index 99451f2..6f05e83 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
@@ -37,12 +37,15 @@
  */
 public class ShadeTouchHandler implements DreamTouchHandler {
     private final Optional<CentralSurfaces> mSurfaces;
+    private final ShadeViewController mShadeViewController;
     private final int mInitiationHeight;
 
     @Inject
     ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
+            ShadeViewController shadeViewController,
             @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
         mSurfaces = centralSurfaces;
+        mShadeViewController = shadeViewController;
         mInitiationHeight = initiationHeight;
     }
 
@@ -54,12 +57,7 @@
         }
 
         session.registerInputListener(ev -> {
-            final ShadeViewController viewController =
-                    mSurfaces.map(CentralSurfaces::getShadeViewController).orElse(null);
-
-            if (viewController != null) {
-                viewController.handleExternalTouch((MotionEvent) ev);
-            }
+            mShadeViewController.handleExternalTouch((MotionEvent) ev);
 
             if (ev instanceof MotionEvent) {
                 if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 79a1728..5f70bfb 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -77,7 +77,7 @@
     // TODO(b/278873737): Tracking Bug
     @JvmField
     val LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE =
-            releasedFlag(278873737, "load_notifications_before_the_user_switch_is_complete")
+        releasedFlag(278873737, "load_notifications_before_the_user_switch_is_complete")
 
     // TODO(b/277338665): Tracking Bug
     @JvmField
@@ -92,11 +92,7 @@
     // TODO(b/288326013): Tracking Bug
     @JvmField
     val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
-            unreleasedFlag(
-                    288326013,
-                    "notification_async_hybrid_view_inflation",
-                    teamfood = false
-            )
+        unreleasedFlag(288326013, "notification_async_hybrid_view_inflation", teamfood = false)
 
     @JvmField
     val ANIMATED_NOTIFICATION_SHADE_INSETS =
@@ -104,18 +100,17 @@
 
     // TODO(b/268005230): Tracking Bug
     @JvmField
-    val SENSITIVE_REVEAL_ANIM =
-        unreleasedFlag(268005230, "sensitive_reveal_anim", teamfood = true)
+    val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim", teamfood = true)
 
     // TODO(b/280783617): Tracking Bug
     @Keep
     @JvmField
     val BUILDER_EXTRAS_OVERRIDE =
-            sysPropBooleanFlag(
-                    128,
-                    "persist.sysui.notification.builder_extras_override",
-                    default = false
-            )
+        sysPropBooleanFlag(
+            128,
+            "persist.sysui.notification.builder_extras_override",
+            default = true
+        )
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
@@ -133,16 +128,17 @@
 
     // TODO(b/254512676): Tracking Bug
     @JvmField
-    val LOCKSCREEN_CUSTOM_CLOCKS = resourceBooleanFlag(
-        207,
-        R.bool.config_enableLockScreenCustomClocks,
-        "lockscreen_custom_clocks"
-    )
+    val LOCKSCREEN_CUSTOM_CLOCKS =
+        resourceBooleanFlag(
+            207,
+            R.bool.config_enableLockScreenCustomClocks,
+            "lockscreen_custom_clocks"
+        )
 
     // TODO(b/275694445): Tracking Bug
     @JvmField
-    val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = releasedFlag(208,
-        "lockscreen_without_secure_lock_when_dreaming")
+    val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING =
+        releasedFlag(208, "lockscreen_without_secure_lock_when_dreaming")
 
     // TODO(b/286092087): Tracking Bug
     @JvmField
@@ -156,8 +152,7 @@
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
      * the digits when the clock moves.
      */
-    @JvmField
-    val STEP_CLOCK_ANIMATION = releasedFlag(212, "step_clock_animation")
+    @JvmField val STEP_CLOCK_ANIMATION = releasedFlag(212, "step_clock_animation")
 
     /**
      * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
@@ -183,13 +178,11 @@
     @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag(221, "biometrics_animation_revamp")
 
     // TODO(b/262780002): Tracking Bug
-    @JvmField
-    val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui")
+    @JvmField val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui")
 
     // flag for controlling auto pin confirmation and material u shapes in bouncer
     @JvmField
-    val AUTO_PIN_CONFIRMATION =
-        releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation")
+    val AUTO_PIN_CONFIRMATION = releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation")
 
     // TODO(b/262859270): Tracking Bug
     @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
@@ -206,20 +199,11 @@
     /** Whether the long-press gesture to open wallpaper picker is enabled. */
     // TODO(b/266242192): Tracking Bug
     @JvmField
-    val LOCK_SCREEN_LONG_PRESS_ENABLED =
-        releasedFlag(
-            228,
-            "lock_screen_long_press_enabled"
-        )
+    val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag(228, "lock_screen_long_press_enabled")
 
     /** Enables UI updates for AI wallpapers in the wallpaper picker. */
     // TODO(b/267722622): Tracking Bug
-    @JvmField
-    val WALLPAPER_PICKER_UI_FOR_AIWP =
-            releasedFlag(
-                    229,
-                    "wallpaper_picker_ui_for_aiwp"
-            )
+    @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag(229, "wallpaper_picker_ui_for_aiwp")
 
     /** Whether to use a new data source for intents to run on keyguard dismissal. */
     // TODO(b/275069969): Tracking bug.
@@ -232,6 +216,12 @@
     val LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP =
         unreleasedFlag(232, "lock_screen_long_press_directly_opens_wallpaper_picker")
 
+    /** Whether page transition animations in the wallpaper picker are enabled */
+    // TODO(b/291710220): Tracking bug.
+    @JvmField
+    val WALLPAPER_PICKER_PAGE_TRANSITIONS =
+        unreleasedFlag(291710220, "wallpaper_picker_page_transitions")
+
     /** Whether to run the new udfps keyguard refactor code. */
     // TODO(b/279440316): Tracking bug.
     @JvmField
@@ -239,27 +229,21 @@
 
     /** Provide new auth messages on the bouncer. */
     // TODO(b/277961132): Tracking bug.
-    @JvmField
-    val REVAMPED_BOUNCER_MESSAGES =
-        unreleasedFlag(234, "revamped_bouncer_messages")
+    @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag(234, "revamped_bouncer_messages")
 
     /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
     // TODO(b/279794160): Tracking bug.
-    @JvmField
-    val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer", teamfood = true)
-
+    @JvmField val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer", teamfood = true)
 
     /** Keyguard Migration */
 
     /** Migrate the indication area to the new keyguard root view. */
     // TODO(b/280067944): Tracking bug.
-    @JvmField
-    val MIGRATE_INDICATION_AREA = releasedFlag(236, "migrate_indication_area")
+    @JvmField val MIGRATE_INDICATION_AREA = releasedFlag(236, "migrate_indication_area")
 
     /**
-     * Migrate the bottom area to the new keyguard root view.
-     * Because there is no such thing as a "bottom area" after this, this also breaks it up into
-     * many smaller, modular pieces.
+     * Migrate the bottom area to the new keyguard root view. Because there is no such thing as a
+     * "bottom area" after this, this also breaks it up into many smaller, modular pieces.
      */
     // TODO(b/290652751): Tracking bug.
     @JvmField
@@ -268,36 +252,29 @@
 
     /** Whether to listen for fingerprint authentication over keyguard occluding activities. */
     // TODO(b/283260512): Tracking bug.
-    @JvmField
-    val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag(237, "fp_listen_occluding_apps")
+    @JvmField val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag(237, "fp_listen_occluding_apps")
 
     /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
     // TODO(b/286563884): Tracking bug
-    @JvmField
-    val KEYGUARD_TALKBACK_FIX = releasedFlag(238, "keyguard_talkback_fix")
+    @JvmField val KEYGUARD_TALKBACK_FIX = releasedFlag(238, "keyguard_talkback_fix")
 
     // TODO(b/287268101): Tracking bug.
-    @JvmField
-    val TRANSIT_CLOCK = unreleasedFlag(239, "lockscreen_custom_transit_clock")
+    @JvmField val TRANSIT_CLOCK = unreleasedFlag(239, "lockscreen_custom_transit_clock")
 
     /** Migrate the lock icon view to the new keyguard root view. */
     // TODO(b/286552209): Tracking bug.
-    @JvmField
-    val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon", teamfood = true)
+    @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon", teamfood = true)
 
     // TODO(b/288276738): Tracking bug.
-    @JvmField
-    val WIDGET_ON_KEYGUARD = unreleasedFlag(241, "widget_on_keyguard")
+    @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag(241, "widget_on_keyguard")
 
     /** Migrate the NSSL to the a sibling to both the panel and keyguard root view. */
     // TODO(b/288074305): Tracking bug.
-    @JvmField
-    val MIGRATE_NSSL = unreleasedFlag(242, "migrate_nssl")
+    @JvmField val MIGRATE_NSSL = unreleasedFlag(242, "migrate_nssl")
 
     /** Migrate the status view from the notification panel to keyguard root view. */
     // TODO(b/291767565): Tracking bug.
-    @JvmField
-    val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag(243, "migrate_keyguard_status_view")
+    @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag(243, "migrate_keyguard_status_view")
 
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
@@ -316,8 +293,7 @@
 
     // TODO(b/270223352): Tracking Bug
     @JvmField
-    val HIDE_SMARTSPACE_ON_DREAM_OVERLAY =
-        releasedFlag(404, "hide_smartspace_on_dream_overlay")
+    val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = releasedFlag(404, "hide_smartspace_on_dream_overlay")
 
     // TODO(b/271460958): Tracking Bug
     @JvmField
@@ -357,8 +333,7 @@
 
     /** Enables Font Scaling Quick Settings tile */
     // TODO(b/269341316): Tracking Bug
-    @JvmField
-    val ENABLE_FONT_SCALING_TILE = releasedFlag(509, "enable_font_scaling_tile")
+    @JvmField val ENABLE_FONT_SCALING_TILE = releasedFlag(509, "enable_font_scaling_tile")
 
     /** Enables new QS Edit Mode visual refresh */
     // TODO(b/269787742): Tracking Bug
@@ -367,23 +342,11 @@
 
     // 600- status bar
 
-    // TODO(b/256614753): Tracking Bug
-    val NEW_STATUS_BAR_MOBILE_ICONS = releasedFlag(606, "new_status_bar_mobile_icons")
-
-    // TODO(b/256614751): Tracking Bug
-    val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
-        unreleasedFlag(608, "new_status_bar_mobile_icons_backend", teamfood = true)
-
-    // TODO(b/260881289): Tracking Bug
-    val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
-        unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
-
     // TODO(b/265892345): Tracking Bug
     val PLUG_IN_STATUS_BAR_CHIP = releasedFlag(265892345, "plug_in_status_bar_chip")
 
     // TODO(b/280426085): Tracking Bug
-    @JvmField
-    val NEW_BLUETOOTH_REPOSITORY = releasedFlag(612, "new_bluetooth_repository")
+    @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag(612, "new_bluetooth_repository")
 
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
@@ -416,12 +379,6 @@
     // TODO(b/254512502): Tracking Bug
     val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions")
 
-    // TODO(b/254512726): Tracking Bug
-    val MEDIA_NEARBY_DEVICES = releasedFlag(903, "media_nearby_devices")
-
-    // TODO(b/254512695): Tracking Bug
-    val MEDIA_MUTE_AWAIT = releasedFlag(904, "media_mute_await")
-
     // TODO(b/254512654): Tracking Bug
     @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag(905, "dream_media_complication")
 
@@ -437,9 +394,6 @@
     // TODO(b/263272731): Tracking Bug
     val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple")
 
-    // TODO(b/265813373): Tracking Bug
-    val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = releasedFlag(912, "media_ttt_dismiss_gesture")
-
     // TODO(b/266157412): Tracking Bug
     val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
 
@@ -460,8 +414,8 @@
 
     // TODO(b/273509374): Tracking Bug
     @JvmField
-    val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = releasedFlag(1006,
-        "always_show_home_controls_on_dreams")
+    val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS =
+        releasedFlag(1006, "always_show_home_controls_on_dreams")
 
     // 1100 - windowing
     @Keep
@@ -509,11 +463,7 @@
     @Keep
     @JvmField
     val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
-        sysPropBooleanFlag(
-            1110,
-            "persist.wm.debug.enable_pip_keep_clear_algorithm",
-            default = true
-        )
+        sysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", default = true)
 
     // TODO(b/256873975): Tracking Bug
     @JvmField
@@ -551,7 +501,8 @@
 
     // TODO(b/273443374): Tracking Bug
     @Keep
-    @JvmField val LOCKSCREEN_LIVE_WALLPAPER =
+    @JvmField
+    val LOCKSCREEN_LIVE_WALLPAPER =
         sysPropBooleanFlag(1117, "persist.wm.debug.lockscreen_live_wallpaper", default = true)
 
     // TODO(b/281648899): Tracking bug
@@ -566,7 +517,6 @@
     val ENABLE_PIP2_IMPLEMENTATION =
         sysPropBooleanFlag(1119, "persist.wm.debug.enable_pip2_implementation", default = false)
 
-
     // 1200 - predictive back
     @Keep
     @JvmField
@@ -592,8 +542,7 @@
         unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
 
     // TODO(b/270987164): Tracking Bug
-    @JvmField
-    val TRACKPAD_GESTURE_FEATURES = releasedFlag(1205, "trackpad_gesture_features")
+    @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag(1205, "trackpad_gesture_features")
 
     // TODO(b/263826204): Tracking Bug
     @JvmField
@@ -616,8 +565,7 @@
         unreleasedFlag(1209, "persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true)
 
     // TODO(b/273800936): Tracking Bug
-    @JvmField
-    val TRACKPAD_GESTURE_COMMON = releasedFlag(1210, "trackpad_gesture_common")
+    @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag(1210, "trackpad_gesture_common")
 
     // 1300 - screenshots
     // TODO(b/264916608): Tracking Bug
@@ -642,16 +590,12 @@
     // 1700 - clipboard
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
     // TODO(b/278714186) Tracking Bug
-    @JvmField val CLIPBOARD_IMAGE_TIMEOUT =
-            unreleasedFlag(1702, "clipboard_image_timeout", teamfood = true)
+    @JvmField
+    val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag(1702, "clipboard_image_timeout", teamfood = true)
     // TODO(b/279405451): Tracking Bug
     @JvmField
     val CLIPBOARD_SHARED_TRANSITIONS = unreleasedFlag(1703, "clipboard_shared_transitions")
 
-    // 1800 - shade container
-    // TODO(b/265944639): Tracking Bug
-    @JvmField val DUAL_SHADE = unreleasedFlag(1801, "dual_shade")
-
     // TODO(b/283300105): Tracking Bug
     @JvmField val SCENE_CONTAINER = unreleasedFlag(1802, "scene_container")
 
@@ -661,19 +605,15 @@
     // 2000 - device controls
     @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels")
 
-    @JvmField
-    val APP_PANELS_ALL_APPS_ALLOWED =
-        releasedFlag(2001, "app_panels_all_apps_allowed")
+    @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag(2001, "app_panels_all_apps_allowed")
 
     @JvmField
-    val CONTROLS_MANAGEMENT_NEW_FLOWS =
-        releasedFlag(2002, "controls_management_new_flows")
+    val CONTROLS_MANAGEMENT_NEW_FLOWS = releasedFlag(2002, "controls_management_new_flows")
 
     // Enables removing app from Home control panel as a part of a new flow
     // TODO(b/269132640): Tracking Bug
     @JvmField
-    val APP_PANELS_REMOVE_APPS_ALLOWED =
-        releasedFlag(2003, "app_panels_remove_apps_allowed")
+    val APP_PANELS_REMOVE_APPS_ALLOWED = releasedFlag(2003, "app_panels_remove_apps_allowed")
 
     // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
     // TODO(b/259264861): Tracking Bug
@@ -684,11 +624,9 @@
 
     // 2300 - stylus
     @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used")
+    @JvmField val ENABLE_STYLUS_CHARGING_UI = releasedFlag(2301, "enable_stylus_charging_ui")
     @JvmField
-    val ENABLE_STYLUS_CHARGING_UI = releasedFlag(2301, "enable_stylus_charging_ui")
-    @JvmField
-    val ENABLE_USI_BATTERY_NOTIFICATIONS =
-        releasedFlag(2302, "enable_usi_battery_notifications")
+    val ENABLE_USI_BATTERY_NOTIFICATIONS = releasedFlag(2302, "enable_usi_battery_notifications")
     @JvmField val ENABLE_STYLUS_EDUCATION = releasedFlag(2303, "enable_stylus_education")
 
     // 2400 - performance tools and debugging info
@@ -700,11 +638,10 @@
     // TODO(b/283071711): Tracking bug
     @JvmField
     val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK =
-            unreleasedFlag(2401, "trim_resources_with_background_trim_on_lock")
+        unreleasedFlag(2401, "trim_resources_with_background_trim_on_lock")
 
     // TODO:(b/283203305): Tracking bug
-    @JvmField
-    val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag(2402, "trim_font_caches_on_unlock")
+    @JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag(2402, "trim_font_caches_on_unlock")
 
     // 2700 - unfold transitions
     // TODO(b/265764985): Tracking Bug
@@ -727,27 +664,21 @@
     @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = releasedFlag(2600, "shortcut_list_search_layout")
 
     // TODO(b/259428678): Tracking Bug
-    @JvmField
-    val KEYBOARD_BACKLIGHT_INDICATOR = releasedFlag(2601, "keyboard_backlight_indicator")
+    @JvmField val KEYBOARD_BACKLIGHT_INDICATOR = releasedFlag(2601, "keyboard_backlight_indicator")
 
     // TODO(b/277192623): Tracking Bug
-    @JvmField
-    val KEYBOARD_EDUCATION =
-        unreleasedFlag(2603, "keyboard_education", teamfood = false)
+    @JvmField val KEYBOARD_EDUCATION = unreleasedFlag(2603, "keyboard_education", teamfood = false)
 
     // TODO(b/277201412): Tracking Bug
     @JvmField
-    val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION =
-            releasedFlag(2805, "split_shade_subpixel_optimization")
+    val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = releasedFlag(2805, "split_shade_subpixel_optimization")
 
     // TODO(b/288868056): Tracking Bug
     @JvmField
-    val PARTIAL_SCREEN_SHARING_TASK_SWITCHER =
-            unreleasedFlag(288868056, "pss_task_switcher")
+    val PARTIAL_SCREEN_SHARING_TASK_SWITCHER = unreleasedFlag(288868056, "pss_task_switcher")
 
     // TODO(b/278761837): Tracking Bug
-    @JvmField
-    val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
+    @JvmField val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
 
     // 2900 - Zero Jank fixes. Naming convention is: zj_<bug number>_<cuj name>
 
@@ -763,34 +694,31 @@
         unreleasedFlag(3000, name = "enable_lockscreen_wallpaper_dream")
 
     // TODO(b/283084712): Tracking Bug
-    @JvmField
-    val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations")
+    @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations")
 
     // TODO(b/283447257): Tracking bug
     @JvmField
     val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
-            unreleasedFlag(283447257, "bigpicture_notification_lazy_loading")
+        unreleasedFlag(283447257, "bigpicture_notification_lazy_loading")
 
     // TODO(b/283740863): Tracking Bug
     @JvmField
     val ENABLE_NEW_PRIVACY_DIALOG =
-            unreleasedFlag(283740863, "enable_new_privacy_dialog", teamfood = false)
+        unreleasedFlag(283740863, "enable_new_privacy_dialog", teamfood = false)
 
     // TODO(b/289573946): Tracking Bug
-    @JvmField
-    val PRECOMPUTED_TEXT =
-        unreleasedFlag(289573946, "precomputed_text")
+    @JvmField val PRECOMPUTED_TEXT = unreleasedFlag(289573946, "precomputed_text")
 
     // 2900 - CentralSurfaces-related flags
 
     // TODO(b/285174336): Tracking Bug
     @JvmField
-    val USE_REPOS_FOR_BOUNCER_SHOWING = unreleasedFlag(2900, "use_repos_for_bouncer_showing")
+    val USE_REPOS_FOR_BOUNCER_SHOWING =
+        unreleasedFlag(2900, "use_repos_for_bouncer_showing", teamfood = true)
 
     // 3100 - Haptic interactions
 
     // TODO(b/290213663): Tracking Bug
     @JvmField
-    val ONE_WAY_HAPTICS_API_MIGRATION =
-        unreleasedFlag(3100, "oneway_haptics_api_migration")
+    val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag(3100, "oneway_haptics_api_migration")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java
deleted file mode 100644
index b304c3c..0000000
--- a/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.hdmi;
-
-import android.hardware.hdmi.HdmiControlManager;
-import android.os.Bundle;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-import com.android.systemui.tv.TvBottomSheetActivity;
-
-import javax.inject.Inject;
-
-/**
- * Confirmation dialog shown when Set Menu Language CEC message was received.
- */
-public class HdmiCecSetMenuLanguageActivity extends TvBottomSheetActivity
-        implements View.OnClickListener {
-    private static final String TAG = HdmiCecSetMenuLanguageActivity.class.getSimpleName();
-
-    private final HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper;
-
-    @Inject
-    public HdmiCecSetMenuLanguageActivity(
-            HdmiCecSetMenuLanguageHelper hdmiCecSetMenuLanguageHelper) {
-        mHdmiCecSetMenuLanguageHelper = hdmiCecSetMenuLanguageHelper;
-    }
-
-    @Override
-    public final void onCreate(Bundle b) {
-        super.onCreate(b);
-        getWindow().addPrivateFlags(
-                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-        String languageTag = getIntent().getStringExtra(HdmiControlManager.EXTRA_LOCALE);
-        mHdmiCecSetMenuLanguageHelper.setLocale(languageTag);
-        if (mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()) {
-            finish();
-        }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        CharSequence title = getString(R.string.hdmi_cec_set_menu_language_title,
-                mHdmiCecSetMenuLanguageHelper.getLocale().getDisplayLanguage());
-        CharSequence text = getString(R.string.hdmi_cec_set_menu_language_description);
-        initUI(title, text);
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (v.getId() == R.id.bottom_sheet_positive_button) {
-            mHdmiCecSetMenuLanguageHelper.acceptLocale();
-        } else {
-            mHdmiCecSetMenuLanguageHelper.declineLocale();
-        }
-        finish();
-    }
-
-    void initUI(CharSequence title, CharSequence text) {
-        TextView titleTextView = findViewById(R.id.bottom_sheet_title);
-        TextView contentTextView = findViewById(R.id.bottom_sheet_body);
-        ImageView icon = findViewById(R.id.bottom_sheet_icon);
-        ImageView secondIcon = findViewById(R.id.bottom_sheet_second_icon);
-        Button okButton = findViewById(R.id.bottom_sheet_positive_button);
-        Button cancelButton = findViewById(R.id.bottom_sheet_negative_button);
-
-        titleTextView.setText(title);
-        contentTextView.setText(text);
-        icon.setImageResource(com.android.internal.R.drawable.ic_settings_language);
-        secondIcon.setVisibility(View.GONE);
-
-        okButton.setText(R.string.hdmi_cec_set_menu_language_accept);
-        okButton.setOnClickListener(this);
-
-        cancelButton.setText(R.string.hdmi_cec_set_menu_language_decline);
-        cancelButton.setOnClickListener(this);
-        cancelButton.requestFocus();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java
deleted file mode 100644
index 1f61647..0000000
--- a/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.hdmi;
-
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.internal.app.LocalePicker;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-/**
- * Helper class to separate model and view for system language change initiated by HDMI CEC.
- */
-@SysUISingleton
-public class HdmiCecSetMenuLanguageHelper {
-    private static final String TAG = HdmiCecSetMenuLanguageHelper.class.getSimpleName();
-    private static final String SEPARATOR = ",";
-
-    private final Executor mBackgroundExecutor;
-    private final SecureSettings mSecureSettings;
-
-    private Locale mLocale;
-    private HashSet<String> mDenylist;
-
-    @Inject
-    public HdmiCecSetMenuLanguageHelper(@Background Executor executor,
-            SecureSettings secureSettings) {
-        mBackgroundExecutor = executor;
-        mSecureSettings = secureSettings;
-        String denylist = mSecureSettings.getStringForUser(
-                Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, UserHandle.USER_CURRENT);
-        mDenylist = new HashSet<>(denylist == null
-                ? Collections.EMPTY_SET
-                : Arrays.asList(denylist.split(SEPARATOR)));
-    }
-
-    /**
-     * Set internal locale based on given language tag.
-     */
-    public void setLocale(String languageTag) {
-        mLocale = Locale.forLanguageTag(languageTag);
-    }
-
-    /**
-     * Returns the locale from {@code <Set Menu Language>} CEC message.
-     */
-    public Locale getLocale() {
-        return mLocale;
-    }
-
-    /**
-     * Returns whether the locale from {@code <Set Menu Language>} CEC message was already
-     * denylisted.
-     */
-    public boolean isLocaleDenylisted() {
-        return mDenylist.contains(mLocale.toLanguageTag());
-    }
-
-    /**
-     * Accepts the new locale and updates system language.
-     */
-    public void acceptLocale() {
-        mBackgroundExecutor.execute(() -> LocalePicker.updateLocale(mLocale));
-    }
-
-    /**
-     * Declines the locale and puts it on the denylist.
-     */
-    public void declineLocale() {
-        mDenylist.add(mLocale.toLanguageTag());
-        mSecureSettings.putStringForUser(Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST,
-                String.join(SEPARATOR, mDenylist), UserHandle.USER_CURRENT);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 3646144..489d2ab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -171,7 +171,6 @@
          * for the canned animation (if applicable) so interested parties can sync with it. If no
          * canned animation is playing, these are both 0.
          */
-        @JvmDefault
         fun onUnlockAnimationStarted(
             playingCannedAnimation: Boolean,
             isWakeAndUnlockNotFromDream: Boolean,
@@ -184,7 +183,6 @@
          * The keyguard is no longer visible in this state and the app/launcher behind the keyguard
          * is now completely visible.
          */
-        @JvmDefault
         fun onUnlockAnimationFinished() {}
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 23f6fa6..03a270e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -25,19 +25,24 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
 import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.shade.NotificationShadeWindowView
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 /** Binds keyguard views on startup, and also exposes methods to allow rebinding if views change */
+@ExperimentalCoroutinesApi
 @SysUISingleton
 class KeyguardViewConfigurator
 @Inject
@@ -51,11 +56,14 @@
     private val indicationController: KeyguardIndicationController,
     private val keyguardLayoutManager: KeyguardLayoutManager,
     private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener,
+    private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
+    private val chipbarCoordinator: ChipbarCoordinator,
 ) : CoreStartable {
 
     private var indicationAreaHandle: DisposableHandle? = null
 
     override fun start() {
+        bindKeyguardRootView()
         val notificationPanel =
             notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
         bindIndicationArea(notificationPanel)
@@ -116,4 +124,13 @@
             }
         }
     }
+
+    private fun bindKeyguardRootView() {
+        KeyguardRootViewBinder.bind(
+            keyguardRootView,
+            featureFlags,
+            occludingAppDeviceEntryMessageViewModel,
+            chipbarCoordinator,
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e0834bb..82d0c8b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -444,6 +444,7 @@
     private final LockPatternUtils mLockPatternUtils;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private boolean mKeyguardDonePending = false;
+    private boolean mUnlockingAndWakingFromDream = false;
     private boolean mHideAnimationRun = false;
     private boolean mHideAnimationRunning = false;
 
@@ -802,6 +803,25 @@
             mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false);
             mKeyguardDisplayManager.hide();
             mUpdateMonitor.startBiometricWatchdog();
+
+            // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while
+            // dreaming. It's time to wake up.
+            if (mUnlockingAndWakingFromDream) {
+                Log.d(TAG, "waking from dream after unlock");
+                mUnlockingAndWakingFromDream = false;
+
+                if (mKeyguardStateController.isShowing()) {
+                    Log.d(TAG, "keyguard showing after keyguardGone, dismiss");
+                    mKeyguardViewControllerLazy.get()
+                            .notifyKeyguardAuthenticated(!mWakeAndUnlocking);
+                } else {
+                    Log.d(TAG, "keyguard gone, waking up from dream");
+                    mPM.wakeUp(mSystemClock.uptimeMillis(),
+                            mWakeAndUnlocking ? PowerManager.WAKE_REASON_BIOMETRIC
+                            : PowerManager.WAKE_REASON_GESTURE,
+                            "com.android.systemui:UNLOCK_DREAMING");
+                }
+            }
             Trace.endSection();
         }
 
@@ -2673,6 +2693,7 @@
 
             mKeyguardExitAnimationRunner = null;
             mWakeAndUnlocking = false;
+            mUnlockingAndWakingFromDream = false;
             setPendingLock(false);
 
             // Force if we we're showing in the middle of hiding, to ensure we end up in the correct
@@ -2795,7 +2816,13 @@
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleHide");
 
-            if (mShowing && !mOccluded) {
+            mUnlockingAndWakingFromDream = mStatusBarStateController.isDreaming()
+                && !mStatusBarStateController.isDozing();
+
+            if ((mShowing && !mOccluded) || mUnlockingAndWakingFromDream) {
+                if (mUnlockingAndWakingFromDream) {
+                    Log.d(TAG, "hiding keyguard before waking from dream");
+                }
                 mHiding = true;
                 mKeyguardGoingAwayRunnable.run();
             } else {
@@ -2806,13 +2833,6 @@
                         mHideAnimation.getDuration());
                 onKeyguardExitFinished();
             }
-
-            // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while
-            // dreaming. It's time to wake up.
-            if (mDreamOverlayShowing || mUpdateMonitor.isDreaming()) {
-                mPM.wakeUp(mSystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
-                        "com.android.systemui:UNLOCK_DREAMING");
-            }
         }
         Trace.endSection();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index 1978b3d..039460d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -61,28 +61,23 @@
 
     override fun start() {
         Log.d(LOG_TAG, "Resource trimmer registered.")
-        if (
-            !(featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) ||
-                featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK))
-        ) {
-            return
-        }
-
-        applicationScope.launch(bgDispatcher) {
-            // We need to wait for the AoD transition (and animation) to complete.
-            // This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f
-            // signal. This is to make sure we don't clear font caches during animation which
-            // would jank and leave stale data in memory.
-            val isDozingFully =
-                keyguardInteractor.dozeAmount.map { it == 1f }.distinctUntilChanged()
-            combine(
-                    keyguardInteractor.wakefulnessModel.map { it.state },
-                    keyguardInteractor.isDreaming,
-                    isDozingFully,
-                    ::Triple
-                )
-                .distinctUntilChanged()
-                .collect { onWakefulnessUpdated(it.first, it.second, it.third) }
+        if (featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)) {
+            applicationScope.launch(bgDispatcher) {
+                // We need to wait for the AoD transition (and animation) to complete.
+                // This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f
+                // signal. This is to make sure we don't clear font caches during animation which
+                // would jank and leave stale data in memory.
+                val isDozingFully =
+                    keyguardInteractor.dozeAmount.map { it == 1f }.distinctUntilChanged()
+                combine(
+                        keyguardInteractor.wakefulnessModel.map { it.state },
+                        keyguardInteractor.isDreaming,
+                        isDozingFully,
+                        ::Triple
+                    )
+                    .distinctUntilChanged()
+                    .collect { onWakefulnessUpdated(it.first, it.second, it.third) }
+            }
         }
 
         applicationScope.launch(bgDispatcher) {
@@ -97,17 +92,16 @@
 
     @WorkerThread
     private fun onKeyguardGone() {
-        if (!featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK)) {
-            return
-        }
-
-        if (DEBUG) {
-            Log.d(LOG_TAG, "Trimming font caches since keyguard went away.")
-        }
         // We want to clear temporary caches we've created while rendering and animating
         // lockscreen elements, especially clocks.
+        Log.d(LOG_TAG, "Sending TRIM_MEMORY_UI_HIDDEN.")
         globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
-        globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
+        if (featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK)) {
+            if (DEBUG) {
+                Log.d(LOG_TAG, "Trimming font caches since keyguard went away.")
+            }
+            globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
+        }
     }
 
     @WorkerThread
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 6edf40f..bf1e75b 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
@@ -102,6 +102,9 @@
     /** Whether bypass is currently enabled */
     val isBypassEnabled: Flow<Boolean>
 
+    /** Set whether face authentication should be locked out or not */
+    fun lockoutFaceAuth()
+
     /**
      * Trigger face authentication.
      *
@@ -199,6 +202,10 @@
         }
             ?: flowOf(false)
 
+    override fun lockoutFaceAuth() {
+        _isLockedOut.value = true
+    }
+
     private val faceLockoutResetCallback =
         object : FaceManager.LockoutResetCallback() {
             override fun onLockoutReset(sensorId: Int) {
@@ -396,7 +403,7 @@
     private val faceAuthCallback =
         object : FaceManager.AuthenticationCallback() {
             override fun onAuthenticationFailed() {
-                _authenticationStatus.value = FailedFaceAuthenticationStatus
+                _authenticationStatus.value = FailedFaceAuthenticationStatus()
                 _isAuthenticated.value = false
                 faceAuthLogger.authenticationFailed()
                 onFaceAuthRequestCompleted()
@@ -545,11 +552,11 @@
             faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal)
             return
         }
-        if (_isAuthRunning.value || detectCancellationSignal != null) {
+        if (_isAuthRunning.value) {
             faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
             return
         }
-
+        detectCancellationSignal?.cancel()
         detectCancellationSignal = CancellationSignal()
         withContext(mainDispatcher) {
             // We always want to invoke face detect in the main thread.
@@ -574,6 +581,7 @@
         if (authCancellationSignal == null) return
 
         authCancellationSignal?.cancel()
+        cancelNotReceivedHandlerJob?.cancel()
         cancelNotReceivedHandlerJob =
             applicationScope.launch {
                 delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT)
@@ -583,6 +591,7 @@
                     cancellationInProgress,
                     faceAuthRequestedWhileCancellation
                 )
+                _authenticationStatus.value = ErrorFaceAuthenticationStatus.cancelNotReceivedError()
                 onFaceAuthRequestCompleted()
             }
         cancellationInProgress = true
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 482e9a3d..6a2511f 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
@@ -20,10 +20,13 @@
 
 import android.content.Context
 import android.graphics.Point
+import androidx.core.animation.Animator
+import androidx.core.animation.ValueAnimator
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.WakeSleepReason.TAP
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LiftReveal
 import com.android.systemui.statusbar.LightRevealEffect
@@ -31,9 +34,12 @@
 import javax.inject.Inject
 import kotlin.math.max
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -52,6 +58,10 @@
      * at the current screen position of the appropriate sensor.
      */
     val revealEffect: Flow<LightRevealEffect>
+
+    val revealAmount: Flow<Float>
+
+    fun startRevealAmountAnimator(reveal: Boolean)
 }
 
 @SysUISingleton
@@ -108,13 +118,30 @@
 
     /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
     private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
-        keyguardRepository.wakefulness.flatMapLatest { wakefulnessModel ->
-            when {
-                wakefulnessModel.isTransitioningFromPowerButton() -> powerButtonRevealEffect
-                wakefulnessModel.isAwakeFromTap() -> tapRevealEffect
-                else -> flowOf(LiftReveal)
+        keyguardRepository.wakefulness
+            .filter { it.isStartingToWake() || it.isStartingToSleep() }
+            .flatMapLatest { wakefulnessModel ->
+                when {
+                    wakefulnessModel.isTransitioningFromPowerButton() -> powerButtonRevealEffect
+                    wakefulnessModel.isWakingFrom(TAP) -> tapRevealEffect
+                    else -> flowOf(LiftReveal)
+                }
             }
-        }
+
+    private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = 500 }
+
+    override val revealAmount: Flow<Float> = callbackFlow {
+        val updateListener =
+            Animator.AnimatorUpdateListener {
+                trySend((it as ValueAnimator).animatedValue as Float)
+            }
+        revealAmountAnimator.addUpdateListener(updateListener)
+        awaitClose { revealAmountAnimator.removeUpdateListener(updateListener) }
+    }
+
+    override fun startRevealAmountAnimator(reveal: Boolean) {
+        if (reveal) revealAmountAnimator.start() else revealAmountAnimator.reverse()
+    }
 
     override val revealEffect =
         combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
index 27e3a74..5ef9a9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -55,6 +55,8 @@
     override val isBypassEnabled: Flow<Boolean>
         get() = emptyFlow()
 
+    override fun lockoutFaceAuth() = Unit
+
     /**
      * Trigger face authentication.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 141b130..e57c919 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -60,6 +60,7 @@
     fun onNotificationPanelClicked()
     fun onSwipeUpOnBouncer()
     fun onPrimaryBouncerUserInput()
+    fun onAccessibilityAction()
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 324d443..40e0604 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -409,6 +409,10 @@
             KeyguardPickerFlag(
                 name = Contract.FlagsTable.FLAG_NAME_TRANSIT_CLOCK,
                 value = featureFlags.isEnabled(Flags.TRANSIT_CLOCK)
+            ),
+            KeyguardPickerFlag(
+                name = Contract.FlagsTable.FLAG_NAME_PAGE_TRANSITIONS,
+                value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PAGE_TRANSITIONS)
             )
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 833eda7..4244e55 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -17,28 +17,44 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
 
 @ExperimentalCoroutinesApi
 @SysUISingleton
 class LightRevealScrimInteractor
 @Inject
 constructor(
-    transitionRepository: KeyguardTransitionRepository,
-    transitionInteractor: KeyguardTransitionInteractor,
-    lightRevealScrimRepository: LightRevealScrimRepository,
+    private val transitionInteractor: KeyguardTransitionInteractor,
+    private val lightRevealScrimRepository: LightRevealScrimRepository,
+    @Application private val scope: CoroutineScope,
 ) {
 
+    init {
+        listenForStartedKeyguardTransitionStep()
+    }
+
+    private fun listenForStartedKeyguardTransitionStep() {
+        scope.launch {
+            transitionInteractor.startedKeyguardTransitionStep.collect {
+                if (willTransitionChangeEndState(it)) {
+                    lightRevealScrimRepository.startRevealAmountAnimator(
+                        willBeRevealedInState(it.to)
+                    )
+                }
+            }
+        }
+    }
+
     /**
      * Whenever a keyguard transition starts, sample the latest reveal effect from the repository
      * and use that for the starting transition.
@@ -54,17 +70,7 @@
             lightRevealScrimRepository.revealEffect
         )
 
-    /**
-     * The reveal amount to use for the light reveal scrim, which is derived from the keyguard
-     * transition steps.
-     */
-    val revealAmount: Flow<Float> =
-        transitionRepository.transitions
-            // Only listen to transitions that change the reveal amount.
-            .filter { willTransitionAffectRevealAmount(it) }
-            // Use the transition amount as the reveal amount, inverting it if we're transitioning
-            // to a non-revealed (hidden) state.
-            .map { step -> if (willBeRevealedInState(step.to)) step.value else 1f - step.value }
+    val revealAmount = lightRevealScrimRepository.revealAmount
 
     companion object {
 
@@ -72,7 +78,7 @@
          * Whether the transition requires a change in the reveal amount of the light reveal scrim.
          * If not, we don't care about the transition and don't need to listen to it.
          */
-        fun willTransitionAffectRevealAmount(transition: TransitionStep): Boolean {
+        fun willTransitionChangeEndState(transition: TransitionStep): Boolean {
             return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to)
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
index 10dd900..596a1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -60,4 +60,5 @@
 
     override fun onSwipeUpOnBouncer() {}
     override fun onPrimaryBouncerUserInput() {}
+    override fun onAccessibilityAction() {}
 }
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
new file mode 100644
index 0000000..a2287c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -0,0 +1,138 @@
+/*
+ * 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 android.content.Context
+import android.content.Intent
+import android.hardware.fingerprint.FingerprintManager
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+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.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 javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
+
+/** Business logic for handling authentication events when an app is occluding the lockscreen. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class OccludingAppDeviceEntryInteractor
+@Inject
+constructor(
+    biometricMessageInteractor: BiometricMessageInteractor,
+    fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    keyguardInteractor: KeyguardInteractor,
+    primaryBouncerInteractor: PrimaryBouncerInteractor,
+    alternateBouncerInteractor: AlternateBouncerInteractor,
+    @Application scope: CoroutineScope,
+    private val context: Context,
+    activityStarter: ActivityStarter,
+) {
+    private val keyguardOccludedByApp: Flow<Boolean> =
+        combine(
+                keyguardInteractor.isKeyguardOccluded,
+                keyguardInteractor.isKeyguardShowing,
+                primaryBouncerInteractor.isShowing,
+                alternateBouncerInteractor.isVisible,
+            ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible ->
+                occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
+            }
+            .distinctUntilChanged()
+    private val fingerprintUnlockSuccessEvents: Flow<Unit> =
+        fingerprintAuthRepository.authenticationStatus
+            .ifKeyguardOccludedByApp()
+            .filter { it is SuccessFingerprintAuthenticationStatus }
+            .map {} // maps FingerprintAuthenticationStatus => Unit
+    private val fingerprintLockoutEvents: Flow<Unit> =
+        fingerprintAuthRepository.authenticationStatus
+            .ifKeyguardOccludedByApp()
+            .filter {
+                it is ErrorFingerprintAuthenticationStatus &&
+                    (it.msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT ||
+                        it.msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+            }
+            .map {} // maps FingerprintAuthenticationStatus => Unit
+    val message: Flow<BiometricMessage?> =
+        merge(
+                biometricMessageInteractor.fingerprintErrorMessage,
+                biometricMessageInteractor.fingerprintFailMessage,
+                biometricMessageInteractor.fingerprintHelpMessage,
+            )
+            .ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null))
+
+    init {
+        scope.launch {
+            // On fingerprint success, go to the home screen
+            fingerprintUnlockSuccessEvents.collect { goToHomeScreen() }
+        }
+
+        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
+                )
+            }
+        }
+    }
+
+    /** Launches an Activity which forces the current app to background by going home. */
+    private fun goToHomeScreen() {
+        context.startActivity(
+            Intent(Intent.ACTION_MAIN).apply {
+                addCategory(Intent.CATEGORY_HOME)
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            }
+        )
+    }
+
+    private fun <T> Flow<T>.ifKeyguardOccludedByApp(elseFlow: Flow<T> = emptyFlow()): Flow<T> {
+        return keyguardOccludedByApp.flatMapLatest { keyguardOccludedByApp ->
+            if (keyguardOccludedByApp) {
+                this
+            } else {
+                elseFlow
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 6e7a20b..8f4776f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -67,6 +68,7 @@
     private val featureFlags: FeatureFlags,
     private val faceAuthenticationLogger: FaceAuthenticationLogger,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
 ) : CoreStartable, KeyguardFaceAuthInteractor {
 
     private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -117,6 +119,15 @@
                 )
             }
             .launchIn(applicationScope)
+
+        deviceEntryFingerprintAuthRepository.isLockedOut
+            .onEach {
+                if (it) {
+                    faceAuthenticationLogger.faceLockedOut("Fingerprint locked out")
+                    repository.lockoutFaceAuth()
+                }
+            }
+            .launchIn(applicationScope)
     }
 
     override fun onSwipeUpOnBouncer() {
@@ -143,6 +154,10 @@
         runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false)
     }
 
+    override fun onAccessibilityAction() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_ACCESSIBILITY_ACTION, false)
+    }
+
     override fun registerListener(listener: FaceAuthenticationListener) {
         listeners.add(listener)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
index bba0e37..c0308e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
@@ -21,10 +21,16 @@
 import android.animation.IntEvaluator
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.statusbar.phone.hideAffordancesRequest
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 /** Encapsulates business logic for transitions between UDFPS states on the keyguard. */
 @ExperimentalCoroutinesApi
@@ -35,6 +41,8 @@
     configRepo: ConfigurationRepository,
     burnInInteractor: BurnInInteractor,
     keyguardInteractor: KeyguardInteractor,
+    shadeRepository: ShadeRepository,
+    dialogManager: SystemUIDialogManager,
 ) {
     private val intEvaluator = IntEvaluator()
     private val floatEvaluator = FloatEvaluator()
@@ -56,6 +64,26 @@
                 floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress),
             )
         }
+
+    val dialogHideAffordancesRequest: Flow<Boolean> = dialogManager.hideAffordancesRequest
+
+    val qsProgress: Flow<Float> =
+        shadeRepository.qsExpansion // swipe from top of LS
+            .map { (it * 2).coerceIn(0f, 1f) }
+            .onStart { emit(0f) }
+
+    val shadeExpansion: Flow<Float> =
+        combine(
+                shadeRepository.udfpsTransitionToFullShadeProgress, // swipe from middle of LS
+                keyguardInteractor.statusBarState, // quick swipe from middle of LS
+            ) { shadeProgress, statusBarState ->
+                if (statusBarState == StatusBarState.SHADE_LOCKED) {
+                    1f
+                } else {
+                    shadeProgress
+                }
+            }
+            .onStart { emit(0f) }
 }
 
 data class BurnInOffsets(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index 0f6d82e..3de3666 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -26,25 +26,37 @@
 sealed class FaceAuthenticationStatus
 
 /** Success authentication status. */
-data class SuccessFaceAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
-    FaceAuthenticationStatus()
+data class SuccessFaceAuthenticationStatus(
+    val successResult: FaceManager.AuthenticationResult,
+    // present to break equality check if the same error occurs repeatedly.
+    @JvmField val createdAt: Long = elapsedRealtime()
+) : FaceAuthenticationStatus()
 
 /** Face authentication help message. */
-data class HelpFaceAuthenticationStatus(val msgId: Int, val msg: String?) :
-    FaceAuthenticationStatus()
+data class HelpFaceAuthenticationStatus(
+    val msgId: Int,
+    val msg: String?, // present to break equality check if the same error occurs repeatedly.
+    @JvmField val createdAt: Long = elapsedRealtime()
+) : FaceAuthenticationStatus()
 
 /** Face acquired message. */
-data class AcquiredFaceAuthenticationStatus(val acquiredInfo: Int) : FaceAuthenticationStatus()
+data class AcquiredFaceAuthenticationStatus(
+    val acquiredInfo: Int, // present to break equality check if the same error occurs repeatedly.
+    @JvmField val createdAt: Long = elapsedRealtime()
+) : FaceAuthenticationStatus()
 
 /** Face authentication failed message. */
-object FailedFaceAuthenticationStatus : FaceAuthenticationStatus()
+data class FailedFaceAuthenticationStatus(
+    // present to break equality check if the same error occurs repeatedly.
+    @JvmField val createdAt: Long = elapsedRealtime()
+) : FaceAuthenticationStatus()
 
 /** Face authentication error message */
 data class ErrorFaceAuthenticationStatus(
     val msgId: Int,
     val msg: String? = null,
     // present to break equality check if the same error occurs repeatedly.
-    val createdAt: Long = elapsedRealtime()
+    @JvmField val createdAt: Long = elapsedRealtime()
 ) : FaceAuthenticationStatus() {
     /**
      * Method that checks if [msgId] is a lockout error. A lockout error means that face
@@ -63,7 +75,21 @@
     fun isHardwareError() =
         msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE ||
             msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS
+
+    companion object {
+        /**
+         * Error message that is created when cancel confirmation is not received from FaceManager
+         * after we request for a cancellation of face auth.
+         */
+        fun cancelNotReceivedError() = ErrorFaceAuthenticationStatus(-1, "")
+    }
 }
 
 /** Face detection success message. */
-data class FaceDetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
+data class FaceDetectionStatus(
+    val sensorId: Int,
+    val userId: Int,
+    val isStrongBiometric: Boolean,
+    // present to break equality check if the same error occurs repeatedly.
+    @JvmField val createdAt: Long = elapsedRealtime()
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index cfd9e08..62f43ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -16,6 +16,13 @@
 package com.android.systemui.keyguard.shared.model
 
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.WakeSleepReason.GESTURE
+import com.android.systemui.keyguard.shared.model.WakeSleepReason.POWER_BUTTON
+import com.android.systemui.keyguard.shared.model.WakeSleepReason.TAP
+import com.android.systemui.keyguard.shared.model.WakefulnessState.ASLEEP
+import com.android.systemui.keyguard.shared.model.WakefulnessState.AWAKE
+import com.android.systemui.keyguard.shared.model.WakefulnessState.STARTING_TO_SLEEP
+import com.android.systemui.keyguard.shared.model.WakefulnessState.STARTING_TO_WAKE
 
 /** Model device wakefulness states. */
 data class WakefulnessModel(
@@ -23,33 +30,31 @@
     val lastWakeReason: WakeSleepReason,
     val lastSleepReason: WakeSleepReason,
 ) {
-    fun isStartingToWake() = state == WakefulnessState.STARTING_TO_WAKE
+    fun isStartingToWake() = state == STARTING_TO_WAKE
 
-    fun isStartingToSleep() = state == WakefulnessState.STARTING_TO_SLEEP
+    fun isStartingToSleep() = state == STARTING_TO_SLEEP
 
-    private fun isAsleep() = state == WakefulnessState.ASLEEP
+    private fun isAsleep() = state == ASLEEP
+
+    private fun isAwake() = state == AWAKE
+
+    fun isStartingToWakeOrAwake() = isStartingToWake() || isAwake()
 
     fun isStartingToSleepOrAsleep() = isStartingToSleep() || isAsleep()
 
     fun isDeviceInteractive() = !isAsleep()
 
-    fun isStartingToWakeOrAwake() = isStartingToWake() || state == WakefulnessState.AWAKE
+    fun isWakingFrom(wakeSleepReason: WakeSleepReason) =
+        isStartingToWake() && lastWakeReason == wakeSleepReason
 
-    fun isStartingToSleepFromPowerButton() =
-        isStartingToSleep() && lastWakeReason == WakeSleepReason.POWER_BUTTON
-
-    fun isWakingFromPowerButton() =
-        isStartingToWake() && lastWakeReason == WakeSleepReason.POWER_BUTTON
+    fun isStartingToSleepFrom(wakeSleepReason: WakeSleepReason) =
+        isStartingToSleep() && lastSleepReason == wakeSleepReason
 
     fun isTransitioningFromPowerButton() =
-        isStartingToSleepFromPowerButton() || isWakingFromPowerButton()
-
-    fun isAwakeFromTap() =
-        state == WakefulnessState.STARTING_TO_WAKE && lastWakeReason == WakeSleepReason.TAP
+        isStartingToSleepFrom(POWER_BUTTON) || isWakingFrom(POWER_BUTTON)
 
     fun isDeviceInteractiveFromTapOrGesture(): Boolean {
-        return isDeviceInteractive() &&
-            (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
+        return isDeviceInteractive() && (lastWakeReason == TAP || lastWakeReason == GESTURE)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
new file mode 100644
index 0000000..1db596b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.annotation.DrawableRes
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.temporarydisplay.ViewPriority
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+/** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
+@ExperimentalCoroutinesApi
+object KeyguardRootViewBinder {
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        featureFlags: FeatureFlags,
+        occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
+        chipbarCoordinator: ChipbarCoordinator,
+    ) {
+        if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch {
+                        occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
+                            ->
+                            if (biometricMessage?.message != null) {
+                                chipbarCoordinator.displayView(
+                                    createChipbarInfo(
+                                        biometricMessage.message,
+                                        R.drawable.ic_lock,
+                                    )
+                                )
+                            } else {
+                                chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates an instance of [ChipbarInfo] that can be sent to [ChipbarCoordinator] for display.
+     */
+    private fun createChipbarInfo(message: String, @DrawableRes icon: Int): ChipbarInfo {
+        return ChipbarInfo(
+            startIcon =
+                TintedIcon(
+                    Icon.Resource(icon, null),
+                    ChipbarInfo.DEFAULT_ICON_TINT,
+                ),
+            text = Text.Loaded(message),
+            endItem = null,
+            vibrationEffect = null,
+            windowTitle = "OccludingAppUnlockMsgChip",
+            wakeReason = "OCCLUDING_APP_UNLOCK_MSG_CHIP",
+            timeoutMs = 3500,
+            id = ID,
+            priority = ViewPriority.CRITICAL,
+            instanceId = null,
+        )
+    }
+
+    private const val ID = "occluding_app_device_entry_unlock_msg"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index 389cf76..f1ceaaa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -20,20 +20,18 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 
 /** View-model for the keyguard indication area view */
-@OptIn(ExperimentalCoroutinesApi::class)
 class KeyguardIndicationAreaViewModel
 @Inject
 constructor(
     private val keyguardInteractor: KeyguardInteractor,
-    private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
-    private val keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
+    bottomAreaInteractor: KeyguardBottomAreaInteractor,
+    keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
     private val burnInHelperWrapper: BurnInHelperWrapper,
 ) {
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
index a46d441..82f40bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
@@ -19,12 +19,14 @@
 import com.android.systemui.keyguard.domain.interactor.LightRevealScrimInteractor
 import com.android.systemui.statusbar.LightRevealEffect
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 
 /**
  * Models UI state for the light reveal scrim, which is used during screen on and off animations to
  * draw a gradient that reveals/hides the contents of the screen.
  */
+@OptIn(ExperimentalCoroutinesApi::class)
 class LightRevealScrimViewModel @Inject constructor(interactor: LightRevealScrimInteractor) {
     val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect
     val revealAmount: Flow<Float> = interactor.revealAmount
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt
new file mode 100644
index 0000000..3a162d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.BiometricMessage
+import com.android.systemui.keyguard.domain.interactor.OccludingAppDeviceEntryInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/** Shows authentication messages over occcluding apps over the lockscreen. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class OccludingAppDeviceEntryMessageViewModel
+@Inject
+constructor(
+    interactor: OccludingAppDeviceEntryInteractor,
+) {
+    val message: Flow<BiometricMessage?> = interactor.message
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
index fd4b666..b307f1b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
@@ -21,13 +21,18 @@
 import com.android.settingslib.Utils.getColorAttrDefaultColor
 import com.android.systemui.R
 import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlin.math.roundToInt
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 
@@ -38,6 +43,8 @@
     lockscreenColorResId: Int,
     alternateBouncerColorResId: Int,
     transitionInteractor: KeyguardTransitionInteractor,
+    udfpsKeyguardInteractor: UdfpsKeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
 ) {
     private val toLockscreen: Flow<TransitionViewModel> =
         transitionInteractor.anyStateToLockscreenTransition.map {
@@ -54,46 +61,53 @@
         }
 
     private val toAlternateBouncer: Flow<TransitionViewModel> =
-        transitionInteractor.anyStateToAlternateBouncerTransition.map {
-            TransitionViewModel(
-                alpha = 1f,
-                scale =
-                    if (visibleInKeyguardState(it.from)) {
-                        1f
-                    } else {
-                        it.value
-                    },
-                color = getColorAttrDefaultColor(context, alternateBouncerColorResId),
-            )
+        keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
+            transitionInteractor.anyStateToAlternateBouncerTransition.map {
+                TransitionViewModel(
+                    alpha = 1f,
+                    scale =
+                        if (visibleInKeyguardState(it.from, statusBarState)) {
+                            1f
+                        } else {
+                            Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
+                        },
+                    color = getColorAttrDefaultColor(context, alternateBouncerColorResId),
+                )
+            }
         }
 
     private val fadeOut: Flow<TransitionViewModel> =
-        merge(
-                transitionInteractor.anyStateToGoneTransition,
-                transitionInteractor.anyStateToAodTransition,
-                transitionInteractor.anyStateToOccludedTransition,
-                transitionInteractor.anyStateToPrimaryBouncerTransition,
-                transitionInteractor.anyStateToDreamingTransition,
-            )
-            .map {
-                TransitionViewModel(
-                    alpha =
-                        if (visibleInKeyguardState(it.from)) {
-                            1f - it.value
-                        } else {
-                            0f
-                        },
-                    scale = 1f,
-                    color =
-                        if (it.from == KeyguardState.ALTERNATE_BOUNCER) {
-                            getColorAttrDefaultColor(context, alternateBouncerColorResId)
-                        } else {
-                            getColorAttrDefaultColor(context, lockscreenColorResId)
-                        },
+        keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
+            merge(
+                    transitionInteractor.anyStateToGoneTransition,
+                    transitionInteractor.anyStateToAodTransition,
+                    transitionInteractor.anyStateToOccludedTransition,
+                    transitionInteractor.anyStateToPrimaryBouncerTransition,
+                    transitionInteractor.anyStateToDreamingTransition,
                 )
-            }
+                .map {
+                    TransitionViewModel(
+                        alpha =
+                            if (visibleInKeyguardState(it.from, statusBarState)) {
+                                1f - it.value
+                            } else {
+                                0f
+                            },
+                        scale = 1f,
+                        color =
+                            if (it.from == KeyguardState.ALTERNATE_BOUNCER) {
+                                getColorAttrDefaultColor(context, alternateBouncerColorResId)
+                            } else {
+                                getColorAttrDefaultColor(context, lockscreenColorResId)
+                            },
+                    )
+                }
+        }
 
-    private fun visibleInKeyguardState(state: KeyguardState): Boolean {
+    private fun visibleInKeyguardState(
+        state: KeyguardState,
+        statusBarState: StatusBarState
+    ): Boolean {
         return when (state) {
             KeyguardState.OFF,
             KeyguardState.DOZING,
@@ -102,17 +116,53 @@
             KeyguardState.PRIMARY_BOUNCER,
             KeyguardState.GONE,
             KeyguardState.OCCLUDED -> false
-            KeyguardState.LOCKSCREEN,
+            KeyguardState.LOCKSCREEN -> statusBarState == StatusBarState.KEYGUARD
             KeyguardState.ALTERNATE_BOUNCER -> true
         }
     }
 
-    val transition: Flow<TransitionViewModel> =
+    private val keyguardStateTransition =
         merge(
             toAlternateBouncer,
             toLockscreen,
             fadeOut,
         )
+
+    private val dialogHideAffordancesAlphaMultiplier: Flow<Float> =
+        udfpsKeyguardInteractor.dialogHideAffordancesRequest.map { hideAffordances ->
+            if (hideAffordances) {
+                0f
+            } else {
+                1f
+            }
+        }
+
+    private val alphaMultiplier: Flow<Float> =
+        combine(
+            transitionInteractor.startedKeyguardState,
+            dialogHideAffordancesAlphaMultiplier,
+            udfpsKeyguardInteractor.shadeExpansion,
+            udfpsKeyguardInteractor.qsProgress,
+        ) { startedKeyguardState, dialogHideAffordancesAlphaMultiplier, shadeExpansion, qsProgress
+            ->
+            if (startedKeyguardState == KeyguardState.ALTERNATE_BOUNCER) {
+                1f
+            } else {
+                dialogHideAffordancesAlphaMultiplier * (1f - shadeExpansion) * (1f - qsProgress)
+            }
+        }
+
+    val transition: Flow<TransitionViewModel> =
+        combine(
+            alphaMultiplier,
+            keyguardStateTransition,
+        ) { alphaMultiplier, keyguardStateTransition ->
+            TransitionViewModel(
+                alpha = keyguardStateTransition.alpha * alphaMultiplier,
+                scale = keyguardStateTransition.scale,
+                color = keyguardStateTransition.color,
+            )
+        }
     val visible: Flow<Boolean> = transition.map { it.alpha != 0f }
 }
 
@@ -123,12 +173,15 @@
     val context: Context,
     transitionInteractor: KeyguardTransitionInteractor,
     interactor: UdfpsKeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
 ) :
     UdfpsLockscreenViewModel(
         context,
         android.R.attr.textColorPrimary,
         com.android.internal.R.attr.materialColorOnPrimaryFixed,
         transitionInteractor,
+        interactor,
+        keyguardInteractor,
     ) {
     val dozeAmount: Flow<Float> = interactor.dozeAmount
     val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
@@ -147,12 +200,16 @@
 constructor(
     val context: Context,
     transitionInteractor: KeyguardTransitionInteractor,
+    interactor: UdfpsKeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
 ) :
     UdfpsLockscreenViewModel(
         context,
         com.android.internal.R.attr.colorSurface,
         com.android.internal.R.attr.materialColorPrimaryFixed,
         transitionInteractor,
+        interactor,
+        keyguardInteractor,
     )
 
 data class TransitionViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 373f705..66067b1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -8,6 +8,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.FaceAuthLog
+import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
 private const val TAG = "DeviceEntryFaceAuthRepositoryLog"
@@ -264,4 +265,8 @@
     fun watchdogScheduled() {
         logBuffer.log(TAG, DEBUG, "FaceManager Biometric watchdog scheduled.")
     }
+
+    fun faceLockedOut(@CompileTimeConstant reason: String) {
+        logBuffer.log(TAG, DEBUG, "Face auth has been locked out: $reason")
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 8d3c6d5..8f884d24 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -30,6 +30,9 @@
 import android.os.ResultReceiver
 import android.os.UserHandle
 import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider
@@ -54,7 +57,11 @@
     /** This is used to override the dependency in a screenshot test */
     @VisibleForTesting
     private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
-) : ChooserActivity(), MediaProjectionAppSelectorView, MediaProjectionAppSelectorResultHandler {
+) :
+    ChooserActivity(),
+    MediaProjectionAppSelectorView,
+    MediaProjectionAppSelectorResultHandler,
+    LifecycleOwner {
 
     @Inject
     constructor(
@@ -62,6 +69,8 @@
         activityLauncher: AsyncActivityLauncher
     ) : this(componentFactory, activityLauncher, listControllerFactory = null)
 
+    private val lifecycleRegistry = LifecycleRegistry(this)
+    override val lifecycle = lifecycleRegistry
     private lateinit var configurationController: ConfigurationController
     private lateinit var controller: MediaProjectionAppSelectorController
     private lateinit var recentsViewController: MediaProjectionRecentsViewController
@@ -75,7 +84,9 @@
     override fun getLayoutResource() = R.layout.media_projection_app_selector
 
     public override fun onCreate(bundle: Bundle?) {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
         component = componentFactory.create(activity = this, view = this, resultHandler = this)
+        component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
 
         // Create a separate configuration controller for this activity as the configuration
         // might be different from the global one
@@ -96,6 +107,26 @@
         controller.init()
     }
 
+    override fun onStart() {
+        super.onStart()
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
+    }
+
+    override fun onPause() {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+        super.onPause()
+    }
+
+    override fun onStop() {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
+        super.onStop()
+    }
+
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
         configurationController.onConfigurationChanged(newConfig)
@@ -152,6 +183,8 @@
     }
 
     override fun onDestroy() {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+        component.lifecycleObservers.forEach { lifecycle.removeObserver(it) }
         // onDestroy is also called when an app is selected, in that case we only want to send
         // RECORD_CONTENT_TASK but not RECORD_CANCEL
         if (!taskSelected) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index f6a2f37..72352e3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -330,6 +330,8 @@
         // Don't send cancel if the user has moved on to the next activity.
         if (!mUserSelectingTask) {
             finish(RECORD_CANCEL, /* projection= */ null);
+        } else {
+            super.finish();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
index 6ad36ca..c90a3c1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
@@ -144,7 +144,7 @@
         val oldKey: String?,
         val controller: MediaController?,
         val localMediaManager: LocalMediaManager,
-        val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
+        val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager,
     ) :
         LocalMediaManager.DeviceCallback,
         MediaController.Callback(),
@@ -180,7 +180,7 @@
                 if (!started) {
                     localMediaManager.registerCallback(this)
                     localMediaManager.startScan()
-                    muteAwaitConnectionManager?.startListening()
+                    muteAwaitConnectionManager.startListening()
                     playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
                     playbackVolumeControlId = controller?.playbackInfo?.volumeControlId
                     controller?.registerCallback(this)
@@ -198,7 +198,7 @@
                     controller?.unregisterCallback(this)
                     localMediaManager.stopScan()
                     localMediaManager.unregisterCallback(this)
-                    muteAwaitConnectionManager?.stopListening()
+                    muteAwaitConnectionManager.stopListening()
                     configurationController.removeCallback(configListener)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 09aef88..f2db088 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -34,15 +34,6 @@
         return enabled || featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
     }
 
-    /** Check whether we support displaying information about mute await connections. */
-    fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT)
-
-    /**
-     * Check whether we enable support for nearby media devices. See
-     * [android.app.StatusBarManager.registerNearbyMediaDevicesProvider] for more information.
-     */
-    fun areNearbyMediaDevicesEnabled() = featureFlags.isEnabled(Flags.MEDIA_NEARBY_DEVICES)
-
     /**
      * If true, keep active media controls for the lifetime of the MediaSession, regardless of
      * whether the underlying notification was dismissed
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 46efac5..888cd0b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -23,10 +23,7 @@
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.media.controls.ui.MediaHostStatesManager;
-import com.android.systemui.media.controls.util.MediaFlags;
 import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
-import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
 import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogBuffer;
@@ -119,29 +116,4 @@
         }
         return Optional.of(helperLazy.get());
     }
-
-    /** */
-    @Provides
-    @SysUISingleton
-    static Optional<MediaMuteAwaitConnectionCli> providesMediaMuteAwaitConnectionCli(
-            MediaFlags mediaFlags,
-            Lazy<MediaMuteAwaitConnectionCli> muteAwaitConnectionCliLazy
-    ) {
-        if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
-            return Optional.empty();
-        }
-        return Optional.of(muteAwaitConnectionCliLazy.get());
-    }
-
-    /** */
-    @Provides
-    @SysUISingleton
-    static Optional<NearbyMediaDevicesManager> providesNearbyMediaDevicesManager(
-            MediaFlags mediaFlags,
-            Lazy<NearbyMediaDevicesManager> nearbyMediaDevicesManagerLazy) {
-        if (!mediaFlags.areNearbyMediaDevicesEnabled()) {
-            return Optional.empty();
-        }
-        return Optional.of(nearbyMediaDevicesManagerLazy.get());
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index a1e9995..18d5103 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -46,7 +45,7 @@
     private val notifCollection: CommonNotifCollection,
     private val uiEventLogger: UiEventLogger,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
+    private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
     private val audioManager: AudioManager,
     private val powerExemptionManager: PowerExemptionManager,
     private val keyGuardManager: KeyguardManager,
@@ -62,7 +61,7 @@
 
         val controller = MediaOutputController(context, packageName,
                 mediaSessionManager, lbm, starter, notifCollection,
-                dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
+                dialogLaunchAnimator, nearbyMediaDevicesManager, audioManager,
                 powerExemptionManager, keyGuardManager, featureFlags, userTracker)
         val dialog =
                 MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index f87f53c..83631b0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -100,7 +100,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -178,7 +177,7 @@
             lbm, ActivityStarter starter,
             CommonNotifCollection notifCollection,
             DialogLaunchAnimator dialogLaunchAnimator,
-            Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional,
+            NearbyMediaDevicesManager nearbyMediaDevicesManager,
             AudioManager audioManager,
             PowerExemptionManager powerExemptionManager,
             KeyguardManager keyGuardManager,
@@ -199,7 +198,7 @@
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mNearbyMediaDevicesManager = nearbyMediaDevicesManagerOptional.orElse(null);
+        mNearbyMediaDevicesManager = nearbyMediaDevicesManager;
         mColorItemContent = Utils.getColorStateListDefaultColor(mContext,
                 R.color.media_dialog_item_main_content);
         mColorSeekbarProgress = Utils.getColorStateListDefaultColor(mContext,
@@ -826,13 +825,7 @@
     }
 
     List<RoutingSessionInfo> getActiveRemoteMediaDevices() {
-        final List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
-        for (RoutingSessionInfo info : mLocalMediaManager.getActiveMediaSession()) {
-            if (!info.isSystemSession()) {
-                sessionInfos.add(info);
-            }
-        }
-        return sessionInfos;
+        return new ArrayList<>(mLocalMediaManager.getRemoteRoutingSessions());
     }
 
     void adjustVolume(MediaDevice device, int volume) {
@@ -928,7 +921,7 @@
     void launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender) {
         MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
                 mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
-                mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
+                mNotifCollection, mDialogLaunchAnimator, mNearbyMediaDevicesManager,
                 mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags,
                 mUserTracker);
         MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 4c168ec..af65937 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -48,7 +47,7 @@
     private val notifCollection: CommonNotifCollection,
     private val uiEventLogger: UiEventLogger,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
+    private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
     private val audioManager: AudioManager,
     private val powerExemptionManager: PowerExemptionManager,
     private val keyGuardManager: KeyguardManager,
@@ -68,7 +67,7 @@
         val controller = MediaOutputController(
             context, packageName,
             mediaSessionManager, lbm, starter, notifCollection,
-            dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
+            dialogLaunchAnimator, nearbyMediaDevicesManager, audioManager,
             powerExemptionManager, keyGuardManager, featureFlags, userTracker)
         val dialog =
             MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller,
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
index e260894..97ec654 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
@@ -21,14 +21,12 @@
 import com.android.settingslib.media.LocalMediaManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.controls.util.MediaFlags
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
 /** Factory class to create [MediaMuteAwaitConnectionManager] instances. */
 @SysUISingleton
 class MediaMuteAwaitConnectionManagerFactory @Inject constructor(
-    private val mediaFlags: MediaFlags,
     private val context: Context,
     private val logger: MediaMuteAwaitLogger,
     @Main private val mainExecutor: Executor
@@ -36,10 +34,7 @@
     private val deviceIconUtil = DeviceIconUtil()
 
     /** Creates a [MediaMuteAwaitConnectionManager]. */
-    fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager? {
-        if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
-            return null
-        }
+    fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager {
         return MediaMuteAwaitConnectionManager(
                 mainExecutor, localMediaManager, context, deviceIconUtil, logger
         )
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
index 60504e4..8a565fa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -30,8 +30,4 @@
     /** Check whether the flag for the receiver success state is enabled. */
     fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
         featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
-
-    /** True if the media transfer chip can be dismissed via a gesture. */
-    fun isMediaTttDismissGestureEnabled(): Boolean =
-        featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 3088d8b..11538fa 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -20,6 +20,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.os.UserHandle
+import androidx.lifecycle.DefaultLifecycleObserver
 import com.android.launcher3.icons.IconFactory
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.MediaProjectionAppSelectorActivity
@@ -46,6 +47,7 @@
 import dagger.Subcomponent
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
 import javax.inject.Qualifier
 import javax.inject.Scope
 import kotlinx.coroutines.CoroutineScope
@@ -100,6 +102,12 @@
     @MediaProjectionAppSelectorScope
     fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
 
+    @Binds
+    @IntoSet
+    fun taskPreviewSizeProviderAsLifecycleObserver(
+        impl: TaskPreviewSizeProvider
+    ): DefaultLifecycleObserver
+
     companion object {
         @Provides
         @MediaProjectionAppSelector
@@ -166,4 +174,5 @@
     @get:PersonalProfile val personalProfileUserHandle: UserHandle
 
     @MediaProjectionAppSelector val configurationController: ConfigurationController
+    val lifecycleObservers: Set<DefaultLifecycleObserver>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
index 89f66b7..864d35a 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -21,6 +21,8 @@
 import android.graphics.Rect
 import android.view.WindowInsets.Type
 import android.view.WindowManager
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
 import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen
@@ -35,18 +37,22 @@
 constructor(
     private val context: Context,
     private val windowManager: WindowManager,
-    configurationController: ConfigurationController
-) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener {
+    private val configurationController: ConfigurationController,
+) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener, DefaultLifecycleObserver {
 
     /** Returns the size of the task preview on the screen in pixels */
     val size: Rect = calculateSize()
 
     private val listeners = arrayListOf<TaskPreviewSizeListener>()
 
-    init {
+    override fun onCreate(owner: LifecycleOwner) {
         configurationController.addCallback(this)
     }
 
+    override fun onDestroy(owner: LifecycleOwner) {
+        configurationController.removeCallback(this)
+    }
+
     override fun onConfigChanged(newConfig: Configuration) {
         val newSize = calculateSize()
         if (newSize != size) {
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
new file mode 100644
index 0000000..c74a71c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.model
+
+import com.android.systemui.dagger.qualifiers.DisplayId
+
+/**
+ * In-bulk updates multiple flag values and commits the update.
+ *
+ * Example:
+ * ```
+ * sysuiState.updateFlags(
+ *     displayId,
+ *     SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to (sceneKey != SceneKey.Gone),
+ *     SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to (sceneKey == SceneKey.Shade),
+ *     SYSUI_STATE_QUICK_SETTINGS_EXPANDED to (sceneKey == SceneKey.QuickSettings),
+ *     SYSUI_STATE_BOUNCER_SHOWING to (sceneKey == SceneKey.Bouncer),
+ *     SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to (sceneKey == SceneKey.Lockscreen),
+ * )
+ * ```
+ *
+ * You can inject [displayId] by injecting it using:
+ * ```
+ *     @DisplayId private val displayId: Int`,
+ * ```
+ */
+fun SysUiState.updateFlags(
+    @DisplayId displayId: Int,
+    vararg flagValuePairs: Pair<Int, Boolean>,
+) {
+    flagValuePairs.forEach { (flag, enabled) -> setFlag(flag, enabled) }
+    commitUpdate(displayId)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
deleted file mode 100644
index c48028c..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.multishade.data.model
-
-import com.android.systemui.multishade.shared.model.ShadeId
-
-/** Models the current interaction with one of the shades. */
-data class MultiShadeInteractionModel(
-    /** The ID of the shade that the user is currently interacting with. */
-    val shadeId: ShadeId,
-    /** Whether the interaction is proxied (as in: coming from an external app or different UI). */
-    val isProxied: Boolean,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt
deleted file mode 100644
index 86f0c0d..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.multishade.data.remoteproxy
-
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import javax.inject.Inject
-import javax.inject.Singleton
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
-
-/**
- * Acts as a hub for routing proxied user input into the multi shade system.
- *
- * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
- * In other words: it's not user input that's occurring directly on the shade UI itself. This class
- * is that proxy.
- */
-@Singleton
-class MultiShadeInputProxy @Inject constructor() {
-    private val _proxiedTouch =
-        MutableSharedFlow<ProxiedInputModel>(
-            replay = 1,
-            onBufferOverflow = BufferOverflow.DROP_OLDEST,
-        )
-    val proxiedInput: Flow<ProxiedInputModel> = _proxiedTouch.asSharedFlow()
-
-    fun onProxiedInput(proxiedInput: ProxiedInputModel) {
-        _proxiedTouch.tryEmit(proxiedInput)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt
deleted file mode 100644
index 1172030..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * 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.multishade.data.repository
-
-import android.content.Context
-import androidx.annotation.FloatRange
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeConfig
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.android.systemui.multishade.shared.model.ShadeModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Encapsulates application state for all shades. */
-@SysUISingleton
-class MultiShadeRepository
-@Inject
-constructor(
-    @Application private val applicationContext: Context,
-    inputProxy: MultiShadeInputProxy,
-) {
-    /**
-     * Remote input coming from sources outside of system UI (for example, swiping down on the
-     * Launcher or from the status bar).
-     */
-    val proxiedInput: Flow<ProxiedInputModel> = inputProxy.proxiedInput
-
-    /** Width of the left-hand side shade, in pixels. */
-    private val leftShadeWidthPx =
-        applicationContext.resources.getDimensionPixelSize(R.dimen.left_shade_width)
-
-    /** Width of the right-hand side shade, in pixels. */
-    private val rightShadeWidthPx =
-        applicationContext.resources.getDimensionPixelSize(R.dimen.right_shade_width)
-
-    /**
-     * The amount that the user must swipe up when the shade is fully expanded to automatically
-     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully expanded once the user stops swiping.
-     *
-     * This is a fraction between `0` and `1`.
-     */
-    private val swipeCollapseThreshold =
-        checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_collapse_threshold))
-
-    /**
-     * The amount that the user must swipe down when the shade is fully collapsed to automatically
-     * expand once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully collapsed once the user stops swiping.
-     *
-     * This is a fraction between `0` and `1`.
-     */
-    private val swipeExpandThreshold =
-        checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_expand_threshold))
-
-    /**
-     * Maximum opacity when the scrim that shows up behind the dual shades is fully visible.
-     *
-     * This is a fraction between `0` and `1`.
-     */
-    private val dualShadeScrimAlpha =
-        checkInBounds(applicationContext.resources.getFloat(R.dimen.dual_shade_scrim_alpha))
-
-    /** The current configuration of the shade system. */
-    val shadeConfig: StateFlow<ShadeConfig> =
-        MutableStateFlow(
-                if (applicationContext.resources.getBoolean(R.bool.dual_shade_enabled)) {
-                    ShadeConfig.DualShadeConfig(
-                        leftShadeWidthPx = leftShadeWidthPx,
-                        rightShadeWidthPx = rightShadeWidthPx,
-                        swipeCollapseThreshold = swipeCollapseThreshold,
-                        swipeExpandThreshold = swipeExpandThreshold,
-                        splitFraction =
-                            applicationContext.resources.getFloat(
-                                R.dimen.dual_shade_split_fraction
-                            ),
-                        scrimAlpha = dualShadeScrimAlpha,
-                    )
-                } else {
-                    ShadeConfig.SingleShadeConfig(
-                        swipeCollapseThreshold = swipeCollapseThreshold,
-                        swipeExpandThreshold = swipeExpandThreshold,
-                    )
-                }
-            )
-            .asStateFlow()
-
-    private val _forceCollapseAll = MutableStateFlow(false)
-    /** Whether all shades should be collapsed. */
-    val forceCollapseAll: StateFlow<Boolean> = _forceCollapseAll.asStateFlow()
-
-    private val _shadeInteraction = MutableStateFlow<MultiShadeInteractionModel?>(null)
-    /** The current shade interaction or `null` if no shade is interacted with currently. */
-    val shadeInteraction: StateFlow<MultiShadeInteractionModel?> = _shadeInteraction.asStateFlow()
-
-    private val stateByShade = mutableMapOf<ShadeId, MutableStateFlow<ShadeModel>>()
-
-    /** The model for the shade with the given ID. */
-    fun getShade(
-        shadeId: ShadeId,
-    ): StateFlow<ShadeModel> {
-        return getMutableShade(shadeId).asStateFlow()
-    }
-
-    /** Sets the expansion amount for the shade with the given ID. */
-    fun setExpansion(
-        shadeId: ShadeId,
-        @FloatRange(from = 0.0, to = 1.0) expansion: Float,
-    ) {
-        getMutableShade(shadeId).let { mutableState ->
-            mutableState.value = mutableState.value.copy(expansion = expansion)
-        }
-    }
-
-    /** Sets whether all shades should be immediately forced to collapse. */
-    fun setForceCollapseAll(isForced: Boolean) {
-        _forceCollapseAll.value = isForced
-    }
-
-    /** Sets the current shade interaction; use `null` if no shade is interacted with currently. */
-    fun setShadeInteraction(shadeInteraction: MultiShadeInteractionModel?) {
-        _shadeInteraction.value = shadeInteraction
-    }
-
-    private fun getMutableShade(id: ShadeId): MutableStateFlow<ShadeModel> {
-        return stateByShade.getOrPut(id) { MutableStateFlow(ShadeModel(id)) }
-    }
-
-    /** Asserts that the given [Float] is in the range of `0` and `1`, inclusive. */
-    private fun checkInBounds(float: Float): Float {
-        check(float in 0f..1f) { "$float isn't between 0 and 1." }
-        return float
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
deleted file mode 100644
index ebb8639..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * 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.multishade.domain.interactor
-
-import androidx.annotation.FloatRange
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.data.repository.MultiShadeRepository
-import com.android.systemui.multishade.shared.math.isZero
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeConfig
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.android.systemui.multishade.shared.model.ShadeModel
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.shareIn
-import kotlinx.coroutines.yield
-
-/** Encapsulates business logic related to interactions with the multi-shade system. */
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class MultiShadeInteractor
-@Inject
-constructor(
-    @Application private val applicationScope: CoroutineScope,
-    private val repository: MultiShadeRepository,
-    private val inputProxy: MultiShadeInputProxy,
-) {
-    /** The current configuration of the shade system. */
-    val shadeConfig: StateFlow<ShadeConfig> = repository.shadeConfig
-
-    /** The expansion of the shade that's most expanded. */
-    val maxShadeExpansion: Flow<Float> =
-        repository.shadeConfig.flatMapLatest { shadeConfig ->
-            combine(allShades(shadeConfig)) { shadeModels ->
-                shadeModels.maxOfOrNull { it.expansion } ?: 0f
-            }
-        }
-
-    /** Whether any shade is expanded, even a little bit. */
-    val isAnyShadeExpanded: Flow<Boolean> =
-        maxShadeExpansion.map { maxExpansion -> !maxExpansion.isZero() }.distinctUntilChanged()
-
-    /**
-     * A _processed_ version of the proxied input flow.
-     *
-     * All internal dependencies on the proxied input flow *must* use this one for two reasons:
-     * 1. It's a [SharedFlow] so we only do the upstream work once, no matter how many usages we
-     *    actually have.
-     * 2. It actually does some preprocessing as the proxied input events stream through, handling
-     *    common things like recording the current state of the system based on incoming input
-     *    events.
-     */
-    private val processedProxiedInput: SharedFlow<ProxiedInputModel> =
-        combine(
-                repository.shadeConfig,
-                repository.proxiedInput.distinctUntilChanged(),
-                ::Pair,
-            )
-            .map { (shadeConfig, proxiedInput) ->
-                if (proxiedInput !is ProxiedInputModel.OnTap) {
-                    // If the user is interacting with any other gesture type (for instance,
-                    // dragging),
-                    // we no longer want to force collapse all shades.
-                    repository.setForceCollapseAll(false)
-                }
-
-                when (proxiedInput) {
-                    is ProxiedInputModel.OnDrag -> {
-                        val affectedShadeId = affectedShadeId(shadeConfig, proxiedInput.xFraction)
-                        // This might be the start of a new drag gesture, let's update our
-                        // application
-                        // state to record that fact.
-                        onUserInteractionStarted(
-                            shadeId = affectedShadeId,
-                            isProxied = true,
-                        )
-                    }
-                    is ProxiedInputModel.OnTap -> {
-                        // Tapping outside any shade collapses all shades. This code path is not hit
-                        // for
-                        // taps that happen _inside_ a shade as that input event is directly applied
-                        // through the UI and is, hence, not a proxied input.
-                        collapseAll()
-                    }
-                    else -> Unit
-                }
-
-                proxiedInput
-            }
-            .shareIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                replay = 1,
-            )
-
-    /** Whether the shade with the given ID should be visible. */
-    fun isVisible(shadeId: ShadeId): Flow<Boolean> {
-        return repository.shadeConfig.map { shadeConfig -> shadeConfig.shadeIds.contains(shadeId) }
-    }
-
-    /** Whether direct user input is allowed on the shade with the given ID. */
-    fun isNonProxiedInputAllowed(shadeId: ShadeId): Flow<Boolean> {
-        return combine(
-                isForceCollapsed(shadeId),
-                repository.shadeInteraction,
-                ::Pair,
-            )
-            .map { (isForceCollapsed, shadeInteraction) ->
-                !isForceCollapsed && shadeInteraction?.isProxied != true
-            }
-    }
-
-    /** Whether the shade with the given ID is forced to collapse. */
-    fun isForceCollapsed(shadeId: ShadeId): Flow<Boolean> {
-        return combine(
-                repository.forceCollapseAll,
-                repository.shadeInteraction.map { it?.shadeId },
-                ::Pair,
-            )
-            .map { (collapseAll, userInteractedShadeIdOrNull) ->
-                val counterpartShadeIdOrNull =
-                    when (shadeId) {
-                        ShadeId.SINGLE -> null
-                        ShadeId.LEFT -> ShadeId.RIGHT
-                        ShadeId.RIGHT -> ShadeId.LEFT
-                    }
-
-                when {
-                    // If all shades have been told to collapse (by a tap outside, for example),
-                    // then this shade is collapsed.
-                    collapseAll -> true
-                    // A shade that doesn't have a counterpart shade cannot be force-collapsed by
-                    // interactions on the counterpart shade.
-                    counterpartShadeIdOrNull == null -> false
-                    // If the current user interaction is on the counterpart shade, then this shade
-                    // should be force-collapsed.
-                    else -> userInteractedShadeIdOrNull == counterpartShadeIdOrNull
-                }
-            }
-    }
-
-    /**
-     * Proxied input affecting the shade with the given ID. This is input coming from sources
-     * outside of system UI (for example, swiping down on the Launcher or from the status bar) or
-     * outside the UI of any shade (for example, the scrim that's shown behind the shades).
-     */
-    fun proxiedInput(shadeId: ShadeId): Flow<ProxiedInputModel?> {
-        return combine(
-                processedProxiedInput,
-                isForceCollapsed(shadeId).distinctUntilChanged(),
-                repository.shadeInteraction,
-                ::Triple,
-            )
-            .map { (proxiedInput, isForceCollapsed, shadeInteraction) ->
-                when {
-                    // If the shade is force-collapsed, we ignored proxied input on it.
-                    isForceCollapsed -> null
-                    // If the proxied input does not belong to this shade, ignore it.
-                    shadeInteraction?.shadeId != shadeId -> null
-                    // If there is ongoing non-proxied user input on any shade, ignore the
-                    // proxied input.
-                    !shadeInteraction.isProxied -> null
-                    // Otherwise, send the proxied input downstream.
-                    else -> proxiedInput
-                }
-            }
-            .onEach { proxiedInput ->
-                // We use yield() to make sure that the following block of code happens _after_
-                // downstream collectors had a chance to process the proxied input. Otherwise, we
-                // might change our state to clear the current UserInteraction _before_ those
-                // downstream collectors get a chance to process the proxied input, which will make
-                // them ignore it (since they ignore proxied input when the current user interaction
-                // doesn't match their shade).
-                yield()
-
-                if (
-                    proxiedInput is ProxiedInputModel.OnDragEnd ||
-                        proxiedInput is ProxiedInputModel.OnDragCancel
-                ) {
-                    onUserInteractionEnded(shadeId = shadeId, isProxied = true)
-                }
-            }
-    }
-
-    /** Sets the expansion amount for the shade with the given ID. */
-    fun setExpansion(
-        shadeId: ShadeId,
-        @FloatRange(from = 0.0, to = 1.0) expansion: Float,
-    ) {
-        repository.setExpansion(shadeId, expansion)
-    }
-
-    /** Collapses all shades. */
-    fun collapseAll() {
-        repository.setForceCollapseAll(true)
-    }
-
-    /**
-     * Notifies that a new non-proxied interaction may have started. Safe to call multiple times for
-     * the same interaction as it won't overwrite an existing interaction.
-     *
-     * Existing interactions can be cleared by calling [onUserInteractionEnded].
-     */
-    fun onUserInteractionStarted(shadeId: ShadeId) {
-        onUserInteractionStarted(
-            shadeId = shadeId,
-            isProxied = false,
-        )
-    }
-
-    /**
-     * Notifies that the current non-proxied interaction has ended.
-     *
-     * Safe to call multiple times, even if there's no current interaction or even if the current
-     * interaction doesn't belong to the given shade or is proxied as the code is a no-op unless
-     * there's a match between the parameters and the current interaction.
-     */
-    fun onUserInteractionEnded(
-        shadeId: ShadeId,
-    ) {
-        onUserInteractionEnded(
-            shadeId = shadeId,
-            isProxied = false,
-        )
-    }
-
-    fun sendProxiedInput(proxiedInput: ProxiedInputModel) {
-        inputProxy.onProxiedInput(proxiedInput)
-    }
-
-    /**
-     * Notifies that a new interaction may have started. Safe to call multiple times for the same
-     * interaction as it won't overwrite an existing interaction.
-     *
-     * Existing interactions can be cleared by calling [onUserInteractionEnded].
-     */
-    private fun onUserInteractionStarted(
-        shadeId: ShadeId,
-        isProxied: Boolean,
-    ) {
-        if (repository.shadeInteraction.value != null) {
-            return
-        }
-
-        repository.setShadeInteraction(
-            MultiShadeInteractionModel(
-                shadeId = shadeId,
-                isProxied = isProxied,
-            )
-        )
-    }
-
-    /**
-     * Notifies that the current interaction has ended.
-     *
-     * Safe to call multiple times, even if there's no current interaction or even if the current
-     * interaction doesn't belong to the given shade or [isProxied] value as the code is a no-op
-     * unless there's a match between the parameters and the current interaction.
-     */
-    private fun onUserInteractionEnded(
-        shadeId: ShadeId,
-        isProxied: Boolean,
-    ) {
-        repository.shadeInteraction.value?.let { (interactionShadeId, isInteractionProxied) ->
-            if (shadeId == interactionShadeId && isProxied == isInteractionProxied) {
-                repository.setShadeInteraction(null)
-            }
-        }
-    }
-
-    /**
-     * Returns the ID of the shade that's affected by user input at a given coordinate.
-     *
-     * @param config The shade configuration being used.
-     * @param xFraction The horizontal position of the user input as a fraction along the width of
-     *   its container where `0` is all the way to the left and `1` is all the way to the right.
-     */
-    private fun affectedShadeId(
-        config: ShadeConfig,
-        @FloatRange(from = 0.0, to = 1.0) xFraction: Float,
-    ): ShadeId {
-        return if (config is ShadeConfig.DualShadeConfig) {
-            if (xFraction <= config.splitFraction) {
-                ShadeId.LEFT
-            } else {
-                ShadeId.RIGHT
-            }
-        } else {
-            ShadeId.SINGLE
-        }
-    }
-
-    /** Returns the list of flows of all the shades in the given configuration. */
-    private fun allShades(
-        config: ShadeConfig,
-    ): List<Flow<ShadeModel>> {
-        return config.shadeIds.map { shadeId -> repository.getShade(shadeId) }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
deleted file mode 100644
index 1894bc4..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * 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.multishade.domain.interactor
-
-import android.content.Context
-import android.view.MotionEvent
-import android.view.ViewConfiguration
-import com.android.systemui.classifier.Classifier
-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.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.multishade.shared.math.isZero
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.shade.ShadeController
-import javax.inject.Inject
-import kotlin.math.abs
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-
-/**
- * Encapsulates business logic to handle [MotionEvent]-based user input.
- *
- * This class is meant purely for the legacy `View`-based system to be able to pass `MotionEvent`s
- * into the newer multi-shade framework for processing.
- */
-@SysUISingleton
-class MultiShadeMotionEventInteractor
-@Inject
-constructor(
-    @Application private val applicationContext: Context,
-    @Application private val applicationScope: CoroutineScope,
-    private val multiShadeInteractor: MultiShadeInteractor,
-    featureFlags: FeatureFlags,
-    keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    private val falsingManager: FalsingManager,
-    private val shadeController: ShadeController,
-) {
-    init {
-        if (featureFlags.isEnabled(Flags.DUAL_SHADE)) {
-            applicationScope.launch {
-                multiShadeInteractor.isAnyShadeExpanded.collect {
-                    if (!it && !shadeController.isKeyguard) {
-                        shadeController.makeExpandedInvisible()
-                    } else {
-                        shadeController.makeExpandedVisible(false)
-                    }
-                }
-            }
-        }
-    }
-
-    private val isAnyShadeExpanded: StateFlow<Boolean> =
-        multiShadeInteractor.isAnyShadeExpanded.stateIn(
-            scope = applicationScope,
-            started = SharingStarted.Eagerly,
-            initialValue = false,
-        )
-
-    private val isBouncerShowing: StateFlow<Boolean> =
-        keyguardTransitionInteractor
-            .transitionValue(state = KeyguardState.PRIMARY_BOUNCER)
-            .map { !it.isZero() }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue = false,
-            )
-
-    private var interactionState: InteractionState? = null
-
-    /**
-     * Returns `true` if the given [MotionEvent] and the rest of events in this gesture should be
-     * passed to this interactor's [onTouchEvent] method.
-     *
-     * Note: the caller should continue to pass [MotionEvent] instances into this method, even if it
-     * returns `false` as the gesture may be intercepted mid-stream.
-     */
-    fun shouldIntercept(event: MotionEvent): Boolean {
-        if (isAnyShadeExpanded.value) {
-            // If any shade is expanded, we assume that touch handling outside the shades is handled
-            // by the scrim that appears behind the shades. No need to intercept anything here.
-            return false
-        }
-
-        if (isBouncerShowing.value) {
-            return false
-        }
-
-        return when (event.actionMasked) {
-            MotionEvent.ACTION_DOWN -> {
-                // Record where the pointer was placed and which pointer it was.
-                interactionState =
-                    InteractionState(
-                        initialX = event.x,
-                        initialY = event.y,
-                        currentY = event.y,
-                        pointerId = event.getPointerId(0),
-                        isDraggingHorizontally = false,
-                        isDraggingShade = false,
-                    )
-
-                false
-            }
-            MotionEvent.ACTION_MOVE -> {
-                onMove(event)
-
-                // We want to intercept the rest of the gesture if we're dragging the shade.
-                isDraggingShade()
-            }
-            MotionEvent.ACTION_UP,
-            MotionEvent.ACTION_CANCEL ->
-                // Make sure that we intercept the up or cancel if we're dragging the shade, to
-                // handle drag end or cancel.
-                isDraggingShade()
-            else -> false
-        }
-    }
-
-    /**
-     * Notifies that a [MotionEvent] in a series of events of a gesture that was intercepted due to
-     * the result of [shouldIntercept] has been received.
-     *
-     * @param event The [MotionEvent] to handle.
-     * @param viewWidthPx The width of the view, in pixels.
-     * @return `true` if the event was consumed, `false` otherwise.
-     */
-    fun onTouchEvent(event: MotionEvent, viewWidthPx: Int): Boolean {
-        return when (event.actionMasked) {
-            MotionEvent.ACTION_MOVE -> {
-                interactionState?.let {
-                    if (it.isDraggingShade) {
-                        val pointerIndex = event.findPointerIndex(it.pointerId)
-                        val previousY = it.currentY
-                        val currentY = event.getY(pointerIndex)
-                        interactionState = it.copy(currentY = currentY)
-
-                        val yDragAmountPx = currentY - previousY
-
-                        if (yDragAmountPx != 0f) {
-                            multiShadeInteractor.sendProxiedInput(
-                                ProxiedInputModel.OnDrag(
-                                    xFraction = event.x / viewWidthPx,
-                                    yDragAmountPx = yDragAmountPx,
-                                )
-                            )
-                        }
-                        true
-                    } else {
-                        onMove(event)
-                        isDraggingShade()
-                    }
-                }
-                    ?: false
-            }
-            MotionEvent.ACTION_UP -> {
-                if (isDraggingShade()) {
-                    // We finished dragging the shade. Record that so the multi-shade framework can
-                    // issue a fling, if the velocity reached in the drag was high enough, for
-                    // example.
-                    multiShadeInteractor.sendProxiedInput(ProxiedInputModel.OnDragEnd)
-
-                    if (falsingManager.isFalseTouch(Classifier.SHADE_DRAG)) {
-                        multiShadeInteractor.collapseAll()
-                    }
-                }
-
-                interactionState = null
-                true
-            }
-            MotionEvent.ACTION_POINTER_UP -> {
-                val removedPointerId = event.getPointerId(event.actionIndex)
-                if (removedPointerId == interactionState?.pointerId && event.pointerCount > 1) {
-                    // We removed the original pointer but there must be another pointer because the
-                    // gesture is still ongoing. Let's switch to that pointer.
-                    interactionState =
-                        event.firstUnremovedPointerId(removedPointerId)?.let { replacementPointerId
-                            ->
-                            interactionState?.copy(
-                                pointerId = replacementPointerId,
-                                // We want to update the currentY of our state so that the
-                                // transition to the next pointer doesn't report a big jump between
-                                // the Y coordinate of the removed pointer and the Y coordinate of
-                                // the replacement pointer.
-                                currentY = event.getY(replacementPointerId),
-                            )
-                        }
-                }
-                true
-            }
-            MotionEvent.ACTION_CANCEL -> {
-                if (isDraggingShade()) {
-                    // Our drag gesture was canceled by the system. This happens primarily in one of
-                    // two occasions: (a) the parent view has decided to intercept the gesture
-                    // itself and/or route it to a different child view or (b) the pointer has
-                    // traveled beyond the bounds of our view and/or the touch display. Either way,
-                    // we pass the cancellation event to the multi-shade framework to record it.
-                    // Doing that allows the multi-shade framework to know that the gesture ended to
-                    // allow new gestures to be accepted.
-                    multiShadeInteractor.sendProxiedInput(ProxiedInputModel.OnDragCancel)
-
-                    if (falsingManager.isFalseTouch(Classifier.SHADE_DRAG)) {
-                        multiShadeInteractor.collapseAll()
-                    }
-                }
-
-                interactionState = null
-                true
-            }
-            else -> false
-        }
-    }
-
-    /**
-     * Handles [MotionEvent.ACTION_MOVE] and sets whether or not we are dragging shade in our
-     * current interaction
-     *
-     * @param event The [MotionEvent] to handle.
-     */
-    private fun onMove(event: MotionEvent) {
-        interactionState?.let {
-            val pointerIndex = event.findPointerIndex(it.pointerId)
-            val currentX = event.getX(pointerIndex)
-            val currentY = event.getY(pointerIndex)
-            if (!it.isDraggingHorizontally && !it.isDraggingShade) {
-                val xDistanceTravelled = currentX - it.initialX
-                val yDistanceTravelled = currentY - it.initialY
-                val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
-                interactionState =
-                    when {
-                        yDistanceTravelled > touchSlop -> it.copy(isDraggingShade = true)
-                        abs(xDistanceTravelled) > touchSlop ->
-                            it.copy(isDraggingHorizontally = true)
-                        else -> interactionState
-                    }
-            }
-        }
-    }
-
-    private data class InteractionState(
-        val initialX: Float,
-        val initialY: Float,
-        val currentY: Float,
-        val pointerId: Int,
-        /** Whether the current gesture is dragging horizontally. */
-        val isDraggingHorizontally: Boolean,
-        /** Whether the current gesture is dragging the shade vertically. */
-        val isDraggingShade: Boolean,
-    )
-
-    private fun isDraggingShade(): Boolean {
-        return interactionState?.isDraggingShade ?: false
-    }
-
-    /**
-     * Returns the index of the first pointer that is not [removedPointerId] or `null`, if there is
-     * no other pointer.
-     */
-    private fun MotionEvent.firstUnremovedPointerId(removedPointerId: Int): Int? {
-        return (0 until pointerCount)
-            .firstOrNull { pointerIndex ->
-                val pointerId = getPointerId(pointerIndex)
-                pointerId != removedPointerId
-            }
-            ?.let { pointerIndex -> getPointerId(pointerIndex) }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt
deleted file mode 100644
index c2eaf72..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.multishade.shared.math
-
-import androidx.annotation.VisibleForTesting
-import kotlin.math.abs
-
-/** Returns `true` if this [Float] is within [epsilon] of `0`. */
-fun Float.isZero(epsilon: Float = EPSILON): Boolean {
-    return abs(this) < epsilon
-}
-
-@VisibleForTesting private const val EPSILON = 0.0001f
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt
deleted file mode 100644
index ee1dd65..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.multishade.shared.model
-
-import androidx.annotation.FloatRange
-
-/**
- * Models a part of an ongoing proxied user input gesture.
- *
- * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
- * In other words: it's not user input that's occurring directly on the shade UI itself.
- */
-sealed class ProxiedInputModel {
-    /** The user is dragging their pointer. */
-    data class OnDrag(
-        /**
-         * The relative position of the pointer as a fraction of its container width where `0` is
-         * all the way to the left and `1` is all the way to the right.
-         */
-        @FloatRange(from = 0.0, to = 1.0) val xFraction: Float,
-        /** The amount that the pointer was dragged, in pixels. */
-        val yDragAmountPx: Float,
-    ) : ProxiedInputModel()
-
-    /** The user finished dragging by lifting up their pointer. */
-    object OnDragEnd : ProxiedInputModel()
-
-    /**
-     * The drag gesture has been canceled. Usually because the pointer exited the draggable area.
-     */
-    object OnDragCancel : ProxiedInputModel()
-
-    /** The user has tapped (clicked). */
-    object OnTap : ProxiedInputModel()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
deleted file mode 100644
index a4cd35c..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.multishade.shared.model
-
-import androidx.annotation.FloatRange
-
-/** Enumerates the various possible configurations of the shade system. */
-sealed class ShadeConfig(
-
-    /** IDs of the shade(s) in this configuration. */
-    open val shadeIds: List<ShadeId>,
-
-    /**
-     * The amount that the user must swipe up when the shade is fully expanded to automatically
-     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully expanded once the user stops swiping.
-     */
-    @FloatRange(from = 0.0, to = 1.0) open val swipeCollapseThreshold: Float,
-
-    /**
-     * The amount that the user must swipe down when the shade is fully collapsed to automatically
-     * expand once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully collapsed once the user stops swiping.
-     */
-    @FloatRange(from = 0.0, to = 1.0) open val swipeExpandThreshold: Float,
-) {
-
-    /** There is a single shade. */
-    data class SingleShadeConfig(
-        @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
-        @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
-    ) :
-        ShadeConfig(
-            shadeIds = listOf(ShadeId.SINGLE),
-            swipeCollapseThreshold = swipeCollapseThreshold,
-            swipeExpandThreshold = swipeExpandThreshold,
-        )
-
-    /** There are two shades arranged side-by-side. */
-    data class DualShadeConfig(
-        /** Width of the left-hand side shade. */
-        val leftShadeWidthPx: Int,
-        /** Width of the right-hand side shade. */
-        val rightShadeWidthPx: Int,
-        @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
-        @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
-        /**
-         * The position of the "split" between interaction areas for each of the shades, as a
-         * fraction of the width of the container.
-         *
-         * Interactions that occur on the start-side (left-hand side in left-to-right languages like
-         * English) affect the start-side shade. Interactions that occur on the end-side (right-hand
-         * side in left-to-right languages like English) affect the end-side shade.
-         */
-        @FloatRange(from = 0.0, to = 1.0) val splitFraction: Float,
-        /** Maximum opacity when the scrim that shows up behind the dual shades is fully visible. */
-        @FloatRange(from = 0.0, to = 1.0) val scrimAlpha: Float,
-    ) :
-        ShadeConfig(
-            shadeIds = listOf(ShadeId.LEFT, ShadeId.RIGHT),
-            swipeCollapseThreshold = swipeCollapseThreshold,
-            swipeExpandThreshold = swipeExpandThreshold,
-        )
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
deleted file mode 100644
index 9e02657..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.multishade.shared.model
-
-/** Enumerates all known shade IDs. */
-enum class ShadeId {
-    /** ID of the shade on the left in dual shade configurations. */
-    LEFT,
-    /** ID of the shade on the right in dual shade configurations. */
-    RIGHT,
-    /** ID of the single shade in single shade configurations. */
-    SINGLE,
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt
deleted file mode 100644
index aecec39..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.multishade.ui.view
-
-import android.content.Context
-import android.util.AttributeSet
-import android.widget.FrameLayout
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.compose.ComposeFacade
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.launch
-
-/**
- * View that hosts the multi-shade system and acts as glue between legacy code and the
- * implementation.
- */
-class MultiShadeView(
-    context: Context,
-    attrs: AttributeSet?,
-) :
-    FrameLayout(
-        context,
-        attrs,
-    ) {
-
-    fun init(
-        interactor: MultiShadeInteractor,
-        clock: SystemClock,
-    ) {
-        repeatWhenAttached {
-            lifecycleScope.launch {
-                repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    addView(
-                        ComposeFacade.createMultiShadeView(
-                            context = context,
-                            viewModel =
-                                MultiShadeViewModel(
-                                    viewModelScope = this,
-                                    interactor = interactor,
-                                ),
-                            clock = clock,
-                        )
-                    )
-                }
-
-                // Here when destroyed.
-                removeAllViews()
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
deleted file mode 100644
index ed92c54..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.multishade.ui.viewmodel
-
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeConfig
-import com.android.systemui.multishade.shared.model.ShadeId
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/** Models UI state for UI that supports multi (or single) shade. */
-@OptIn(ExperimentalCoroutinesApi::class)
-class MultiShadeViewModel(
-    viewModelScope: CoroutineScope,
-    private val interactor: MultiShadeInteractor,
-) {
-    /** Models UI state for the single shade. */
-    val singleShade =
-        ShadeViewModel(
-            viewModelScope,
-            ShadeId.SINGLE,
-            interactor,
-        )
-
-    /** Models UI state for the shade on the left-hand side. */
-    val leftShade =
-        ShadeViewModel(
-            viewModelScope,
-            ShadeId.LEFT,
-            interactor,
-        )
-
-    /** Models UI state for the shade on the right-hand side. */
-    val rightShade =
-        ShadeViewModel(
-            viewModelScope,
-            ShadeId.RIGHT,
-            interactor,
-        )
-
-    /** The amount of alpha that the scrim should have. This is a value between `0` and `1`. */
-    val scrimAlpha: StateFlow<Float> =
-        combine(
-                interactor.maxShadeExpansion,
-                interactor.shadeConfig
-                    .map { it as? ShadeConfig.DualShadeConfig }
-                    .map { dualShadeConfigOrNull -> dualShadeConfigOrNull?.scrimAlpha ?: 0f },
-                ::Pair,
-            )
-            .map { (anyShadeExpansion, scrimAlpha) ->
-                (anyShadeExpansion * scrimAlpha).coerceIn(0f, 1f)
-            }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = 0f,
-            )
-
-    /** Whether the scrim should accept touch events. */
-    val isScrimEnabled: StateFlow<Boolean> =
-        interactor.shadeConfig
-            .flatMapLatest { shadeConfig ->
-                when (shadeConfig) {
-                    // In the dual shade configuration, the scrim is enabled when the expansion is
-                    // greater than zero on any one of the shades.
-                    is ShadeConfig.DualShadeConfig -> interactor.isAnyShadeExpanded
-                    // No scrim in the single shade configuration.
-                    is ShadeConfig.SingleShadeConfig -> flowOf(false)
-                }
-            }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
-    /** Notifies that the scrim has been touched. */
-    fun onScrimTouched(proxiedInput: ProxiedInputModel) {
-        if (!isScrimEnabled.value) {
-            return
-        }
-
-        interactor.sendProxiedInput(proxiedInput)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
deleted file mode 100644
index e828dbd..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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.multishade.ui.viewmodel
-
-import androidx.annotation.FloatRange
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeConfig
-import com.android.systemui.multishade.shared.model.ShadeId
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/** Models UI state for a single shade. */
-class ShadeViewModel(
-    viewModelScope: CoroutineScope,
-    private val shadeId: ShadeId,
-    private val interactor: MultiShadeInteractor,
-) {
-    /** Whether the shade is visible. */
-    val isVisible: StateFlow<Boolean> =
-        interactor
-            .isVisible(shadeId)
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
-    /** Whether swiping on the shade UI is currently enabled. */
-    val isSwipingEnabled: StateFlow<Boolean> =
-        interactor
-            .isNonProxiedInputAllowed(shadeId)
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
-    /** Whether the shade must be collapsed immediately. */
-    val isForceCollapsed: Flow<Boolean> =
-        interactor.isForceCollapsed(shadeId).distinctUntilChanged()
-
-    /** The width of the shade. */
-    val width: StateFlow<Size> =
-        interactor.shadeConfig
-            .map { shadeWidth(it) }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = shadeWidth(interactor.shadeConfig.value),
-            )
-
-    /**
-     * The amount that the user must swipe up when the shade is fully expanded to automatically
-     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully expanded once the user stops swiping.
-     */
-    val swipeCollapseThreshold: StateFlow<Float> =
-        interactor.shadeConfig
-            .map { it.swipeCollapseThreshold }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = interactor.shadeConfig.value.swipeCollapseThreshold,
-            )
-
-    /**
-     * The amount that the user must swipe down when the shade is fully collapsed to automatically
-     * expand once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully collapsed once the user stops swiping.
-     */
-    val swipeExpandThreshold: StateFlow<Float> =
-        interactor.shadeConfig
-            .map { it.swipeExpandThreshold }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = interactor.shadeConfig.value.swipeExpandThreshold,
-            )
-
-    /**
-     * Proxied input affecting the shade. This is input coming from sources outside of system UI
-     * (for example, swiping down on the Launcher or from the status bar) or outside the UI of any
-     * shade (for example, the scrim that's shown behind the shades).
-     */
-    val proxiedInput: Flow<ProxiedInputModel?> =
-        interactor
-            .proxiedInput(shadeId)
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = null,
-            )
-
-    /** Notifies that the expansion amount for the shade has changed. */
-    fun onExpansionChanged(
-        expansion: Float,
-    ) {
-        interactor.setExpansion(shadeId, expansion.coerceIn(0f, 1f))
-    }
-
-    /** Notifies that a drag gesture has started. */
-    fun onDragStarted() {
-        interactor.onUserInteractionStarted(shadeId)
-    }
-
-    /** Notifies that a drag gesture has ended. */
-    fun onDragEnded() {
-        interactor.onUserInteractionEnded(shadeId = shadeId)
-    }
-
-    private fun shadeWidth(shadeConfig: ShadeConfig): Size {
-        return when (shadeId) {
-            ShadeId.LEFT ->
-                Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.leftShadeWidthPx ?: 0)
-            ShadeId.RIGHT ->
-                Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.rightShadeWidthPx ?: 0)
-            ShadeId.SINGLE -> Size.Fraction(1f)
-        }
-    }
-
-    sealed class Size {
-        data class Fraction(
-            @FloatRange(from = 0.0, to = 1.0) val fraction: Float,
-        ) : Size()
-        data class Pixels(
-            val pixels: Int,
-        ) : Size()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 682335e..e134f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -133,6 +133,7 @@
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.rotation.RotationButton;
@@ -199,6 +200,7 @@
     private final SysUiState mSysUiFlagsContainer;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final ShadeController mShadeController;
+    private final ShadeViewController mShadeViewController;
     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
     private final OverviewProxyService mOverviewProxyService;
     private final NavigationModeController mNavigationModeController;
@@ -523,6 +525,7 @@
     @Inject
     NavigationBar(
             NavigationBarView navigationBarView,
+            ShadeController shadeController,
             NavigationBarFrame navigationBarFrame,
             @Nullable Bundle savedState,
             @DisplayId Context context,
@@ -541,7 +544,7 @@
             Optional<Pip> pipOptional,
             Optional<Recents> recentsOptional,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            ShadeController shadeController,
+            ShadeViewController shadeViewController,
             NotificationRemoteInputManager notificationRemoteInputManager,
             NotificationShadeDepthController notificationShadeDepthController,
             @Main Handler mainHandler,
@@ -577,6 +580,7 @@
         mSysUiFlagsContainer = sysUiFlagsContainer;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mShadeController = shadeController;
+        mShadeViewController = shadeViewController;
         mNotificationRemoteInputManager = notificationRemoteInputManager;
         mOverviewProxyService = overviewProxyService;
         mNavigationModeController = navigationModeController;
@@ -739,8 +743,7 @@
         final Display display = mView.getDisplay();
         mView.setComponents(mRecentsOptional);
         if (mCentralSurfacesOptionalLazy.get().isPresent()) {
-            mView.setComponents(
-                    mCentralSurfacesOptionalLazy.get().get().getShadeViewController());
+            mView.setComponents(mShadeViewController);
         }
         mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
         mView.setOnVerticalChangedListener(this::onVerticalChanged);
@@ -1341,9 +1344,10 @@
     }
 
     private void onVerticalChanged(boolean isVertical) {
-        Optional<CentralSurfaces> cs = mCentralSurfacesOptionalLazy.get();
-        if (cs.isPresent() && cs.get().getShadeViewController() != null) {
-            cs.get().getShadeViewController().setQsScrimEnabled(!isVertical);
+        // This check can probably be safely removed. It only remained to reduce regression
+        // risk for a broad change that removed the CentralSurfaces reference in the if block
+        if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+            mShadeViewController.setQsScrimEnabled(!isVertical);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
index d652889..d949a2a 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
@@ -145,13 +145,10 @@
     }
 
     interface Callback {
-        @JvmDefault
         fun onFlagMicCameraChanged(flag: Boolean) {}
 
-        @JvmDefault
         fun onFlagLocationChanged(flag: Boolean) {}
 
-        @JvmDefault
         fun onFlagMediaProjectionChanged(flag: Boolean) {}
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
new file mode 100644
index 0000000..fdc70a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
@@ -0,0 +1,367 @@
+/*
+ * 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.privacy
+
+import android.Manifest
+import android.app.ActivityManager
+import android.app.Dialog
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.permission.PermissionGroupUsage
+import android.permission.PermissionManager
+import android.view.View
+import androidx.annotation.MainThread
+import androidx.annotation.WorkerThread
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.appops.AppOpsController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.privacy.logging.PrivacyLogger
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private val defaultDialogProvider =
+    object : PrivacyDialogControllerV2.DialogProvider {
+        override fun makeDialog(
+            context: Context,
+            list: List<PrivacyDialogV2.PrivacyElement>,
+            manageApp: (String, Int, Intent) -> Unit,
+            closeApp: (String, Int) -> Unit,
+            openPrivacyDashboard: () -> Unit
+        ): PrivacyDialogV2 {
+            return PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+        }
+    }
+
+/**
+ * Controller for [PrivacyDialogV2].
+ *
+ * This controller shows and dismissed the dialog, as well as determining the information to show in
+ * it.
+ */
+@SysUISingleton
+class PrivacyDialogControllerV2(
+    private val permissionManager: PermissionManager,
+    private val packageManager: PackageManager,
+    private val privacyItemController: PrivacyItemController,
+    private val userTracker: UserTracker,
+    private val activityStarter: ActivityStarter,
+    private val backgroundExecutor: Executor,
+    private val uiExecutor: Executor,
+    private val privacyLogger: PrivacyLogger,
+    private val keyguardStateController: KeyguardStateController,
+    private val appOpsController: AppOpsController,
+    private val uiEventLogger: UiEventLogger,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val dialogProvider: DialogProvider
+) {
+
+    @Inject
+    constructor(
+        permissionManager: PermissionManager,
+        packageManager: PackageManager,
+        privacyItemController: PrivacyItemController,
+        userTracker: UserTracker,
+        activityStarter: ActivityStarter,
+        @Background backgroundExecutor: Executor,
+        @Main uiExecutor: Executor,
+        privacyLogger: PrivacyLogger,
+        keyguardStateController: KeyguardStateController,
+        appOpsController: AppOpsController,
+        uiEventLogger: UiEventLogger,
+        dialogLaunchAnimator: DialogLaunchAnimator
+    ) : this(
+        permissionManager,
+        packageManager,
+        privacyItemController,
+        userTracker,
+        activityStarter,
+        backgroundExecutor,
+        uiExecutor,
+        privacyLogger,
+        keyguardStateController,
+        appOpsController,
+        uiEventLogger,
+        dialogLaunchAnimator,
+        defaultDialogProvider
+    )
+
+    private var dialog: Dialog? = null
+
+    private val onDialogDismissed =
+        object : PrivacyDialogV2.OnDialogDismissed {
+            override fun onDialogDismissed() {
+                privacyLogger.logPrivacyDialogDismissed()
+                uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
+                dialog = null
+            }
+        }
+
+    @WorkerThread
+    private fun closeApp(packageName: String, userId: Int) {
+        uiEventLogger.log(
+            PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP,
+            userId,
+            packageName
+        )
+        privacyLogger.logCloseAppFromDialog(packageName, userId)
+        ActivityManager.getService().stopAppForUser(packageName, userId)
+    }
+
+    @MainThread
+    private fun manageApp(packageName: String, userId: Int, navigationIntent: Intent) {
+        uiEventLogger.log(
+            PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+            userId,
+            packageName
+        )
+        privacyLogger.logStartSettingsActivityFromDialog(packageName, userId)
+        startActivity(navigationIntent)
+    }
+
+    @MainThread
+    private fun openPrivacyDashboard() {
+        uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_CLICK_TO_PRIVACY_DASHBOARD)
+        privacyLogger.logStartPrivacyDashboardFromDialog()
+        startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE))
+    }
+
+    @MainThread
+    private fun startActivity(navigationIntent: Intent) {
+        if (!keyguardStateController.isUnlocked) {
+            // If we are locked, hide the dialog so the user can unlock
+            dialog?.hide()
+        }
+        // startActivity calls internally startActivityDismissingKeyguard
+        activityStarter.startActivity(navigationIntent, true) {
+            if (ActivityManager.isStartResultSuccessful(it)) {
+                dismissDialog()
+            } else {
+                dialog?.show()
+            }
+        }
+    }
+
+    @WorkerThread
+    private fun getStartViewPermissionUsageIntent(
+        packageName: String,
+        permGroupName: String,
+        attributionTag: CharSequence?,
+        isAttributionSupported: Boolean
+    ): Intent? {
+        if (attributionTag != null && isAttributionSupported) {
+            val intent = Intent(Intent.ACTION_MANAGE_PERMISSION_USAGE)
+            intent.setPackage(packageName)
+            intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permGroupName)
+            intent.putExtra(Intent.EXTRA_ATTRIBUTION_TAGS, arrayOf(attributionTag.toString()))
+            intent.putExtra(Intent.EXTRA_SHOWING_ATTRIBUTION, true)
+            val resolveInfo =
+                packageManager.resolveActivity(intent, PackageManager.ResolveInfoFlags.of(0))
+            if (
+                resolveInfo?.activityInfo?.permission ==
+                    Manifest.permission.START_VIEW_PERMISSION_USAGE
+            ) {
+                intent.component = ComponentName(packageName, resolveInfo.activityInfo.name)
+                return intent
+            }
+        }
+        return null
+    }
+
+    fun getDefaultManageAppPermissionsIntent(packageName: String, userId: Int): Intent {
+        val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+        intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
+        return intent
+    }
+
+    @WorkerThread
+    private fun permGroupUsage(): List<PermissionGroupUsage> {
+        return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted)
+    }
+
+    /**
+     * Show the [PrivacyDialogV2]
+     *
+     * This retrieves the permission usage from [PermissionManager] and creates a new
+     * [PrivacyDialogV2] with a list of [PrivacyDialogV2.PrivacyElement] to show.
+     *
+     * This list will be filtered by [filterAndSelect]. Only types available by
+     * [PrivacyItemController] will be shown.
+     *
+     * @param context A context to use to create the dialog.
+     * @see filterAndSelect
+     */
+    fun showDialog(context: Context, view: View? = null) {
+        dismissDialog()
+        backgroundExecutor.execute {
+            val usage = permGroupUsage()
+            val userInfos = userTracker.userProfiles
+            privacyLogger.logUnfilteredPermGroupUsage(usage)
+            val items =
+                usage.mapNotNull {
+                    val userInfo =
+                        userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) }
+                    if (
+                        isAvailable(it.permissionGroupName) && (userInfo != null || it.isPhoneCall)
+                    ) {
+                        // Only try to get the app name if we actually need it
+                        val appName =
+                            if (it.isPhoneCall) {
+                                ""
+                            } else {
+                                getLabelForPackage(it.packageName, it.uid)
+                            }
+                        val userId = UserHandle.getUserId(it.uid)
+                        val viewUsageIntent =
+                            getStartViewPermissionUsageIntent(
+                                it.packageName,
+                                it.permissionGroupName,
+                                it.attributionTag,
+                                // attributionLabel is set only when subattribution policies
+                                // are supported and satisfied
+                                it.attributionLabel != null
+                            )
+                        PrivacyDialogV2.PrivacyElement(
+                            permGroupToPrivacyType(it.permissionGroupName)!!,
+                            it.packageName,
+                            userId,
+                            appName,
+                            it.attributionTag,
+                            it.attributionLabel,
+                            it.proxyLabel,
+                            it.lastAccessTimeMillis,
+                            it.isActive,
+                            it.isPhoneCall,
+                            viewUsageIntent != null,
+                            it.permissionGroupName,
+                            viewUsageIntent
+                                ?: getDefaultManageAppPermissionsIntent(it.packageName, userId)
+                        )
+                    } else {
+                        null
+                    }
+                }
+            uiExecutor.execute {
+                val elements = filterAndSelect(items)
+                if (elements.isNotEmpty()) {
+                    val d =
+                        dialogProvider.makeDialog(
+                            context,
+                            elements,
+                            this::manageApp,
+                            this::closeApp,
+                            this::openPrivacyDashboard
+                        )
+                    d.setShowForAllUsers(true)
+                    d.addOnDismissListener(onDialogDismissed)
+                    if (view != null) {
+                        dialogLaunchAnimator.showFromView(d, view)
+                    } else {
+                        d.show()
+                    }
+                    privacyLogger.logShowDialogV2Contents(elements)
+                    dialog = d
+                } else {
+                    privacyLogger.logEmptyDialog()
+                }
+            }
+        }
+    }
+
+    /** Dismisses the dialog */
+    fun dismissDialog() {
+        dialog?.dismiss()
+    }
+
+    @WorkerThread
+    private fun getLabelForPackage(packageName: String, uid: Int): CharSequence {
+        return try {
+            packageManager
+                .getApplicationInfoAsUser(packageName, 0, UserHandle.getUserId(uid))
+                .loadLabel(packageManager)
+        } catch (_: PackageManager.NameNotFoundException) {
+            privacyLogger.logLabelNotFound(packageName)
+            packageName
+        }
+    }
+
+    private fun permGroupToPrivacyType(group: String): PrivacyType? {
+        return when (group) {
+            Manifest.permission_group.CAMERA -> PrivacyType.TYPE_CAMERA
+            Manifest.permission_group.MICROPHONE -> PrivacyType.TYPE_MICROPHONE
+            Manifest.permission_group.LOCATION -> PrivacyType.TYPE_LOCATION
+            else -> null
+        }
+    }
+
+    private fun isAvailable(group: String): Boolean {
+        return when (group) {
+            Manifest.permission_group.CAMERA -> privacyItemController.micCameraAvailable
+            Manifest.permission_group.MICROPHONE -> privacyItemController.micCameraAvailable
+            Manifest.permission_group.LOCATION -> privacyItemController.locationAvailable
+            else -> false
+        }
+    }
+
+    /**
+     * Filters the list of elements to show.
+     *
+     * For each privacy type, it'll return all active elements. If there are no active elements,
+     * it'll return the most recent access
+     */
+    private fun filterAndSelect(
+        list: List<PrivacyDialogV2.PrivacyElement>
+    ): List<PrivacyDialogV2.PrivacyElement> {
+        return list
+            .groupBy { it.type }
+            .toSortedMap()
+            .flatMap { (_, elements) ->
+                val actives = elements.filter { it.isActive }
+                if (actives.isNotEmpty()) {
+                    actives.sortedByDescending { it.lastActiveTimestamp }
+                } else {
+                    elements.maxByOrNull { it.lastActiveTimestamp }?.let { listOf(it) }
+                        ?: emptyList()
+                }
+            }
+    }
+
+    /**
+     * Interface to create a [PrivacyDialogV2].
+     *
+     * Can be used to inject a mock creator.
+     */
+    interface DialogProvider {
+        /** Create a [PrivacyDialogV2]. */
+        fun makeDialog(
+            context: Context,
+            list: List<PrivacyDialogV2.PrivacyElement>,
+            manageApp: (String, Int, Intent) -> Unit,
+            closeApp: (String, Int) -> Unit,
+            openPrivacyDashboard: () -> Unit
+        ): PrivacyDialogV2
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
index 3ecc5a5..250976c 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
@@ -18,13 +18,20 @@
 
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
 
 enum class PrivacyDialogEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
     @UiEvent(doc = "Privacy dialog is clicked by user to go to the app settings page.")
     PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS(904),
 
     @UiEvent(doc = "Privacy dialog is dismissed by user.")
-    PRIVACY_DIALOG_DISMISSED(905);
+    PRIVACY_DIALOG_DISMISSED(905),
+
+    @UiEvent(doc = "Privacy dialog item is clicked by user to close the app using a sensor.")
+    PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP(1396),
+
+    @UiEvent(doc = "Privacy dialog is clicked by user to see the privacy dashboard.")
+    PRIVACY_DIALOG_CLICK_TO_PRIVACY_DASHBOARD(1397);
 
     override fun getId() = _id
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
new file mode 100644
index 0000000..f4aa27d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -0,0 +1,539 @@
+/*
+ * 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.privacy
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageItemInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.content.res.Resources.NotFoundException
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.os.Bundle
+import android.text.TextUtils
+import android.util.Log
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.ColorInt
+import androidx.annotation.DrawableRes
+import androidx.annotation.WorkerThread
+import androidx.core.view.ViewCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.maybeForceFullscreen
+import java.lang.ref.WeakReference
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * Dialog to show ongoing and recent app ops element.
+ *
+ * @param context A context to create the dialog
+ * @param list list of elements to show in the dialog. The elements will show in the same order they
+ *   appear in the list
+ * @param manageApp a callback to start an activity for a given package name, user id, and intent
+ * @param closeApp a callback to close an app for a given package name, user id
+ * @param openPrivacyDashboard a callback to open the privacy dashboard
+ * @see PrivacyDialogControllerV2
+ */
+class PrivacyDialogV2(
+    context: Context,
+    private val list: List<PrivacyElement>,
+    private val manageApp: (String, Int, Intent) -> Unit,
+    private val closeApp: (String, Int) -> Unit,
+    private val openPrivacyDashboard: () -> Unit
+) : SystemUIDialog(context, R.style.Theme_PrivacyDialog) {
+
+    private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>()
+    private val dismissed = AtomicBoolean(false)
+    // Note: this will call the dialog create method during init
+    private val decorViewLayoutListener = maybeForceFullscreen()?.component2()
+
+    /**
+     * Add a listener that will be called when the dialog is dismissed.
+     *
+     * If the dialog has already been dismissed, the listener will be called immediately, in the
+     * same thread.
+     */
+    fun addOnDismissListener(listener: OnDialogDismissed) {
+        if (dismissed.get()) {
+            listener.onDialogDismissed()
+        } else {
+            dismissListeners.add(WeakReference(listener))
+        }
+    }
+
+    override fun stop() {
+        dismissed.set(true)
+        val iterator = dismissListeners.iterator()
+        while (iterator.hasNext()) {
+            val el = iterator.next()
+            iterator.remove()
+            el.get()?.onDialogDismissed()
+        }
+        // Remove the layout change listener we may have added to the DecorView.
+        if (decorViewLayoutListener != null) {
+            window!!.decorView.removeOnLayoutChangeListener(decorViewLayoutListener)
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        window!!.setGravity(Gravity.CENTER)
+        setTitle(R.string.privacy_dialog_title)
+        setContentView(R.layout.privacy_dialog_v2)
+
+        val closeButton = requireViewById<Button>(R.id.privacy_dialog_close_button)
+        closeButton.setOnClickListener { dismiss() }
+
+        val moreButton = requireViewById<Button>(R.id.privacy_dialog_more_button)
+        moreButton.setOnClickListener { openPrivacyDashboard() }
+
+        val itemsContainer = requireViewById<ViewGroup>(R.id.privacy_dialog_items_container)
+        list.forEach { itemsContainer.addView(createView(it, itemsContainer)) }
+    }
+
+    private fun createView(element: PrivacyElement, itemsContainer: ViewGroup): View {
+        val itemCard =
+            LayoutInflater.from(context)
+                .inflate(R.layout.privacy_dialog_item_v2, itemsContainer, false) as ViewGroup
+
+        updateItemHeader(element, itemCard)
+
+        if (element.isPhoneCall) {
+            return itemCard
+        }
+
+        setItemExpansionBehavior(itemCard)
+
+        configureIndicatorActionButtons(element, itemCard)
+
+        return itemCard
+    }
+
+    private fun updateItemHeader(element: PrivacyElement, itemCard: View) {
+        val itemHeader = itemCard.findViewById<ViewGroup>(R.id.privacy_dialog_item_header)!!
+        val permGroupLabel = context.packageManager.getDefaultPermGroupLabel(element.permGroupName)
+
+        val iconView = itemHeader.findViewById<ImageView>(R.id.privacy_dialog_item_header_icon)!!
+        val indicatorIcon = context.getPermGroupIcon(element.permGroupName)
+        updateIconView(iconView, indicatorIcon, element.isActive)
+        iconView.contentDescription = permGroupLabel
+
+        val titleView = itemHeader.findViewById<TextView>(R.id.privacy_dialog_item_header_title)!!
+        titleView.text = permGroupLabel
+        titleView.contentDescription = permGroupLabel
+
+        val usageText = getUsageText(element)
+        val summaryView =
+            itemHeader.findViewById<TextView>(R.id.privacy_dialog_item_header_summary)!!
+        summaryView.text = usageText
+        summaryView.contentDescription = usageText
+    }
+
+    private fun configureIndicatorActionButtons(element: PrivacyElement, itemCard: View) {
+        val expandedLayout =
+            itemCard.findViewById<ViewGroup>(R.id.privacy_dialog_item_header_expanded_layout)!!
+
+        val buttons: MutableList<View> = mutableListOf()
+        configureCloseAppButton(element, expandedLayout)?.also { buttons.add(it) }
+        buttons.add(configureManageButton(element, expandedLayout))
+
+        val backgroundColor = getBackgroundColor(element.isActive)
+        when (buttons.size) {
+            0 -> return
+            1 -> {
+                val background =
+                    getMutableDrawable(R.drawable.privacy_dialog_background_large_top_large_bottom)
+                background.setTint(backgroundColor)
+                buttons[0].background = background
+            }
+            else -> {
+                val firstBackground =
+                    getMutableDrawable(R.drawable.privacy_dialog_background_large_top_small_bottom)
+                val middleBackground =
+                    getMutableDrawable(R.drawable.privacy_dialog_background_small_top_small_bottom)
+                val lastBackground =
+                    getMutableDrawable(R.drawable.privacy_dialog_background_small_top_large_bottom)
+                firstBackground.setTint(backgroundColor)
+                middleBackground.setTint(backgroundColor)
+                lastBackground.setTint(backgroundColor)
+                buttons.forEach { it.background = middleBackground }
+                buttons.first().background = firstBackground
+                buttons.last().background = lastBackground
+            }
+        }
+    }
+
+    private fun configureCloseAppButton(element: PrivacyElement, expandedLayout: ViewGroup): View? {
+        if (element.isService || !element.isActive) {
+            return null
+        }
+        val closeAppButton =
+            window.layoutInflater.inflate(
+                R.layout.privacy_dialog_card_button,
+                expandedLayout,
+                false
+            ) as Button
+        expandedLayout.addView(closeAppButton)
+        closeAppButton.id = R.id.privacy_dialog_close_app_button
+        closeAppButton.setText(R.string.privacy_dialog_close_app_button)
+        closeAppButton.setTextColor(getForegroundColor(true))
+        closeAppButton.tag = element
+        closeAppButton.setOnClickListener { v ->
+            v.tag?.let {
+                val element = it as PrivacyElement
+                closeApp(element.packageName, element.userId)
+                closeAppTransition(element.packageName, element.userId)
+            }
+        }
+        return closeAppButton
+    }
+
+    private fun closeAppTransition(packageName: String, userId: Int) {
+        val itemsContainer = requireViewById<ViewGroup>(R.id.privacy_dialog_items_container)
+        var shouldTransition = false
+        for (i in 0 until itemsContainer.getChildCount()) {
+            val itemCard = itemsContainer.getChildAt(i)
+            val button = itemCard.findViewById<Button>(R.id.privacy_dialog_close_app_button)
+            if (button == null || button.tag == null) {
+                continue
+            }
+            val element = button.tag as PrivacyElement
+            if (element.packageName != packageName || element.userId != userId) {
+                continue
+            }
+
+            itemCard.setEnabled(false)
+
+            val expandToggle =
+                itemCard.findViewById<ImageView>(R.id.privacy_dialog_item_header_expand_toggle)!!
+            expandToggle.visibility = View.GONE
+
+            disableIndicatorCardUi(itemCard, element.applicationName)
+
+            val expandedLayout =
+                itemCard.findViewById<View>(R.id.privacy_dialog_item_header_expanded_layout)!!
+            if (expandedLayout.visibility == View.VISIBLE) {
+                expandedLayout.visibility = View.GONE
+                shouldTransition = true
+            }
+        }
+        if (shouldTransition) {
+            ViewHierarchyAnimator.animateNextUpdate(window!!.decorView)
+        }
+    }
+
+    private fun configureManageButton(element: PrivacyElement, expandedLayout: ViewGroup): View {
+        val manageButton =
+            window.layoutInflater.inflate(
+                R.layout.privacy_dialog_card_button,
+                expandedLayout,
+                false
+            ) as Button
+        expandedLayout.addView(manageButton)
+        manageButton.id = R.id.privacy_dialog_manage_app_button
+        manageButton.setText(
+            if (element.isService) R.string.privacy_dialog_manage_service
+            else R.string.privacy_dialog_manage_permissions
+        )
+        manageButton.setTextColor(getForegroundColor(element.isActive))
+        manageButton.tag = element
+        manageButton.setOnClickListener { v ->
+            v.tag?.let {
+                val element = it as PrivacyElement
+                manageApp(element.packageName, element.userId, element.navigationIntent)
+            }
+        }
+        return manageButton
+    }
+
+    private fun disableIndicatorCardUi(itemCard: View, applicationName: CharSequence) {
+        val iconView = itemCard.findViewById<ImageView>(R.id.privacy_dialog_item_header_icon)!!
+        val indicatorIcon = getMutableDrawable(R.drawable.privacy_dialog_check_icon)
+        updateIconView(iconView, indicatorIcon, false)
+
+        val closedAppText =
+            context.getString(R.string.privacy_dialog_close_app_message, applicationName)
+        val summaryView = itemCard.findViewById<TextView>(R.id.privacy_dialog_item_header_summary)!!
+        summaryView.text = closedAppText
+        summaryView.contentDescription = closedAppText
+    }
+
+    private fun setItemExpansionBehavior(itemCard: ViewGroup) {
+        val itemHeader = itemCard.findViewById<ViewGroup>(R.id.privacy_dialog_item_header)!!
+
+        val expandToggle =
+            itemHeader.findViewById<ImageView>(R.id.privacy_dialog_item_header_expand_toggle)!!
+        expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_down)
+        expandToggle.visibility = View.VISIBLE
+
+        ViewCompat.replaceAccessibilityAction(
+            itemCard,
+            AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+            context.getString(R.string.privacy_dialog_expand_action),
+            null
+        )
+
+        val expandedLayout =
+            itemCard.findViewById<View>(R.id.privacy_dialog_item_header_expanded_layout)!!
+        expandedLayout.setOnClickListener {
+            // Stop clicks from propagating
+        }
+
+        itemCard.setOnClickListener {
+            if (expandedLayout.visibility == View.VISIBLE) {
+                expandedLayout.visibility = View.GONE
+                expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_down)
+                ViewCompat.replaceAccessibilityAction(
+                    it!!,
+                    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+                    context.getString(R.string.privacy_dialog_expand_action),
+                    null
+                )
+            } else {
+                expandedLayout.visibility = View.VISIBLE
+                expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_up)
+                ViewCompat.replaceAccessibilityAction(
+                    it!!,
+                    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+                    context.getString(R.string.privacy_dialog_collapse_action),
+                    null
+                )
+            }
+            ViewHierarchyAnimator.animateNextUpdate(
+                rootView = window!!.decorView,
+                excludedViews = setOf(expandedLayout)
+            )
+        }
+    }
+
+    private fun updateIconView(iconView: ImageView, indicatorIcon: Drawable, active: Boolean) {
+        indicatorIcon.setTint(getForegroundColor(active))
+        val backgroundIcon = getMutableDrawable(R.drawable.privacy_dialog_background_circle)
+        backgroundIcon.setTint(getBackgroundColor(active))
+        val backgroundSize =
+            context.resources.getDimension(R.dimen.ongoing_appops_dialog_circle_size).toInt()
+        val indicatorSize =
+            context.resources.getDimension(R.dimen.ongoing_appops_dialog_icon_size).toInt()
+        iconView.setImageDrawable(
+            constructLayeredIcon(indicatorIcon, indicatorSize, backgroundIcon, backgroundSize)
+        )
+    }
+
+    @ColorInt
+    private fun getForegroundColor(active: Boolean) =
+        Utils.getColorAttrDefaultColor(
+            context,
+            if (active) com.android.internal.R.attr.materialColorOnPrimaryFixed
+            else com.android.internal.R.attr.materialColorOnSurface
+        )
+
+    @ColorInt
+    private fun getBackgroundColor(active: Boolean) =
+        Utils.getColorAttrDefaultColor(
+            context,
+            if (active) com.android.internal.R.attr.materialColorPrimaryFixed
+            else com.android.internal.R.attr.materialColorSurfaceContainerHigh
+        )
+
+    private fun getMutableDrawable(@DrawableRes resId: Int) = context.getDrawable(resId)!!.mutate()
+
+    private fun getUsageText(element: PrivacyElement) =
+        if (element.isPhoneCall) {
+            val phoneCallResId =
+                if (element.isActive) R.string.privacy_dialog_active_call_usage
+                else R.string.privacy_dialog_recent_call_usage
+            context.getString(phoneCallResId)
+        } else if (element.attributionLabel == null && element.proxyLabel == null) {
+            val usageResId: Int =
+                if (element.isActive) R.string.privacy_dialog_active_app_usage
+                else R.string.privacy_dialog_recent_app_usage
+            context.getString(usageResId, element.applicationName)
+        } else if (element.attributionLabel == null || element.proxyLabel == null) {
+            val singleUsageResId: Int =
+                if (element.isActive) R.string.privacy_dialog_active_app_usage_1
+                else R.string.privacy_dialog_recent_app_usage_1
+            context.getString(
+                singleUsageResId,
+                element.applicationName,
+                element.attributionLabel ?: element.proxyLabel
+            )
+        } else {
+            val doubleUsageResId: Int =
+                if (element.isActive) R.string.privacy_dialog_active_app_usage_2
+                else R.string.privacy_dialog_recent_app_usage_2
+            context.getString(
+                doubleUsageResId,
+                element.applicationName,
+                element.attributionLabel,
+                element.proxyLabel
+            )
+        }
+
+    companion object {
+        private const val LOG_TAG = "PrivacyDialogV2"
+        private const val REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE"
+
+        /**
+         * Gets a permission group's icon from the system.
+         *
+         * @param groupName The name of the permission group whose icon we want
+         * @return The permission group's icon, the privacy_dialog_default_permission_icon icon if
+         *   the group has no icon, or the group does not exist
+         */
+        @WorkerThread
+        private fun Context.getPermGroupIcon(groupName: String): Drawable {
+            val groupInfo = packageManager.getGroupInfo(groupName)
+            if (groupInfo != null && groupInfo.icon != 0) {
+                val icon = packageManager.loadDrawable(groupInfo.packageName, groupInfo.icon)
+                if (icon != null) {
+                    return icon
+                }
+            }
+
+            return getDrawable(R.drawable.privacy_dialog_default_permission_icon)!!.mutate()
+        }
+
+        /**
+         * Gets a permission group's label from the system.
+         *
+         * @param groupName The name of the permission group whose label we want
+         * @return The permission group's label, or the group name, if the group is invalid
+         */
+        @WorkerThread
+        private fun PackageManager.getDefaultPermGroupLabel(groupName: String): CharSequence {
+            val groupInfo = getGroupInfo(groupName) ?: return groupName
+            return groupInfo.loadSafeLabel(
+                this,
+                0f,
+                TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+            )
+        }
+
+        /**
+         * Get the [infos][PackageItemInfo] for the given permission group.
+         *
+         * @param groupName the group
+         * @return The info of permission group or null if the group does not have runtime
+         *   permissions.
+         */
+        @WorkerThread
+        private fun PackageManager.getGroupInfo(groupName: String): PackageItemInfo? {
+            try {
+                return getPermissionGroupInfo(groupName, 0)
+            } catch (e: NameNotFoundException) {
+                /* ignore */
+            }
+            try {
+                return getPermissionInfo(groupName, 0)
+            } catch (e: NameNotFoundException) {
+                /* ignore */
+            }
+            return null
+        }
+
+        @WorkerThread
+        private fun PackageManager.loadDrawable(pkg: String, @DrawableRes resId: Int): Drawable? {
+            return try {
+                getResourcesForApplication(pkg).getDrawable(resId, null)?.mutate()
+            } catch (e: NotFoundException) {
+                Log.w(LOG_TAG, "Couldn't get resource", e)
+                null
+            } catch (e: NameNotFoundException) {
+                Log.w(LOG_TAG, "Couldn't get resource", e)
+                null
+            }
+        }
+
+        private fun constructLayeredIcon(
+            icon: Drawable,
+            iconSize: Int,
+            background: Drawable,
+            backgroundSize: Int
+        ): Drawable {
+            val layered = LayerDrawable(arrayOf(background, icon))
+            layered.setLayerSize(0, backgroundSize, backgroundSize)
+            layered.setLayerGravity(0, Gravity.CENTER)
+            layered.setLayerSize(1, iconSize, iconSize)
+            layered.setLayerGravity(1, Gravity.CENTER)
+            return layered
+        }
+    }
+
+    /**  */
+    data class PrivacyElement(
+        val type: PrivacyType,
+        val packageName: String,
+        val userId: Int,
+        val applicationName: CharSequence,
+        val attributionTag: CharSequence?,
+        val attributionLabel: CharSequence?,
+        val proxyLabel: CharSequence?,
+        val lastActiveTimestamp: Long,
+        val isActive: Boolean,
+        val isPhoneCall: Boolean,
+        val isService: Boolean,
+        val permGroupName: String,
+        val navigationIntent: Intent
+    ) {
+        private val builder = StringBuilder("PrivacyElement(")
+
+        init {
+            builder.append("type=${type.logName}")
+            builder.append(", packageName=$packageName")
+            builder.append(", userId=$userId")
+            builder.append(", appName=$applicationName")
+            if (attributionTag != null) {
+                builder.append(", attributionTag=$attributionTag")
+            }
+            if (attributionLabel != null) {
+                builder.append(", attributionLabel=$attributionLabel")
+            }
+            if (proxyLabel != null) {
+                builder.append(", proxyLabel=$proxyLabel")
+            }
+            builder.append(", lastActive=$lastActiveTimestamp")
+            if (isActive) {
+                builder.append(", active")
+            }
+            if (isPhoneCall) {
+                builder.append(", phoneCall")
+            }
+            if (isService) {
+                builder.append(", service")
+            }
+            builder.append(", permGroupName=$permGroupName")
+            builder.append(", navigationIntent=$navigationIntent)")
+        }
+
+        override fun toString(): String = builder.toString()
+    }
+
+    /**  */
+    interface OnDialogDismissed {
+        fun onDialogDismissed()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index a676150..eb8ef9b 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -216,7 +216,6 @@
     interface Callback : PrivacyConfig.Callback {
         fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>)
 
-        @JvmDefault
         fun onFlagAllChanged(flag: Boolean) {}
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
index f934346..1a4642f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -18,11 +18,12 @@
 
 import android.icu.text.SimpleDateFormat
 import android.permission.PermissionGroupUsage
-import com.android.systemui.log.dagger.PrivacyLog
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.dagger.PrivacyLog
 import com.android.systemui.privacy.PrivacyDialog
+import com.android.systemui.privacy.PrivacyDialogV2
 import com.android.systemui.privacy.PrivacyItem
 import java.util.Locale
 import javax.inject.Inject
@@ -126,6 +127,14 @@
         })
     }
 
+    fun logShowDialogV2Contents(contents: List<PrivacyDialogV2.PrivacyElement>) {
+        log(LogLevel.INFO, {
+            str1 = contents.toString()
+        }, {
+            "Privacy dialog shown. Contents: $str1"
+        })
+    }
+
     fun logEmptyDialog() {
         log(LogLevel.WARNING, {}, {
             "Trying to show an empty dialog"
@@ -147,6 +156,23 @@
         })
     }
 
+    fun logCloseAppFromDialog(packageName: String, userId: Int) {
+        log(LogLevel.INFO, {
+            str1 = packageName
+            int1 = userId
+        }, {
+            "Close app from dialog for packageName=$str1, userId=$int1"
+        })
+    }
+
+    fun logStartPrivacyDashboardFromDialog() {
+        log(LogLevel.INFO, {}, { "Start privacy dashboard from dialog" })
+    }
+
+    fun logLabelNotFound(packageName: String) {
+        log(LogLevel.WARNING, { str1 = packageName }, { "Label not found for: $str1" })
+    }
+
     private fun listToString(list: List<PrivacyItem>): String {
         return list.joinToString(separator = ", ", transform = PrivacyItem::log)
     }
@@ -158,4 +184,4 @@
     ) {
         buffer.log(TAG, logLevel, initializer, printer)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/PrivacyChipDrawable.java b/packages/SystemUI/src/com/android/systemui/privacy/television/PrivacyChipDrawable.java
deleted file mode 100644
index 08911d4..0000000
--- a/packages/SystemUI/src/com/android/systemui/privacy/television/PrivacyChipDrawable.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.privacy.television;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.Gravity;
-
-import androidx.annotation.Keep;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.systemui.R;
-
-/**
- * Drawable that can go from being the background of the privacy icons to a small dot.
- * The icons are not included.
- */
-public class PrivacyChipDrawable extends Drawable {
-    private static final String TAG = PrivacyChipDrawable.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
-    private final Paint mChipPaint;
-    private final Paint mBgPaint;
-    private final Rect mTmpRect = new Rect();
-    private final Rect mBgRect = new Rect();
-    private final RectF mTmpRectF = new RectF();
-    private final Path mPath = new Path();
-    private final Animator mCollapse;
-    private final Animator mExpand;
-    private final int mLayoutDirection;
-    private final int mBgWidth;
-    private final int mBgHeight;
-    private final int mBgRadius;
-    private final int mDotSize;
-    private final float mExpandedChipRadius;
-    private final float mCollapsedChipRadius;
-
-    private final boolean mCollapseToDot;
-
-    private boolean mIsExpanded = true;
-    private float mCollapseProgress = 0f;
-
-    public PrivacyChipDrawable(Context context, int chipColorRes, boolean collapseToDot) {
-        mCollapseToDot = collapseToDot;
-
-        mChipPaint = new Paint();
-        mChipPaint.setStyle(Paint.Style.FILL);
-        mChipPaint.setColor(context.getColor(chipColorRes));
-        mChipPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
-
-        mBgPaint = new Paint();
-        mBgPaint.setStyle(Paint.Style.FILL);
-        mBgPaint.setColor(context.getColor(R.color.privacy_chip_dot_bg_tint));
-        mBgPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
-
-        Resources res = context.getResources();
-        mLayoutDirection = res.getConfiguration().getLayoutDirection();
-        mBgWidth = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_bg_width);
-        mBgHeight = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_bg_height);
-        mBgRadius = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_bg_radius);
-        mDotSize = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_size);
-
-        mExpandedChipRadius = res.getDimensionPixelSize(R.dimen.privacy_chip_radius);
-        mCollapsedChipRadius = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_radius);
-
-        mExpand = AnimatorInflater.loadAnimator(context, R.anim.tv_privacy_chip_expand);
-        mExpand.setTarget(this);
-        mCollapse = AnimatorInflater.loadAnimator(context, R.anim.tv_privacy_chip_collapse);
-        mCollapse.setTarget(this);
-    }
-
-    /**
-     * @return how far the chip is currently collapsed.
-     * @see #setCollapseProgress(float)
-     */
-    @Keep
-    public float getCollapseProgress() {
-        return mCollapseProgress;
-    }
-
-    /**
-     * Sets the collapsing progress of the chip to its collapsed state.
-     * @param pct How far the chip is collapsed, in the range 0-1.
-     *            0=fully expanded, 1=fully collapsed.
-     */
-    @Keep
-    public void setCollapseProgress(float pct) {
-        mCollapseProgress = pct;
-        invalidateSelf();
-    }
-
-    @Override
-    public void draw(@NonNull Canvas canvas) {
-        if (mCollapseProgress > 0f) {
-            // draw background
-            getBackgroundBounds(mBgRect);
-            mTmpRectF.set(mBgRect);
-            canvas.drawRoundRect(mTmpRectF, mBgRadius, mBgRadius, mBgPaint);
-        }
-
-        getForegroundBounds(mTmpRectF);
-        float radius = MathUtils.lerp(
-                mExpandedChipRadius,
-                mCollapseToDot ? mCollapsedChipRadius : mBgRadius,
-                mCollapseProgress);
-
-        canvas.drawRoundRect(mTmpRectF, radius, radius, mChipPaint);
-    }
-
-    private void getBackgroundBounds(Rect out) {
-        Rect bounds = getBounds();
-        Gravity.apply(Gravity.END, mBgWidth, mBgHeight, bounds, out, mLayoutDirection);
-    }
-
-    private void getCollapsedForegroundBounds(Rect out) {
-        Rect bounds = getBounds();
-        getBackgroundBounds(mBgRect);
-        if (mCollapseToDot) {
-            Gravity.apply(Gravity.CENTER, mDotSize, mDotSize, mBgRect, out);
-        } else {
-            out.set(bounds.left, mBgRect.top, bounds.right, mBgRect.bottom);
-        }
-    }
-
-    private void getForegroundBounds(RectF out) {
-        Rect bounds = getBounds();
-        getCollapsedForegroundBounds(mTmpRect);
-        lerpRect(bounds, mTmpRect, mCollapseProgress, out);
-    }
-
-    private void lerpRect(Rect start, Rect stop, float amount, RectF out) {
-        float left = MathUtils.lerp(start.left, stop.left, amount);
-        float top = MathUtils.lerp(start.top, stop.top, amount);
-        float right = MathUtils.lerp(start.right, stop.right, amount);
-        float bottom = MathUtils.lerp(start.bottom, stop.bottom, amount);
-        out.set(left, top, right, bottom);
-    }
-
-    /**
-     * Clips the given canvas to this chip's foreground shape.
-     * @param canvas Canvas to clip.
-     */
-    public void clipToForeground(Canvas canvas) {
-        getForegroundBounds(mTmpRectF);
-        float radius = MathUtils.lerp(
-                mExpandedChipRadius,
-                mCollapseToDot ? mCollapsedChipRadius : mBgRadius,
-                mCollapseProgress);
-
-        mPath.reset();
-        mPath.addRoundRect(mTmpRectF, radius, radius, Path.Direction.CW);
-        canvas.clipPath(mPath);
-    }
-
-    @Override
-    protected void onBoundsChange(@NonNull Rect bounds) {
-        super.onBoundsChange(bounds);
-        invalidateSelf();
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mChipPaint.setAlpha(alpha);
-        mBgPaint.setAlpha(alpha);
-    }
-
-    /**
-     * Transitions to a full chip.
-     *
-     * @param animate Whether to animate the change to a full chip, or expand instantly.
-     */
-    public void expand(boolean animate) {
-        if (DEBUG) Log.d(TAG, "expanding");
-        if (mIsExpanded) {
-            return;
-        }
-        mIsExpanded = true;
-        if (animate) {
-            mCollapse.cancel();
-            mExpand.start();
-        } else {
-            mCollapseProgress = 0f;
-            invalidateSelf();
-        }
-    }
-
-    /**
-     * Starts the animation to a dot.
-     */
-    public void collapse() {
-        if (DEBUG) Log.d(TAG, "collapsing");
-        if (!mIsExpanded) {
-            return;
-        }
-        mIsExpanded = false;
-        mExpand.cancel();
-        mCollapse.start();
-    }
-
-    @Override
-    public void setColorFilter(@Nullable ColorFilter colorFilter) {
-        // no-op
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/PrivacyItemsChip.java b/packages/SystemUI/src/com/android/systemui/privacy/television/PrivacyItemsChip.java
deleted file mode 100644
index 158a591..0000000
--- a/packages/SystemUI/src/com/android/systemui/privacy/television/PrivacyItemsChip.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.privacy.television;
-
-import android.annotation.ColorRes;
-import android.annotation.IntDef;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.R;
-import com.android.systemui.privacy.PrivacyType;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * View that shows indicator icons for privacy items.
- */
-public class PrivacyItemsChip extends FrameLayout {
-    private static final String TAG = "PrivacyItemsChip";
-    private static final boolean DEBUG = false;
-
-    /**
-     * Configuration for a PrivacyItemsChip's appearance.
-     */
-    public static class ChipConfig {
-        public final List<PrivacyType> privacyTypes;
-        @ColorRes
-        public final int colorRes;
-        public final boolean collapseToDot;
-
-        /**
-         * @param privacyTypes Privacy types to show icons for, in order.
-         * @param colorRes Color resource for the chip's foreground color.
-         * @param collapseToDot Whether to collapse the chip in to a dot,
-         *                      or just collapse it into a smaller chip with icons still visible.
-         */
-        public ChipConfig(@NonNull List<PrivacyType> privacyTypes, int colorRes,
-                boolean collapseToDot) {
-            this.privacyTypes = privacyTypes;
-            this.colorRes = colorRes;
-            this.collapseToDot = collapseToDot;
-        }
-    }
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"STATE_"}, value = {
-            STATE_NOT_SHOWN,
-            STATE_EXPANDED,
-            STATE_COLLAPSED,
-    })
-    public @interface State {
-    }
-
-    private static final int STATE_NOT_SHOWN = 0;
-    private static final int STATE_EXPANDED = 1;
-    private static final int STATE_COLLAPSED = 2;
-
-    private final ChipConfig mConfig;
-    private final int mIconSize;
-    private final int mCollapsedIconSize;
-    private final int mIconMarginHorizontal;
-    private final PrivacyChipDrawable mChipBackgroundDrawable;
-    private final List<ImageView> mIcons = new ArrayList<>();
-
-    @State
-    private int mState = STATE_NOT_SHOWN;
-
-    public PrivacyItemsChip(@NonNull Context context, @NonNull ChipConfig config) {
-        super(context);
-        mConfig = config;
-        setVisibility(View.GONE);
-
-        Resources res = context.getResources();
-        mIconSize = res.getDimensionPixelSize(R.dimen.privacy_chip_icon_size);
-        mCollapsedIconSize = res.getDimensionPixelSize(R.dimen.privacy_chip_collapsed_icon_size);
-        mIconMarginHorizontal =
-                res.getDimensionPixelSize(R.dimen.privacy_chip_icon_margin_in_between);
-
-        LayoutInflater.from(context).inflate(R.layout.tv_ongoing_privacy_chip, this);
-        LinearLayout iconsContainer = findViewById(R.id.icons_container);
-
-        mChipBackgroundDrawable = new PrivacyChipDrawable(
-                context, config.colorRes, config.collapseToDot);
-        mChipBackgroundDrawable.setCallback(new Drawable.Callback() {
-            @Override
-            public void invalidateDrawable(@NonNull Drawable who) {
-                invalidate();
-            }
-
-            @Override
-            public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
-            }
-
-            @Override
-            public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
-            }
-        });
-
-        setBackground(mChipBackgroundDrawable);
-
-        for (PrivacyType type : config.privacyTypes) {
-            ImageView typeIconView = new ImageView(context);
-            Drawable icon = type.getIcon(context);
-            icon.mutate().setTint(context.getColor(R.color.privacy_icon_tint));
-
-            typeIconView.setImageDrawable(icon);
-            typeIconView.setScaleType(ImageView.ScaleType.FIT_CENTER);
-            mIcons.add(typeIconView);
-            iconsContainer.addView(typeIconView, mIconSize, mIconSize);
-            LinearLayout.LayoutParams lp =
-                    (LinearLayout.LayoutParams) typeIconView.getLayoutParams();
-            lp.leftMargin = mIconMarginHorizontal;
-            lp.rightMargin = mIconMarginHorizontal;
-            typeIconView.setVisibility(View.GONE);
-        }
-    }
-
-    /**
-     * Sets the active privacy types, and expands the chip if there are active items and the chip is
-     * currently collapsed, or hides the chip if there are no active items.
-     *
-     * @param types The set of active privacy types. Only types configured in {@link ChipConfig}
-     *              are shown.
-     */
-    public void expandForTypes(Set<PrivacyType> types) {
-        if (DEBUG) Log.d(TAG, "expandForTypes, state=" + stateToString(mState));
-
-        boolean hasActiveTypes = false;
-
-        for (int i = 0; i < mConfig.privacyTypes.size(); i++) {
-            PrivacyType type = mConfig.privacyTypes.get(i);
-            ImageView icon = mIcons.get(i);
-            boolean isTypeActive = types.contains(type);
-            hasActiveTypes = hasActiveTypes || isTypeActive;
-
-            icon.setVisibility(isTypeActive ? View.VISIBLE : View.GONE);
-
-            // Set icon size to expanded size
-            ViewGroup.LayoutParams lp = icon.getLayoutParams();
-            lp.width = mIconSize;
-            lp.height = mIconSize;
-            icon.requestLayout();
-        }
-
-        if (hasActiveTypes) {
-            if (DEBUG) Log.d(TAG, "Chip has active types, expanding");
-            if (mState == STATE_NOT_SHOWN) {
-                mChipBackgroundDrawable.expand(/* animate= */ false);
-            } else if (mState == STATE_COLLAPSED) {
-                mChipBackgroundDrawable.expand(/* animate= */ true);
-            }
-            setVisibility(View.VISIBLE);
-            setState(STATE_EXPANDED);
-        } else {
-            if (DEBUG) Log.d(TAG, "Chip has no active types, hiding");
-            setVisibility(View.GONE);
-            setState(STATE_NOT_SHOWN);
-        }
-    }
-
-    /**
-     * Collapses this chip if currently expanded.
-     */
-    public void collapse() {
-        if (DEBUG) Log.d(TAG, "collapse");
-
-        if (mState != STATE_EXPANDED) {
-            return;
-        }
-        setState(STATE_COLLAPSED);
-
-        for (ImageView icon : mIcons) {
-            if (mConfig.collapseToDot) {
-                icon.setVisibility(View.GONE);
-            } else {
-                ViewGroup.LayoutParams lp = icon.getLayoutParams();
-                lp.width = mCollapsedIconSize;
-                lp.height = mCollapsedIconSize;
-                icon.requestLayout();
-            }
-        }
-
-        mChipBackgroundDrawable.collapse();
-    }
-
-    public boolean isExpanded() {
-        return mState == STATE_EXPANDED;
-    }
-
-    private void setState(@State int state) {
-        if (mState != state) {
-            if (DEBUG) Log.d(TAG, "State changed: " + stateToString(state));
-            mState = state;
-        }
-    }
-
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        mChipBackgroundDrawable.clipToForeground(canvas);
-        super.dispatchDraw(canvas);
-    }
-
-    /**
-     * Used in debug logs.
-     */
-    private static String stateToString(@State int state) {
-        switch (state) {
-            case STATE_NOT_SHOWN:
-                return "NOT_SHOWN";
-            case STATE_EXPANDED:
-                return "EXPANDED";
-            case STATE_COLLAPSED:
-                return "COLLAPSED";
-            default:
-                return "INVALID";
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java b/packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java
deleted file mode 100644
index 5e4c797..0000000
--- a/packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.privacy.television;
-
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UiThread;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.transition.AutoTransition;
-import android.transition.ChangeBounds;
-import android.transition.Fade;
-import android.transition.Transition;
-import android.transition.TransitionManager;
-import android.transition.TransitionSet;
-import android.util.ArraySet;
-import android.util.Log;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.IWindowManager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.WindowManager;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.R;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.privacy.PrivacyItem;
-import com.android.systemui.privacy.PrivacyItemController;
-import com.android.systemui.privacy.PrivacyType;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-import javax.inject.Inject;
-
-/**
- * A SystemUI component responsible for notifying the user whenever an application is
- * recording audio, camera, the screen, or accessing the location.
- */
-@SysUISingleton
-public class TvPrivacyChipsController 
-        implements CoreStartable, PrivacyItemController.Callback {
-    private static final String TAG = "TvPrivacyChipsController";
-    private static final boolean DEBUG = false;
-
-    // This title is used in CameraMicIndicatorsPermissionTest and
-    // RecognitionServiceMicIndicatorTest.
-    private static final String LAYOUT_PARAMS_TITLE = "MicrophoneCaptureIndicator";
-
-    // Chips configuration. We're not showing a location indicator on TV.
-    static final List<PrivacyItemsChip.ChipConfig> CHIPS = Arrays.asList(
-            new PrivacyItemsChip.ChipConfig(
-                    Collections.singletonList(PrivacyType.TYPE_MEDIA_PROJECTION),
-                    R.color.privacy_media_projection_chip,
-                    /* collapseToDot= */ false),
-            new PrivacyItemsChip.ChipConfig(
-                    Arrays.asList(PrivacyType.TYPE_CAMERA, PrivacyType.TYPE_MICROPHONE),
-                    R.color.privacy_mic_cam_chip,
-                    /* collapseToDot= */ true)
-    );
-
-    // Avoid multiple messages after rapid changes such as starting/stopping both camera and mic.
-    private static final int ACCESSIBILITY_ANNOUNCEMENT_DELAY_MS = 500;
-
-    /**
-     * Time to collect privacy item updates before applying them.
-     * Since MediaProjection and AppOps come from different data sources,
-     * PrivacyItem updates when screen & audio recording ends do not come at the same time.
-     * Without this, if eg. MediaProjection ends first, you'd see the microphone chip expand and
-     * almost immediately fade out as it is expanding. With this, the two chips disappear together.
-     */
-    private static final int PRIVACY_ITEM_DEBOUNCE_TIMEOUT_MS = 200;
-
-    // How long chips stay expanded after an update.
-    private static final int EXPANDED_DURATION_MS = 4000;
-
-    private final Context mContext;
-    private final Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
-    private final Runnable mCollapseRunnable = this::collapseChips;
-    private final Runnable mUpdatePrivacyItemsRunnable = this::updateChipsAndAnnounce;
-    private final Runnable mAccessibilityRunnable = this::makeAccessibilityAnnouncement;
-
-    private final PrivacyItemController mPrivacyItemController;
-    private final IWindowManager mIWindowManager;
-    private final Rect[] mBounds = new Rect[4];
-    private final TransitionSet mTransition;
-    private final TransitionSet mCollapseTransition;
-    private boolean mIsRtl;
-
-    @Nullable
-    private ViewGroup mChipsContainer;
-    @Nullable
-    private List<PrivacyItemsChip> mChips;
-    @NonNull
-    private List<PrivacyItem> mPrivacyItems = Collections.emptyList();
-    @NonNull
-    private final List<PrivacyItem> mItemsBeforeLastAnnouncement = new ArrayList<>();
-
-    @Inject
-    public TvPrivacyChipsController(Context context, PrivacyItemController privacyItemController,
-            IWindowManager iWindowManager) {
-        mContext = context;
-        if (DEBUG) Log.d(TAG, "TvPrivacyChipsController running");
-        mPrivacyItemController = privacyItemController;
-        mIWindowManager = iWindowManager;
-
-        Resources res = mContext.getResources();
-        mIsRtl = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-        updateStaticPrivacyIndicatorBounds();
-
-        Interpolator collapseInterpolator = AnimationUtils.loadInterpolator(context,
-                R.interpolator.tv_privacy_chip_collapse_interpolator);
-        Interpolator expandInterpolator = AnimationUtils.loadInterpolator(context,
-                R.interpolator.tv_privacy_chip_expand_interpolator);
-
-        TransitionSet chipFadeTransition = new TransitionSet()
-                .addTransition(new Fade(Fade.IN))
-                .addTransition(new Fade(Fade.OUT));
-        chipFadeTransition.setOrdering(TransitionSet.ORDERING_TOGETHER);
-        chipFadeTransition.excludeTarget(ImageView.class, true);
-
-        Transition chipBoundsExpandTransition = new ChangeBounds();
-        chipBoundsExpandTransition.excludeTarget(ImageView.class, true);
-        chipBoundsExpandTransition.setInterpolator(expandInterpolator);
-
-        Transition chipBoundsCollapseTransition = new ChangeBounds();
-        chipBoundsCollapseTransition.excludeTarget(ImageView.class, true);
-        chipBoundsCollapseTransition.setInterpolator(collapseInterpolator);
-
-        TransitionSet iconCollapseTransition = new AutoTransition();
-        iconCollapseTransition.setOrdering(TransitionSet.ORDERING_TOGETHER);
-        iconCollapseTransition.addTarget(ImageView.class);
-        iconCollapseTransition.setInterpolator(collapseInterpolator);
-
-        TransitionSet iconExpandTransition = new AutoTransition();
-        iconExpandTransition.setOrdering(TransitionSet.ORDERING_TOGETHER);
-        iconExpandTransition.addTarget(ImageView.class);
-        iconExpandTransition.setInterpolator(expandInterpolator);
-
-        mTransition = new TransitionSet()
-                .addTransition(chipFadeTransition)
-                .addTransition(chipBoundsExpandTransition)
-                .addTransition(iconExpandTransition)
-                .setOrdering(TransitionSet.ORDERING_TOGETHER)
-                .setDuration(res.getInteger(R.integer.privacy_chip_animation_millis));
-
-        mCollapseTransition = new TransitionSet()
-                .addTransition(chipFadeTransition)
-                .addTransition(chipBoundsCollapseTransition)
-                .addTransition(iconCollapseTransition)
-                .setOrdering(TransitionSet.ORDERING_TOGETHER)
-                .setDuration(res.getInteger(R.integer.privacy_chip_animation_millis));
-
-        Transition.TransitionListener transitionListener = new Transition.TransitionListener() {
-            @Override
-            public void onTransitionStart(Transition transition) {
-                if (DEBUG) Log.v(TAG, "onTransitionStart");
-            }
-
-            @Override
-            public void onTransitionEnd(Transition transition) {
-                if (DEBUG) Log.v(TAG, "onTransitionEnd");
-                if (mChips != null) {
-                    boolean hasVisibleChip = false;
-                    boolean hasExpandedChip = false;
-                    for (PrivacyItemsChip chip : mChips) {
-                        hasVisibleChip = hasVisibleChip || chip.getVisibility() == View.VISIBLE;
-                        hasExpandedChip = hasExpandedChip || chip.isExpanded();
-                    }
-
-                    if (!hasVisibleChip) {
-                        if (DEBUG) Log.d(TAG, "No chips visible anymore");
-                        removeIndicatorView();
-                    } else if (hasExpandedChip) {
-                        if (DEBUG) Log.d(TAG, "Has expanded chips");
-                        collapseLater();
-                    }
-                }
-            }
-
-            @Override
-            public void onTransitionCancel(Transition transition) {
-            }
-
-            @Override
-            public void onTransitionPause(Transition transition) {
-            }
-
-            @Override
-            public void onTransitionResume(Transition transition) {
-            }
-        };
-
-        mTransition.addListener(transitionListener);
-        mCollapseTransition.addListener(transitionListener);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration config) {
-        boolean updatedRtl = config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-        if (mIsRtl == updatedRtl) {
-            return;
-        }
-        mIsRtl = updatedRtl;
-
-        // Update privacy chip location.
-        if (mChipsContainer != null) {
-            removeIndicatorView();
-            createAndShowIndicator();
-        }
-        updateStaticPrivacyIndicatorBounds();
-    }
-
-    @Override
-    public void start() {
-        mPrivacyItemController.addCallback(this);
-    }
-
-    @UiThread
-    @Override
-    public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) {
-        if (DEBUG) Log.d(TAG, "onPrivacyItemsChanged");
-
-        List<PrivacyItem> filteredPrivacyItems = new ArrayList<>(privacyItems);
-        if (filteredPrivacyItems.removeIf(
-                privacyItem -> !isPrivacyTypeShown(privacyItem.getPrivacyType()))) {
-            if (DEBUG) Log.v(TAG, "Removed privacy items we don't show");
-        }
-
-        // Do they have the same elements? (order doesn't matter)
-        if (privacyItems.size() == mPrivacyItems.size() && mPrivacyItems.containsAll(
-                privacyItems)) {
-            if (DEBUG) Log.d(TAG, "No change to relevant privacy items");
-            return;
-        }
-
-        mPrivacyItems = privacyItems;
-
-        if (!mUiThreadHandler.hasCallbacks(mUpdatePrivacyItemsRunnable)) {
-            mUiThreadHandler.postDelayed(mUpdatePrivacyItemsRunnable,
-                    PRIVACY_ITEM_DEBOUNCE_TIMEOUT_MS);
-        }
-    }
-
-    private boolean isPrivacyTypeShown(@NonNull PrivacyType type) {
-        for (PrivacyItemsChip.ChipConfig chip : CHIPS) {
-            if (chip.privacyTypes.contains(type)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @UiThread
-    private void updateChipsAndAnnounce() {
-        updateChips();
-        postAccessibilityAnnouncement();
-    }
-
-    private void updateStaticPrivacyIndicatorBounds() {
-        Resources res = mContext.getResources();
-        int mMaxExpandedWidth = res.getDimensionPixelSize(R.dimen.privacy_chips_max_width);
-        int mMaxExpandedHeight = res.getDimensionPixelSize(R.dimen.privacy_chip_height);
-        int mChipMarginTotal = 2 * res.getDimensionPixelSize(R.dimen.privacy_chip_margin);
-
-        final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        Rect screenBounds = windowManager.getCurrentWindowMetrics().getBounds();
-        mBounds[0] = new Rect(
-                mIsRtl ? screenBounds.left
-                        : screenBounds.right - mMaxExpandedWidth,
-                screenBounds.top,
-                mIsRtl ? screenBounds.left + mMaxExpandedWidth
-                        : screenBounds.right,
-                screenBounds.top + mChipMarginTotal + mMaxExpandedHeight
-        );
-
-        if (DEBUG) Log.v(TAG, "privacy indicator bounds: " + mBounds[0].toShortString());
-
-        try {
-            mIWindowManager.updateStaticPrivacyIndicatorBounds(mContext.getDisplayId(), mBounds);
-        } catch (RemoteException e) {
-            Log.w(TAG, "could not update privacy indicator bounds");
-        }
-    }
-
-    @UiThread
-    private void updateChips() {
-        if (DEBUG) Log.d(TAG, "updateChips: " + mPrivacyItems.size() + " privacy items");
-
-        if (mChipsContainer == null) {
-            if (!mPrivacyItems.isEmpty()) {
-                createAndShowIndicator();
-            }
-            return;
-        }
-
-        Set<PrivacyType> activePrivacyTypes = new ArraySet<>();
-        mPrivacyItems.forEach(item -> activePrivacyTypes.add(item.getPrivacyType()));
-
-        TransitionManager.beginDelayedTransition(mChipsContainer, mTransition);
-        mChips.forEach(chip -> chip.expandForTypes(activePrivacyTypes));
-    }
-
-    /**
-     * Collapse the chip {@link #EXPANDED_DURATION_MS} from now.
-     */
-    private void collapseLater() {
-        mUiThreadHandler.removeCallbacks(mCollapseRunnable);
-        if (DEBUG) Log.d(TAG, "Chips will collapse in " + EXPANDED_DURATION_MS + "ms");
-        mUiThreadHandler.postDelayed(mCollapseRunnable, EXPANDED_DURATION_MS);
-    }
-
-    private void collapseChips() {
-        if (DEBUG) Log.d(TAG, "collapseChips");
-        if (mChipsContainer == null) {
-            return;
-        }
-
-        boolean hasExpandedChip = false;
-        for (PrivacyItemsChip chip : mChips) {
-            hasExpandedChip |= chip.isExpanded();
-        }
-
-        if (mChipsContainer != null && hasExpandedChip) {
-            TransitionManager.beginDelayedTransition(mChipsContainer, mCollapseTransition);
-            for (PrivacyItemsChip chip : mChips) {
-                chip.collapse();
-            }
-        }
-    }
-
-    @UiThread
-    private void createAndShowIndicator() {
-        if (DEBUG) Log.i(TAG, "Creating privacy indicators");
-
-        Context privacyChipContext = new ContextThemeWrapper(mContext, R.style.PrivacyChip);
-        mChips = new ArrayList<>();
-        mChipsContainer = (ViewGroup) LayoutInflater.from(privacyChipContext)
-                .inflate(R.layout.tv_privacy_chip_container, null);
-
-        int chipMargins = privacyChipContext.getResources()
-                .getDimensionPixelSize(R.dimen.privacy_chip_margin);
-        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
-        lp.setMarginStart(chipMargins);
-        lp.setMarginEnd(chipMargins);
-
-        for (PrivacyItemsChip.ChipConfig chipConfig : CHIPS) {
-            PrivacyItemsChip chip = new PrivacyItemsChip(privacyChipContext, chipConfig);
-            mChipsContainer.addView(chip, lp);
-            mChips.add(chip);
-        }
-
-        final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        windowManager.addView(mChipsContainer, getWindowLayoutParams());
-
-        final ViewGroup container = mChipsContainer;
-        mChipsContainer.getViewTreeObserver()
-                .addOnGlobalLayoutListener(
-                        new ViewTreeObserver.OnGlobalLayoutListener() {
-                            @Override
-                            public void onGlobalLayout() {
-                                if (DEBUG) Log.v(TAG, "Chips container laid out");
-                                container.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                                updateChips();
-                            }
-                        });
-    }
-
-    private WindowManager.LayoutParams getWindowLayoutParams() {
-        final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
-                WRAP_CONTENT,
-                WRAP_CONTENT,
-                WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
-                PixelFormat.TRANSLUCENT);
-        layoutParams.gravity = Gravity.TOP | (mIsRtl ? Gravity.LEFT : Gravity.RIGHT);
-        layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
-        layoutParams.packageName = mContext.getPackageName();
-        return layoutParams;
-    }
-
-    @UiThread
-    private void removeIndicatorView() {
-        if (DEBUG) Log.d(TAG, "removeIndicatorView");
-        mUiThreadHandler.removeCallbacks(mCollapseRunnable);
-
-        final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        if (windowManager != null && mChipsContainer != null) {
-            windowManager.removeView(mChipsContainer);
-        }
-
-        mChipsContainer = null;
-        mChips = null;
-    }
-
-    /**
-     * Schedules the accessibility announcement to be made after {@link
-     * #ACCESSIBILITY_ANNOUNCEMENT_DELAY_MS} (if possible). This is so that only one announcement is
-     * made instead of two separate ones if both the camera and the mic are started/stopped.
-     */
-    @UiThread
-    private void postAccessibilityAnnouncement() {
-        mUiThreadHandler.removeCallbacks(mAccessibilityRunnable);
-
-        if (mPrivacyItems.size() == 0) {
-            // Announce immediately since announcement cannot be made once the chip is gone.
-            makeAccessibilityAnnouncement();
-        } else {
-            mUiThreadHandler.postDelayed(mAccessibilityRunnable,
-                    ACCESSIBILITY_ANNOUNCEMENT_DELAY_MS);
-        }
-    }
-
-    private void makeAccessibilityAnnouncement() {
-        if (mChipsContainer == null) {
-            return;
-        }
-
-        boolean cameraWasRecording = listContainsPrivacyType(mItemsBeforeLastAnnouncement,
-                PrivacyType.TYPE_CAMERA);
-        boolean cameraIsRecording = listContainsPrivacyType(mPrivacyItems,
-                PrivacyType.TYPE_CAMERA);
-        boolean micWasRecording = listContainsPrivacyType(mItemsBeforeLastAnnouncement,
-                PrivacyType.TYPE_MICROPHONE);
-        boolean micIsRecording = listContainsPrivacyType(mPrivacyItems,
-                PrivacyType.TYPE_MICROPHONE);
-
-        boolean screenWasRecording = listContainsPrivacyType(mItemsBeforeLastAnnouncement,
-                PrivacyType.TYPE_MEDIA_PROJECTION);
-        boolean screenIsRecording = listContainsPrivacyType(mPrivacyItems,
-                PrivacyType.TYPE_MEDIA_PROJECTION);
-
-        int announcement = 0;
-        if (!cameraWasRecording && cameraIsRecording && !micWasRecording && micIsRecording) {
-            // Both started
-            announcement = R.string.mic_and_camera_recording_announcement;
-        } else if (cameraWasRecording && !cameraIsRecording && micWasRecording && !micIsRecording) {
-            // Both stopped
-            announcement = R.string.mic_camera_stopped_recording_announcement;
-        } else {
-            // Did the camera start or stop?
-            if (cameraWasRecording && !cameraIsRecording) {
-                announcement = R.string.camera_stopped_recording_announcement;
-            } else if (!cameraWasRecording && cameraIsRecording) {
-                announcement = R.string.camera_recording_announcement;
-            }
-
-            // Announce camera changes now since we might need a second announcement about the mic.
-            if (announcement != 0) {
-                mChipsContainer.announceForAccessibility(mContext.getString(announcement));
-                announcement = 0;
-            }
-
-            // Did the mic start or stop?
-            if (micWasRecording && !micIsRecording) {
-                announcement = R.string.mic_stopped_recording_announcement;
-            } else if (!micWasRecording && micIsRecording) {
-                announcement = R.string.mic_recording_announcement;
-            }
-        }
-
-        if (announcement != 0) {
-            mChipsContainer.announceForAccessibility(mContext.getString(announcement));
-        }
-
-        if (!screenWasRecording && screenIsRecording) {
-            mChipsContainer.announceForAccessibility(
-                    mContext.getString(R.string.screen_recording_announcement));
-        } else if (screenWasRecording && !screenIsRecording) {
-            mChipsContainer.announceForAccessibility(
-                    mContext.getString(R.string.screen_stopped_recording_announcement));
-        }
-
-        mItemsBeforeLastAnnouncement.clear();
-        mItemsBeforeLastAnnouncement.addAll(mPrivacyItems);
-    }
-
-    private boolean listContainsPrivacyType(List<PrivacyItem> list, PrivacyType privacyType) {
-        for (PrivacyItem item : list) {
-            if (item.getPrivacyType() == privacyType) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
index 995c6a4..0941a20 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
@@ -14,10 +14,13 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.privacy.PrivacyChipEvent
 import com.android.systemui.privacy.PrivacyDialogController
+import com.android.systemui.privacy.PrivacyDialogControllerV2
 import com.android.systemui.privacy.PrivacyItem
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.privacy.logging.PrivacyLogger
@@ -26,7 +29,7 @@
 import javax.inject.Inject
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.shade.ShadeModule.Companion.SHADE_HEADER
+import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import javax.inject.Named
 
@@ -49,6 +52,7 @@
     private val uiEventLogger: UiEventLogger,
     @Named(SHADE_HEADER) private val privacyChip: OngoingPrivacyChip,
     private val privacyDialogController: PrivacyDialogController,
+    private val privacyDialogControllerV2: PrivacyDialogControllerV2,
     private val privacyLogger: PrivacyLogger,
     @Named(SHADE_HEADER) private val iconContainer: StatusIconContainer,
     private val permissionManager: PermissionManager,
@@ -58,7 +62,8 @@
     private val appOpsController: AppOpsController,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val safetyCenterManager: SafetyCenterManager,
-    private val deviceProvisionedController: DeviceProvisionedController
+    private val deviceProvisionedController: DeviceProvisionedController,
+    private val featureFlags: FeatureFlags
 ) {
 
     var chipVisibilityListener: ChipVisibilityListener? = null
@@ -143,7 +148,11 @@
             // If the privacy chip is visible, it means there were some indicators
             uiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK)
             if (safetyCenterEnabled) {
-                showSafetyCenter()
+                if (featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)) {
+                    privacyDialogControllerV2.showDialog(privacyChip.context, privacyChip)
+                } else {
+                    showSafetyCenter()
+                }
             } else {
                 privacyDialogController.showDialog(privacyChip.context)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ChevronImageView.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ChevronImageView.kt
new file mode 100644
index 0000000..8fd1d6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ChevronImageView.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.qs.tileimpl
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ImageView
+
+class ChevronImageView(context: Context, attrs: AttributeSet?) : ImageView(context, attrs) {
+
+    override fun resolveLayoutDirection(): Boolean {
+        val previousLayoutDirection = layoutDirection
+        return super.resolveLayoutDirection().also { resolved ->
+            if (resolved && layoutDirection != previousLayoutDirection) {
+                onRtlPropertiesChanged(layoutDirection)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 207cc139..a737a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -78,7 +78,6 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.IVoiceInteractionSessionListener;
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.internal.util.ScreenshotRequest;
 import com.android.systemui.Dumpable;
@@ -143,6 +142,7 @@
     private final Executor mMainExecutor;
     private final ShellInterface mShellInterface;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+    private final Lazy<ShadeViewController> mShadeViewControllerLazy;
     private SysUiState mSysUiState;
     private final Handler mHandler;
     private final Lazy<NavigationBarController> mNavBarControllerLazy;
@@ -201,11 +201,7 @@
                 // TODO move this logic to message queue
                 mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> {
                     if (event.getActionMasked() == ACTION_DOWN) {
-                        ShadeViewController shadeViewController =
-                                centralSurfaces.getShadeViewController();
-                        if (shadeViewController != null) {
-                            shadeViewController.startExpandLatencyTracking();
-                        }
+                        mShadeViewControllerLazy.get().startExpandLatencyTracking();
                     }
                     mHandler.post(() -> {
                         int action = event.getActionMasked();
@@ -552,8 +548,10 @@
             ShellInterface shellInterface,
             Lazy<NavigationBarController> navBarControllerLazy,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+            Lazy<ShadeViewController> shadeViewControllerLazy,
             NavigationModeController navModeController,
-            NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
+            NotificationShadeWindowController statusBarWinController,
+            SysUiState sysUiState,
             UserTracker userTracker,
             ScreenLifecycle screenLifecycle,
             WakefulnessLifecycle wakefulnessLifecycle,
@@ -573,6 +571,7 @@
         mMainExecutor = mainExecutor;
         mShellInterface = shellInterface;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+        mShadeViewControllerLazy = shadeViewControllerLazy;
         mHandler = new Handler();
         mNavBarControllerLazy = navBarControllerLazy;
         mStatusBarWinController = statusBarWinController;
@@ -677,13 +676,10 @@
                 mNavBarControllerLazy.get().getDefaultNavigationBar();
         final NavigationBarView navBarView =
                 mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
-        final ShadeViewController panelController =
-                mCentralSurfacesOptionalLazy.get()
-                        .map(CentralSurfaces::getShadeViewController)
-                        .orElse(null);
         if (SysUiState.DEBUG) {
             Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
-                    + " navBarView=" + navBarView + " panelController=" + panelController);
+                    + " navBarView=" + navBarView
+                    + " shadeViewController=" + mShadeViewControllerLazy.get());
         }
 
         if (navBarFragment != null) {
@@ -692,9 +688,7 @@
         if (navBarView != null) {
             navBarView.updateDisabledSystemUiStateFlags(mSysUiState);
         }
-        if (panelController != null) {
-            panelController.updateSystemUiStateFlags();
-        }
+        mShadeViewControllerLazy.get().updateSystemUiStateFlags();
         if (mStatusBarWinController != null) {
             mStatusBarWinController.notifyStateChangedCallbacks();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
index b23c4ec..92384d6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
@@ -20,14 +20,22 @@
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.DisplayId
 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.shared.model.WakefulnessState
+import com.android.systemui.model.SysUiState
+import com.android.systemui.model.updateFlags
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.SceneContainerNames
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -49,12 +57,15 @@
     private val authenticationInteractor: AuthenticationInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val featureFlags: FeatureFlags,
+    private val sysUiState: SysUiState,
+    @DisplayId private val displayId: Int,
 ) : CoreStartable {
 
     override fun start() {
         if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
             hydrateVisibility()
             automaticallySwitchScenes()
+            hydrateSystemUiState()
         }
     }
 
@@ -121,6 +132,27 @@
         }
     }
 
+    /** Keeps [SysUiState] up-to-date */
+    private fun hydrateSystemUiState() {
+        applicationScope.launch {
+            sceneInteractor
+                .currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT)
+                .map { it.key }
+                .distinctUntilChanged()
+                .collect { sceneKey ->
+                    sysUiState.updateFlags(
+                        displayId,
+                        SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to (sceneKey != SceneKey.Gone),
+                        SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to (sceneKey == SceneKey.Shade),
+                        SYSUI_STATE_QUICK_SETTINGS_EXPANDED to (sceneKey == SceneKey.QuickSettings),
+                        SYSUI_STATE_BOUNCER_SHOWING to (sceneKey == SceneKey.Bouncer),
+                        SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to
+                            (sceneKey == SceneKey.Lockscreen),
+                    )
+                }
+        }
+    }
+
     private fun switchToScene(targetSceneKey: SceneKey) {
         sceneInteractor.setCurrentScene(
             containerName = CONTAINER_NAME,
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
deleted file mode 100644
index 731b177..0000000
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.sensorprivacy.television;
-
-import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
-import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
-
-import android.annotation.DimenRes;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.Drawable;
-import android.hardware.SensorPrivacyManager;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
-import com.android.systemui.tv.TvBottomSheetActivity;
-import com.android.systemui.util.settings.GlobalSettings;
-
-import javax.inject.Inject;
-
-/**
- * Bottom sheet that is shown when the camera/mic sensors privacy state changed
- * by the global software toggle or physical privacy switch.
- */
-public class TvSensorPrivacyChangedActivity extends TvBottomSheetActivity {
-
-    private static final String TAG = TvSensorPrivacyChangedActivity.class.getSimpleName();
-
-    private static final int ALL_SENSORS = Integer.MAX_VALUE;
-
-    private int mSensor = -1;
-    private int mToggleType = -1;
-
-    private final GlobalSettings mGlobalSettings;
-    private final IndividualSensorPrivacyController mSensorPrivacyController;
-    private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
-    private TextView mTitle;
-    private TextView mContent;
-    private ImageView mIcon;
-    private ImageView mSecondIcon;
-    private Button mPositiveButton;
-    private Button mCancelButton;
-
-    @Inject
-    public TvSensorPrivacyChangedActivity(
-            IndividualSensorPrivacyController individualSensorPrivacyController,
-            GlobalSettings globalSettings) {
-        mSensorPrivacyController = individualSensorPrivacyController;
-        mGlobalSettings = globalSettings;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getWindow().addSystemFlags(
-                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-
-        boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS,
-                false);
-        if (allSensors) {
-            mSensor = ALL_SENSORS;
-        } else {
-            mSensor = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_SENSOR, -1);
-        }
-
-        mToggleType = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_TOGGLE_TYPE, -1);
-
-        if (mSensor == -1 || mToggleType == -1) {
-            Log.v(TAG, "Invalid extras");
-            finish();
-            return;
-        }
-
-        // Do not show for software toggles
-        if (mToggleType == SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE) {
-            finish();
-            return;
-        }
-
-        mSensorPrivacyCallback = (sensor, blocked) -> {
-            updateUI();
-        };
-
-        initUI();
-    }
-
-    private void initUI() {
-        mTitle = findViewById(R.id.bottom_sheet_title);
-        mContent = findViewById(R.id.bottom_sheet_body);
-        mIcon = findViewById(R.id.bottom_sheet_icon);
-        // mic icon if both icons are shown
-        mSecondIcon = findViewById(R.id.bottom_sheet_second_icon);
-        mPositiveButton = findViewById(R.id.bottom_sheet_positive_button);
-        mCancelButton = findViewById(R.id.bottom_sheet_negative_button);
-
-        mCancelButton.setText(android.R.string.cancel);
-        mCancelButton.setOnClickListener(v -> finish());
-
-        updateUI();
-    }
-
-    private void updateUI() {
-        final Resources resources = getResources();
-        setIconTint(resources.getBoolean(R.bool.config_unblockHwSensorIconEnableTint));
-        setIconSize(R.dimen.unblock_hw_sensor_icon_width, R.dimen.unblock_hw_sensor_icon_height);
-
-        switch (mSensor) {
-            case CAMERA:
-                updateUiForCameraUpdate(
-                        mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA));
-                break;
-            case MICROPHONE:
-            default:
-                updateUiForMicUpdate(
-                        mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE));
-                break;
-        }
-
-        // Start animation if drawable is animated
-        Drawable iconDrawable = mIcon.getDrawable();
-        if (iconDrawable instanceof Animatable) {
-            ((Animatable) iconDrawable).start();
-        }
-
-        mPositiveButton.setVisibility(View.GONE);
-        mCancelButton.setText(android.R.string.ok);
-    }
-
-    private void updateUiForMicUpdate(boolean blocked) {
-        if (blocked) {
-            mTitle.setText(R.string.sensor_privacy_mic_turned_off_dialog_title);
-            if (isExplicitUserInteractionAudioBypassAllowed()) {
-                mContent.setText(R.string.sensor_privacy_mic_blocked_with_exception_dialog_content);
-            } else {
-                mContent.setText(R.string.sensor_privacy_mic_blocked_no_exception_dialog_content);
-            }
-            mIcon.setImageResource(R.drawable.unblock_hw_sensor_microphone);
-            mSecondIcon.setVisibility(View.GONE);
-        } else {
-            mTitle.setText(R.string.sensor_privacy_mic_turned_on_dialog_title);
-            mContent.setText(R.string.sensor_privacy_mic_unblocked_dialog_content);
-            mIcon.setImageResource(com.android.internal.R.drawable.ic_mic_allowed);
-            mSecondIcon.setVisibility(View.GONE);
-        }
-    }
-
-    private void updateUiForCameraUpdate(boolean blocked) {
-        if (blocked) {
-            mTitle.setText(R.string.sensor_privacy_camera_turned_off_dialog_title);
-            mContent.setText(R.string.sensor_privacy_camera_blocked_dialog_content);
-            mIcon.setImageResource(R.drawable.unblock_hw_sensor_camera);
-            mSecondIcon.setVisibility(View.GONE);
-        } else {
-            mTitle.setText(R.string.sensor_privacy_camera_turned_on_dialog_title);
-            mContent.setText(R.string.sensor_privacy_camera_unblocked_dialog_content);
-            mIcon.setImageResource(com.android.internal.R.drawable.ic_camera_allowed);
-            mSecondIcon.setVisibility(View.GONE);
-        }
-    }
-
-    private void setIconTint(boolean enableTint) {
-        final Resources resources = getResources();
-
-        if (enableTint) {
-            final ColorStateList iconTint = resources.getColorStateList(
-                    R.color.bottom_sheet_icon_color, getTheme());
-            mIcon.setImageTintList(iconTint);
-            mSecondIcon.setImageTintList(iconTint);
-        } else {
-            mIcon.setImageTintList(null);
-            mSecondIcon.setImageTintList(null);
-        }
-
-        mIcon.invalidate();
-        mSecondIcon.invalidate();
-    }
-
-    private void setIconSize(@DimenRes int widthRes, @DimenRes int heightRes) {
-        final Resources resources = getResources();
-        final int iconWidth = resources.getDimensionPixelSize(widthRes);
-        final int iconHeight = resources.getDimensionPixelSize(heightRes);
-
-        mIcon.getLayoutParams().width = iconWidth;
-        mIcon.getLayoutParams().height = iconHeight;
-        mIcon.invalidate();
-
-        mSecondIcon.getLayoutParams().width = iconWidth;
-        mSecondIcon.getLayoutParams().height = iconHeight;
-        mSecondIcon.invalidate();
-    }
-
-    private boolean isExplicitUserInteractionAudioBypassAllowed() {
-        return mGlobalSettings.getInt(
-                Settings.Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED, 1) == 1;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        updateUI();
-        mSensorPrivacyController.addCallback(mSensorPrivacyCallback);
-    }
-
-    @Override
-    public void onPause() {
-        mSensorPrivacyController.removeCallback(mSensorPrivacyCallback);
-        super.onPause();
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
deleted file mode 100644
index 1b9657f..0000000
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.sensorprivacy.television;
-
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
-import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
-import static android.hardware.SensorPrivacyManager.Sources.OTHER;
-
-import android.annotation.DimenRes;
-import android.app.AppOpsManager;
-import android.app.role.RoleManager;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.Drawable;
-import android.hardware.SensorPrivacyManager;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
-import com.android.systemui.tv.TvBottomSheetActivity;
-
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Bottom sheet that is shown when the camera/mic sensors are blocked by the global software toggle
- * or physical privacy switch.
- */
-public class TvUnblockSensorActivity extends TvBottomSheetActivity {
-
-    private static final String TAG = TvUnblockSensorActivity.class.getSimpleName();
-    private static final String ACTION_MANAGE_CAMERA_PRIVACY =
-            "android.settings.MANAGE_CAMERA_PRIVACY";
-    private static final String ACTION_MANAGE_MICROPHONE_PRIVACY =
-            "android.settings.MANAGE_MICROPHONE_PRIVACY";
-
-    private static final int ALL_SENSORS = Integer.MAX_VALUE;
-
-    private int mSensor = -1;
-
-    private final AppOpsManager mAppOpsManager;
-    private final RoleManager mRoleManager;
-    private final IndividualSensorPrivacyController mSensorPrivacyController;
-    private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
-    private TextView mTitle;
-    private TextView mContent;
-    private ImageView mIcon;
-    private ImageView mSecondIcon;
-    private Button mPositiveButton;
-    private Button mCancelButton;
-
-    @Inject
-    public TvUnblockSensorActivity(
-            IndividualSensorPrivacyController individualSensorPrivacyController,
-            AppOpsManager appOpsManager, RoleManager roleManager) {
-        mSensorPrivacyController = individualSensorPrivacyController;
-        mAppOpsManager = appOpsManager;
-        mRoleManager = roleManager;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getWindow().addSystemFlags(
-                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-
-        boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS,
-                false);
-        if (allSensors) {
-            mSensor = ALL_SENSORS;
-        } else {
-            mSensor = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_SENSOR, -1);
-        }
-
-        if (mSensor == -1) {
-            Log.v(TAG, "Invalid extras");
-            finish();
-            return;
-        }
-
-        mSensorPrivacyCallback = (sensor, blocked) -> {
-            if (mSensor == ALL_SENSORS && !mSensorPrivacyController.isSensorBlocked(CAMERA)
-                    && !mSensorPrivacyController.isSensorBlocked(MICROPHONE)) {
-                showToastAndFinish();
-            } else if (this.mSensor == sensor && !blocked) {
-                showToastAndFinish();
-            } else {
-                updateUI();
-            }
-        };
-
-        initUI();
-    }
-
-    private void showToastAndFinish() {
-        final int toastMsgResId;
-        switch(mSensor) {
-            case MICROPHONE:
-                toastMsgResId = R.string.sensor_privacy_mic_unblocked_toast_content;
-                break;
-            case CAMERA:
-                toastMsgResId = R.string.sensor_privacy_camera_unblocked_toast_content;
-                break;
-            case ALL_SENSORS:
-            default:
-                toastMsgResId = R.string.sensor_privacy_mic_camera_unblocked_toast_content;
-                break;
-        }
-        showToastAndFinish(toastMsgResId);
-    }
-
-    private void showToastAndFinish(int toastMsgResId) {
-        Toast.makeText(this, toastMsgResId, Toast.LENGTH_SHORT).show();
-        finish();
-    }
-
-    private boolean isBlockedByHardwareToggle() {
-        if (mSensor == ALL_SENSORS) {
-            return mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA)
-                    || mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE);
-        } else {
-            return mSensorPrivacyController.isSensorBlockedByHardwareToggle(mSensor);
-        }
-    }
-
-    private void initUI() {
-        mTitle = findViewById(R.id.bottom_sheet_title);
-        mContent = findViewById(R.id.bottom_sheet_body);
-        mIcon = findViewById(R.id.bottom_sheet_icon);
-        // mic icon if both icons are shown
-        mSecondIcon = findViewById(R.id.bottom_sheet_second_icon);
-        mPositiveButton = findViewById(R.id.bottom_sheet_positive_button);
-        mCancelButton = findViewById(R.id.bottom_sheet_negative_button);
-
-        mCancelButton.setText(android.R.string.cancel);
-        mCancelButton.setOnClickListener(v -> finish());
-
-        updateUI();
-    }
-
-    private void updateUI() {
-        if (isHTTAccessDisabled()) {
-            updateUiForHTT();
-        } else if (isBlockedByHardwareToggle()) {
-            updateUiForHardwareToggle();
-        } else {
-            updateUiForSoftwareToggle();
-        }
-    }
-
-    private void updateUiForHardwareToggle() {
-        final Resources resources = getResources();
-
-        boolean micBlocked = (mSensor == MICROPHONE || mSensor == ALL_SENSORS)
-                && mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE);
-        boolean cameraBlocked = (mSensor == CAMERA || mSensor == ALL_SENSORS)
-                && mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA);
-
-        setIconTint(resources.getBoolean(R.bool.config_unblockHwSensorIconEnableTint));
-        setIconSize(R.dimen.unblock_hw_sensor_icon_width, R.dimen.unblock_hw_sensor_icon_height);
-
-        if (micBlocked && cameraBlocked) {
-            mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_title);
-            mContent.setText(
-                    R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_content);
-            mIcon.setImageResource(R.drawable.unblock_hw_sensor_all);
-
-            Drawable secondIconDrawable = resources.getDrawable(
-                    R.drawable.unblock_hw_sensor_all_second, getTheme());
-            if (secondIconDrawable == null) {
-                mSecondIcon.setVisibility(View.GONE);
-            } else {
-                mSecondIcon.setImageDrawable(secondIconDrawable);
-            }
-        } else if (cameraBlocked) {
-            mTitle.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_title);
-            mContent.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_content);
-            mIcon.setImageResource(R.drawable.unblock_hw_sensor_camera);
-            mSecondIcon.setVisibility(View.GONE);
-        } else if (micBlocked) {
-            mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
-            mContent.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_content);
-            mIcon.setImageResource(R.drawable.unblock_hw_sensor_microphone);
-            mSecondIcon.setVisibility(View.GONE);
-        }
-
-        // Start animation if drawable is animated
-        Drawable iconDrawable = mIcon.getDrawable();
-        if (iconDrawable instanceof Animatable) {
-            ((Animatable) iconDrawable).start();
-        }
-
-        mPositiveButton.setVisibility(View.GONE);
-        mCancelButton.setText(android.R.string.ok);
-    }
-
-    private void updateUiForSoftwareToggle() {
-        setIconTint(true);
-        setIconSize(R.dimen.bottom_sheet_icon_size, R.dimen.bottom_sheet_icon_size);
-
-        switch (mSensor) {
-            case MICROPHONE:
-                mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
-                mContent.setText(R.string.sensor_privacy_start_use_mic_dialog_content);
-                mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
-                mSecondIcon.setVisibility(View.GONE);
-                break;
-            case CAMERA:
-                mTitle.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_title);
-                mContent.setText(R.string.sensor_privacy_start_use_camera_dialog_content);
-                mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
-                mSecondIcon.setVisibility(View.GONE);
-                break;
-            case ALL_SENSORS:
-            default:
-                mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_title);
-                mContent.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_content);
-                mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
-                mSecondIcon.setImageResource(
-                        com.android.internal.R.drawable.perm_group_microphone);
-                break;
-        }
-
-        mPositiveButton.setText(
-                com.android.internal.R.string.sensor_privacy_start_use_dialog_turn_on_button);
-        mPositiveButton.setOnClickListener(v -> {
-            if (mSensor == ALL_SENSORS) {
-                mSensorPrivacyController.setSensorBlocked(OTHER, CAMERA, false);
-                mSensorPrivacyController.setSensorBlocked(OTHER, MICROPHONE, false);
-            } else {
-                mSensorPrivacyController.setSensorBlocked(OTHER, mSensor, false);
-            }
-        });
-    }
-
-    private void updateUiForHTT() {
-        setIconTint(true);
-        setIconSize(R.dimen.bottom_sheet_icon_size, R.dimen.bottom_sheet_icon_size);
-
-        mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
-        mContent.setText(R.string.sensor_privacy_htt_blocked_dialog_content);
-        mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
-        mSecondIcon.setVisibility(View.GONE);
-
-        mPositiveButton.setText(R.string.sensor_privacy_dialog_open_settings);
-        mPositiveButton.setOnClickListener(v -> {
-            Intent openPrivacySettings = new Intent(ACTION_MANAGE_MICROPHONE_PRIVACY);
-            ActivityInfo activityInfo = openPrivacySettings.resolveActivityInfo(getPackageManager(),
-                    MATCH_SYSTEM_ONLY);
-            if (activityInfo == null) {
-                showToastAndFinish(com.android.internal.R.string.noApplications);
-            } else {
-                startActivity(openPrivacySettings);
-                finish();
-            }
-        });
-    }
-
-    private void setIconTint(boolean enableTint) {
-        final Resources resources = getResources();
-
-        if (enableTint) {
-            final ColorStateList iconTint = resources.getColorStateList(
-                    R.color.bottom_sheet_icon_color, getTheme());
-            mIcon.setImageTintList(iconTint);
-            mSecondIcon.setImageTintList(iconTint);
-        } else {
-            mIcon.setImageTintList(null);
-            mSecondIcon.setImageTintList(null);
-        }
-
-        mIcon.invalidate();
-        mSecondIcon.invalidate();
-    }
-
-    private void setIconSize(@DimenRes int widthRes, @DimenRes int heightRes) {
-        final Resources resources = getResources();
-        final int iconWidth = resources.getDimensionPixelSize(widthRes);
-        final int iconHeight = resources.getDimensionPixelSize(heightRes);
-
-        mIcon.getLayoutParams().width = iconWidth;
-        mIcon.getLayoutParams().height = iconHeight;
-        mIcon.invalidate();
-
-        mSecondIcon.getLayoutParams().width = iconWidth;
-        mSecondIcon.getLayoutParams().height = iconHeight;
-        mSecondIcon.invalidate();
-    }
-
-    private boolean isHTTAccessDisabled() {
-        String pkg = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-        List<String> assistantPkgs = mRoleManager.getRoleHolders(RoleManager.ROLE_ASSISTANT);
-        if (!assistantPkgs.contains(pkg)) {
-            return false;
-        }
-
-        return (mAppOpsManager.checkOpNoThrow(
-                AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, UserHandle.myUserId(),
-                pkg) != AppOpsManager.MODE_ALLOWED);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        updateUI();
-        mSensorPrivacyController.addCallback(mSensorPrivacyCallback);
-    }
-
-    @Override
-    public void onPause() {
-        mSensorPrivacyController.removeCallback(mSensorPrivacyCallback);
-        super.onPause();
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
index bb7f721..468a75d 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
@@ -52,12 +52,12 @@
     interface Callback {
 
         /** Notifies that a display has been added. */
-        @JvmDefault fun onDisplayAdded(displayId: Int) {}
+        fun onDisplayAdded(displayId: Int) {}
 
         /** Notifies that a display has been removed. */
-        @JvmDefault fun onDisplayRemoved(displayId: Int) {}
+        fun onDisplayRemoved(displayId: Int) {}
 
         /** Notifies a display has been changed */
-        @JvmDefault fun onDisplayChanged(displayId: Int) {}
+        fun onDisplayChanged(displayId: Int) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index 33a3125..93a3e90 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -71,7 +71,6 @@
          * Same as {@link onUserChanging(Int, Context, CountDownLatch)} but the latch will be
          * auto-decremented after the completion of this method.
          */
-        @JvmDefault
         fun onUserChanging(newUser: Int, userContext: Context) {}
 
         /**
@@ -82,7 +81,6 @@
          * user switch duration. When overriding this method, countDown() MUST be called on the
          * latch once execution is complete.
          */
-        @JvmDefault
         fun onUserChanging(newUser: Int, userContext: Context, latch: CountDownLatch) {
             onUserChanging(newUser, userContext)
             latch.countDown()
@@ -93,13 +91,11 @@
          * Override this method to run things after the screen is unfrozen for the user switch.
          * Please see {@link #onUserChanging} if you need to hide jank.
          */
-        @JvmDefault
         fun onUserChanged(newUser: Int, userContext: Context) {}
 
         /**
          * Notifies that the current user's profiles have changed.
          */
-        @JvmDefault
         fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 9399d48..e428976 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -147,7 +147,6 @@
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -365,7 +364,6 @@
     private KeyguardBottomAreaView mKeyguardBottomArea;
     private boolean mExpanding;
     private boolean mSplitShadeEnabled;
-    private boolean mDualShadeEnabled;
     /** The bottom padding reserved for elements of the keyguard measuring notifications. */
     private float mKeyguardNotificationBottomPadding;
     /**
@@ -599,7 +597,6 @@
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final KeyguardInteractor mKeyguardInteractor;
     private final KeyguardViewConfigurator mKeyguardViewConfigurator;
-    private final @Nullable MultiShadeInteractor mMultiShadeInteractor;
     private final CoroutineDispatcher mMainDispatcher;
     private boolean mIsAnyMultiShadeExpanded;
     private boolean mIsOcclusionTransitionRunning = false;
@@ -735,7 +732,6 @@
             LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
             @Main CoroutineDispatcher mainDispatcher,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
-            Provider<MultiShadeInteractor> multiShadeInteractorProvider,
             DumpManager dumpManager,
             KeyguardLongPressViewModel keyguardLongPressViewModel,
             KeyguardInteractor keyguardInteractor,
@@ -839,8 +835,6 @@
         mFeatureFlags = featureFlags;
         mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
         mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
-        mDualShadeEnabled = mFeatureFlags.isEnabled(Flags.DUAL_SHADE);
-        mMultiShadeInteractor = mDualShadeEnabled ? multiShadeInteractorProvider.get() : null;
         mFalsingCollector = falsingCollector;
         mPowerManager = powerManager;
         mWakeUpCoordinator = coordinator;
@@ -1079,11 +1073,6 @@
         mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
                 controller.setup(mNotificationContainerParent));
 
-        if (mDualShadeEnabled) {
-            collectFlow(mView, mMultiShadeInteractor.isAnyShadeExpanded(),
-                    mMultiShadeExpansionConsumer, mMainDispatcher);
-        }
-
         // Dreaming->Lockscreen
         collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
                 mDreamingToLockscreenTransition, mMainDispatcher);
@@ -1169,6 +1158,9 @@
     private void updateViewControllers(KeyguardStatusView keyguardStatusView,
             FrameLayout userAvatarView,
             KeyguardUserSwitcherView keyguardUserSwitcherView) {
+        if (mKeyguardStatusViewController != null) {
+            mKeyguardStatusViewController.onDestroy();
+        }
         // Re-associate the KeyguardStatusViewController
         KeyguardStatusViewComponent statusViewComponent =
                 mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 108ea68..6afed1d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -31,9 +31,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewStub;
-
-import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.AuthKeyguardMessageArea;
@@ -46,7 +43,6 @@
 import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.flags.FeatureFlags;
@@ -57,9 +53,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
 import com.android.systemui.log.BouncerLogger;
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
-import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor;
-import com.android.systemui.multishade.ui.view.MultiShadeView;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
 import com.android.systemui.statusbar.DragDownHelper;
@@ -83,7 +76,6 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 
 /**
  * Controller for {@link NotificationShadeWindowView}.
@@ -135,7 +127,6 @@
                     step.getTransitionState() == TransitionState.RUNNING;
             };
     private final SystemClock mClock;
-    private final @Nullable MultiShadeMotionEventInteractor mMultiShadeMotionEventInteractor;
 
     @Inject
     public NotificationShadeWindowViewController(
@@ -167,9 +158,7 @@
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
             FeatureFlags featureFlags,
-            Provider<MultiShadeInteractor> multiShadeInteractorProvider,
             SystemClock clock,
-            Provider<MultiShadeMotionEventInteractor> multiShadeMotionEventInteractorProvider,
             BouncerMessageInteractor bouncerMessageInteractor,
             BouncerLogger bouncerLogger) {
         mLockscreenShadeTransitionController = transitionController;
@@ -219,17 +208,6 @@
                     progressProvider -> progressProvider.addCallback(
                             mDisableSubpixelTextTransitionListener));
         }
-        if (ComposeFacade.INSTANCE.isComposeAvailable()
-                && featureFlags.isEnabled(Flags.DUAL_SHADE)) {
-            mMultiShadeMotionEventInteractor = multiShadeMotionEventInteractorProvider.get();
-            final ViewStub multiShadeViewStub = mView.findViewById(R.id.multi_shade_stub);
-            if (multiShadeViewStub != null) {
-                final MultiShadeView multiShadeView = (MultiShadeView) multiShadeViewStub.inflate();
-                multiShadeView.init(multiShadeInteractorProvider.get(), clock);
-            }
-        } else {
-            mMultiShadeMotionEventInteractor = null;
-        }
     }
 
     /**
@@ -395,10 +373,7 @@
                     return true;
                 }
 
-                if (mMultiShadeMotionEventInteractor != null) {
-                    // This interactor is not null only if the dual shade feature is enabled.
-                    return mMultiShadeMotionEventInteractor.shouldIntercept(ev);
-                } else if (mNotificationPanelViewController.isFullyExpanded()
+                if (mNotificationPanelViewController.isFullyExpanded()
                         && mDragDownHelper.isDragDownEnabled()
                         && !mService.isBouncerShowing()
                         && !mStatusBarStateController.isDozing()) {
@@ -428,10 +403,7 @@
                     return true;
                 }
 
-                if (mMultiShadeMotionEventInteractor != null) {
-                    // This interactor is not null only if the dual shade feature is enabled.
-                    return mMultiShadeMotionEventInteractor.onTouchEvent(ev, mView.getWidth());
-                } else if (mDragDownHelper.isDragDownEnabled()
+                if (mDragDownHelper.isDragDownEnabled()
                         || mDragDownHelper.isDraggingDown()) {
                     // we still want to finish our drag down gesture when locking the screen
                     return mDragDownHelper.onTouchEvent(ev) || handled;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index ee4e98e..fe4832f0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade
 
+import android.graphics.Point
 import android.hardware.display.AmbientDisplayConfiguration
 import android.os.PowerManager
 import android.provider.Settings
@@ -25,6 +26,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -52,6 +54,7 @@
         private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
         private val statusBarStateController: StatusBarStateController,
         private val shadeLogger: ShadeLogger,
+        private val dozeInteractor: DozeInteractor,
         userTracker: UserTracker,
         tunerService: TunerService,
         dumpManager: DumpManager
@@ -86,6 +89,7 @@
             shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
             if (proximityIsNotNear && isNotAFalseTap) {
                 shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
+                dozeInteractor.setLastTapToWakePosition(Point(e.x.toInt(), e.y.toInt()))
                 powerInteractor.wakeUpIfDozing("PULSING_SINGLE_TAP", PowerManager.WAKE_REASON_TAP)
             }
             return true
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
new file mode 100644
index 0000000..7a803867
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -0,0 +1,33 @@
+/*
+ * 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 com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+
+/** Fulfills dependencies on the shade with empty implementations for variants with no shade. */
+@Module
+abstract class ShadeEmptyImplModule {
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeViewController(svc: ShadeViewControllerEmptyImpl): ShadeViewController
+
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 8b89ff4..529f12e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -54,7 +54,7 @@
 import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
 import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
 import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
-import com.android.systemui.shade.ShadeModule.Companion.SHADE_HEADER
+import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER
 import com.android.systemui.shade.carrier.ShadeCarrierGroup
 import com.android.systemui.shade.carrier.ShadeCarrierGroupController
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index be21df1..89aaaaf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -16,293 +16,21 @@
 
 package com.android.systemui.shade
 
-import android.annotation.SuppressLint
-import android.content.ContentResolver
-import android.os.Handler
-import android.view.LayoutInflater
-import android.view.ViewStub
-import androidx.constraintlayout.motion.widget.MotionLayout
-import com.android.keyguard.LockIconView
-import com.android.systemui.CoreStartable
-import com.android.systemui.R
-import com.android.systemui.battery.BatteryMeterView
-import com.android.systemui.battery.BatteryMeterViewController
-import com.android.systemui.biometrics.AuthRippleController
-import com.android.systemui.biometrics.AuthRippleView
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.ui.view.KeyguardRootView
-import com.android.systemui.privacy.OngoingPrivacyChip
-import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.statusbar.NotificationShelf
-import com.android.systemui.statusbar.NotificationShelfController
-import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
-import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
-import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
-import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.phone.StatusIconContainer
-import com.android.systemui.statusbar.phone.TapAgainView
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.tuner.TunerService
+
 import dagger.Binds
 import dagger.Module
-import dagger.Provides
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import javax.inject.Named
-import javax.inject.Provider
 
 /** Module for classes related to the notification shade. */
-@Module(includes = [StartShadeModule::class])
+@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
 abstract class ShadeModule {
-
-    @Binds
-    @IntoMap
-    @ClassKey(AuthRippleController::class)
-    abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
-
     @Binds
     @SysUISingleton
     abstract fun bindsShadeViewController(
         notificationPanelViewController: NotificationPanelViewController
     ): ShadeViewController
 
-    companion object {
-        const val SHADE_HEADER = "large_screen_shade_header"
-
-        @SuppressLint("InflateParams") // Root views don't have parents.
-        @Provides
-        @SysUISingleton
-        fun providesWindowRootView(
-            layoutInflater: LayoutInflater,
-            featureFlags: FeatureFlags,
-        ): WindowRootView {
-            return if (
-                featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable()
-            ) {
-                layoutInflater.inflate(R.layout.scene_window_root, null)
-            } else {
-                layoutInflater.inflate(R.layout.super_notification_shade, null)
-            }
-                as WindowRootView?
-                ?: throw IllegalStateException("Window root view could not be properly inflated")
-        }
-
-        @Provides
-        @SysUISingleton
-        // TODO(b/277762009): Do something similar to
-        //  {@link StatusBarWindowModule.InternalWindowView} so that only
-        //  {@link NotificationShadeWindowViewController} can inject this view.
-        fun providesNotificationShadeWindowView(
-            root: WindowRootView,
-            featureFlags: FeatureFlags,
-        ): NotificationShadeWindowView {
-            if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
-                return root.findViewById(R.id.legacy_window_root)
-            }
-            return root as NotificationShadeWindowView?
-                ?: throw IllegalStateException("root view not a NotificationShadeWindowView")
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesNotificationStackScrollLayout(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): NotificationStackScrollLayout {
-            return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller)
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesNotificationShelfController(
-            featureFlags: FeatureFlags,
-            newImpl: Provider<NotificationShelfViewBinderWrapperControllerImpl>,
-            notificationShelfComponentBuilder: NotificationShelfComponent.Builder,
-            layoutInflater: LayoutInflater,
-            notificationStackScrollLayout: NotificationStackScrollLayout,
-        ): NotificationShelfController {
-            return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
-                newImpl.get()
-            } else {
-                val shelfView =
-                    layoutInflater.inflate(
-                        R.layout.status_bar_notification_shelf,
-                        notificationStackScrollLayout,
-                        false
-                    ) as NotificationShelf
-                val component =
-                    notificationShelfComponentBuilder.notificationShelf(shelfView).build()
-                val notificationShelfController = component.notificationShelfController
-                notificationShelfController.init()
-                notificationShelfController
-            }
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesNotificationPanelView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): NotificationPanelView {
-            return notificationShadeWindowView.findViewById(R.id.notification_panel)
-        }
-
-        /**
-         * Constructs a new, unattached [KeyguardBottomAreaView].
-         *
-         * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
-         */
-        @Provides
-        fun providesKeyguardBottomAreaView(
-            npv: NotificationPanelView,
-            layoutInflater: LayoutInflater,
-        ): KeyguardBottomAreaView {
-            return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
-                as KeyguardBottomAreaView
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesLightRevealScrim(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): LightRevealScrim {
-            return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim)
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesKeyguardRootView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): KeyguardRootView {
-            return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesSharedNotificationContainer(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): SharedNotificationContainer {
-            return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesAuthRippleView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): AuthRippleView? {
-            return notificationShadeWindowView.findViewById(R.id.auth_ripple)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesLockIconView(
-            keyguardRootView: KeyguardRootView,
-            notificationPanelView: NotificationPanelView,
-            featureFlags: FeatureFlags
-        ): LockIconView {
-            if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
-                return keyguardRootView.findViewById(R.id.lock_icon_view)
-            } else {
-                return notificationPanelView.findViewById(R.id.lock_icon_view)
-            }
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesTapAgainView(
-            notificationPanelView: NotificationPanelView,
-        ): TapAgainView {
-            return notificationPanelView.findViewById(R.id.shade_falsing_tap_again)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesNotificationsQuickSettingsContainer(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): NotificationsQuickSettingsContainer {
-            return notificationShadeWindowView.findViewById(R.id.notification_container_parent)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesShadeHeaderView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): MotionLayout {
-            val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub)
-            val layoutId = R.layout.combined_qs_header
-            stub.layoutResource = layoutId
-            return stub.inflate() as MotionLayout
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesCombinedShadeHeadersConstraintManager(): CombinedShadeHeadersConstraintManager {
-            return CombinedShadeHeadersConstraintManagerImpl
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
-            return view.findViewById(R.id.batteryRemainingIcon)
-        }
-
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesBatteryMeterViewController(
-            @Named(SHADE_HEADER) batteryMeterView: BatteryMeterView,
-            userTracker: UserTracker,
-            configurationController: ConfigurationController,
-            tunerService: TunerService,
-            @Main mainHandler: Handler,
-            contentResolver: ContentResolver,
-            batteryController: BatteryController,
-        ): BatteryMeterViewController {
-            return BatteryMeterViewController(
-                batteryMeterView,
-                StatusBarLocation.QS,
-                userTracker,
-                configurationController,
-                tunerService,
-                mainHandler,
-                contentResolver,
-                batteryController,
-            )
-        }
-
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesOngoingPrivacyChip(
-            @Named(SHADE_HEADER) header: MotionLayout,
-        ): OngoingPrivacyChip {
-            return header.findViewById(R.id.privacy_chip)
-        }
-
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesStatusIconContainer(
-            @Named(SHADE_HEADER) header: MotionLayout,
-        ): StatusIconContainer {
-            return header.findViewById(R.id.statusIcons)
-        }
-    }
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeController(shadeControllerImpl: ShadeControllerImpl): ShadeController
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
index 56bb1a6..5804040 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
@@ -29,12 +29,12 @@
     interface ShadeStateEventsListener {
 
         /** Invoked when the notification panel starts or stops collapsing. */
-        @JvmDefault fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
+        fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
 
         /**
          * Invoked when the notification panel starts or stops launching an [android.app.Activity].
          */
-        @JvmDefault fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
+        fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
 
         /**
          * Invoked when the "expand immediate" attribute changes.
@@ -45,6 +45,6 @@
          * Another example is when full QS is showing, and we swipe up from the bottom. Instead of
          * going to QQS, the panel fully collapses.
          */
-        @JvmDefault fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
+        fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
new file mode 100644
index 0000000..fc6479e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -0,0 +1,309 @@
+/*
+ * 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.annotation.SuppressLint
+import android.content.ContentResolver
+import android.os.Handler
+import android.view.LayoutInflater
+import android.view.ViewStub
+import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.keyguard.LockIconView
+import com.android.systemui.R
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.biometrics.AuthRippleView
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneContainerNames
+import com.android.systemui.scene.ui.view.SceneWindowRootView
+import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.phone.TapAgainView
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.tuner.TunerService
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+import javax.inject.Provider
+
+/** Module for providing views related to the shade. */
+@Module
+abstract class ShadeViewProviderModule {
+    companion object {
+        const val SHADE_HEADER = "large_screen_shade_header"
+
+        @SuppressLint("InflateParams") // Root views don't have parents.
+        @Provides
+        @SysUISingleton
+        fun providesWindowRootView(
+            layoutInflater: LayoutInflater,
+            featureFlags: FeatureFlags,
+            @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+            viewModelProvider: Provider<SceneContainerViewModel>,
+            @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+            containerConfigProvider: Provider<SceneContainerConfig>,
+            @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+            scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
+        ): WindowRootView {
+            return if (
+                featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable()
+            ) {
+                val sceneWindowRootView =
+                    layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
+                sceneWindowRootView.init(
+                    viewModel = viewModelProvider.get(),
+                    containerConfig = containerConfigProvider.get(),
+                    scenes = scenesProvider.get(),
+                )
+                sceneWindowRootView
+            } else {
+                layoutInflater.inflate(R.layout.super_notification_shade, null)
+            }
+                as WindowRootView?
+                ?: throw IllegalStateException("Window root view could not be properly inflated")
+        }
+
+        @Provides
+        @SysUISingleton
+        // TODO(b/277762009): Do something similar to
+        //  {@link StatusBarWindowModule.InternalWindowView} so that only
+        //  {@link NotificationShadeWindowViewController} can inject this view.
+        fun providesNotificationShadeWindowView(
+            root: WindowRootView,
+            featureFlags: FeatureFlags,
+        ): NotificationShadeWindowView {
+            if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+                return root.findViewById(R.id.legacy_window_root)
+            }
+            return root as NotificationShadeWindowView?
+                ?: throw IllegalStateException("root view not a NotificationShadeWindowView")
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesNotificationStackScrollLayout(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): NotificationStackScrollLayout {
+            return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller)
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesNotificationShelfController(
+            featureFlags: FeatureFlags,
+            newImpl: Provider<NotificationShelfViewBinderWrapperControllerImpl>,
+            notificationShelfComponentBuilder: NotificationShelfComponent.Builder,
+            layoutInflater: LayoutInflater,
+            notificationStackScrollLayout: NotificationStackScrollLayout,
+        ): NotificationShelfController {
+            return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+                newImpl.get()
+            } else {
+                val shelfView =
+                    layoutInflater.inflate(
+                        R.layout.status_bar_notification_shelf,
+                        notificationStackScrollLayout,
+                        false
+                    ) as NotificationShelf
+                val component =
+                    notificationShelfComponentBuilder.notificationShelf(shelfView).build()
+                val notificationShelfController = component.notificationShelfController
+                notificationShelfController.init()
+                notificationShelfController
+            }
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesNotificationPanelView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): NotificationPanelView {
+            return notificationShadeWindowView.findViewById(R.id.notification_panel)
+        }
+
+        /**
+         * Constructs a new, unattached [KeyguardBottomAreaView].
+         *
+         * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
+         */
+        @Provides
+        fun providesKeyguardBottomAreaView(
+            npv: NotificationPanelView,
+            layoutInflater: LayoutInflater,
+        ): KeyguardBottomAreaView {
+            return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
+                as KeyguardBottomAreaView
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesLightRevealScrim(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): LightRevealScrim {
+            return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim)
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesKeyguardRootView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): KeyguardRootView {
+            return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesSharedNotificationContainer(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): SharedNotificationContainer {
+            return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesAuthRippleView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): AuthRippleView? {
+            return notificationShadeWindowView.findViewById(R.id.auth_ripple)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesLockIconView(
+            keyguardRootView: KeyguardRootView,
+            notificationPanelView: NotificationPanelView,
+            featureFlags: FeatureFlags
+        ): LockIconView {
+            if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+                return keyguardRootView.findViewById(R.id.lock_icon_view)
+            } else {
+                return notificationPanelView.findViewById(R.id.lock_icon_view)
+            }
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesTapAgainView(
+            notificationPanelView: NotificationPanelView,
+        ): TapAgainView {
+            return notificationPanelView.findViewById(R.id.shade_falsing_tap_again)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesNotificationsQuickSettingsContainer(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): NotificationsQuickSettingsContainer {
+            return notificationShadeWindowView.findViewById(R.id.notification_container_parent)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesShadeHeaderView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): MotionLayout {
+            val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub)
+            val layoutId = R.layout.combined_qs_header
+            stub.layoutResource = layoutId
+            return stub.inflate() as MotionLayout
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesCombinedShadeHeadersConstraintManager(): CombinedShadeHeadersConstraintManager {
+            return CombinedShadeHeadersConstraintManagerImpl
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
+            return view.findViewById(R.id.batteryRemainingIcon)
+        }
+
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesBatteryMeterViewController(
+            @Named(SHADE_HEADER) batteryMeterView: BatteryMeterView,
+            userTracker: UserTracker,
+            configurationController: ConfigurationController,
+            tunerService: TunerService,
+            @Main mainHandler: Handler,
+            contentResolver: ContentResolver,
+            batteryController: BatteryController,
+        ): BatteryMeterViewController {
+            return BatteryMeterViewController(
+                batteryMeterView,
+                StatusBarLocation.QS,
+                userTracker,
+                configurationController,
+                tunerService,
+                mainHandler,
+                contentResolver,
+                batteryController,
+            )
+        }
+
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesOngoingPrivacyChip(
+            @Named(SHADE_HEADER) header: MotionLayout,
+        ): OngoingPrivacyChip {
+            return header.findViewById(R.id.privacy_chip)
+        }
+
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesStatusIconContainer(
+            @Named(SHADE_HEADER) header: MotionLayout,
+        ): StatusIconContainer {
+            return header.findViewById(R.id.statusIcons)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
index c50693c..15ec18c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.AuthRippleController
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -28,4 +29,9 @@
     @IntoMap
     @ClassKey(ShadeController::class)
     abstract fun bind(shadeController: ShadeController): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(AuthRippleController::class)
+    abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
index 5d06f8d0..15ff31f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
@@ -21,11 +21,12 @@
 
 /**
  * A temporary base class that's shared between our old status bar connectivity view implementations
- * ([StatusBarMobileView]) and our new status bar implementations ([ModernStatusBarWifiView],
- * [ModernStatusBarMobileView]).
+ * and our new status bar implementations ([ModernStatusBarWifiView], [ModernStatusBarMobileView]).
  *
  * Once our refactor is over, we should be able to delete this go-between class and the old view
  * class.
+ *
+ * NOTE: the old classes are now deleted, and this class can be removed.
  */
 abstract class BaseStatusBarFrameLayout
 @JvmOverloads
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index fb88a96..763400b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -27,10 +27,12 @@
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Point;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
@@ -41,6 +43,7 @@
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
 import android.util.Log;
+import android.view.Display;
 import android.view.View;
 import android.widget.ImageView;
 
@@ -74,11 +77,15 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import dagger.Lazy;
 
@@ -138,6 +145,14 @@
     private BackDropView mBackdrop;
     private ImageView mBackdropFront;
     private ImageView mBackdropBack;
+    private final Point mTmpDisplaySize = new Point();
+
+    private final DisplayManager mDisplayManager;
+    @Nullable
+    private List<String> mSmallerInternalDisplayUids;
+    private Display mCurrentDisplay;
+
+    private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable;
 
     private final MediaController.Callback mMediaListener = new MediaController.Callback() {
         @Override
@@ -184,7 +199,8 @@
             SysuiColorExtractor colorExtractor,
             KeyguardStateController keyguardStateController,
             DumpManager dumpManager,
-            WallpaperManager wallpaperManager) {
+            WallpaperManager wallpaperManager,
+            DisplayManager displayManager) {
         mContext = context;
         mMediaArtworkProcessor = mediaArtworkProcessor;
         mKeyguardBypassController = keyguardBypassController;
@@ -200,6 +216,7 @@
         mStatusBarStateController = statusBarStateController;
         mColorExtractor = colorExtractor;
         mKeyguardStateController = keyguardStateController;
+        mDisplayManager = displayManager;
         mIsLockscreenLiveWallpaperEnabled = wallpaperManager.isLockscreenLiveWallpaperEnabled();
 
         setupNotifPipeline();
@@ -477,6 +494,48 @@
     }
 
     /**
+     * Notify lockscreen wallpaper drawable the current internal display.
+     */
+    public void onDisplayUpdated(Display display) {
+        Trace.beginSection("NotificationMediaManager#onDisplayUpdated");
+        mCurrentDisplay = display;
+        if (mWallapperDrawable != null) {
+            mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays());
+        }
+        Trace.endSection();
+    }
+
+    private boolean isOnSmallerInternalDisplays() {
+        if (mSmallerInternalDisplayUids == null) {
+            mSmallerInternalDisplayUids = findSmallerInternalDisplayUids();
+        }
+        return mSmallerInternalDisplayUids.contains(mCurrentDisplay.getUniqueId());
+    }
+
+    private List<String> findSmallerInternalDisplayUids() {
+        if (mSmallerInternalDisplayUids != null) {
+            return mSmallerInternalDisplayUids;
+        }
+        List<Display> internalDisplays = Arrays.stream(mDisplayManager.getDisplays(
+                        DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED))
+                .filter(display -> display.getType() == Display.TYPE_INTERNAL)
+                .collect(Collectors.toList());
+        if (internalDisplays.isEmpty()) {
+            return List.of();
+        }
+        Display largestDisplay = internalDisplays.stream()
+                .max(Comparator.comparingInt(this::getRealDisplayArea))
+                .orElse(internalDisplays.get(0));
+        internalDisplays.remove(largestDisplay);
+        return internalDisplays.stream().map(Display::getUniqueId).collect(Collectors.toList());
+    }
+
+    private int getRealDisplayArea(Display display) {
+        display.getRealSize(mTmpDisplaySize);
+        return mTmpDisplaySize.x * mTmpDisplaySize.y;
+    }
+
+    /**
      * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
      */
     public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
@@ -551,7 +610,7 @@
                     mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null;
             if (lockWallpaper != null) {
                 artworkDrawable = new LockscreenWallpaper.WallpaperDrawable(
-                        mBackdropBack.getResources(), lockWallpaper);
+                        mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays());
                 // We're in the SHADE mode on the SIM screen - yet we still need to show
                 // the lockscreen wallpaper in that mode.
                 allowWhenShade = mStatusBarStateController.getState() == KEYGUARD;
@@ -611,6 +670,10 @@
                     mBackdropBack.setBackgroundColor(0xFFFFFFFF);
                     mBackdropBack.setImageDrawable(new ColorDrawable(c));
                 } else {
+                    if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) {
+                        mWallapperDrawable =
+                                (LockscreenWallpaper.WallpaperDrawable) artworkDrawable;
+                    }
                     mBackdropBack.setImageDrawable(artworkDrawable);
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 0e20df6..b624115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -553,7 +553,6 @@
          */
         fun onWallpaperZoomOutChanged(zoomOut: Float)
 
-        @JvmDefault
         fun onBlurRadiusChanged(blurRadius: Int) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
deleted file mode 100644
index d6f6c2c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
-import static com.android.systemui.plugins.DarkIconDispatcher.isInAreas;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.DualToneHandler;
-import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-
-import java.util.ArrayList;
-
-/**
- * View group for the mobile icon in the status bar
- */
-public class StatusBarMobileView extends BaseStatusBarFrameLayout implements DarkReceiver,
-        StatusIconDisplayable {
-    private static final String TAG = "StatusBarMobileView";
-
-    /// Used to show etc dots
-    private StatusBarIconView mDotView;
-    /// The main icon view
-    private LinearLayout mMobileGroup;
-    private String mSlot;
-    private MobileIconState mState;
-    private SignalDrawable mMobileDrawable;
-    private View mInoutContainer;
-    private ImageView mIn;
-    private ImageView mOut;
-    private ImageView mMobile, mMobileType, mMobileRoaming;
-    private View mMobileRoamingSpace;
-    @StatusBarIconView.VisibleState
-    private int mVisibleState = STATE_HIDDEN;
-    private DualToneHandler mDualToneHandler;
-    private boolean mForceHidden;
-
-    /**
-     * Designated constructor
-     *
-     * This view is special, in that it is the only view in SystemUI that allows for a configuration
-     * override on a MCC/MNC-basis. This means that for every mobile view inflated, we have to
-     * construct a context with that override, since the resource system doesn't have a way to
-     * handle this for us.
-     *
-     * @param context A context with resources configured by MCC/MNC
-     * @param slot The string key defining which slot this icon refers to. Always "mobile" for the
-     *             mobile icon
-     */
-    public static StatusBarMobileView fromContext(
-            Context context,
-            String slot
-    ) {
-        LayoutInflater inflater = LayoutInflater.from(context);
-        StatusBarMobileView v = (StatusBarMobileView)
-                inflater.inflate(R.layout.status_bar_mobile_signal_group, null);
-        v.setSlot(slot);
-        v.init();
-        v.setVisibleState(STATE_ICON);
-        return v;
-    }
-
-    public StatusBarMobileView(Context context) {
-        super(context);
-    }
-
-    public StatusBarMobileView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    public void getDrawingRect(Rect outRect) {
-        super.getDrawingRect(outRect);
-        float translationX = getTranslationX();
-        float translationY = getTranslationY();
-        outRect.left += translationX;
-        outRect.right += translationX;
-        outRect.top += translationY;
-        outRect.bottom += translationY;
-    }
-
-    private void init() {
-        mDualToneHandler = new DualToneHandler(getContext());
-        mMobileGroup = findViewById(R.id.mobile_group);
-        mMobile = findViewById(R.id.mobile_signal);
-        mMobileType = findViewById(R.id.mobile_type);
-        mMobileRoaming = findViewById(R.id.mobile_roaming);
-        mMobileRoamingSpace = findViewById(R.id.mobile_roaming_space);
-        mIn = findViewById(R.id.mobile_in);
-        mOut = findViewById(R.id.mobile_out);
-        mInoutContainer = findViewById(R.id.inout_container);
-
-        mMobileDrawable = new SignalDrawable(getContext());
-        mMobile.setImageDrawable(mMobileDrawable);
-
-        initDotView();
-    }
-
-    private void initDotView() {
-        mDotView = new StatusBarIconView(mContext, mSlot, null);
-        mDotView.setVisibleState(STATE_DOT);
-
-        int width = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size_sp);
-        LayoutParams lp = new LayoutParams(width, width);
-        lp.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
-        addView(mDotView, lp);
-    }
-
-    public void applyMobileState(MobileIconState state) {
-        boolean requestLayout = false;
-        if (state == null) {
-            requestLayout = getVisibility() != View.GONE;
-            setVisibility(View.GONE);
-            mState = null;
-        } else if (mState == null) {
-            requestLayout = true;
-            mState = state.copy();
-            initViewState();
-        } else if (!mState.equals(state)) {
-            requestLayout = updateState(state.copy());
-        }
-
-        if (requestLayout) {
-            requestLayout();
-        }
-    }
-
-    private void initViewState() {
-        setContentDescription(mState.contentDescription);
-        if (!mState.visible || mForceHidden) {
-            mMobileGroup.setVisibility(View.GONE);
-        } else {
-            mMobileGroup.setVisibility(View.VISIBLE);
-        }
-        mMobileDrawable.setLevel(mState.strengthId);
-        if (mState.typeId > 0) {
-            mMobileType.setContentDescription(mState.typeContentDescription);
-            mMobileType.setImageResource(mState.typeId);
-            mMobileType.setVisibility(View.VISIBLE);
-        } else {
-            mMobileType.setVisibility(View.GONE);
-        }
-        mMobile.setVisibility(mState.showTriangle ? View.VISIBLE : View.GONE);
-        mMobileRoaming.setVisibility(mState.roaming ? View.VISIBLE : View.GONE);
-        mMobileRoamingSpace.setVisibility(mState.roaming ? View.VISIBLE : View.GONE);
-        mIn.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE);
-        mOut.setVisibility(mState.activityOut ? View.VISIBLE : View.GONE);
-        mInoutContainer.setVisibility((mState.activityIn || mState.activityOut)
-                ? View.VISIBLE : View.GONE);
-    }
-
-    private boolean updateState(MobileIconState state) {
-        boolean needsLayout = false;
-
-        setContentDescription(state.contentDescription);
-        int newVisibility = state.visible && !mForceHidden ? View.VISIBLE : View.GONE;
-        if (newVisibility != mMobileGroup.getVisibility() && STATE_ICON == mVisibleState) {
-            mMobileGroup.setVisibility(newVisibility);
-            needsLayout = true;
-        }
-        if (mState.strengthId != state.strengthId) {
-            mMobileDrawable.setLevel(state.strengthId);
-        }
-        if (mState.typeId != state.typeId) {
-            needsLayout |= state.typeId == 0 || mState.typeId == 0;
-            if (state.typeId != 0) {
-                mMobileType.setContentDescription(state.typeContentDescription);
-                mMobileType.setImageResource(state.typeId);
-                mMobileType.setVisibility(View.VISIBLE);
-            } else {
-                mMobileType.setVisibility(View.GONE);
-            }
-        }
-
-        mMobile.setVisibility(state.showTriangle ? View.VISIBLE : View.GONE);
-        mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
-        mMobileRoamingSpace.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
-        mIn.setVisibility(state.activityIn ? View.VISIBLE : View.GONE);
-        mOut.setVisibility(state.activityOut ? View.VISIBLE : View.GONE);
-        mInoutContainer.setVisibility((state.activityIn || state.activityOut)
-                ? View.VISIBLE : View.GONE);
-
-        needsLayout |= state.roaming != mState.roaming
-                || state.activityIn != mState.activityIn
-                || state.activityOut != mState.activityOut
-                || state.showTriangle != mState.showTriangle;
-
-        mState = state;
-        return needsLayout;
-    }
-
-    @Override
-    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
-        float intensity = isInAreas(areas, this) ? darkIntensity : 0;
-        mMobileDrawable.setTintList(
-                ColorStateList.valueOf(mDualToneHandler.getSingleColor(intensity)));
-        ColorStateList color = ColorStateList.valueOf(getTint(areas, this, tint));
-        mIn.setImageTintList(color);
-        mOut.setImageTintList(color);
-        mMobileType.setImageTintList(color);
-        mMobileRoaming.setImageTintList(color);
-        mDotView.setDecorColor(tint);
-        mDotView.setIconColor(tint, false);
-    }
-
-    @Override
-    public String getSlot() {
-        return mSlot;
-    }
-
-    public void setSlot(String slot) {
-        mSlot = slot;
-    }
-
-    @Override
-    public void setStaticDrawableColor(int color) {
-        ColorStateList list = ColorStateList.valueOf(color);
-        mMobileDrawable.setTintList(list);
-        mIn.setImageTintList(list);
-        mOut.setImageTintList(list);
-        mMobileType.setImageTintList(list);
-        mMobileRoaming.setImageTintList(list);
-        mDotView.setDecorColor(color);
-    }
-
-    @Override
-    public void setDecorColor(int color) {
-        mDotView.setDecorColor(color);
-    }
-
-    @Override
-    public boolean isIconVisible() {
-        return mState.visible && !mForceHidden;
-    }
-
-    @Override
-    public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
-        if (state == mVisibleState) {
-            return;
-        }
-
-        mVisibleState = state;
-        switch (state) {
-            case STATE_ICON:
-                mMobileGroup.setVisibility(View.VISIBLE);
-                mDotView.setVisibility(View.GONE);
-                break;
-            case STATE_DOT:
-                mMobileGroup.setVisibility(View.INVISIBLE);
-                mDotView.setVisibility(View.VISIBLE);
-                break;
-            case STATE_HIDDEN:
-            default:
-                mMobileGroup.setVisibility(View.INVISIBLE);
-                mDotView.setVisibility(View.INVISIBLE);
-                break;
-        }
-    }
-
-    /**
-     * Forces the state to be hidden (views will be GONE) and if necessary updates the layout.
-     *
-     * Makes sure that the {@link StatusBarIconController} cannot make it visible while this flag
-     * is enabled.
-     * @param forceHidden {@code true} if the icon should be GONE in its view regardless of its
-     *                                state.
-     *               {@code false} if the icon should show as determined by its controller.
-     */
-    public void forceHidden(boolean forceHidden) {
-        if (mForceHidden != forceHidden) {
-            mForceHidden = forceHidden;
-            updateState(mState);
-            requestLayout();
-        }
-    }
-
-    @Override
-    @StatusBarIconView.VisibleState
-    public int getVisibleState() {
-        return mVisibleState;
-    }
-
-    @VisibleForTesting
-    public MobileIconState getState() {
-        return mState;
-    }
-
-    @Override
-    public String toString() {
-        return "StatusBarMobileView(slot=" + mSlot + " state=" + mState + ")";
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index 324e972..645595c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -23,6 +23,7 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.view.View;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -151,4 +152,20 @@
                 BIOMETRIC_ERROR_VIBRATION_EFFECT, reason,
                 HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
     }
+
+    /**
+     * Perform a vibration using a view and the one-way API with flags
+     * @see View#performHapticFeedback(int feedbackConstant, int flags)
+     */
+    public void performHapticFeedback(@NonNull View view, int feedbackConstant, int flags) {
+        view.performHapticFeedback(feedbackConstant, flags);
+    }
+
+    /**
+     * Perform a vibration using a view and the one-way API
+     * @see View#performHapticFeedback(int feedbackConstant)
+     */
+    public void performHapticFeedback(@NonNull View view, int feedbackConstant) {
+        view.performHapticFeedback(feedbackConstant);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 9aa28c3..93b9ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -1282,7 +1282,7 @@
             }
         }
         String sims = args.getString("sims");
-        if (sims != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
+        if (sims != null) {
             int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
             List<SubscriptionInfo> subs = new ArrayList<>();
             if (num != mMobileSignalControllers.size()) {
@@ -1305,7 +1305,7 @@
             mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
         }
         String mobile = args.getString("mobile");
-        if (mobile != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
+        if (mobile != null) {
             boolean show = mobile.equals("show");
             String datatype = args.getString("datatype");
             String slotString = args.getString("slot");
@@ -1390,7 +1390,7 @@
             controller.notifyListeners();
         }
         String carrierNetworkChange = args.getString("carriernetworkchange");
-        if (carrierNetworkChange != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
+        if (carrierNetworkChange != null) {
             boolean show = carrierNetworkChange.equals("show");
             for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                 MobileSignalController controller = mMobileSignalControllers.valueAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt
index 599beec..6be407a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt
@@ -29,7 +29,6 @@
      *
      * @param wifiIndicators a box type containing enough information to properly draw a Wi-Fi icon
      */
-    @JvmDefault
     fun setWifiIndicators(wifiIndicators: WifiIndicators) {}
 
     /**
@@ -42,7 +41,6 @@
      * NOTE: phones can have multiple subscriptions, so this [mobileDataIndicators] object should be
      * indexed based on its [subId][MobileDataIndicators.subId]
      */
-    @JvmDefault
     fun setMobileDataIndicators(mobileDataIndicators: MobileDataIndicators) {}
 
     /**
@@ -51,7 +49,6 @@
      *
      * @param subs a [SubscriptionInfo] for each subscription that we know about
      */
-    @JvmDefault
     fun setSubs(subs: List<@JvmSuppressWildcards SubscriptionInfo>) {}
 
     /**
@@ -63,7 +60,6 @@
      * @param show whether or not to show a "no sim" view
      * @param simDetected whether any SIM is detected or not
      */
-    @JvmDefault
     fun setNoSims(show: Boolean, simDetected: Boolean) {}
 
     /**
@@ -72,7 +68,6 @@
      *
      * @param icon an [IconState] for the current ethernet status
      */
-    @JvmDefault
     fun setEthernetIndicators(icon: IconState) {}
 
     /**
@@ -80,7 +75,6 @@
      *
      * @param icon an [IconState] for the current airplane mode status
      */
-    @JvmDefault
     fun setIsAirplaneMode(icon: IconState) {}
 
     /**
@@ -88,7 +82,6 @@
      *
      * @param enabled the current mobile data feature ennabled state
      */
-    @JvmDefault
     fun setMobileDataEnabled(enabled: Boolean) {}
 
     /**
@@ -97,7 +90,6 @@
      * @param noValidatedNetwork whether there is any validated network.
      * @param noNetworksAvailable whether there is any WiFi networks available.
      */
-    @JvmDefault
     fun setConnectivityStatus(
         noDefaultNetwork: Boolean,
         noValidatedNetwork: Boolean,
@@ -109,7 +101,6 @@
      * @param statusIcon the icon for the call indicator
      * @param subId subscription ID for which to update the UI
      */
-    @JvmDefault
     fun setCallIndicator(statusIcon: IconState, subId: Int) {}
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index e5ba3ce..1c7a186 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -19,6 +19,7 @@
 import android.app.IActivityManager;
 import android.app.WallpaperManager;
 import android.content.Context;
+import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
 import android.service.dreams.IDreamManager;
 import android.util.Log;
@@ -146,7 +147,8 @@
             SysuiColorExtractor colorExtractor,
             KeyguardStateController keyguardStateController,
             DumpManager dumpManager,
-            WallpaperManager wallpaperManager) {
+            WallpaperManager wallpaperManager,
+            DisplayManager displayManager) {
         return new NotificationMediaManager(
                 context,
                 centralSurfacesOptionalLazy,
@@ -162,7 +164,8 @@
                 colorExtractor,
                 keyguardStateController,
                 dumpManager,
-                wallpaperManager);
+                wallpaperManager,
+                displayManager);
     }
 
     /** */
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 5639000..6e8b8bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -265,6 +265,11 @@
                 // not animating then [prepareChipAnimation] will take care of it for us
                 currentAnimatedView?.let {
                     updateChipBounds(it, newContentArea)
+                    // Since updateCurrentAnimatedView can only be called during an animation, we
+                    // have to create a dummy animator here to apply the new chip bounds
+                    val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
+                    animator.addUpdateListener { updateCurrentAnimatedView() }
+                    animator.start()
                 }
             }
         })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
index 2a18f1f..ef90890 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -49,11 +49,10 @@
     fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator? { return null }
 
     // Best method name, change my mind
-    @JvmDefault
     fun onSystemStatusAnimationTransitionToPersistentDot(contentDescription: String?): Animator? {
         return null
     }
-    @JvmDefault fun onHidePersistentDot(): Animator? { return null }
+    fun onHidePersistentDot(): Animator? { return null }
 }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 577ad20..bac8982 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification
 
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import javax.inject.Inject
@@ -25,12 +23,8 @@
 class NotifPipelineFlags
 @Inject
 constructor(
-    private val featureFlags: FeatureFlags,
-    private val sysPropFlags: FlagResolver,
+    private val featureFlags: FeatureFlags
 ) {
     fun isDevLoggingEnabled(): Boolean =
         featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
-
-    fun allowDismissOngoing(): Boolean =
-        sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 9ba2199..8d1e8d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -613,20 +613,20 @@
 
     interface WakeUpListener {
         /** Called whenever the notifications are fully hidden or shown */
-        @JvmDefault fun onFullyHiddenChanged(isFullyHidden: Boolean) {}
+        fun onFullyHiddenChanged(isFullyHidden: Boolean) {}
 
         /**
          * Called whenever the pulseExpansion changes
          *
          * @param expandingChanged if the user has started or stopped expanding
          */
-        @JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {}
+        fun onPulseExpansionChanged(expandingChanged: Boolean) {}
 
         /**
          * Called when the animator started by [scheduleDelayedDozeAmountAnimation] begins running
          * after the start delay, or after it ends/is cancelled.
          */
-        @JvmDefault fun onDelayedDozeAmountAnimationRunning(running: Boolean) {}
+        fun onDelayedDozeAmountAnimationRunning(running: Boolean) {}
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 1c5aa3c..8029f48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -27,37 +27,31 @@
 
     /** Current top roundness */
     @get:FloatRange(from = 0.0, to = 1.0)
-    @JvmDefault
     val topRoundness: Float
         get() = roundableState.topRoundness
 
     /** Current bottom roundness */
     @get:FloatRange(from = 0.0, to = 1.0)
-    @JvmDefault
     val bottomRoundness: Float
         get() = roundableState.bottomRoundness
 
     /** Max radius in pixel */
-    @JvmDefault
     val maxRadius: Float
         get() = roundableState.maxRadius
 
     /** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
-    @JvmDefault
     val topCornerRadius: Float
         get() =
             if (roundableState.newHeadsUpAnim.isEnabled) roundableState.topCornerRadius
             else topRoundness * maxRadius
 
     /** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
-    @JvmDefault
     val bottomCornerRadius: Float
         get() =
             if (roundableState.newHeadsUpAnim.isEnabled) roundableState.bottomCornerRadius
             else bottomRoundness * maxRadius
 
     /** Get and update the current radii */
-    @JvmDefault
     val updatedRadii: FloatArray
         get() =
             roundableState.radiiBuffer.also { radii ->
@@ -80,7 +74,6 @@
      * @param sourceType the source from which the request for roundness comes.
      * @return Whether the roundness was changed.
      */
-    @JvmDefault
     fun requestTopRoundness(
         @FloatRange(from = 0.0, to = 1.0) value: Float,
         sourceType: SourceType,
@@ -125,7 +118,6 @@
      * @param sourceType the source from which the request for roundness comes.
      * @return Whether the roundness was changed.
      */
-    @JvmDefault
     fun requestTopRoundness(
         @FloatRange(from = 0.0, to = 1.0) value: Float,
         sourceType: SourceType,
@@ -149,7 +141,6 @@
      * @param sourceType the source from which the request for roundness comes.
      * @return Whether the roundness was changed.
      */
-    @JvmDefault
     fun requestBottomRoundness(
         @FloatRange(from = 0.0, to = 1.0) value: Float,
         sourceType: SourceType,
@@ -194,7 +185,6 @@
      * @param sourceType the source from which the request for roundness comes.
      * @return Whether the roundness was changed.
      */
-    @JvmDefault
     fun requestBottomRoundness(
         @FloatRange(from = 0.0, to = 1.0) value: Float,
         sourceType: SourceType,
@@ -219,7 +209,6 @@
      * @param animate true if it should animate to that value.
      * @return Whether the roundness was changed.
      */
-    @JvmDefault
     fun requestRoundness(
         @FloatRange(from = 0.0, to = 1.0) top: Float,
         @FloatRange(from = 0.0, to = 1.0) bottom: Float,
@@ -246,7 +235,6 @@
      * @param sourceType the source from which the request for roundness comes.
      * @return Whether the roundness was changed.
      */
-    @JvmDefault
     fun requestRoundness(
         @FloatRange(from = 0.0, to = 1.0) top: Float,
         @FloatRange(from = 0.0, to = 1.0) bottom: Float,
@@ -270,7 +258,6 @@
      * @param sourceType the source from which the request for roundness comes.
      * @param animate true if it should animate to that value.
      */
-    @JvmDefault
     fun requestRoundnessReset(sourceType: SourceType, animate: Boolean) {
         requestRoundness(top = 0f, bottom = 0f, sourceType = sourceType, animate = animate)
     }
@@ -284,19 +271,16 @@
      *
      * @param sourceType the source from which the request for roundness comes.
      */
-    @JvmDefault
     fun requestRoundnessReset(sourceType: SourceType) {
         requestRoundnessReset(sourceType = sourceType, animate = roundableState.targetView.isShown)
     }
 
     /** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */
-    @JvmDefault
     fun applyRoundnessAndInvalidate() {
         roundableState.targetView.invalidate()
     }
 
     /** @return true if top or bottom roundness is not zero. */
-    @JvmDefault
     fun hasRoundedCorner(): Boolean {
         return topRoundness != 0f || bottomRoundness != 0f
     }
@@ -307,7 +291,6 @@
      *
      * This method reuses the previous [radii] for performance reasons.
      */
-    @JvmDefault
     fun updateRadii(
         topCornerRadius: Float,
         bottomCornerRadius: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 7898736..affd2d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -791,28 +791,6 @@
         return !mSbn.isOngoing() || !isLocked;
     }
 
-    /**
-     * @return Can the underlying notification be individually dismissed?
-     * @see #canViewBeDismissed()
-     */
-    // TODO: This logic doesn't belong on NotificationEntry. It should be moved to a controller
-    // that can be added as a dependency to any class that needs to answer this question.
-    public boolean legacyIsDismissableRecursive() {
-        if  (mSbn.isOngoing()) {
-            return false;
-        }
-        List<NotificationEntry> children = getAttachedNotifChildren();
-        if (children != null && children.size() > 0) {
-            for (int i = 0; i < children.size(); i++) {
-                NotificationEntry child =  children.get(i);
-                if (child.getSbn().isOngoing()) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
     public boolean canViewBeDismissed() {
         if (row == null) return true;
         return row.canViewBeDismissed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index 1494574..93a34af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -48,6 +48,14 @@
     private val mAlwaysExpandNonGroupedNotification =
         context.resources.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications)
 
+    /**
+     * `true` if the first non-group expandable notification should be expanded automatically
+     * when possible. If `false`, then the first non-group expandable notification should not
+     * be expanded.
+     */
+    private val mAutoExpandFirstNotification =
+            context.resources.getBoolean(R.bool.config_autoExpandFirstNotification)
+
     override fun attach(pipeline: NotifPipeline) {
         pipeline.addOnBeforeRenderListListener(::onBeforeRenderList)
         pipeline.addOnAfterRenderEntryListener(::onAfterRenderEntry)
@@ -61,8 +69,10 @@
 
     private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) {
         // If mAlwaysExpandNonGroupedNotification is false, then only expand the
-        // very first notification and if it's not a child of grouped notifications.
-        controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification || entry == entryToExpand)
+        // very first notification if it's not a child of grouped notifications and when
+        // mAutoExpandFirstNotification is true.
+        controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification ||
+                (mAutoExpandFirstNotification && entry == entryToExpand))
         // Show/hide the feedback icon
         controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry))
         // Show the "alerted" bell icon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
index b318252..78e9a74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.withIncreasedIndent
@@ -28,9 +27,7 @@
 import javax.inject.Inject
 
 @SysUISingleton
-class NotificationDismissibilityProviderImpl
-@Inject
-constructor(private val notifPipelineFlags: NotifPipelineFlags, dumpManager: DumpManager) :
+class NotificationDismissibilityProviderImpl @Inject constructor(dumpManager: DumpManager) :
     NotificationDismissibilityProvider, Dumpable {
 
     init {
@@ -43,11 +40,7 @@
         private set
 
     override fun isDismissable(entry: NotificationEntry): Boolean {
-        return if (notifPipelineFlags.allowDismissOngoing()) {
-            entry.key !in nonDismissableEntryKeys
-        } else {
-            entry.legacyIsDismissableRecursive()
-        }
+        return entry.key !in nonDismissableEntryKeys
     }
 
     @Synchronized
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 42b99a1..ed489a6c 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
@@ -3678,6 +3678,7 @@
             pw.print(", mShowingPublicInitialized: " + mShowingPublicInitialized);
             NotificationContentView showingLayout = getShowingLayout();
             pw.print(", privateShowing: " + (showingLayout == mPrivateLayout));
+            pw.print(", mShowNoBackground: " + mShowNoBackground);
             pw.println();
             showingLayout.dump(pw, args);
 
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 26b51a9..dcd18dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -69,6 +70,7 @@
     private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
     private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
     private val shadeControllerLazy: Lazy<ShadeController>,
+    private val shadeViewControllerLazy: Lazy<ShadeViewController>,
     private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
     private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
     private val activityLaunchAnimator: ActivityLaunchAnimator,
@@ -896,7 +898,7 @@
                 if (dismissShade) {
                     return StatusBarLaunchAnimatorController(
                         animationController,
-                        it.shadeViewController,
+                        shadeViewControllerLazy.get(),
                         shadeControllerLazy.get(),
                         notifShadeWindowControllerLazy.get(),
                         isLaunchForActivity
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index dc021fe..661d71d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -160,7 +160,6 @@
     private KeyguardViewController mKeyguardViewController;
     private DozeScrimController mDozeScrimController;
     private KeyguardViewMediator mKeyguardViewMediator;
-    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private PendingAuthenticated mPendingAuthenticated = null;
     private boolean mHasScreenTurnedOnSinceAuthenticating;
     private boolean mFadedAwayAfterWakeAndUnlock;
@@ -281,8 +280,7 @@
             LatencyTracker latencyTracker,
             ScreenOffAnimationController screenOffAnimationController,
             VibratorHelper vibrator,
-            SystemClock systemClock,
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager
+            SystemClock systemClock
     ) {
         mPowerManager = powerManager;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -310,7 +308,6 @@
         mVibratorHelper = vibrator;
         mLogger = biometricUnlockLogger;
         mSystemClock = systemClock;
-        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
 
         dumpManager.registerDumpable(this);
     }
@@ -452,19 +449,8 @@
         // During wake and unlock, we need to draw black before waking up to avoid abrupt
         // brightness changes due to display state transitions.
         Runnable wakeUp = ()-> {
-            // Check to see if we are still locked when we are waking and unlocking from dream.
-            // This runnable should be executed after unlock. If that's true, we could be not
-            // dreaming, but still locked. In this case, we should attempt to authenticate instead
-            // of waking up.
-            if (mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
-                    && !mKeyguardStateController.isUnlocked()
-                    && !mUpdateMonitor.isDreaming()) {
-                // Post wakeUp runnable is called from a callback in keyguard.
-                mHandler.post(() -> mKeyguardViewController.notifyKeyguardAuthenticated(
-                        false /* primaryAuth */));
-            } else if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
+            if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
                 mLogger.i("bio wakelock: Authenticated, waking up...");
-
                 mPowerManager.wakeUp(
                         mSystemClock.uptimeMillis(),
                         PowerManager.WAKE_REASON_BIOMETRIC,
@@ -476,7 +462,10 @@
             Trace.endSection();
         };
 
-        if (mMode != MODE_NONE && mMode != MODE_WAKE_AND_UNLOCK_FROM_DREAM) {
+        final boolean wakingFromDream = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
+                && !mStatusBarStateController.isDozing();
+
+        if (mMode != MODE_NONE && !wakingFromDream) {
             wakeUp.run();
         }
         switch (mMode) {
@@ -498,10 +487,6 @@
                 Trace.endSection();
                 break;
             case MODE_WAKE_AND_UNLOCK_FROM_DREAM:
-                // In the case of waking and unlocking from dream, waking up is delayed until after
-                // unlock is complete to avoid conflicts during each sequence's transitions.
-                mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(wakeUp);
-                // Execution falls through here to proceed unlocking.
             case MODE_WAKE_AND_UNLOCK_PULSING:
             case MODE_WAKE_AND_UNLOCK:
                 if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index acd6e49..5c28be3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -44,7 +44,6 @@
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.qs.QSPanelController;
-import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -195,9 +194,6 @@
     @Override
     Lifecycle getLifecycle();
 
-    /** */
-    ShadeViewController getShadeViewController();
-
     /** Get the Keyguard Message Area that displays auth messages. */
     AuthKeyguardMessageArea getKeyguardMessageArea();
 
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 8361d6b..6eeb25f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1326,7 +1326,7 @@
             }
         });
 
-        mScreenOffAnimationController.initialize(this, mLightRevealScrim);
+        mScreenOffAnimationController.initialize(this, mShadeSurface, mLightRevealScrim);
         updateLightRevealScrimVisibility();
 
         mShadeSurface.initDependencies(
@@ -1677,8 +1677,7 @@
         Trace.endSection();
     }
 
-    @Override
-    public ShadeViewController getShadeViewController() {
+    protected ShadeViewController getShadeViewController() {
         return mShadeSurface;
     }
 
@@ -2061,6 +2060,7 @@
     void updateDisplaySize() {
         mDisplay.getMetrics(mDisplayMetrics);
         mDisplay.getSize(mCurrentDisplaySize);
+        mMediaManager.onDisplayUpdated(mDisplay);
         if (DEBUG_GESTURES) {
             mGestureRec.tag("display",
                     String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 8e9f382..374543d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
@@ -34,10 +33,7 @@
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
-import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
@@ -52,7 +48,6 @@
     private static final String TAG = "DemoStatusIcons";
 
     private final LinearLayout mStatusIcons;
-    private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>();
     private final ArrayList<ModernStatusBarMobileView> mModernMobileViews = new ArrayList<>();
     private final int mIconSize;
 
@@ -91,7 +86,6 @@
     }
 
     public void remove() {
-        mMobileViews.clear();
         ((ViewGroup) getParent()).removeView(this);
     }
 
@@ -127,7 +121,6 @@
         mDemoMode = false;
         mStatusIcons.setVisibility(View.VISIBLE);
         mModernMobileViews.clear();
-        mMobileViews.clear();
         setVisibility(View.GONE);
     }
 
@@ -236,22 +229,6 @@
     }
 
     /**
-     * Add a new mobile icon view
-     */
-    public void addMobileView(MobileIconState state, Context mobileContext) {
-        Log.d(TAG, "addMobileView: ");
-        StatusBarMobileView view = StatusBarMobileView
-                .fromContext(mobileContext, state.slot);
-
-        view.applyMobileState(state);
-        view.setStaticDrawableColor(mColor);
-
-        // mobile always goes at the end
-        mMobileViews.add(view);
-        addView(view, getChildCount(), createLayoutParams());
-    }
-
-    /**
      * Add a {@link ModernStatusBarMobileView}
      * @param mobileContext possibly mcc/mnc overridden mobile context
      * @param subId the subscriptionId for this mobile view
@@ -285,8 +262,7 @@
         // If we have mobile views, put wifi before them
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
-            if (child instanceof StatusBarMobileView
-                    || child instanceof ModernStatusBarMobileView) {
+            if (child instanceof ModernStatusBarMobileView) {
                 viewIndex = i;
                 break;
             }
@@ -297,26 +273,6 @@
         addView(view, viewIndex, createLayoutParams());
     }
 
-    /**
-     * Apply an update to a mobile icon view for the given {@link MobileIconState}. For
-     * compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
-     * update it, since the context (and thus the {@link Configuration}) may have changed
-     */
-    public void updateMobileState(MobileIconState state, Context mobileContext) {
-        Log.d(TAG, "updateMobileState: " + state);
-
-        // The mobile config provided by MobileContextProvider could have changed; always recreate
-        for (int i = 0; i < mMobileViews.size(); i++) {
-            StatusBarMobileView view = mMobileViews.get(i);
-            if (view.getState().subId == state.subId) {
-                removeView(view);
-            }
-        }
-
-        // Add the replacement or new icon
-        addMobileView(state, mobileContext);
-    }
-
     public void onRemoveIcon(StatusIconDisplayable view) {
         if (view.getSlot().equals("wifi")) {
             if (view instanceof ModernStatusBarWifiView) {
@@ -324,12 +280,6 @@
                 removeView(mModernWifiView);
                 mModernWifiView = null;
             }
-        } else if (view instanceof StatusBarMobileView) {
-            StatusBarMobileView mobileView = matchingMobileView(view);
-            if (mobileView != null) {
-                removeView(mobileView);
-                mMobileViews.remove(mobileView);
-            }
         } else if (view instanceof ModernStatusBarMobileView) {
             ModernStatusBarMobileView mobileView = matchingModernMobileView(
                     (ModernStatusBarMobileView) view);
@@ -340,21 +290,6 @@
         }
     }
 
-    private StatusBarMobileView matchingMobileView(StatusIconDisplayable otherView) {
-        if (!(otherView instanceof StatusBarMobileView)) {
-            return null;
-        }
-
-        StatusBarMobileView v = (StatusBarMobileView) otherView;
-        for (StatusBarMobileView view : mMobileViews) {
-            if (view.getState().subId == v.getState().subId) {
-                return view;
-            }
-        }
-
-        return null;
-    }
-
     private ModernStatusBarMobileView matchingModernMobileView(ModernStatusBarMobileView other) {
         for (ModernStatusBarMobileView v : mModernMobileViews) {
             if (v.getSubId() == other.getSubId()) {
@@ -376,9 +311,6 @@
         if (mModernWifiView != null) {
             mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
         }
-        for (StatusBarMobileView view : mMobileViews) {
-            view.onDarkChanged(areas, darkIntensity, tint);
-        }
         for (ModernStatusBarMobileView view : mModernMobileViews) {
             view.onDarkChanged(areas, darkIntensity, tint);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index b2c39f7..92c786f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -306,19 +306,25 @@
 
     /**
      * Drawable that aligns left horizontally and center vertically (like ImageWallpaper).
+     *
+     * <p>Aligns to the center when showing on the smaller internal display of a multi display
+     * device.
      */
     public static class WallpaperDrawable extends DrawableWrapper {
 
         private final ConstantState mState;
         private final Rect mTmpRect = new Rect();
+        private boolean mIsOnSmallerInternalDisplays;
 
-        public WallpaperDrawable(Resources r, Bitmap b) {
-            this(r, new ConstantState(b));
+        public WallpaperDrawable(Resources r, Bitmap b, boolean isOnSmallerInternalDisplays) {
+            this(r, new ConstantState(b), isOnSmallerInternalDisplays);
         }
 
-        private WallpaperDrawable(Resources r, ConstantState state) {
+        private WallpaperDrawable(Resources r, ConstantState state,
+                boolean isOnSmallerInternalDisplays) {
             super(new BitmapDrawable(r, state.mBackground));
             mState = state;
+            mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
         }
 
         @Override
@@ -357,10 +363,17 @@
             }
             dy = (vheight - dheight * scale) * 0.5f;
 
+            int offsetX = 0;
+            // Offset to show the center area of the wallpaper on a smaller display for multi
+            // display device
+            if (mIsOnSmallerInternalDisplays) {
+                offsetX = bounds.centerX() - (Math.round(dwidth * scale) / 2);
+            }
+
             mTmpRect.set(
-                    bounds.left,
+                    bounds.left + offsetX,
                     bounds.top + Math.round(dy),
-                    bounds.left + Math.round(dwidth * scale),
+                    bounds.left + Math.round(dwidth * scale) + offsetX,
                     bounds.top + Math.round(dheight * scale + dy));
 
             super.onBoundsChange(mTmpRect);
@@ -371,6 +384,17 @@
             return mState;
         }
 
+        /**
+         * Update bounds when the hosting display or the display size has changed.
+         *
+         * @param isOnSmallerInternalDisplays true if the drawable is on one of the internal
+         *                                    displays with the smaller area.
+         */
+        public void onDisplayUpdated(boolean isOnSmallerInternalDisplays) {
+            mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
+            onBoundsChange(getBounds());
+        }
+
         static class ConstantState extends Drawable.ConstantState {
 
             private final Bitmap mBackground;
@@ -386,7 +410,7 @@
 
             @Override
             public Drawable newDrawable(@Nullable Resources res) {
-                return new WallpaperDrawable(res, this);
+                return new WallpaperDrawable(res, this, /* isOnSmallerInternalDisplays= */ false);
             }
 
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 1d934d6..79151fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -548,7 +548,7 @@
                 final int iconResId = mUserManager.getUserStatusBarIconResId(userId);
                 // TODO(b/170249807, b/230779281): Handle non-managed-profile String
                 String accessibilityString = getManagedProfileAccessibilityString();
-                mHandler.post(() -> {
+                mMainExecutor.execute(() -> {
                     final boolean showIcon;
                     if (iconResId != Resources.ID_NULL && (!mKeyguardStateController.isShowing()
                             || mKeyguardStateController.isOccluded())) {
@@ -629,6 +629,13 @@
     }
 
     @Override
+    public void appTransitionFinished(int displayId) {
+        if (mDisplayId == displayId) {
+            updateProfileIcon();
+        }
+    }
+
+    @Override
     public void onKeyguardShowingChanged() {
         updateProfileIcon();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index c817466..89c3a02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -18,6 +18,7 @@
 import android.view.View
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.unfold.FoldAodAnimationController
 import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -37,8 +38,12 @@
     private val animations: List<ScreenOffAnimation> =
         listOfNotNull(foldToAodAnimation, unlockedScreenOffAnimation)
 
-    fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
-        animations.forEach { it.initialize(centralSurfaces, lightRevealScrim) }
+    fun initialize(
+            centralSurfaces: CentralSurfaces,
+            shadeViewController: ShadeViewController,
+            lightRevealScrim: LightRevealScrim,
+    ) {
+        animations.forEach { it.initialize(centralSurfaces, shadeViewController, lightRevealScrim) }
         wakefulnessLifecycle.addObserver(this)
     }
 
@@ -197,7 +202,11 @@
 }
 
 interface ScreenOffAnimation {
-    fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {}
+    fun initialize(
+            centralSurfaces: CentralSurfaces,
+            shadeViewController: ShadeViewController,
+            lightRevealScrim: LightRevealScrim,
+    ) {}
 
     /**
      * Called when started going to sleep, should return true if the animation will be played
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 42b0a4f..d5cb6b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -15,7 +15,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
 
@@ -24,10 +23,8 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.ArraySet;
-import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.LinearLayout.LayoutParams;
 
@@ -41,12 +38,9 @@
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
@@ -100,13 +94,10 @@
      */
     void setNewWifiIcon();
 
-    /** */
-    void setMobileIcons(String slot, List<MobileIconState> states);
-
     /**
-     * This method completely replaces {@link #setMobileIcons} with the information from the new
-     * mobile data pipeline. Icons will automatically keep their state up to date, so we don't have
-     * to worry about funneling MobileIconState objects through anymore.
+     * Notify this class that there is a new set of mobile icons to display, keyed off of this list
+     * of subIds. The icons will be added and bound to the mobile data pipeline via
+     * {@link com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder}.
      */
     void setNewMobileIconSubIds(List<Integer> subIds);
     /**
@@ -168,14 +159,12 @@
         public DarkIconManager(
                 LinearLayout linearLayout,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(linearLayout,
                     location,
-                    statusBarPipelineFlags,
                     wifiUiAdapter,
                     mobileUiAdapter,
                     mobileContextProvider);
@@ -235,7 +224,6 @@
 
         @SysUISingleton
         public static class Factory {
-            private final StatusBarPipelineFlags mStatusBarPipelineFlags;
             private final WifiUiAdapter mWifiUiAdapter;
             private final MobileContextProvider mMobileContextProvider;
             private final MobileUiAdapter mMobileUiAdapter;
@@ -243,12 +231,10 @@
 
             @Inject
             public Factory(
-                    StatusBarPipelineFlags statusBarPipelineFlags,
                     WifiUiAdapter wifiUiAdapter,
                     MobileContextProvider mobileContextProvider,
                     MobileUiAdapter mobileUiAdapter,
                     DarkIconDispatcher darkIconDispatcher) {
-                mStatusBarPipelineFlags = statusBarPipelineFlags;
                 mWifiUiAdapter = wifiUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
                 mMobileUiAdapter = mobileUiAdapter;
@@ -259,7 +245,6 @@
                 return new DarkIconManager(
                         group,
                         location,
-                        mStatusBarPipelineFlags,
                         mWifiUiAdapter,
                         mMobileUiAdapter,
                         mMobileContextProvider,
@@ -277,14 +262,12 @@
         public TintedIconManager(
                 ViewGroup group,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider
         ) {
             super(group,
                     location,
-                    statusBarPipelineFlags,
                     wifiUiAdapter,
                     mobileUiAdapter,
                     mobileContextProvider);
@@ -319,19 +302,16 @@
 
         @SysUISingleton
         public static class Factory {
-            private final StatusBarPipelineFlags mStatusBarPipelineFlags;
             private final WifiUiAdapter mWifiUiAdapter;
             private final MobileContextProvider mMobileContextProvider;
             private final MobileUiAdapter mMobileUiAdapter;
 
             @Inject
             public Factory(
-                    StatusBarPipelineFlags statusBarPipelineFlags,
                     WifiUiAdapter wifiUiAdapter,
                     MobileUiAdapter mobileUiAdapter,
                     MobileContextProvider mobileContextProvider
             ) {
-                mStatusBarPipelineFlags = statusBarPipelineFlags;
                 mWifiUiAdapter = wifiUiAdapter;
                 mMobileUiAdapter = mobileUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
@@ -341,7 +321,6 @@
                 return new TintedIconManager(
                         group,
                         location,
-                        mStatusBarPipelineFlags,
                         mWifiUiAdapter,
                         mMobileUiAdapter,
                         mMobileContextProvider);
@@ -354,7 +333,6 @@
      */
     class IconManager implements DemoModeCommandReceiver {
         protected final ViewGroup mGroup;
-        private final StatusBarPipelineFlags mStatusBarPipelineFlags;
         private final MobileContextProvider mMobileContextProvider;
         private final LocationBasedWifiViewModel mWifiViewModel;
         private final MobileIconsViewModel mMobileIconsViewModel;
@@ -376,27 +354,21 @@
         public IconManager(
                 ViewGroup group,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider
         ) {
             mGroup = group;
-            mStatusBarPipelineFlags = statusBarPipelineFlags;
             mMobileContextProvider = mobileContextProvider;
             mContext = group.getContext();
             mLocation = location;
 
             reloadDimens();
 
-            if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
-                // This starts the flow for the new pipeline, and will notify us of changes if
-                // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
-                mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
-                MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
-            } else {
-                mMobileIconsViewModel = null;
-            }
+            // This starts the flow for the new pipeline, and will notify us of changes via
+            // {@link #setNewMobileIconIds}
+            mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
+            MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
 
             mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
         }
@@ -449,9 +421,6 @@
                 case TYPE_WIFI_NEW:
                     return addNewWifiIcon(index, slot);
 
-                case TYPE_MOBILE:
-                    return addMobileIcon(index, slot, holder.getMobileState());
-
                 case TYPE_MOBILE_NEW:
                     return addNewMobileIcon(index, slot, holder.getTag());
             }
@@ -479,40 +448,12 @@
             return view;
         }
 
-        @VisibleForTesting
-        protected StatusIconDisplayable addMobileIcon(
-                int index,
-                String slot,
-                MobileIconState state
-        ) {
-            if (mStatusBarPipelineFlags.useNewMobileIcons()) {
-                throw new IllegalStateException("Attempting to add a mobile icon while the new "
-                        + "icons are enabled is not supported");
-            }
-
-            // Use the `subId` field as a key to query for the correct context
-            StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
-            mobileView.applyMobileState(state);
-            mGroup.addView(mobileView, index, onCreateLayoutParams());
-
-            if (mIsInDemoMode) {
-                Context mobileContext = mMobileContextProvider
-                        .getMobileContextForSub(state.subId, mContext);
-                mDemoStatusIcons.addMobileView(state, mobileContext);
-            }
-            return mobileView;
-        }
 
         protected StatusIconDisplayable addNewMobileIcon(
                 int index,
                 String slot,
                 int subId
         ) {
-            if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
-                throw new IllegalStateException("Attempting to add a mobile icon using the new"
-                        + "pipeline, but the enabled flag is false.");
-            }
-
             BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
             mGroup.addView(view, index, onCreateLayoutParams());
 
@@ -536,13 +477,6 @@
             return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
         }
 
-        private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
-            Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
-            StatusBarMobileView view = StatusBarMobileView
-                    .fromContext(mobileContext, slot);
-            return view;
-        }
-
         private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
                 String slot, int subId) {
             Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
@@ -568,15 +502,6 @@
                     com.android.internal.R.dimen.status_bar_icon_size_sp);
         }
 
-        private void setHeightAndCenter(ImageView imageView, int height) {
-            ViewGroup.LayoutParams params = imageView.getLayoutParams();
-            params.height = height;
-            if (params instanceof LinearLayout.LayoutParams) {
-                ((LinearLayout.LayoutParams) params).gravity = Gravity.CENTER_VERTICAL;
-            }
-            imageView.setLayoutParams(params);
-        }
-
         protected void onRemoveIcon(int viewIndex) {
             if (mIsInDemoMode) {
                 mDemoStatusIcons.onRemoveIcon((StatusIconDisplayable) mGroup.getChildAt(viewIndex));
@@ -594,9 +519,6 @@
                 case TYPE_ICON:
                     onSetIcon(viewIndex, holder.getIcon());
                     return;
-                case TYPE_MOBILE:
-                    onSetMobileIcon(viewIndex, holder.getMobileState());
-                    return;
                 case TYPE_MOBILE_NEW:
                 case TYPE_WIFI_NEW:
                     // Nothing, the new icons update themselves
@@ -606,23 +528,6 @@
             }
         }
 
-        public void onSetMobileIcon(int viewIndex, MobileIconState state) {
-            View view = mGroup.getChildAt(viewIndex);
-            if (view instanceof StatusBarMobileView) {
-                ((StatusBarMobileView) view).applyMobileState(state);
-            } else {
-                // ModernStatusBarMobileView automatically updates via the ViewModel
-                throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
-                        + "the new pipeline");
-            }
-
-            if (mIsInDemoMode) {
-                Context mobileContext = mMobileContextProvider
-                        .getMobileContextForSub(state.subId, mContext);
-                mDemoStatusIcons.updateMobileState(state, mobileContext);
-            }
-        }
-
         @Override
         public void dispatchDemoCommand(String command, Bundle args) {
             if (!mDemoable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index d1a02d6..553cbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -39,7 +39,6 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -215,41 +214,10 @@
     /**
      * Accept a list of MobileIconStates, which all live in the same slot(?!), and then are sorted
      * by subId. Don't worry this definitely makes sense and works.
-     * @param slot da slot
-     * @param iconStates All of the mobile icon states
+     * @param subIds list of subscription ID integers that provide the key to the icon to display.
      */
     @Override
-    public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
-        if (mStatusBarPipelineFlags.useNewMobileIcons()) {
-            Log.d(TAG, "ignoring old pipeline callbacks, because the new mobile "
-                    + "icons are enabled");
-            return;
-        }
-        Slot mobileSlot = mStatusBarIconList.getSlot(slot);
-
-        // Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
-        // StatusBarIconList has UI design that first items go to the right of second items.
-        Collections.reverse(iconStates);
-
-        for (MobileIconState state : iconStates) {
-            StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
-            if (holder == null) {
-                holder = StatusBarIconHolder.fromMobileIconState(state);
-                setIcon(slot, holder);
-            } else {
-                holder.setMobileState(state);
-                handleSet(slot, holder);
-            }
-        }
-    }
-
-    @Override
     public void setNewMobileIconSubIds(List<Integer> subIds) {
-        if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
-            Log.d(TAG, "ignoring new pipeline callback, "
-                    + "since the new mobile icons are disabled");
-            return;
-        }
         String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile);
         Slot mobileSlot = mStatusBarIconList.getSlot(slotName);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 01fd247..7048a78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -24,7 +24,6 @@
 
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
 
 import java.lang.annotation.Retention;
@@ -35,7 +34,6 @@
  */
 public class StatusBarIconHolder {
     public static final int TYPE_ICON = 0;
-    public static final int TYPE_MOBILE = 2;
     /**
      * TODO (b/249790733): address this once the new pipeline is in place
      * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
@@ -63,7 +61,6 @@
 
     @IntDef({
             TYPE_ICON,
-            TYPE_MOBILE,
             TYPE_MOBILE_NEW,
             TYPE_WIFI_NEW
     })
@@ -71,7 +68,6 @@
     @interface IconType {}
 
     private StatusBarIcon mIcon;
-    private MobileIconState mMobileState;
     private @IconType int mType = TYPE_ICON;
     private int mTag = 0;
 
@@ -79,7 +75,6 @@
     public static String getTypeString(@IconType int type) {
         switch(type) {
             case TYPE_ICON: return "ICON";
-            case TYPE_MOBILE: return "MOBILE_OLD";
             case TYPE_MOBILE_NEW: return "MOBILE_NEW";
             case TYPE_WIFI_NEW: return "WIFI_NEW";
             default: return "UNKNOWN";
@@ -103,15 +98,6 @@
         return holder;
     }
 
-    /** */
-    public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        holder.mMobileState = state;
-        holder.mType = TYPE_MOBILE;
-        holder.mTag = state.subId;
-        return holder;
-    }
-
     /**
      * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
      * determine icon ordering and building the correct view model
@@ -153,21 +139,10 @@
         mIcon = icon;
     }
 
-    @Nullable
-    public MobileIconState getMobileState() {
-        return mMobileState;
-    }
-
-    public void setMobileState(MobileIconState state) {
-        mMobileState = state;
-    }
-
     public boolean isVisible() {
         switch (mType) {
             case TYPE_ICON:
                 return mIcon.visible;
-            case TYPE_MOBILE:
-                return mMobileState.visible;
             case TYPE_MOBILE_NEW:
             case TYPE_WIFI_NEW:
                 // The new pipeline controls visibilities via the view model and view binder, so
@@ -188,10 +163,6 @@
                 mIcon.visible = visible;
                 break;
 
-            case TYPE_MOBILE:
-                mMobileState.visible = visible;
-                break;
-
             case TYPE_MOBILE_NEW:
             case TYPE_WIFI_NEW:
                 // The new pipeline controls visibilities via the view model and view binder, so
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 6919996..344e56c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Handler;
-import android.telephony.SubscriptionInfo;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -27,7 +26,6 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.connectivity.IconState;
-import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.policy.SecurityController;
@@ -71,7 +69,6 @@
     // Track as little state as possible, and only for padding purposes
     private boolean mIsAirplaneMode = false;
 
-    private ArrayList<MobileIconState> mMobileStates = new ArrayList<>();
     private ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>();
     private boolean mInitialized;
 
@@ -102,7 +99,7 @@
         mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
     }
 
-    /** Call to initilaize and register this classw with the system. */
+    /** Call to initialize and register this class with the system. */
     public void init() {
         if (mInitialized) {
             return;
@@ -189,34 +186,6 @@
                 CallIndicatorIconState.copyStates(mCallIndicatorStates));
     }
 
-    @Override
-    public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) {
-        if (DEBUG) {
-            Log.d(TAG, "setMobileDataIndicators: " + indicators);
-        }
-        MobileIconState state = getState(indicators.subId);
-        if (state == null) {
-            return;
-        }
-
-        state.visible = indicators.statusIcon.visible && !mHideMobile;
-        state.strengthId = indicators.statusIcon.icon;
-        state.typeId = indicators.statusType;
-        state.contentDescription = indicators.statusIcon.contentDescription;
-        state.typeContentDescription = indicators.typeContentDescription;
-        state.showTriangle = indicators.showTriangle;
-        state.roaming = indicators.roaming;
-        state.activityIn = indicators.activityIn && mActivityEnabled;
-        state.activityOut = indicators.activityOut && mActivityEnabled;
-
-        if (DEBUG) {
-            Log.d(TAG, "MobileIconStates: "
-                    + (mMobileStates == null ? "" : mMobileStates.toString()));
-        }
-        // Always send a copy to maintain value type semantics
-        mIconController.setMobileIcons(mSlotMobile, MobileIconState.copyStates(mMobileStates));
-    }
-
     private CallIndicatorIconState getNoCallingState(int subId) {
         for (CallIndicatorIconState state : mCallIndicatorStates) {
             if (state.subId == subId) {
@@ -227,74 +196,8 @@
         return null;
     }
 
-    private MobileIconState getState(int subId) {
-        for (MobileIconState state : mMobileStates) {
-            if (state.subId == subId) {
-                return state;
-            }
-        }
-        Log.e(TAG, "Unexpected subscription " + subId);
-        return null;
-    }
-
-    /**
-     * It is expected that a call to setSubs will be immediately followed by setMobileDataIndicators
-     * so we don't have to update the icon manager at this point, just remove the old ones
-     * @param subs list of mobile subscriptions, displayed as mobile data indicators (max 8)
-     */
-    @Override
-    public void setSubs(List<SubscriptionInfo> subs) {
-        if (DEBUG) Log.d(TAG, "setSubs: " + (subs == null ? "" : subs.toString()));
-        if (hasCorrectSubs(subs)) {
-            return;
-        }
-
-        mIconController.removeAllIconsForSlot(mSlotMobile);
-        mIconController.removeAllIconsForSlot(mSlotNoCalling);
-        mIconController.removeAllIconsForSlot(mSlotCallStrength);
-        mMobileStates.clear();
-        List<CallIndicatorIconState> noCallingStates = new ArrayList<CallIndicatorIconState>();
-        noCallingStates.addAll(mCallIndicatorStates);
-        mCallIndicatorStates.clear();
-        final int n = subs.size();
-        for (int i = 0; i < n; i++) {
-            mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
-            boolean isNewSub = true;
-            for (CallIndicatorIconState state : noCallingStates) {
-                if (state.subId == subs.get(i).getSubscriptionId()) {
-                    mCallIndicatorStates.add(state);
-                    isNewSub = false;
-                    break;
-                }
-            }
-            if (isNewSub) {
-                mCallIndicatorStates.add(
-                        new CallIndicatorIconState(subs.get(i).getSubscriptionId()));
-            }
-        }
-    }
-
-    private boolean hasCorrectSubs(List<SubscriptionInfo> subs) {
-        final int N = subs.size();
-        if (N != mMobileStates.size()) {
-            return false;
-        }
-        for (int i = 0; i < N; i++) {
-            if (mMobileStates.get(i).subId != subs.get(i).getSubscriptionId()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public void setNoSims(boolean show, boolean simDetected) {
-        // Noop yay!
-    }
-
     @Override
     public void setEthernetIndicators(IconState state) {
-        boolean visible = state.visible && !mHideEthernet;
         int resId = state.icon;
         String description = state.contentDescription;
 
@@ -324,11 +227,6 @@
         }
     }
 
-    @Override
-    public void setMobileDataEnabled(boolean enabled) {
-        // Don't care.
-    }
-
     /**
      * Stores the statusbar state for no Calling & SMS.
      */
@@ -388,117 +286,4 @@
             return outStates;
         }
     }
-
-    private static abstract class SignalIconState {
-        public boolean visible;
-        public boolean activityOut;
-        public boolean activityIn;
-        public String slot;
-        public String contentDescription;
-
-        @Override
-        public boolean equals(Object o) {
-            // Skipping reference equality bc this should be more of a value type
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            SignalIconState that = (SignalIconState) o;
-            return visible == that.visible &&
-                    activityOut == that.activityOut &&
-                    activityIn == that.activityIn &&
-                    Objects.equals(contentDescription, that.contentDescription) &&
-                    Objects.equals(slot, that.slot);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(visible, activityOut, slot);
-        }
-
-        protected void copyTo(SignalIconState other) {
-            other.visible = visible;
-            other.activityIn = activityIn;
-            other.activityOut = activityOut;
-            other.slot = slot;
-            other.contentDescription = contentDescription;
-        }
-    }
-
-    /**
-     * A little different. This one delegates to SignalDrawable instead of a specific resId
-     */
-    public static class MobileIconState extends SignalIconState {
-        public int subId;
-        public int strengthId;
-        public int typeId;
-        public boolean showTriangle;
-        public boolean roaming;
-        public boolean needsLeadingPadding;
-        public CharSequence typeContentDescription;
-
-        private MobileIconState(int subId) {
-            super();
-            this.subId = subId;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            if (!super.equals(o)) {
-                return false;
-            }
-            MobileIconState that = (MobileIconState) o;
-            return subId == that.subId
-                    && strengthId == that.strengthId
-                    && typeId == that.typeId
-                    && showTriangle == that.showTriangle
-                    && roaming == that.roaming
-                    && needsLeadingPadding == that.needsLeadingPadding
-                    && Objects.equals(typeContentDescription, that.typeContentDescription);
-        }
-
-        @Override
-        public int hashCode() {
-
-            return Objects
-                    .hash(super.hashCode(), subId, strengthId, typeId, showTriangle, roaming,
-                            needsLeadingPadding, typeContentDescription);
-        }
-
-        public MobileIconState copy() {
-            MobileIconState copy = new MobileIconState(this.subId);
-            copyTo(copy);
-            return copy;
-        }
-
-        public void copyTo(MobileIconState other) {
-            super.copyTo(other);
-            other.subId = subId;
-            other.strengthId = strengthId;
-            other.typeId = typeId;
-            other.showTriangle = showTriangle;
-            other.roaming = roaming;
-            other.needsLeadingPadding = needsLeadingPadding;
-            other.typeContentDescription = typeContentDescription;
-        }
-
-        private static List<MobileIconState> copyStates(List<MobileIconState> inStates) {
-            ArrayList<MobileIconState> outStates = new ArrayList<>();
-            for (MobileIconState state : inStates) {
-                MobileIconState copy = new MobileIconState(state.subId);
-                state.copyTo(copy);
-                outStates.add(copy);
-            }
-
-            return outStates;
-        }
-
-        @Override public String toString() {
-            return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId
-                    + ", showTriangle=" + showTriangle + ", roaming=" + roaming
-                    + ", typeId=" + typeId + ", visible=" + visible + ")";
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt
new file mode 100644
index 0000000..fbc6b95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt
@@ -0,0 +1,34 @@
+/*
+ * 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 com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Whether dialogs are requesting for affordances to be hidden or not. */
+val SystemUIDialogManager.hideAffordancesRequest: Flow<Boolean>
+    get() = conflatedCallbackFlow {
+        val callback =
+            SystemUIDialogManager.Listener { hideAffordance ->
+                trySendWithFailureLogging(hideAffordance, "dialogHideAffordancesRequest")
+            }
+        registerListener(callback)
+        trySendWithFailureLogging(shouldHideAffordance(), "dialogHideAffordancesRequestInitial")
+        awaitClose { unregisterListener(callback) }
+    }
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 96a4d90..7e9172d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -66,7 +67,8 @@
     private val powerManager: PowerManager,
     private val handler: Handler = Handler()
 ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
-    private lateinit var mCentralSurfaces: CentralSurfaces
+    private lateinit var centralSurfaces: CentralSurfaces
+    private lateinit var shadeViewController: ShadeViewController
     /**
      * Whether or not [initialize] has been called to provide us with the StatusBar,
      * NotificationPanelViewController, and LightRevealSrim so that we can run the unlocked screen
@@ -126,7 +128,7 @@
         lightRevealAnimator.start()
     }
 
-    val animatorDurationScaleObserver = object : ContentObserver(null) {
+    private val animatorDurationScaleObserver = object : ContentObserver(null) {
         override fun onChange(selfChange: Boolean) {
             updateAnimatorDurationScale()
         }
@@ -134,11 +136,13 @@
 
     override fun initialize(
         centralSurfaces: CentralSurfaces,
+        shadeViewController: ShadeViewController,
         lightRevealScrim: LightRevealScrim
     ) {
         this.initialized = true
         this.lightRevealScrim = lightRevealScrim
-        this.mCentralSurfaces = centralSurfaces
+        this.centralSurfaces = centralSurfaces
+        this.shadeViewController = shadeViewController
 
         updateAnimatorDurationScale()
         globalSettings.registerContentObserver(
@@ -198,7 +202,7 @@
 
                     // Tell the CentralSurfaces to become keyguard for real - we waited on that
                     // since it is slow and would have caused the animation to jank.
-                    mCentralSurfaces.updateIsKeyguard()
+                    centralSurfaces.updateIsKeyguard()
 
                     // Run the callback given to us by the KeyguardVisibilityHelper.
                     after.run()
@@ -251,7 +255,7 @@
             // even if we're going from SHADE to SHADE or KEYGUARD to KEYGUARD, since we might have
             // changed parts of the UI (such as showing AOD in the shade) without actually changing
             // the StatusBarState. This ensures that the UI definitely reflects the desired state.
-            mCentralSurfaces.updateIsKeyguard(true /* forceStateChange */)
+            centralSurfaces.updateIsKeyguard(true /* forceStateChange */)
         }
     }
 
@@ -280,7 +284,7 @@
 
                     // Show AOD. That'll cause the KeyguardVisibilityHelper to call
                     // #animateInKeyguard.
-                    mCentralSurfaces.shadeViewController.showAodUi()
+                    shadeViewController.showAodUi()
                 }
             }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
 
@@ -328,8 +332,8 @@
         // We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's
         // already expanded and showing notifications/QS, the animation looks really messy. For now,
         // disable it if the notification panel is expanded.
-        if ((!this::mCentralSurfaces.isInitialized ||
-                mCentralSurfaces.shadeViewController.isPanelExpanded) &&
+        if ((!this::centralSurfaces.isInitialized ||
+                shadeViewController.isPanelExpanded) &&
                 // Status bar might be expanded because we have started
                 // playing the animation already
                 !isAnimationPlaying()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 29829e4..6e51ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -18,8 +18,6 @@
 
 import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import javax.inject.Inject
 
 /** All flagging methods related to the new status bar pipeline (see b/238425913). */
@@ -28,30 +26,10 @@
 @Inject
 constructor(
     context: Context,
-    private val featureFlags: FeatureFlags,
 ) {
     private val mobileSlot = context.getString(com.android.internal.R.string.status_bar_mobile)
     private val wifiSlot = context.getString(com.android.internal.R.string.status_bar_wifi)
 
-    /** True if we should display the mobile icons using the new status bar data pipeline. */
-    fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
-
-    /**
-     * True if we should run the new mobile icons backend to get the logging.
-     *
-     * Does *not* affect whether we render the mobile icons using the new backend data. See
-     * [useNewMobileIcons] for that.
-     */
-    fun runNewMobileIconsBackend(): Boolean =
-        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()
-
-    /**
-     * Returns true if we should apply some coloring to the icons that were rendered with the new
-     * pipeline to help with debugging.
-     */
-    fun useDebugColoring(): Boolean =
-        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING)
-
     /**
      * For convenience in the StatusBarIconController, we want to gate some actions based on slot
      * name and the flag together.
@@ -59,5 +37,5 @@
      * @return true if this icon is controlled by any of the status bar pipeline flags
      */
     fun isIconControlledByFlags(slotName: String): Boolean =
-        slotName == wifiSlot || (slotName == mobileSlot && useNewMobileIcons())
+        slotName == wifiSlot || slotName == mobileSlot
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index a05ab84..d7fcf48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.StatusBarIconController
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import java.io.PrintWriter
@@ -46,24 +45,18 @@
     val mobileIconsViewModel: MobileIconsViewModel,
     private val logger: MobileViewLogger,
     @Application private val scope: CoroutineScope,
-    private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) : CoreStartable {
     private var isCollecting: Boolean = false
     private var lastValue: List<Int>? = null
 
     override fun start() {
-        // Only notify the icon controller if we want to *render* the new icons.
-        // Note that this flow may still run if
-        // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
-        // get the logging data without rendering.
-        if (statusBarPipelineFlags.useNewMobileIcons()) {
-            scope.launch {
-                isCollecting = true
-                mobileIconsViewModel.subscriptionIdsFlow.collectLatest {
-                    logger.logUiAdapterSubIdsSentToIconController(it)
-                    lastValue = it
-                    iconController.setNewMobileIconSubIds(it)
-                }
+        // Start notifying the icon controller of subscriptions
+        scope.launch {
+            isCollecting = true
+            mobileIconsViewModel.subscriptionIdsFlow.collectLatest {
+                logger.logUiAdapterSubIdsSentToIconController(it)
+                lastValue = it
+                iconController.setNewMobileIconSubIds(it)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index a2a247a..c221109 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -183,16 +183,10 @@
             }
 
             override fun onIconTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 iconTint.value = newTint
             }
 
             override fun onDecorTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 decorTint.value = newTint
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
index f775940..a51982c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.graphics.Color
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
 
 /**
@@ -32,24 +31,14 @@
  */
 abstract class LocationBasedMobileViewModel(
     val commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-    debugTint: Int,
     val locationName: String,
     val verboseLogger: VerboseMobileViewLogger?,
 ) : MobileIconViewModelCommon by commonImpl {
-    val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
-
-    val defaultColor: Int =
-        if (useDebugColoring) {
-            debugTint
-        } else {
-            Color.WHITE
-        }
+    val defaultColor: Int = Color.WHITE
 
     companion object {
         fun viewModelForLocation(
             commonImpl: MobileIconViewModelCommon,
-            statusBarPipelineFlags: StatusBarPipelineFlags,
             verboseMobileViewLogger: VerboseMobileViewLogger,
             loc: StatusBarLocation,
         ): LocationBasedMobileViewModel =
@@ -57,39 +46,31 @@
                 StatusBarLocation.HOME ->
                     HomeMobileIconViewModel(
                         commonImpl,
-                        statusBarPipelineFlags,
                         verboseMobileViewLogger,
                     )
-                StatusBarLocation.KEYGUARD ->
-                    KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
-                StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+                StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl)
+                StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl)
             }
     }
 }
 
 class HomeMobileIconViewModel(
     commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
     verboseMobileViewLogger: VerboseMobileViewLogger,
 ) :
     MobileIconViewModelCommon,
     LocationBasedMobileViewModel(
         commonImpl,
-        statusBarPipelineFlags,
-        debugTint = Color.CYAN,
         locationName = "Home",
         verboseMobileViewLogger,
     )
 
 class QsMobileIconViewModel(
     commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
 ) :
     MobileIconViewModelCommon,
     LocationBasedMobileViewModel(
         commonImpl,
-        statusBarPipelineFlags,
-        debugTint = Color.GREEN,
         locationName = "QS",
         // Only do verbose logging for the Home location.
         verboseLogger = null,
@@ -97,13 +78,10 @@
 
 class KeyguardMobileIconViewModel(
     commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
 ) :
     MobileIconViewModelCommon,
     LocationBasedMobileViewModel(
         commonImpl,
-        statusBarPipelineFlags,
-        debugTint = Color.MAGENTA,
         locationName = "Keyguard",
         // Only do verbose logging for the Home location.
         verboseLogger = null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 40b8c90..5cf887e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -98,7 +98,6 @@
         val common = commonViewModelForSub(subId)
         return LocationBasedMobileViewModel.viewModelForLocation(
             common,
-            statusBarPipelineFlags,
             verboseLogger,
             location,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
index 6d71823..7a60d96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel.Companion.viewModelForLocation
@@ -46,7 +45,6 @@
 constructor(
     private val iconController: StatusBarIconController,
     private val wifiViewModel: WifiViewModel,
-    private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) {
     /**
      * Binds the container for all the status bar icons to a view model, so that we inflate the wifi
@@ -60,8 +58,7 @@
         statusBarIconGroup: ViewGroup,
         location: StatusBarLocation,
     ): LocationBasedWifiViewModel {
-        val locationViewModel =
-            viewModelForLocation(wifiViewModel, statusBarPipelineFlags, location)
+        val locationViewModel = viewModelForLocation(wifiViewModel, location)
 
         statusBarIconGroup.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index e819c4f..3082a66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -157,16 +157,10 @@
             }
 
             override fun onIconTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 iconTint.value = newTint
             }
 
             override fun onDecorTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 decorTint.value = newTint
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index b731a41..cd5b92c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.graphics.Color
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 
 /**
  * A view model for a wifi icon in a specific location. This allows us to control parameters that
@@ -27,18 +26,9 @@
  * Must be subclassed for each distinct location.
  */
 abstract class LocationBasedWifiViewModel(
-    val commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-    debugTint: Int,
+    private val commonImpl: WifiViewModelCommon,
 ) : WifiViewModelCommon by commonImpl {
-    val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
-
-    val defaultColor: Int =
-        if (useDebugColoring) {
-            debugTint
-        } else {
-            Color.WHITE
-        }
+    val defaultColor: Int = Color.WHITE
 
     companion object {
         /**
@@ -47,13 +37,12 @@
          */
         fun viewModelForLocation(
             commonImpl: WifiViewModelCommon,
-            flags: StatusBarPipelineFlags,
             location: StatusBarLocation,
         ): LocationBasedWifiViewModel =
             when (location) {
-                StatusBarLocation.HOME -> HomeWifiViewModel(commonImpl, flags)
-                StatusBarLocation.KEYGUARD -> KeyguardWifiViewModel(commonImpl, flags)
-                StatusBarLocation.QS -> QsWifiViewModel(commonImpl, flags)
+                StatusBarLocation.HOME -> HomeWifiViewModel(commonImpl)
+                StatusBarLocation.KEYGUARD -> KeyguardWifiViewModel(commonImpl)
+                StatusBarLocation.QS -> QsWifiViewModel(commonImpl)
             }
     }
 }
@@ -64,23 +53,14 @@
  */
 class HomeWifiViewModel(
     commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-) :
-    WifiViewModelCommon,
-    LocationBasedWifiViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.CYAN)
+) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
 
 /** A view model for the wifi icon shown on keyguard (lockscreen). */
 class KeyguardWifiViewModel(
     commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-) :
-    WifiViewModelCommon,
-    LocationBasedWifiViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.MAGENTA)
+) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
 
 /** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
 class QsWifiViewModel(
     commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-) :
-    WifiViewModelCommon,
-    LocationBasedWifiViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.GREEN)
+) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
deleted file mode 100644
index a601e9b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-# Android TV Core Framework
-rgl@google.com
-valiiftime@google.com
-galinap@google.com
-patrikf@google.com
-robhor@google.com
-sergeynv@google.com
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
deleted file mode 100644
index d35d340..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.tv;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.KeyboardShortcuts;
-
-import dagger.Lazy;
-
-import javax.inject.Inject;
-
-/**
- * Status bar implementation for "large screen" products that mostly present no on-screen nav.
- * Serves as a collection of UI components, rather than showing its own UI.
- */
-@SysUISingleton
-public class TvStatusBar implements CoreStartable, CommandQueue.Callbacks {
-
-    private static final String ACTION_SHOW_PIP_MENU =
-            "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
-    private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
-
-    private final Context mContext;
-    private final CommandQueue mCommandQueue;
-    private final Lazy<AssistManager> mAssistManagerLazy;
-
-    @Inject
-    public TvStatusBar(Context context, CommandQueue commandQueue,
-            Lazy<AssistManager> assistManagerLazy) {
-        mContext = context;
-        mCommandQueue = commandQueue;
-        mAssistManagerLazy = assistManagerLazy;
-    }
-
-    @Override
-    public void start() {
-        final IStatusBarService barService = IStatusBarService.Stub.asInterface(
-                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-        mCommandQueue.addCallback(this);
-        try {
-            barService.registerStatusBar(mCommandQueue);
-        } catch (RemoteException ex) {
-            // If the system process isn't there we're doomed anyway.
-        }
-    }
-
-    @Override
-    public void startAssist(Bundle args) {
-        mAssistManagerLazy.get().startAssist(args);
-    }
-
-    @Override
-    public void showPictureInPictureMenu() {
-        mContext.sendBroadcast(
-                new Intent(ACTION_SHOW_PIP_MENU).setPackage(mContext.getPackageName()),
-                SYSTEMUI_PERMISSION);
-    }
-
-    @Override
-    public void toggleKeyboardShortcutsMenu(int deviceId) {
-        KeyboardShortcuts.show(mContext, deviceId);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt
deleted file mode 100644
index b938c90..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt
+++ /dev/null
@@ -1,140 +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.
- */
-
-@file:JvmName("VpnStatusObserver")
-
-package com.android.systemui.statusbar.tv
-
-import android.app.Notification
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.content.Context
-import com.android.internal.messages.nano.SystemMessageProto
-import com.android.internal.net.VpnConfig
-import com.android.systemui.CoreStartable
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.policy.SecurityController
-import javax.inject.Inject
-
-/**
- * Observes if a vpn connection is active and displays a notification to the user
- */
-@SysUISingleton
-class VpnStatusObserver @Inject constructor(
-    private val context: Context,
-    private val securityController: SecurityController
-) : CoreStartable,
-        SecurityController.SecurityControllerCallback {
-
-    private var vpnConnected = false
-    private val notificationManager = NotificationManager.from(context)
-    private val notificationChannel = createNotificationChannel()
-    private val vpnConnectedNotificationBuilder = createVpnConnectedNotificationBuilder()
-    private val vpnDisconnectedNotification = createVpnDisconnectedNotification()
-
-    private val vpnIconId: Int
-        get() = if (securityController.isVpnBranded) {
-            R.drawable.stat_sys_branded_vpn
-        } else {
-            R.drawable.stat_sys_vpn_ic
-        }
-
-    private val vpnName: String?
-        get() = securityController.primaryVpnName ?: securityController.workProfileVpnName
-
-    override fun start() {
-        // register callback to vpn state changes
-        securityController.addCallback(this)
-    }
-
-    override fun onStateChanged() {
-        securityController.isVpnEnabled.let { newVpnConnected ->
-            if (vpnConnected != newVpnConnected) {
-                if (newVpnConnected) {
-                    notifyVpnConnected()
-                } else {
-                    notifyVpnDisconnected()
-                }
-                vpnConnected = newVpnConnected
-            }
-        }
-    }
-
-    private fun notifyVpnConnected() = notificationManager.notify(
-            NOTIFICATION_TAG,
-            SystemMessageProto.SystemMessage.NOTE_VPN_STATUS,
-            createVpnConnectedNotification()
-    )
-
-    private fun notifyVpnDisconnected() = notificationManager.run {
-        // remove existing connected notification
-        cancel(NOTIFICATION_TAG, SystemMessageProto.SystemMessage.NOTE_VPN_STATUS)
-        // show the disconnected notification only for a short while
-        notify(NOTIFICATION_TAG, SystemMessageProto.SystemMessage.NOTE_VPN_DISCONNECTED,
-                vpnDisconnectedNotification)
-    }
-
-    private fun createNotificationChannel() =
-            NotificationChannel(
-                    NOTIFICATION_CHANNEL_TV_VPN,
-                    NOTIFICATION_CHANNEL_TV_VPN,
-                    NotificationManager.IMPORTANCE_HIGH
-            ).also {
-                notificationManager.createNotificationChannel(it)
-            }
-
-    private fun createVpnConnectedNotification() =
-            vpnConnectedNotificationBuilder
-                    .apply {
-                        vpnName?.let {
-                            setContentText(
-                                    context.getString(
-                                            R.string.notification_disclosure_vpn_text, it
-                                    )
-                            )
-                        }
-                    }
-                    .build()
-
-    private fun createVpnConnectedNotificationBuilder() =
-            Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN)
-                    .setSmallIcon(vpnIconId)
-                    .setVisibility(Notification.VISIBILITY_PUBLIC)
-                    .setCategory(Notification.CATEGORY_SYSTEM)
-                    .extend(Notification.TvExtender())
-                    .setOngoing(true)
-                    .setContentTitle(context.getString(R.string.notification_vpn_connected))
-                    .setContentIntent(VpnConfig.getIntentForStatusPanel(context))
-
-    private fun createVpnDisconnectedNotification() =
-            Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN)
-                    .setSmallIcon(vpnIconId)
-                    .setVisibility(Notification.VISIBILITY_PUBLIC)
-                    .setCategory(Notification.CATEGORY_SYSTEM)
-                    .extend(Notification.TvExtender())
-                    .setTimeoutAfter(VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS)
-                    .setContentTitle(context.getString(R.string.notification_vpn_disconnected))
-                    .build()
-
-    companion object {
-        const val NOTIFICATION_CHANNEL_TV_VPN = "VPN Status"
-        val NOTIFICATION_TAG: String = VpnStatusObserver::class.java.simpleName
-
-        private const val TAG = "TvVpnNotification"
-        private const val VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS = 5_000L
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
deleted file mode 100644
index fd7c30f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.tv.notifications;
-
-import android.app.BroadcastOptions;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.systemui.R;
-
-/**
- * Adapter for the VerticalGridView of the TvNotificationsPanelView.
- */
-public class TvNotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
-    private static final String TAG = "TvNotificationAdapter";
-    private SparseArray<StatusBarNotification> mNotifications;
-
-    public TvNotificationAdapter() {
-        setHasStableIds(true);
-    }
-
-    @NonNull
-    @Override
-    public TvNotificationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.tv_notification_item,
-                parent, false);
-        return new TvNotificationViewHolder(view);
-    }
-
-    @Override
-    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
-        if (mNotifications == null) {
-            Log.e(TAG, "Could not bind view holder because the notification is missing");
-            return;
-        }
-
-        TvNotificationViewHolder holder = (TvNotificationViewHolder) viewHolder;
-        Notification notification = mNotifications.valueAt(position).getNotification();
-        holder.mTitle.setText(notification.extras.getString(Notification.EXTRA_TITLE));
-        holder.mDetails.setText(notification.extras.getString(Notification.EXTRA_TEXT));
-        holder.mPendingIntent = notification.contentIntent;
-    }
-
-    @Override
-    public int getItemCount() {
-        return mNotifications == null ? 0 : mNotifications.size();
-    }
-
-    @Override
-    public long getItemId(int position) {
-        // the item id is the notification id
-        return mNotifications.keyAt(position);
-    }
-
-    /**
-     * Updates the notifications and calls notifyDataSetChanged().
-     */
-    public void setNotifications(SparseArray<StatusBarNotification> notifications) {
-        this.mNotifications = notifications;
-        notifyDataSetChanged();
-    }
-
-    private static class TvNotificationViewHolder extends RecyclerView.ViewHolder implements
-            View.OnClickListener {
-        final TextView mTitle;
-        final TextView mDetails;
-        PendingIntent mPendingIntent;
-
-        protected TvNotificationViewHolder(View itemView) {
-            super(itemView);
-            mTitle = itemView.findViewById(R.id.tv_notification_title);
-            mDetails = itemView.findViewById(R.id.tv_notification_details);
-            itemView.setOnClickListener(this);
-        }
-
-        @Override
-        public void onClick(View v) {
-            try {
-                if (mPendingIntent != null) {
-                    BroadcastOptions options = BroadcastOptions.makeBasic();
-                    options.setInteractive(true);
-                    options.setPendingIntentBackgroundActivityStartMode(
-                            BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-                    mPendingIntent.send(options.toBundle());
-                }
-            } catch (PendingIntent.CanceledException e) {
-                Log.d(TAG, "Pending intent canceled for : " + mPendingIntent);
-            }
-        }
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java
deleted file mode 100644
index b92725b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.tv.notifications;
-
-import android.annotation.Nullable;
-import android.app.Notification;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.NotificationListener;
-
-import javax.inject.Inject;
-
-/**
- * Keeps track of the notifications on TV.
- */
-@SysUISingleton
-public class TvNotificationHandler implements CoreStartable,
-        NotificationListener.NotificationHandler {
-    private static final String TAG = "TvNotificationHandler";
-    private final NotificationListener mNotificationListener;
-    private final SparseArray<StatusBarNotification> mNotifications = new SparseArray<>();
-    @Nullable
-    private Listener mUpdateListener;
-
-    @Inject
-    public TvNotificationHandler(NotificationListener notificationListener) {
-        mNotificationListener = notificationListener;
-    }
-
-    public SparseArray<StatusBarNotification> getCurrentNotifications() {
-        return mNotifications;
-    }
-
-    public void setTvNotificationListener(Listener listener) {
-        mUpdateListener = listener;
-    }
-
-    @Override
-    public void start() {
-        mNotificationListener.addNotificationHandler(this);
-        mNotificationListener.registerAsSystemService();
-    }
-
-    @Override
-    public void onNotificationPosted(StatusBarNotification sbn,
-            NotificationListenerService.RankingMap rankingMap) {
-        if (!new Notification.TvExtender(sbn.getNotification()).isAvailableOnTv()) {
-            Log.v(TAG, "Notification not added because it isn't relevant for tv");
-            return;
-        }
-
-        mNotifications.put(sbn.getId(), sbn);
-        if (mUpdateListener != null) {
-            mUpdateListener.notificationsUpdated(mNotifications);
-        }
-        Log.d(TAG, "Notification added");
-    }
-
-    @Override
-    public void onNotificationRemoved(StatusBarNotification sbn,
-            NotificationListenerService.RankingMap rankingMap) {
-
-        if (mNotifications.contains(sbn.getId())) {
-            mNotifications.remove(sbn.getId());
-            Log.d(TAG, "Notification removed");
-
-            if (mUpdateListener != null) {
-                mUpdateListener.notificationsUpdated(mNotifications);
-            }
-        }
-    }
-
-    @Override
-    public void onNotificationRemoved(StatusBarNotification sbn,
-            NotificationListenerService.RankingMap rankingMap, int reason) {
-        onNotificationRemoved(sbn, rankingMap);
-    }
-
-    @Override
-    public void onNotificationRankingUpdate(NotificationListenerService.RankingMap rankingMap) {
-        // noop
-    }
-
-    @Override
-    public void onNotificationsInitialized() {
-        // noop
-    }
-
-    /**
-     * Get notified when the notifications are updated.
-     */
-    interface Listener {
-        void notificationsUpdated(SparseArray<StatusBarNotification> sbns);
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
deleted file mode 100644
index dbbd0b8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.tv.notifications;
-
-import android.Manifest;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.UserHandle;
-import android.util.Log;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.CommandQueue;
-
-import javax.inject.Inject;
-
-/**
- * Offers control methods for the notification panel handler on TV devices.
- */
-@SysUISingleton
-public class TvNotificationPanel implements CoreStartable, CommandQueue.Callbacks {
-    private static final String TAG = "TvNotificationPanel";
-    private final Context mContext;
-    private final CommandQueue mCommandQueue;
-    private final String mNotificationHandlerPackage;
-
-    @Inject
-    public TvNotificationPanel(Context context, CommandQueue commandQueue) {
-        mContext = context;
-        mCommandQueue = commandQueue;
-        mNotificationHandlerPackage = mContext.getResources().getString(
-                com.android.internal.R.string.config_notificationHandlerPackage);
-    }
-
-    @Override
-    public void start() {
-        mCommandQueue.addCallback(this);
-    }
-
-    @Override
-    public void togglePanel() {
-        if (!mNotificationHandlerPackage.isEmpty()) {
-            startNotificationHandlerActivity(
-                    new Intent(NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL));
-        } else {
-            openInternalNotificationPanel(
-                    NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL);
-        }
-    }
-
-    @Override
-    public void animateExpandNotificationsPanel() {
-        if (!mNotificationHandlerPackage.isEmpty()) {
-            startNotificationHandlerActivity(
-                    new Intent(NotificationManager.ACTION_OPEN_NOTIFICATION_HANDLER_PANEL));
-        } else {
-            openInternalNotificationPanel(
-                    NotificationManager.ACTION_OPEN_NOTIFICATION_HANDLER_PANEL);
-        }
-    }
-
-    @Override
-    public void animateCollapsePanels(int flags, boolean force) {
-        if (!mNotificationHandlerPackage.isEmpty()
-                && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
-            Intent closeNotificationIntent = new Intent(
-                    NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL);
-            closeNotificationIntent.setPackage(mNotificationHandlerPackage);
-            mContext.sendBroadcastAsUser(closeNotificationIntent, UserHandle.CURRENT);
-        } else {
-            openInternalNotificationPanel(
-                    NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL);
-        }
-    }
-
-    private void openInternalNotificationPanel(String action) {
-        Intent intent = new Intent(mContext, TvNotificationPanelActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
-        intent.setAction(action);
-        mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
-    }
-
-    /**
-     * Starts the activity intent if all of the following are true
-     * <ul>
-     * <li> the notification handler package is a system component </li>
-     * <li> the provided intent is handled by the notification handler package </li>
-     * <li> the notification handler requests the
-     * {@link android.Manifest.permission#STATUS_BAR_SERVICE} permission for the given intent</li>
-     * </ul>
-     *
-     * @param intent The intent for starting the desired activity
-     */
-    private void startNotificationHandlerActivity(Intent intent) {
-        intent.setPackage(mNotificationHandlerPackage);
-        PackageManager pm = mContext.getPackageManager();
-        ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_SYSTEM_ONLY);
-        if (ri != null && ri.activityInfo != null) {
-            if (ri.activityInfo.permission != null && ri.activityInfo.permission.equals(
-                    Manifest.permission.STATUS_BAR_SERVICE)) {
-                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
-                mContext.startActivityAsUser(intent, UserHandle.CURRENT);
-            } else {
-                Log.e(TAG,
-                        "Not launching notification handler activity: Notification handler does "
-                                + "not require the STATUS_BAR_SERVICE permission for intent "
-                                + intent.getAction());
-            }
-        } else {
-            Log.e(TAG,
-                    "Not launching notification handler activity: Could not resolve activityInfo "
-                            + "for intent "
-                            + intent.getAction());
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java
deleted file mode 100644
index b325b10..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.tv.notifications;
-
-import android.annotation.NonNull;
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.content.Intent;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.service.notification.StatusBarNotification;
-import android.util.SparseArray;
-import android.view.Gravity;
-import android.view.View;
-
-import androidx.leanback.widget.VerticalGridView;
-
-import com.android.systemui.R;
-
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-/**
- * This Activity shows a notification panel for tv. It is used if no other app (e.g. a launcher) can
- * be found to show the notifications.
- */
-public class TvNotificationPanelActivity extends Activity implements
-        TvNotificationHandler.Listener {
-    private final TvNotificationHandler mTvNotificationHandler;
-    private TvNotificationAdapter mTvNotificationAdapter;
-    private VerticalGridView mNotificationListView;
-    private View mNotificationPlaceholder;
-    private boolean mPanelAlreadyOpen = false;
-    private final Consumer<Boolean> mBlurConsumer = this::enableBlur;
-
-    @Inject
-    public TvNotificationPanelActivity(TvNotificationHandler tvNotificationHandler) {
-        super();
-        mTvNotificationHandler = tvNotificationHandler;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        if (maybeClosePanel(getIntent())) {
-            return;
-        }
-        mPanelAlreadyOpen = true;
-
-        setContentView(R.layout.tv_notification_panel);
-
-        mNotificationPlaceholder = findViewById(R.id.no_tv_notifications);
-        mTvNotificationAdapter = new TvNotificationAdapter();
-
-        mNotificationListView = findViewById(R.id.notifications_list);
-        mNotificationListView.setAdapter(mTvNotificationAdapter);
-        mNotificationListView.setColumnWidth(R.dimen.tv_notification_panel_width);
-
-        mTvNotificationHandler.setTvNotificationListener(this);
-        notificationsUpdated(mTvNotificationHandler.getCurrentNotifications());
-    }
-
-    @Override
-    public void notificationsUpdated(@NonNull SparseArray<StatusBarNotification> notificationList) {
-        mTvNotificationAdapter.setNotifications(notificationList);
-
-        boolean noNotifications = notificationList.size() == 0;
-        mNotificationListView.setVisibility(noNotifications ? View.GONE : View.VISIBLE);
-        mNotificationPlaceholder.setVisibility(noNotifications ? View.VISIBLE : View.GONE);
-    }
-
-    @Override
-    public void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        maybeClosePanel(intent);
-    }
-
-    /**
-     * Handles intents from onCreate and onNewIntent.
-     *
-     * @return true if the panel is being closed, false if it is being opened
-     */
-    private boolean maybeClosePanel(Intent intent) {
-        if (NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL.equals(intent.getAction())
-                || (mPanelAlreadyOpen
-                && NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL.equals(
-                intent.getAction()))) {
-            finish();
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        getWindow().setGravity(Gravity.END);
-        getWindowManager().addCrossWindowBlurEnabledListener(mBlurConsumer);
-    }
-
-    private void enableBlur(boolean enabled) {
-        if (enabled) {
-            int blurRadius = getResources().getDimensionPixelSize(
-                    R.dimen.tv_notification_blur_radius);
-            getWindow().setBackgroundDrawable(
-                    new ColorDrawable(getColor(R.color.tv_notification_blur_background_color)));
-            getWindow().setBackgroundBlurRadius(blurRadius);
-        } else {
-            getWindow().setBackgroundDrawable(
-                    new ColorDrawable(getColor(R.color.tv_notification_default_background_color)));
-            getWindow().setBackgroundBlurRadius(0);
-        }
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        getWindowManager().removeCrossWindowBlurEnabledListener(mBlurConsumer);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mTvNotificationHandler.setTvNotificationListener(null);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 7ed56e7..7aeba66 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -88,7 +88,7 @@
     private val chipbarAnimator: ChipbarAnimator,
     private val falsingManager: FalsingManager,
     private val falsingCollector: FalsingCollector,
-    private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler?,
+    private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler,
     private val viewUtil: ViewUtil,
     private val vibratorHelper: VibratorHelper,
     wakeLockBuilder: WakeLock.Builder,
@@ -289,10 +289,6 @@
     }
 
     private fun updateGestureListening() {
-        if (swipeChipbarAwayGestureHandler == null) {
-            return
-        }
-
         val currentDisplayInfo = getCurrentDisplayInfo()
         if (currentDisplayInfo != null && currentDisplayInfo.info.allowSwipeToDismiss) {
             swipeChipbarAwayGestureHandler.setViewFetcher { currentDisplayInfo.view }
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
index 9dbc4b3..80de523 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
@@ -19,10 +19,12 @@
 import android.content.Context
 import android.view.MotionEvent
 import android.view.View
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.statusbar.gesture.SwipeUpGestureHandler
 import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
 import com.android.systemui.util.boundsOnScreen
+import javax.inject.Inject
 
 /**
  * A class to detect when a user has swiped the chipbar away.
@@ -30,7 +32,10 @@
  * Effectively [SysUISingleton]. But, this shouldn't be created if the gesture isn't enabled. See
  * [TemporaryDisplayModule.provideSwipeChipbarAwayGestureHandler].
  */
-class SwipeChipbarAwayGestureHandler(
+@SysUISingleton
+class SwipeChipbarAwayGestureHandler
+@Inject
+constructor(
     context: Context,
     displayTracker: DisplayTracker,
     logger: SwipeUpGestureLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
index cae1308..2d05573 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
@@ -16,14 +16,9 @@
 
 package com.android.systemui.temporarydisplay.dagger
 
-import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
-import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
 import dagger.Module
 import dagger.Provides
 
@@ -36,20 +31,5 @@
         fun provideChipbarLogBuffer(factory: LogBufferFactory): LogBuffer {
             return factory.create("ChipbarLog", 40)
         }
-
-        @Provides
-        @SysUISingleton
-        fun provideSwipeChipbarAwayGestureHandler(
-            mediaTttFlags: MediaTttFlags,
-            context: Context,
-            displayTracker: DisplayTracker,
-            logger: SwipeUpGestureLogger,
-        ): SwipeChipbarAwayGestureHandler? {
-            return if (mediaTttFlags.isMediaTttDismissGestureEnabled()) {
-                SwipeChipbarAwayGestureHandler(context, displayTracker, logger)
-            } else {
-                null
-            }
-        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
deleted file mode 100644
index 573fbf7..0000000
--- a/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.tv
-
-import com.android.systemui.CoreStartable
-import com.android.systemui.SliceBroadcastRelayHandler
-import com.android.systemui.accessibility.WindowMagnification
-import com.android.systemui.dagger.qualifiers.PerUser
-import com.android.systemui.globalactions.GlobalActionsComponent
-import com.android.systemui.keyboard.KeyboardUI
-import com.android.systemui.media.RingtonePlayer
-import com.android.systemui.media.systemsounds.HomeSoundEffectController
-import com.android.systemui.power.PowerUI
-import com.android.systemui.privacy.television.TvPrivacyChipsController
-import com.android.systemui.shortcut.ShortcutKeyDispatcher
-import com.android.systemui.statusbar.notification.InstantAppNotifier
-import com.android.systemui.statusbar.tv.TvStatusBar
-import com.android.systemui.statusbar.tv.VpnStatusObserver
-import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler
-import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel
-import com.android.systemui.theme.ThemeOverlayController
-import com.android.systemui.toast.ToastUI
-import com.android.systemui.usb.StorageNotification
-import com.android.systemui.util.NotificationChannels
-import com.android.systemui.volume.VolumeUI
-import com.android.systemui.wmshell.WMShell
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-
-/**
- * Collection of {@link CoreStartable}s that should be run on TV.
- */
-@Module
-abstract class TVSystemUICoreStartableModule {
-    /** Inject into GlobalActionsComponent.  */
-    @Binds
-    @IntoMap
-    @ClassKey(GlobalActionsComponent::class)
-    abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable
-
-    /** Inject into HomeSoundEffectController.  */
-    @Binds
-    @IntoMap
-    @ClassKey(HomeSoundEffectController::class)
-    abstract fun bindHomeSoundEffectController(sysui: HomeSoundEffectController): CoreStartable
-
-    /** Inject into InstantAppNotifier.  */
-    @Binds
-    @IntoMap
-    @ClassKey(InstantAppNotifier::class)
-    abstract fun bindInstantAppNotifier(sysui: InstantAppNotifier): CoreStartable
-
-    /** Inject into KeyboardUI.  */
-    @Binds
-    @IntoMap
-    @ClassKey(KeyboardUI::class)
-    abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable
-
-    /** Inject into NotificationChannels.  */
-    @Binds
-    @IntoMap
-    @ClassKey(NotificationChannels::class)
-    @PerUser
-    abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
-
-    /** Inject into PowerUI.  */
-    @Binds
-    @IntoMap
-    @ClassKey(PowerUI::class)
-    abstract fun bindPowerUI(sysui: PowerUI): CoreStartable
-
-    /** Inject into RingtonePlayer.  */
-    @Binds
-    @IntoMap
-    @ClassKey(RingtonePlayer::class)
-    abstract fun bind(sysui: RingtonePlayer): CoreStartable
-
-    /** Inject into ShortcutKeyDispatcher.  */
-    @Binds
-    @IntoMap
-    @ClassKey(ShortcutKeyDispatcher::class)
-    abstract fun bindShortcutKeyDispatcher(sysui: ShortcutKeyDispatcher): CoreStartable
-
-    /** Inject into SliceBroadcastRelayHandler.  */
-    @Binds
-    @IntoMap
-    @ClassKey(SliceBroadcastRelayHandler::class)
-    abstract fun bindSliceBroadcastRelayHandler(sysui: SliceBroadcastRelayHandler): CoreStartable
-
-    /** Inject into StorageNotification.  */
-    @Binds
-    @IntoMap
-    @ClassKey(StorageNotification::class)
-    abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
-
-    /** Inject into ThemeOverlayController.  */
-    @Binds
-    @IntoMap
-    @ClassKey(ThemeOverlayController::class)
-    abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable
-
-    /** Inject into ToastUI.  */
-    @Binds
-    @IntoMap
-    @ClassKey(ToastUI::class)
-    abstract fun bindToastUI(service: ToastUI): CoreStartable
-
-    /** Inject into TvNotificationHandler.  */
-    @Binds
-    @IntoMap
-    @ClassKey(TvNotificationHandler::class)
-    abstract fun bindTvNotificationHandler(sysui: TvNotificationHandler): CoreStartable
-
-    /** Inject into TvNotificationPanel.  */
-    @Binds
-    @IntoMap
-    @ClassKey(TvNotificationPanel::class)
-    abstract fun bindTvNotificationPanel(sysui: TvNotificationPanel): CoreStartable
-
-    /** Inject into TvPrivacyChipsController.  */
-    @Binds
-    @IntoMap
-    @ClassKey(TvPrivacyChipsController::class)
-    abstract fun bindTvPrivacyChipsController(sysui: TvPrivacyChipsController): CoreStartable
-
-    /** Inject into TvStatusBar.  */
-    @Binds
-    @IntoMap
-    @ClassKey(TvStatusBar::class)
-    abstract fun bindTvStatusBar(sysui: TvStatusBar): CoreStartable
-
-    /** Inject into VolumeUI.  */
-    @Binds
-    @IntoMap
-    @ClassKey(VolumeUI::class)
-    abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable
-
-    /** Inject into VpnStatusObserver.  */
-    @Binds
-    @IntoMap
-    @ClassKey(VpnStatusObserver::class)
-    abstract fun bindVpnStatusObserver(sysui: VpnStatusObserver): CoreStartable
-
-    /** Inject into WindowMagnification.  */
-    @Binds
-    @IntoMap
-    @ClassKey(WindowMagnification::class)
-    abstract fun bindWindowMagnification(sysui: WindowMagnification): CoreStartable
-
-    /** Inject into WMShell.  */
-    @Binds
-    @IntoMap
-    @ClassKey(WMShell::class)
-    abstract fun bindWMShell(sysui: WMShell): CoreStartable
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvBottomSheetActivity.java b/packages/SystemUI/src/com/android/systemui/tv/TvBottomSheetActivity.java
deleted file mode 100644
index 90f2434..0000000
--- a/packages/SystemUI/src/com/android/systemui/tv/TvBottomSheetActivity.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.tv;
-
-import android.app.Activity;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.systemui.R;
-
-import java.util.Collections;
-import java.util.function.Consumer;
-
-/**
- * Generic bottom sheet with up to two icons in the beginning and two buttons.
- */
-public abstract class TvBottomSheetActivity extends Activity {
-
-    private static final String TAG = TvBottomSheetActivity.class.getSimpleName();
-    private Drawable mBackgroundWithBlur;
-    private Drawable mBackgroundWithoutBlur;
-
-    private final Consumer<Boolean> mBlurConsumer = this::onBlurChanged;
-
-    private void onBlurChanged(boolean enabled) {
-        Log.v(TAG, "blur enabled: " + enabled);
-        getWindow().setBackgroundDrawable(enabled ? mBackgroundWithBlur : mBackgroundWithoutBlur);
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.tv_bottom_sheet);
-
-        overridePendingTransition(R.anim.tv_bottom_sheet_enter, 0);
-
-        mBackgroundWithBlur = getResources()
-                .getDrawable(R.drawable.bottom_sheet_background_with_blur);
-        mBackgroundWithoutBlur = getResources().getDrawable(R.drawable.bottom_sheet_background);
-
-        DisplayMetrics metrics = getResources().getDisplayMetrics();
-        int screenWidth = metrics.widthPixels;
-        int screenHeight = metrics.heightPixels;
-        int marginPx = getResources().getDimensionPixelSize(R.dimen.bottom_sheet_margin);
-
-        WindowManager.LayoutParams windowParams = getWindow().getAttributes();
-        windowParams.width = screenWidth - marginPx * 2;
-        windowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
-        windowParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
-        windowParams.horizontalMargin = 0f;
-        windowParams.verticalMargin = (float) marginPx / screenHeight;
-        windowParams.format = PixelFormat.TRANSPARENT;
-        windowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
-        windowParams.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
-        windowParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-        getWindow().setAttributes(windowParams);
-        getWindow().setElevation(getWindow().getElevation() + 5);
-        getWindow().setBackgroundBlurRadius(getResources().getDimensionPixelSize(
-                R.dimen.bottom_sheet_background_blur_radius));
-
-        final View rootView = findViewById(R.id.bottom_sheet);
-        rootView.addOnLayoutChangeListener((view, l, t, r, b, oldL, oldT, oldR, oldB) -> {
-            rootView.setUnrestrictedPreferKeepClearRects(
-                    Collections.singletonList(new Rect(0, 0, r - l, b - t)));
-        });
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        getWindowManager().addCrossWindowBlurEnabledListener(mBlurConsumer);
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        getWindowManager().removeCrossWindowBlurEnabledListener(mBlurConsumer);
-        super.onDetachedFromWindow();
-    }
-
-    @Override
-    public void finish() {
-        super.finish();
-        overridePendingTransition(0, R.anim.tv_bottom_sheet_exit);
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
deleted file mode 100644
index 117cba7..0000000
--- a/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
+++ /dev/null
@@ -1,45 +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.tv;
-
-import com.android.systemui.dagger.GlobalModule;
-import com.android.systemui.dagger.GlobalRootComponent;
-
-import javax.inject.Singleton;
-
-import dagger.Component;
-
-/**
- * Root component for Dagger injection.
- */
-@Singleton
-@Component(modules = {GlobalModule.class})
-public interface TvGlobalRootComponent extends GlobalRootComponent {
-    /**
-     * Component Builder interface. This allows to bind Context instance in the component
-     */
-    @Component.Builder
-    interface Builder extends GlobalRootComponent.Builder {
-        TvGlobalRootComponent build();
-    }
-
-    @Override
-    TvWMComponent.Builder getWMComponentBuilder();
-
-    @Override
-    TvSysUIComponent.Builder getSysUIComponent();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
deleted file mode 100644
index 82589d3..0000000
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ /dev/null
@@ -1,61 +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.tv;
-
-import com.android.systemui.dagger.DefaultComponentBinder;
-import com.android.systemui.dagger.DependencyProvider;
-import com.android.systemui.dagger.SysUIComponent;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.SystemUIModule;
-import com.android.systemui.globalactions.ShutdownUiModule;
-import com.android.systemui.keyguard.dagger.KeyguardModule;
-import com.android.systemui.recents.RecentsModule;
-import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
-import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
-import com.android.systemui.statusbar.notification.row.NotificationRowModule;
-import com.android.systemui.wallpapers.dagger.NoopWallpaperModule;
-
-import dagger.Subcomponent;
-
-/**
- * Dagger Subcomponent for Core SysUI.
- */
-@SysUISingleton
-@Subcomponent(modules = {
-        CentralSurfacesDependenciesModule.class,
-        DefaultComponentBinder.class,
-        DependencyProvider.class,
-        KeyguardModule.class,
-        NoopWallpaperModule.class,
-        NotificationRowModule.class,
-        NotificationsModule.class,
-        RecentsModule.class,
-        ShutdownUiModule.class,
-        SystemUIModule.class,
-        TvSystemUIBinder.class,
-        TVSystemUICoreStartableModule.class,
-        TvSystemUIModule.class})
-public interface TvSysUIComponent extends SysUIComponent {
-
-    /**
-     * Builder for a SysUIComponent.
-     */
-    @Subcomponent.Builder
-    interface Builder extends SysUIComponent.Builder {
-        TvSysUIComponent build();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
deleted file mode 100644
index 23f37ec..0000000
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ /dev/null
@@ -1,28 +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.tv;
-
-import com.android.systemui.dagger.GlobalRootComponent;
-
-import dagger.Binds;
-import dagger.Module;
-
-@Module
-interface TvSystemUIBinder {
-    @Binds
-    GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIInitializer.java
deleted file mode 100644
index fabbb2c..0000000
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIInitializer.java
+++ /dev/null
@@ -1,37 +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.tv;
-
-import android.content.Context;
-
-import com.android.systemui.SystemUIInitializer;
-import com.android.systemui.dagger.GlobalRootComponent;
-
-/**
- * TV variant {@link SystemUIInitializer}, that substitutes default {@link GlobalRootComponent} for
- * {@link TvGlobalRootComponent}
- */
-public class TvSystemUIInitializer extends SystemUIInitializer {
-    public TvSystemUIInitializer(Context context) {
-        super(context);
-    }
-
-    @Override
-    protected GlobalRootComponent.Builder getGlobalRootComponentBuilder() {
-        return DaggerTvGlobalRootComponent.builder();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
deleted file mode 100644
index 8cf71a0..0000000
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ /dev/null
@@ -1,224 +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.tv;
-
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME;
-
-import android.content.Context;
-import android.hardware.SensorPrivacyManager;
-import android.os.Handler;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.keyguard.KeyguardViewController;
-import com.android.systemui.dagger.ReferenceSystemUIModule;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.dock.DockManagerImpl;
-import com.android.systemui.doze.DozeHost;
-import com.android.systemui.navigationbar.gestural.GestureModule;
-import com.android.systemui.plugins.qs.QSFactory;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.dagger.PowerModule;
-import com.android.systemui.privacy.MediaProjectionPrivacyItemMonitor;
-import com.android.systemui.privacy.PrivacyItemMonitor;
-import com.android.systemui.qs.dagger.QSModule;
-import com.android.systemui.qs.tileimpl.QSFactoryImpl;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsImplementation;
-import com.android.systemui.screenshot.ReferenceScreenshotModule;
-import com.android.systemui.settings.dagger.MultiUserUtilsModule;
-import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeControllerEmptyImpl;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.KeyboardShortcutsModule;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.events.StatusBarEventsModule;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.phone.DozeServiceHost;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.AospPolicyModule;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
-import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerImpl;
-import com.android.systemui.statusbar.policy.SensorPrivacyController;
-import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
-import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
-import com.android.systemui.volume.dagger.VolumeModule;
-
-import dagger.Binds;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.IntoSet;
-
-import javax.inject.Named;
-
-/**
- * A TV specific version of {@link ReferenceSystemUIModule}.
- *
- * Code here should be specific to the TV variant of SystemUI and will not be included in other
- * variants of SystemUI.
- */
-@Module(
-        includes = {
-                AospPolicyModule.class,
-                GestureModule.class,
-                MultiUserUtilsModule.class,
-                PowerModule.class,
-                QSModule.class,
-                ReferenceScreenshotModule.class,
-                StatusBarEventsModule.class,
-                VolumeModule.class,
-                KeyboardShortcutsModule.class
-        }
-)
-public abstract class TvSystemUIModule {
-
-    @SysUISingleton
-    @Provides
-    @Named(LEAK_REPORT_EMAIL_NAME)
-    static String provideLeakReportEmail() {
-        return "";
-    }
-
-    @Binds
-    abstract NotificationLockscreenUserManager bindNotificationLockscreenUserManager(
-            NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
-
-    @Provides
-    @SysUISingleton
-    static SensorPrivacyController provideSensorPrivacyController(
-            SensorPrivacyManager sensorPrivacyManager) {
-        SensorPrivacyController spC = new SensorPrivacyControllerImpl(sensorPrivacyManager);
-        spC.init();
-        return spC;
-    }
-
-    @Provides
-    @SysUISingleton
-    static IndividualSensorPrivacyController provideIndividualSensorPrivacyController(
-            SensorPrivacyManager sensorPrivacyManager) {
-        IndividualSensorPrivacyController spC = new IndividualSensorPrivacyControllerImpl(
-                sensorPrivacyManager);
-        spC.init();
-        return spC;
-    }
-
-    @Binds
-    @SysUISingleton
-    abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
-
-    @Binds
-    abstract DockManager bindDockManager(DockManagerImpl dockManager);
-
-    @Binds
-    abstract ShadeController provideShadeController(ShadeControllerEmptyImpl shadeController);
-
-    @SysUISingleton
-    @Provides
-    @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
-    static boolean provideAllowNotificationLongPress() {
-        return true;
-    }
-
-    @SysUISingleton
-    @Provides
-    static HeadsUpManagerPhone provideHeadsUpManagerPhone(
-            Context context,
-            HeadsUpManagerLogger headsUpManagerLogger,
-            StatusBarStateController statusBarStateController,
-            KeyguardBypassController bypassController,
-            GroupMembershipManager groupManager,
-            VisualStabilityProvider visualStabilityProvider,
-            ConfigurationController configurationController,
-            @Main Handler handler,
-            AccessibilityManagerWrapper accessibilityManagerWrapper,
-            UiEventLogger uiEventLogger,
-            ShadeExpansionStateManager shadeExpansionStateManager) {
-        return new HeadsUpManagerPhone(
-                context,
-                headsUpManagerLogger,
-                statusBarStateController,
-                bypassController,
-                groupManager,
-                visualStabilityProvider,
-                configurationController,
-                handler,
-                accessibilityManagerWrapper,
-                uiEventLogger,
-                shadeExpansionStateManager
-        );
-    }
-
-    @Binds
-    abstract HeadsUpManager bindHeadsUpManagerPhone(HeadsUpManagerPhone headsUpManagerPhone);
-
-    @Provides
-    @SysUISingleton
-    static Recents provideRecents(Context context, RecentsImplementation recentsImplementation,
-            CommandQueue commandQueue) {
-        return new Recents(context, recentsImplementation, commandQueue);
-    }
-
-    @SysUISingleton
-    @Provides
-    static DeviceProvisionedController providesDeviceProvisionedController(
-            DeviceProvisionedControllerImpl deviceProvisionedController) {
-        deviceProvisionedController.init();
-        return deviceProvisionedController;
-    }
-
-    @Binds
-    abstract KeyguardViewController bindKeyguardViewController(
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager);
-
-    @Binds
-    abstract NotificationShadeWindowController bindNotificationShadeController(
-            NotificationShadeWindowControllerImpl notificationShadeWindowController);
-
-    @Binds
-    abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
-
-    @Provides
-    @SysUISingleton
-    static TvNotificationHandler provideTvNotificationHandler(
-            NotificationListener notificationListener) {
-        return new TvNotificationHandler(notificationListener);
-    }
-
-    /**
-     * Binds {@link MediaProjectionPrivacyItemMonitor} into the set of {@link PrivacyItemMonitor}.
-     */
-    @Binds
-    @IntoSet
-    abstract PrivacyItemMonitor bindMediaProjectionPrivacyItemMonitor(
-            MediaProjectionPrivacyItemMonitor mediaProjectionPrivacyItemMonitor);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvWMComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvWMComponent.java
deleted file mode 100644
index 8370615..0000000
--- a/packages/SystemUI/src/com/android/systemui/tv/TvWMComponent.java
+++ /dev/null
@@ -1,40 +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.tv;
-
-import com.android.systemui.dagger.WMComponent;
-import com.android.wm.shell.dagger.WMSingleton;
-import com.android.wm.shell.dagger.TvWMShellModule;
-
-import dagger.Subcomponent;
-
-
-/**
- * Dagger Subcomponent for WindowManager.
- */
-@WMSingleton
-@Subcomponent(modules = {TvWMShellModule.class})
-public interface TvWMComponent extends WMComponent {
-
-    /**
-     * Builder for a SysUIComponent.
-     */
-    @Subcomponent.Builder
-    interface Builder extends WMComponent.Builder {
-        TvWMComponent build();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index cbe4020..098d51e 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.shade.ShadeFoldAnimator
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.ScreenOffAnimation
@@ -62,7 +63,7 @@
     private val keyguardInteractor: Lazy<KeyguardInteractor>,
 ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
 
-    private lateinit var centralSurfaces: CentralSurfaces
+    private lateinit var shadeViewController: ShadeViewController
 
     private var isFolded = false
     private var isFoldHandled = true
@@ -87,8 +88,12 @@
         )
     }
 
-    override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
-        this.centralSurfaces = centralSurfaces
+    override fun initialize(
+            centralSurfaces: CentralSurfaces,
+            shadeViewController: ShadeViewController,
+            lightRevealScrim: LightRevealScrim,
+    ) {
+        this.shadeViewController = shadeViewController
 
         deviceStateManager.registerCallback(mainExecutor, FoldListener())
         wakefulnessLifecycle.addObserver(this)
@@ -128,7 +133,7 @@
     }
 
     private fun getShadeFoldAnimator(): ShadeFoldAnimator =
-        centralSurfaces.shadeViewController.shadeFoldAnimator
+        shadeViewController.shadeFoldAnimator
 
     private fun setAnimationState(playing: Boolean) {
         shouldPlayAnimation = playing
diff --git a/packages/SystemUI/src/com/android/systemui/usb/tv/TvUsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/tv/TvUsbConfirmActivity.java
deleted file mode 100644
index b03bedc..0000000
--- a/packages/SystemUI/src/com/android/systemui/usb/tv/TvUsbConfirmActivity.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.usb.tv;
-
-import com.android.systemui.R;
-
-/**
- * Dialog shown to confirm the package to start when a USB device or accessory is attached and there
- * is only one package that claims to handle this USB device or accessory.
- */
-public class TvUsbConfirmActivity extends TvUsbDialogActivity {
-    private static final String TAG = TvUsbConfirmActivity.class.getSimpleName();
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        final int strId;
-        if (mDialogHelper.isUsbDevice()) {
-            boolean useRecordWarning = mDialogHelper.deviceHasAudioCapture()
-                    && !mDialogHelper.packageHasAudioRecordingPermission();
-            strId = useRecordWarning
-                    ? R.string.usb_device_confirm_prompt_warn
-                    : R.string.usb_device_confirm_prompt;
-        } else {
-            // UsbAccessory case
-            strId = R.string.usb_accessory_confirm_prompt;
-        }
-        CharSequence text = getString(strId, mDialogHelper.getAppName(),
-                mDialogHelper.getDeviceDescription());
-        initUI(mDialogHelper.getAppName(), text);
-    }
-
-    @Override
-    void onConfirm() {
-        mDialogHelper.grantUidAccessPermission();
-        mDialogHelper.confirmDialogStartActivity();
-        finish();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/tv/TvUsbDialogActivity.java b/packages/SystemUI/src/com/android/systemui/usb/tv/TvUsbDialogActivity.java
deleted file mode 100644
index 1c003ea..0000000
--- a/packages/SystemUI/src/com/android/systemui/usb/tv/TvUsbDialogActivity.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.usb.tv;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-import com.android.systemui.tv.TvBottomSheetActivity;
-import com.android.systemui.usb.UsbDialogHelper;
-
-abstract class TvUsbDialogActivity extends TvBottomSheetActivity implements View.OnClickListener {
-    private static final String TAG = TvUsbDialogActivity.class.getSimpleName();
-    UsbDialogHelper mDialogHelper;
-
-    @Override
-    public final void onCreate(Bundle b) {
-        super.onCreate(b);
-        getWindow().addPrivateFlags(
-                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-        try {
-            mDialogHelper = new UsbDialogHelper(getApplicationContext(), getIntent());
-        } catch (IllegalStateException e) {
-            Log.e(TAG, "unable to initialize", e);
-            finish();
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mDialogHelper.registerUsbDisconnectedReceiver(this);
-    }
-
-    @Override
-    protected void onPause() {
-        if (mDialogHelper != null) {
-            mDialogHelper.unregisterUsbDisconnectedReceiver(this);
-        }
-        super.onPause();
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (v.getId() == R.id.bottom_sheet_positive_button) {
-            onConfirm();
-        } else {
-            finish();
-        }
-    }
-
-    /**
-     * Called when the ok button is clicked.
-     */
-    abstract void onConfirm();
-
-    void initUI(CharSequence title, CharSequence text) {
-        TextView titleTextView = findViewById(R.id.bottom_sheet_title);
-        TextView contentTextView = findViewById(R.id.bottom_sheet_body);
-        ImageView icon = findViewById(R.id.bottom_sheet_icon);
-        ImageView secondIcon = findViewById(R.id.bottom_sheet_second_icon);
-        Button okButton = findViewById(R.id.bottom_sheet_positive_button);
-        Button cancelButton = findViewById(R.id.bottom_sheet_negative_button);
-
-        titleTextView.setText(title);
-        contentTextView.setText(text);
-        icon.setImageResource(com.android.internal.R.drawable.ic_usb_48dp);
-        secondIcon.setVisibility(View.GONE);
-        okButton.setText(android.R.string.ok);
-        okButton.setOnClickListener(this);
-
-        cancelButton.setText(android.R.string.cancel);
-        cancelButton.setOnClickListener(this);
-        cancelButton.requestFocus();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/tv/TvUsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/tv/TvUsbPermissionActivity.java
deleted file mode 100644
index 7b415b7..0000000
--- a/packages/SystemUI/src/com/android/systemui/usb/tv/TvUsbPermissionActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.usb.tv;
-
-import com.android.systemui.R;
-
-/**
- * Dialog shown when a package requests access to a USB device or accessory on TVs.
- */
-public class TvUsbPermissionActivity extends TvUsbDialogActivity {
-    private static final String TAG = TvUsbPermissionActivity.class.getSimpleName();
-
-    private boolean mPermissionGranted = false;
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        final int strId;
-        if (mDialogHelper.isUsbDevice()) {
-            boolean useRecordWarning = mDialogHelper.deviceHasAudioCapture()
-                    && !mDialogHelper.packageHasAudioRecordingPermission();
-            strId = useRecordWarning
-                    ? R.string.usb_device_permission_prompt_warn
-                    : R.string.usb_device_permission_prompt;
-        } else {
-            // UsbAccessory case
-            strId = R.string.usb_accessory_permission_prompt;
-        }
-        CharSequence text = getString(strId, mDialogHelper.getAppName(),
-                mDialogHelper.getDeviceDescription());
-        initUI(mDialogHelper.getAppName(), text);
-    }
-
-    @Override
-    protected void onPause() {
-        if (isFinishing()) {
-            mDialogHelper.sendPermissionDialogResponse(mPermissionGranted);
-        }
-        super.onPause();
-    }
-
-    @Override
-    void onConfirm() {
-        mDialogHelper.grantUidAccessPermission();
-        mPermissionGranted = true;
-        finish();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt b/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt
index 693c270..5582ced 100644
--- a/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt
@@ -23,7 +23,6 @@
  * changes.
  */
 interface UserAwareController {
-    @JvmDefault
     fun changeUser(newUser: UserHandle) {}
 
     val currentUserId: Int
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index a5365fb..2acd4b9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -621,13 +621,9 @@
     }
 
     private boolean isDismissableFromBubbles(NotificationEntry e) {
-        if (mNotifPipelineFlags.allowDismissOngoing()) {
-            // Bubbles are only accessible from the unlocked state,
-            // so we can calculate this from the Notification flags only.
-            return e.isDismissableForState(/*isLocked=*/ false);
-        } else {
-            return e.legacyIsDismissableRecursive();
-        }
+        // Bubbles are only accessible from the unlocked state,
+        // so we can calculate this from the Notification flags only.
+        return e.isDismissableForState(/*isLocked=*/ false);
     }
 
     private boolean shouldBubbleUp(NotificationEntry e) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index d447174..3abae6b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -37,6 +37,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
 import com.android.systemui.biometrics.SideFpsController
 import com.android.systemui.biometrics.SideFpsUiRequestSource
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
@@ -123,6 +124,7 @@
     @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
     @Mock private lateinit var audioManager: AudioManager
     @Mock private lateinit var userInteractor: UserInteractor
+    @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
 
     @Captor
     private lateinit var swipeListenerArgumentCaptor:
@@ -224,6 +226,7 @@
                 mock(),
                 { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
                 userInteractor,
+                faceAuthAccessibilityDelegate,
             ) {
                 sceneInteractor
             }
@@ -244,6 +247,11 @@
     }
 
     @Test
+    fun setAccessibilityDelegate() {
+        verify(view).accessibilityDelegate = eq(faceAuthAccessibilityDelegate)
+    }
+
+    @Test
     fun showSecurityScreen_canInflateAllModes() {
         val modes = SecurityMode.values()
         for (mode in modes) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 512e5dc..7114c22 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -27,6 +27,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ClockConfig;
 import com.android.systemui.plugins.ClockController;
@@ -62,6 +63,8 @@
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private InteractionJankMonitor mInteractionJankMonitor;
 
+    @Mock private DumpManager mDumpManager;
+
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
 
@@ -82,7 +85,8 @@
                 mScreenOffAnimationController,
                 mKeyguardLogger,
                 mFeatureFlags,
-                mInteractionJankMonitor) {
+                mInteractionJankMonitor,
+                mDumpManager) {
                     @Override
                     void setProperty(
                             AnimatableProperty property,
@@ -170,4 +174,12 @@
         verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(false);
         verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(true);
     }
+
+    @Test
+    public void correctlyDump() {
+        mController.onInit();
+        verify(mDumpManager).registerDumpable(mController);
+        mController.onDestroy();
+        verify(mDumpManager, times(1)).unregisterDumpable(KeyguardStatusViewController.TAG);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
index 025c88c..576f689 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
@@ -39,6 +39,7 @@
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
@@ -65,6 +66,8 @@
     @Mock
     private ShadeController mShadeController;
     @Mock
+    private ShadeViewController mShadeViewController;
+    @Mock
     private Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     @Mock
     private Optional<Recents> mRecentsOptional;
@@ -82,7 +85,8 @@
         mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
         mContext.addMockSystemService(InputManager.class, mInputManager);
         mSystemActions = new SystemActions(mContext, mUserTracker, mNotificationShadeController,
-                mShadeController, mCentralSurfacesOptionalLazy, mRecentsOptional, mDisplayTracker);
+                mShadeController, () -> mShadeViewController, mCentralSurfacesOptionalLazy,
+                mRecentsOptional, mDisplayTracker);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
index d5e6881..7b99314 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -15,11 +15,13 @@
  */
 package com.android.systemui.accessibility.fontscaling
 
+import android.content.res.Configuration
 import android.os.Handler
 import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.ViewGroup
+import android.widget.Button
 import android.widget.SeekBar
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
@@ -61,6 +63,7 @@
     private lateinit var secureSettings: SecureSettings
     private lateinit var systemClock: FakeSystemClock
     private lateinit var backgroundDelayableExecutor: FakeExecutor
+    private lateinit var testableLooper: TestableLooper
     private val fontSizeValueArray: Array<String> =
         mContext
             .getResources()
@@ -73,7 +76,8 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        val mainHandler = Handler(TestableLooper.get(this).getLooper())
+        testableLooper = TestableLooper.get(this)
+        val mainHandler = Handler(testableLooper.looper)
         systemSettings = FakeSettings()
         // Guarantee that the systemSettings always starts with the default font scale.
         systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
@@ -286,4 +290,26 @@
         verify(fontScalingDialog).createTextPreview(/* index= */ 0)
         fontScalingDialog.dismiss()
     }
+
+    @Test
+    fun changeFontSize_buttonIsDisabledBeforeFontSizeChangeFinishes() {
+        fontScalingDialog.show()
+
+        val iconEndFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_end_frame)!!
+        val doneButton: Button = fontScalingDialog.findViewById(com.android.internal.R.id.button1)!!
+
+        iconEndFrame.performClick()
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
+        // Verify that the button is disabled before receiving onConfigurationChanged
+        assertThat(doneButton.isEnabled).isFalse()
+
+        val config = Configuration()
+        config.fontScale = 1.15f
+        fontScalingDialog.onConfigurationChanged(config)
+        testableLooper.processAllMessages()
+        assertThat(doneButton.isEnabled).isTrue()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
new file mode 100644
index 0000000..0056970
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.authentication.data.repository
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class AuthenticationRepositoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+
+    private val testUtils = SceneTestUtils(this)
+    private val testScope = testUtils.testScope
+    private val userRepository = FakeUserRepository()
+
+    private lateinit var underTest: AuthenticationRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        userRepository.setUserInfos(USER_INFOS)
+        runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
+
+        underTest =
+            AuthenticationRepositoryImpl(
+                applicationScope = testScope.backgroundScope,
+                getSecurityMode = { KeyguardSecurityModel.SecurityMode.PIN },
+                backgroundDispatcher = testUtils.testDispatcher,
+                userRepository = userRepository,
+                keyguardRepository = testUtils.keyguardRepository,
+                lockPatternUtils = lockPatternUtils,
+            )
+    }
+
+    @Test
+    fun isAutoConfirmEnabled() =
+        testScope.runTest {
+            whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[0].id)).thenReturn(true)
+            whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[1].id)).thenReturn(false)
+
+            val values by collectValues(underTest.isAutoConfirmEnabled)
+            assertThat(values.first()).isFalse()
+            assertThat(values.last()).isTrue()
+
+            userRepository.setSelectedUserInfo(USER_INFOS[1])
+            assertThat(values.last()).isFalse()
+        }
+
+    @Test
+    fun isPatternVisible() =
+        testScope.runTest {
+            whenever(lockPatternUtils.isVisiblePatternEnabled(USER_INFOS[0].id)).thenReturn(false)
+            whenever(lockPatternUtils.isVisiblePatternEnabled(USER_INFOS[1].id)).thenReturn(true)
+
+            val values by collectValues(underTest.isPatternVisible)
+            assertThat(values.first()).isTrue()
+            assertThat(values.last()).isFalse()
+
+            userRepository.setSelectedUserInfo(USER_INFOS[1])
+            assertThat(values.last()).isTrue()
+        }
+
+    companion object {
+        private val USER_INFOS =
+            listOf(
+                UserInfo(
+                    /* id= */ 100,
+                    /* name= */ "First user",
+                    /* flags= */ 0,
+                ),
+                UserInfo(
+                    /* id= */ 101,
+                    /* name= */ "Second user",
+                    /* flags= */ 0,
+                ),
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
new file mode 100644
index 0000000..ec17794
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.biometrics
+
+import android.testing.TestableLooper
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.FaceAuthApiRequestReason
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class FaceAuthAccessibilityDelegateTest : SysuiTestCase() {
+
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var hostView: View
+    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    private lateinit var underTest: FaceAuthAccessibilityDelegate
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            FaceAuthAccessibilityDelegate(
+                context.resources,
+                keyguardUpdateMonitor,
+                faceAuthInteractor,
+            )
+    }
+
+    @Test
+    fun shouldListenForFaceTrue_onInitializeAccessibilityNodeInfo_clickActionAdded() {
+        whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+
+        // WHEN node is initialized
+        val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
+        underTest.onInitializeAccessibilityNodeInfo(hostView, mockedNodeInfo)
+
+        // THEN a11y action is added
+        val argumentCaptor = argumentCaptor<AccessibilityNodeInfo.AccessibilityAction>()
+        verify(mockedNodeInfo).addAction(argumentCaptor.capture())
+
+        // AND the a11y action is a click action
+        assertEquals(
+            AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+            argumentCaptor.value.id
+        )
+    }
+
+    @Test
+    fun shouldListenForFaceFalse_onInitializeAccessibilityNodeInfo_clickActionNotAdded() {
+        whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
+
+        // WHEN node is initialized
+        val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
+        underTest.onInitializeAccessibilityNodeInfo(hostView, mockedNodeInfo)
+
+        // THEN a11y action is NOT added
+        verify(mockedNodeInfo, never())
+            .addAction(any(AccessibilityNodeInfo.AccessibilityAction::class.java))
+    }
+
+    @Test
+    fun performAccessibilityAction_actionClick_retriesFaceAuth() {
+        whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+
+        // WHEN click action is performed
+        underTest.performAccessibilityAction(
+            hostView,
+            AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+            null
+        )
+
+        // THEN retry face auth
+        verify(keyguardUpdateMonitor)
+            .requestFaceAuth(eq(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION))
+        verify(faceAuthInteractor).onAccessibilityAction()
+    }
+}
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 ecc776b..3169b09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -44,6 +44,9 @@
 import android.view.ViewPropertyAnimator
 import android.view.WindowInsets
 import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
 import android.view.WindowMetrics
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -53,14 +56,9 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.SysuiTestableContext
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dump.DumpManager
@@ -101,7 +99,7 @@
 @SmallTest
 @RoboPilotTest
 @RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@TestableLooper.RunWithLooper
 class SideFpsControllerTest : SysuiTestCase() {
 
     @JvmField @Rule var rule = MockitoJUnit.rule()
@@ -120,8 +118,6 @@
     private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     private lateinit var displayStateInteractor: DisplayStateInteractor
-    private lateinit var sideFpsOverlayViewModel: SideFpsOverlayViewModel
-    private val fingerprintRepository = FakeFingerprintPropertyRepository()
 
     private val executor = FakeExecutor(FakeSystemClock())
     private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
@@ -163,15 +159,6 @@
                 executor,
                 rearDisplayStateRepository
             )
-        sideFpsOverlayViewModel =
-            SideFpsOverlayViewModel(context, SideFpsOverlayInteractorImpl(fingerprintRepository))
-
-        fingerprintRepository.setProperties(
-            sensorId = 1,
-            strength = SensorStrength.STRONG,
-            sensorType = FingerprintSensorType.REAR,
-            sensorLocations = mapOf("" to SensorLocationInternal("", 2500, 0, 0))
-        )
 
         context.addMockSystemService(DisplayManager::class.java, displayManager)
         context.addMockSystemService(WindowManager::class.java, windowManager)
@@ -278,7 +265,6 @@
                 executor,
                 handler,
                 alternateBouncerInteractor,
-                { sideFpsOverlayViewModel },
                 TestCoroutineScope(),
                 dumpManager
             )
@@ -697,6 +683,106 @@
         verify(windowManager).removeView(any())
     }
 
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
+     * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
+     * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
+     * in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForXAlignedSensor_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_0 }
+        ) {
+            sideFpsController.overlayOffsets = sensorLocation
+
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
+        }
+
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
+     * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
+     * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
+     * correctly, tests for indicator placement in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_270 }
+        ) {
+            sideFpsController.overlayOffsets = sensorLocation
+
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
+        }
+
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
+     * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
+     * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
+     * in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForYAlignedSensor_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_0 }
+        ) {
+            sideFpsController.overlayOffsets = sensorLocation
+
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
+        }
+
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
+     * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
+     * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
+     * correctly, tests for indicator placement in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_270 }
+        ) {
+            sideFpsController.overlayOffsets = sensorLocation
+
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
+        }
+
     @Test
     fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
         // By default all those tests assume the side fps sensor is available.
@@ -709,6 +795,51 @@
 
         assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
     }
+
+    @Test
+    fun testLayoutParams_isKeyguardDialogType() =
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+            val lpType = overlayViewParamsCaptor.value.type
+
+            assertThat((lpType and TYPE_KEYGUARD_DIALOG) != 0).isTrue()
+        }
+
+    @Test
+    fun testLayoutParams_hasNoMoveAnimationWindowFlag() =
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+            val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+            assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
+        }
+
+    @Test
+    fun testLayoutParams_hasTrustedOverlayWindowFlag() =
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+            val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+            assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
+        }
 }
 
 private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index ea25615..239e317 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -73,15 +73,10 @@
     @Test
     fun initializeProperties() =
         testScope.runTest {
-            val sensorId by collectLastValue(repository.sensorId)
-            val strength by collectLastValue(repository.strength)
-            val sensorType by collectLastValue(repository.sensorType)
-            val sensorLocations by collectLastValue(repository.sensorLocations)
+            val isInitialized = collectLastValue(repository.isInitialized)
 
-            // Assert default properties.
-            assertThat(sensorId).isEqualTo(-1)
-            assertThat(strength).isEqualTo(SensorStrength.CONVENIENCE)
-            assertThat(sensorType).isEqualTo(FingerprintSensorType.UNKNOWN)
+            assertDefaultProperties()
+            assertThat(isInitialized()).isFalse()
 
             val fingerprintProps =
                 listOf(
@@ -120,24 +115,31 @@
 
             fingerprintAuthenticatorsCaptor.value.onAllAuthenticatorsRegistered(fingerprintProps)
 
-            assertThat(sensorId).isEqualTo(1)
-            assertThat(strength).isEqualTo(SensorStrength.STRONG)
-            assertThat(sensorType).isEqualTo(FingerprintSensorType.REAR)
+            assertThat(repository.sensorId.value).isEqualTo(1)
+            assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG)
+            assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR)
 
-            assertThat(sensorLocations?.size).isEqualTo(2)
-            assertThat(sensorLocations).containsKey("display_id_1")
-            with(sensorLocations?.get("display_id_1")!!) {
+            assertThat(repository.sensorLocations.value.size).isEqualTo(2)
+            assertThat(repository.sensorLocations.value).containsKey("display_id_1")
+            with(repository.sensorLocations.value["display_id_1"]!!) {
                 assertThat(displayId).isEqualTo("display_id_1")
                 assertThat(sensorLocationX).isEqualTo(100)
                 assertThat(sensorLocationY).isEqualTo(300)
                 assertThat(sensorRadius).isEqualTo(20)
             }
-            assertThat(sensorLocations).containsKey("")
-            with(sensorLocations?.get("")!!) {
+            assertThat(repository.sensorLocations.value).containsKey("")
+            with(repository.sensorLocations.value[""]!!) {
                 assertThat(displayId).isEqualTo("")
                 assertThat(sensorLocationX).isEqualTo(540)
                 assertThat(sensorLocationY).isEqualTo(1636)
                 assertThat(sensorRadius).isEqualTo(130)
             }
+            assertThat(isInitialized()).isTrue()
         }
+
+    private fun assertDefaultProperties() {
+        assertThat(repository.sensorId.value).isEqualTo(-1)
+        assertThat(repository.strength.value).isEqualTo(SensorStrength.CONVENIENCE)
+        assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.UNKNOWN)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
index 896f9b11..fd96cf4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -52,9 +51,8 @@
     }
 
     @Test
-    fun testGetOverlayoffsets() =
+    fun testGetOverlayOffsets() =
         testScope.runTest {
-            // Arrange.
             fingerprintRepository.setProperties(
                 sensorId = 1,
                 strength = SensorStrength.STRONG,
@@ -78,33 +76,16 @@
                     )
             )
 
-            // Act.
-            val offsets by collectLastValue(interactor.overlayOffsets)
-            val displayId by collectLastValue(interactor.displayId)
+            var offsets = interactor.getOverlayOffsets("display_id_1")
+            assertThat(offsets.displayId).isEqualTo("display_id_1")
+            assertThat(offsets.sensorLocationX).isEqualTo(100)
+            assertThat(offsets.sensorLocationY).isEqualTo(300)
+            assertThat(offsets.sensorRadius).isEqualTo(20)
 
-            // Assert offsets of empty displayId.
-            assertThat(displayId).isEqualTo("")
-            assertThat(offsets?.displayId).isEqualTo("")
-            assertThat(offsets?.sensorLocationX).isEqualTo(540)
-            assertThat(offsets?.sensorLocationY).isEqualTo(1636)
-            assertThat(offsets?.sensorRadius).isEqualTo(130)
-
-            // Offsets should be updated correctly.
-            interactor.changeDisplay("display_id_1")
-            assertThat(displayId).isEqualTo("display_id_1")
-            assertThat(offsets?.displayId).isEqualTo("display_id_1")
-            assertThat(offsets?.sensorLocationX).isEqualTo(100)
-            assertThat(offsets?.sensorLocationY).isEqualTo(300)
-            assertThat(offsets?.sensorRadius).isEqualTo(20)
-
-            // Should return default offset when the displayId is invalid.
-            interactor.changeDisplay("invalid_display_id")
-            assertThat(displayId).isEqualTo("invalid_display_id")
-            assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
-            assertThat(offsets?.sensorLocationX)
-                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
-            assertThat(offsets?.sensorLocationY)
-                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
-            assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
+            offsets = interactor.getOverlayOffsets("invalid_display_id")
+            assertThat(offsets.displayId).isEqualTo("")
+            assertThat(offsets.sensorLocationX).isEqualTo(540)
+            assertThat(offsets.sensorLocationY).isEqualTo(1636)
+            assertThat(offsets.sensorRadius).isEqualTo(130)
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
new file mode 100644
index 0000000..f9b590f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
@@ -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.systemui.biometrics.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.shared.model.BiometricModality
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PromptHistoryImplTest : SysuiTestCase() {
+
+    private lateinit var history: PromptHistoryImpl
+
+    @Before
+    fun setup() {
+        history = PromptHistoryImpl()
+    }
+
+    @Test
+    fun empty() {
+        assertThat(history.faceFailed).isFalse()
+        assertThat(history.fingerprintFailed).isFalse()
+    }
+
+    @Test
+    fun faceFailed() =
+        repeat(2) {
+            history.failure(BiometricModality.None)
+            history.failure(BiometricModality.Face)
+
+            assertThat(history.faceFailed).isTrue()
+            assertThat(history.fingerprintFailed).isFalse()
+        }
+
+    @Test
+    fun fingerprintFailed() =
+        repeat(2) {
+            history.failure(BiometricModality.None)
+            history.failure(BiometricModality.Fingerprint)
+
+            assertThat(history.faceFailed).isFalse()
+            assertThat(history.fingerprintFailed).isTrue()
+        }
+
+    @Test
+    fun coexFailed() =
+        repeat(2) {
+            history.failure(BiometricModality.Face)
+            history.failure(BiometricModality.Fingerprint)
+
+            assertThat(history.faceFailed).isTrue()
+            assertThat(history.fingerprintFailed).isTrue()
+
+            history.failure(BiometricModality.None)
+        }
+}
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 91140a9..40b1f20 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
@@ -335,7 +335,7 @@
                     error,
                     messageAfterError = "or me",
                     authenticateAfterError = false,
-                    suppressIf = { _ -> true },
+                    suppressIf = { _, _ -> true },
                 )
             }
         }
@@ -364,7 +364,7 @@
                     error,
                     messageAfterError = "$error $afterSuffix",
                     authenticateAfterError = false,
-                    suppressIf = { currentMessage -> suppress && currentMessage.isError },
+                    suppressIf = { currentMessage, _ -> suppress && currentMessage.isError },
                 )
             }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
deleted file mode 100644
index a859321..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * 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.biometrics.ui.viewmodel
-
-import android.graphics.Rect
-import android.hardware.biometrics.SensorLocationInternal
-import android.hardware.display.DisplayManagerGlobal
-import android.view.Display
-import android.view.DisplayAdjustments
-import android.view.DisplayInfo
-import android.view.Surface
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.SysuiTestableContext
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers
-import org.mockito.Mockito
-import org.mockito.junit.MockitoJUnit
-
-private const val DISPLAY_ID = 2
-
-@SmallTest
-@RunWith(JUnit4::class)
-class SideFpsOverlayViewModelTest : SysuiTestCase() {
-
-    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-    private var testScope: TestScope = TestScope(StandardTestDispatcher())
-
-    private val fingerprintRepository = FakeFingerprintPropertyRepository()
-    private lateinit var interactor: SideFpsOverlayInteractor
-    private lateinit var viewModel: SideFpsOverlayViewModel
-
-    enum class DeviceConfig {
-        X_ALIGNED,
-        Y_ALIGNED,
-    }
-
-    private lateinit var deviceConfig: DeviceConfig
-    private lateinit var indicatorBounds: Rect
-    private lateinit var displayBounds: Rect
-    private lateinit var sensorLocation: SensorLocationInternal
-    private var displayWidth: Int = 0
-    private var displayHeight: Int = 0
-    private var boundsWidth: Int = 0
-    private var boundsHeight: Int = 0
-
-    @Before
-    fun setup() {
-        interactor = SideFpsOverlayInteractorImpl(fingerprintRepository)
-
-        fingerprintRepository.setProperties(
-            sensorId = 1,
-            strength = SensorStrength.STRONG,
-            sensorType = FingerprintSensorType.REAR,
-            sensorLocations =
-                mapOf(
-                    "" to
-                        SensorLocationInternal(
-                            "" /* displayId */,
-                            540 /* sensorLocationX */,
-                            1636 /* sensorLocationY */,
-                            130 /* sensorRadius */
-                        ),
-                    "display_id_1" to
-                        SensorLocationInternal(
-                            "display_id_1" /* displayId */,
-                            100 /* sensorLocationX */,
-                            300 /* sensorLocationY */,
-                            20 /* sensorRadius */
-                        )
-                )
-        )
-    }
-
-    @Test
-    fun testOverlayOffsets() =
-        testScope.runTest {
-            viewModel = SideFpsOverlayViewModel(mContext, interactor)
-
-            val interactorOffsets by collectLastValue(interactor.overlayOffsets)
-            val viewModelOffsets by collectLastValue(viewModel.overlayOffsets)
-
-            assertThat(viewModelOffsets).isEqualTo(interactorOffsets)
-        }
-
-    private fun testWithDisplay(
-        deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
-        isReverseDefaultRotation: Boolean = false,
-        initInfo: DisplayInfo.() -> Unit = {},
-        block: () -> Unit
-    ) {
-        this.deviceConfig = deviceConfig
-
-        when (deviceConfig) {
-            DeviceConfig.X_ALIGNED -> {
-                displayWidth = 3000
-                displayHeight = 1500
-                sensorLocation = SensorLocationInternal("", 2500, 0, 0)
-                boundsWidth = 200
-                boundsHeight = 100
-            }
-            DeviceConfig.Y_ALIGNED -> {
-                displayWidth = 2500
-                displayHeight = 2000
-                sensorLocation = SensorLocationInternal("", 0, 300, 0)
-                boundsWidth = 100
-                boundsHeight = 200
-            }
-        }
-
-        indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
-        displayBounds = Rect(0, 0, displayWidth, displayHeight)
-
-        val displayInfo = DisplayInfo()
-        displayInfo.initInfo()
-
-        val dmGlobal = Mockito.mock(DisplayManagerGlobal::class.java)
-        val display =
-            Display(
-                dmGlobal,
-                DISPLAY_ID,
-                displayInfo,
-                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
-            )
-
-        whenever(dmGlobal.getDisplayInfo(ArgumentMatchers.eq(DISPLAY_ID))).thenReturn(displayInfo)
-
-        val sideFpsOverlayViewModelContext =
-            context.createDisplayContext(display) as SysuiTestableContext
-        sideFpsOverlayViewModelContext.orCreateTestableResources.addOverride(
-            com.android.internal.R.bool.config_reverseDefaultRotation,
-            isReverseDefaultRotation
-        )
-        viewModel = SideFpsOverlayViewModel(sideFpsOverlayViewModelContext, interactor)
-
-        block()
-    }
-
-    /**
-     * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
-     * ROTATION_0, and uses RotateUtils.rotateBounds to map to the correct indicator location given
-     * the device rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator
-     * placement in other rotations have been omitted.
-     */
-    @Test
-    fun verifiesIndicatorPlacementForXAlignedSensor_0() =
-        testScope.runTest {
-            testWithDisplay(
-                deviceConfig = DeviceConfig.X_ALIGNED,
-                isReverseDefaultRotation = false,
-                { rotation = Surface.ROTATION_0 }
-            ) {
-                viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
-
-                val displayInfo: DisplayInfo = DisplayInfo()
-                context.display!!.getDisplayInfo(displayInfo)
-                assertThat(displayInfo.rotation).isEqualTo(Surface.ROTATION_0)
-
-                assertThat(viewModel.sensorBounds.value).isNotNull()
-                assertThat(viewModel.sensorBounds.value.left)
-                    .isEqualTo(sensorLocation.sensorLocationX)
-                assertThat(viewModel.sensorBounds.value.top).isEqualTo(0)
-            }
-        }
-
-    /**
-     * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
-     * ROTATION_270 in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the
-     * correct indicator location given the device rotation. Assuming RotationUtils.rotateBounds
-     * works correctly, tests for indicator placement in other rotations have been omitted.
-     */
-    @Test
-    fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
-        testScope.runTest {
-            testWithDisplay(
-                deviceConfig = DeviceConfig.X_ALIGNED,
-                isReverseDefaultRotation = true,
-                { rotation = Surface.ROTATION_270 }
-            ) {
-                viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
-
-                assertThat(viewModel.sensorBounds.value).isNotNull()
-                assertThat(viewModel.sensorBounds.value.left)
-                    .isEqualTo(sensorLocation.sensorLocationX)
-                assertThat(viewModel.sensorBounds.value.top).isEqualTo(0)
-            }
-        }
-
-    /**
-     * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
-     * ROTATION_0, and uses RotateUtils.rotateBounds to map to the correct indicator location given
-     * the device rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator
-     * placement in other rotations have been omitted.
-     */
-    @Test
-    fun verifiesIndicatorPlacementForYAlignedSensor_0() =
-        testScope.runTest {
-            testWithDisplay(
-                deviceConfig = DeviceConfig.Y_ALIGNED,
-                isReverseDefaultRotation = false,
-                { rotation = Surface.ROTATION_0 }
-            ) {
-                viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
-
-                assertThat(viewModel.sensorBounds.value).isNotNull()
-                assertThat(viewModel.sensorBounds.value.left).isEqualTo(displayWidth - boundsWidth)
-                assertThat(viewModel.sensorBounds.value.top)
-                    .isEqualTo(sensorLocation.sensorLocationY)
-            }
-        }
-
-    /**
-     * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
-     * ROTATION_270 in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the
-     * correct indicator location given the device rotation. Assuming RotationUtils.rotateBounds
-     * works correctly, tests for indicator placement in other rotations have been omitted.
-     */
-    @Test
-    fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
-        testScope.runTest {
-            testWithDisplay(
-                deviceConfig = DeviceConfig.Y_ALIGNED,
-                isReverseDefaultRotation = true,
-                { rotation = Surface.ROTATION_270 }
-            ) {
-                viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
-
-                assertThat(viewModel.sensorBounds.value).isNotNull()
-                assertThat(viewModel.sensorBounds.value.left).isEqualTo(displayWidth - boundsWidth)
-                assertThat(viewModel.sensorBounds.value.top)
-                    .isEqualTo(sensorLocation.sensorLocationY)
-            }
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 45d1af7..8edc6cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -29,6 +29,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -77,7 +78,7 @@
             val currentScene by
                 collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val entries by collectLastValue(underTest.pinEntries)
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(
                 SceneTestUtils.CONTAINER_1,
@@ -88,7 +89,7 @@
             underTest.onShown()
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN)
-            assertThat(entries).hasSize(0)
+            assertThat(pin).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
@@ -98,7 +99,7 @@
             val currentScene by
                 collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val entries by collectLastValue(underTest.pinEntries)
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(
@@ -112,8 +113,7 @@
             underTest.onPinButtonClicked(1)
 
             assertThat(message?.text).isEmpty()
-            assertThat(entries).hasSize(1)
-            assertThat(entries?.map { it.input }).containsExactly(1)
+            assertThat(pin).containsExactly(1)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
@@ -123,7 +123,7 @@
             val currentScene by
                 collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val entries by collectLastValue(underTest.pinEntries)
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(
@@ -134,12 +134,12 @@
             underTest.onShown()
             runCurrent()
             underTest.onPinButtonClicked(1)
-            assertThat(entries).hasSize(1)
+            assertThat(pin).hasSize(1)
 
             underTest.onBackspaceButtonClicked()
 
             assertThat(message?.text).isEmpty()
-            assertThat(entries).hasSize(0)
+            assertThat(pin).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
@@ -148,7 +148,7 @@
         testScope.runTest {
             val currentScene by
                 collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
-            val entries by collectLastValue(underTest.pinEntries)
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(
@@ -166,9 +166,7 @@
             underTest.onPinButtonClicked(4)
             underTest.onPinButtonClicked(5)
 
-            assertThat(entries).hasSize(3)
-            assertThat(entries?.map { it.input }).containsExactly(1, 4, 5).inOrder()
-            assertThat(entries?.map { it.sequenceNumber }).isInStrictOrder()
+            assertThat(pin).containsExactly(1, 4, 5).inOrder()
         }
 
     @Test
@@ -177,7 +175,7 @@
             val currentScene by
                 collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val entries by collectLastValue(underTest.pinEntries)
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(
@@ -195,7 +193,7 @@
             underTest.onBackspaceButtonLongPressed()
 
             assertThat(message?.text).isEmpty()
-            assertThat(entries).hasSize(0)
+            assertThat(pin).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
@@ -227,7 +225,7 @@
             val currentScene by
                 collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val entries by collectLastValue(underTest.pinEntries)
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(
@@ -244,7 +242,7 @@
 
             underTest.onAuthenticateButtonClicked()
 
-            assertThat(entries).hasSize(0)
+            assertThat(pin).isEmpty()
             assertThat(message?.text).isEqualTo(WRONG_PIN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
@@ -255,7 +253,7 @@
             val currentScene by
                 collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val entries by collectLastValue(underTest.pinEntries)
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             sceneInteractor.setCurrentScene(
@@ -271,7 +269,7 @@
             underTest.onPinButtonClicked(5) // PIN is now wrong!
             underTest.onAuthenticateButtonClicked()
             assertThat(message?.text).isEqualTo(WRONG_PIN)
-            assertThat(entries).hasSize(0)
+            assertThat(pin).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct PIN:
@@ -312,7 +310,7 @@
             val currentScene by
                 collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val entries by collectLastValue(underTest.pinEntries)
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             utils.authenticationRepository.setAutoConfirmEnabled(true)
@@ -329,7 +327,7 @@
                 FakeAuthenticationRepository.DEFAULT_PIN.last() + 1
             ) // PIN is now wrong!
 
-            assertThat(entries).hasSize(0)
+            assertThat(pin).isEmpty()
             assertThat(message?.text).isEqualTo(WRONG_PIN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
new file mode 100644
index 0000000..4c279ea
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
@@ -0,0 +1,277 @@
+package com.android.systemui.bouncer.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.ui.viewmodel.EntryToken.ClearAll
+import com.android.systemui.bouncer.ui.viewmodel.EntryToken.Digit
+import com.android.systemui.bouncer.ui.viewmodel.PinInputSubject.Companion.assertThat
+import com.android.systemui.bouncer.ui.viewmodel.PinInputViewModel.Companion.empty
+import com.google.common.truth.Fact
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth.assertAbout
+import com.google.common.truth.Truth.assertThat
+import java.lang.Character.isDigit
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * This test uses a mnemonic code to create and verify PinInput instances: strings of digits [0-9]
+ * for [Digit] tokens, as well as a `C` for the [ClearAll] token.
+ */
+@SmallTest
+@RunWith(JUnit4::class)
+class PinInputViewModelTest : SysuiTestCase() {
+
+    @Test
+    fun create_emptyList_throws() {
+        assertThrows(IllegalArgumentException::class.java) { PinInputViewModel(emptyList()) }
+    }
+
+    @Test
+    fun create_inputWithoutLeadingClearAll_throws() {
+        val exception =
+            assertThrows(IllegalArgumentException::class.java) {
+                PinInputViewModel(listOf(Digit(0)))
+            }
+        assertThat(exception).hasMessageThat().contains("does not begin with a ClearAll token")
+    }
+
+    @Test
+    fun create_inputNotInAscendingOrder_throws() {
+        val sentinel = ClearAll()
+        val first = Digit(0)
+        val second = Digit(1)
+        // [first] is created before [second] is created, thus their sequence numbers are ordered.
+        check(first.sequenceNumber < second.sequenceNumber)
+
+        val exception =
+            assertThrows(IllegalArgumentException::class.java) {
+                // Passing the [Digit] tokens in reverse order throws.
+                PinInputViewModel(listOf(sentinel, second, first))
+            }
+        assertThat(exception).hasMessageThat().contains("EntryTokens are not sorted")
+    }
+
+    @Test
+    fun append_digitToEmptyInput() {
+        val result = empty().append(0)
+        assertThat(result).matches("C0")
+    }
+
+    @Test
+    fun append_digitToExistingPin() {
+        val subject = pinInput("C1")
+        assertThat(subject.append(2)).matches("C12")
+    }
+
+    @Test
+    fun append_withTwoCompletePinsEntered_dropsFirst() {
+        val subject = pinInput("C12C34C")
+        assertThat(subject.append(5)).matches("C34C5")
+    }
+
+    @Test
+    fun deleteLast_removesLastDigit() {
+        val subject = pinInput("C12")
+        assertThat(subject.deleteLast()).matches("C1")
+    }
+
+    @Test
+    fun deleteLast_onEmptyInput_returnsSameInstance() {
+        val subject = empty()
+        assertThat(subject.deleteLast()).isSameInstanceAs(subject)
+    }
+
+    @Test
+    fun deleteLast_onInputEndingInClearAll_returnsSameInstance() {
+        val subject = pinInput("C12C")
+        assertThat(subject.deleteLast()).isSameInstanceAs(subject)
+    }
+
+    @Test
+    fun clearAll_appendsClearAllEntryToExistingInput() {
+        val subject = pinInput("C12")
+        assertThat(subject.clearAll()).matches("C12C")
+    }
+
+    @Test
+    fun clearAll_onInputEndingInClearAll_returnsSameInstance() {
+        val subject = pinInput("C12C")
+        assertThat(subject.clearAll()).isSameInstanceAs(subject)
+    }
+
+    @Test
+    fun clearAll_retainsUpToTwoPinEntries() {
+        val subject = pinInput("C12C34")
+        assertThat(subject.clearAll()).matches("C12C34C")
+    }
+
+    @Test
+    fun isEmpty_onEmptyInput_returnsTrue() {
+        val subject = empty()
+        assertThat(subject.isEmpty()).isTrue()
+    }
+
+    @Test
+    fun isEmpty_whenLastEntryIsDigit_returnsFalse() {
+        val subject = pinInput("C1234")
+        assertThat(subject.isEmpty()).isFalse()
+    }
+
+    @Test
+    fun isEmpty_whenLastEntryIsClearAll_returnsTrue() {
+        val subject = pinInput("C1234C")
+        assertThat(subject.isEmpty()).isTrue()
+    }
+
+    @Test
+    fun getPin_onEmptyInput_returnsEmptyList() {
+        val subject = empty()
+        assertThat(subject.getPin()).isEmpty()
+    }
+
+    @Test
+    fun getPin_whenLastEntryIsDigit_returnsPin() {
+        val subject = pinInput("C1234")
+        assertThat(subject.getPin()).containsExactly(1, 2, 3, 4)
+    }
+
+    @Test
+    fun getPin_withMultiplePins_returnsLastPin() {
+        val subject = pinInput("C1234C5678")
+        assertThat(subject.getPin()).containsExactly(5, 6, 7, 8)
+    }
+
+    @Test
+    fun getPin_whenLastEntryIsClearAll_returnsEmptyList() {
+        val subject = pinInput("C1234C")
+        assertThat(subject.getPin()).isEmpty()
+    }
+
+    @Test
+    fun mostRecentClearAllMarker_onEmptyInput_returnsSentinel() {
+        val subject = empty()
+        val sentinel = subject.input[0] as ClearAll
+
+        assertThat(subject.mostRecentClearAll()).isSameInstanceAs(sentinel)
+    }
+
+    @Test
+    fun mostRecentClearAllMarker_whenLastEntryIsDigit_returnsSentinel() {
+        val subject = pinInput("C1234")
+        val sentinel = subject.input[0] as ClearAll
+
+        assertThat(subject.mostRecentClearAll()).isSameInstanceAs(sentinel)
+    }
+
+    @Test
+    fun mostRecentClearAllMarker_withMultiplePins_returnsLastMarker() {
+        val subject = pinInput("C1234C5678")
+        val lastMarker = subject.input[5] as ClearAll
+
+        assertThat(subject.mostRecentClearAll()).isSameInstanceAs(lastMarker)
+    }
+
+    @Test
+    fun mostRecentClearAllMarker_whenLastEntryIsClearAll_returnsLastEntry() {
+        val subject = pinInput("C1234C")
+        val lastEntry = subject.input[5] as ClearAll
+
+        assertThat(subject.mostRecentClearAll()).isSameInstanceAs(lastEntry)
+    }
+
+    @Test
+    fun getDigits_invalidClearAllMarker_onEmptyInput_returnsEmptyList() {
+        val subject = empty()
+        assertThat(subject.getDigits(ClearAll())).isEmpty()
+    }
+
+    @Test
+    fun getDigits_invalidClearAllMarker_whenLastEntryIsDigit_returnsEmptyList() {
+        val subject = pinInput("C1234")
+        assertThat(subject.getDigits(ClearAll())).isEmpty()
+    }
+
+    @Test
+    fun getDigits_clearAllMarkerPointsToFirstPin_returnsFirstPinDigits() {
+        val subject = pinInput("C1234C5678")
+        val marker = subject.input[0] as ClearAll
+
+        assertThat(subject.getDigits(marker).map { it.input }).containsExactly(1, 2, 3, 4)
+    }
+
+    @Test
+    fun getDigits_clearAllMarkerPointsToLastPin_returnsLastPinDigits() {
+        val subject = pinInput("C1234C5678")
+        val marker = subject.input[5] as ClearAll
+
+        assertThat(subject.getDigits(marker).map { it.input }).containsExactly(5, 6, 7, 8)
+    }
+
+    @Test
+    fun entryToken_equality() {
+        val clearAll = ClearAll()
+        val zero = Digit(0)
+        val one = Digit(1)
+
+        // Guava's EqualsTester is not available in this codebase.
+        assertThat(zero.equals(zero.copy())).isTrue()
+
+        assertThat(zero.equals(one)).isFalse()
+        assertThat(zero.equals(clearAll)).isFalse()
+
+        assertThat(clearAll.equals(clearAll.copy())).isTrue()
+        assertThat(clearAll.equals(zero)).isFalse()
+
+        // Not equal when the sequence number does not match
+        assertThat(zero.equals(Digit(0))).isFalse()
+        assertThat(clearAll.equals(ClearAll())).isFalse()
+    }
+
+    private fun pinInput(mnemonics: String): PinInputViewModel {
+        return PinInputViewModel(
+            mnemonics.map {
+                when {
+                    it == 'C' -> ClearAll()
+                    isDigit(it) -> Digit(it.digitToInt())
+                    else -> throw AssertionError()
+                }
+            }
+        )
+    }
+}
+
+private class PinInputSubject
+private constructor(metadata: FailureMetadata, private val actual: PinInputViewModel) :
+    Subject(metadata, actual) {
+
+    fun matches(mnemonics: String) {
+        val actualMnemonics =
+            actual.input
+                .map { entry ->
+                    when (entry) {
+                        is Digit -> entry.input.digitToChar()
+                        is ClearAll -> 'C'
+                        else -> throw IllegalArgumentException()
+                    }
+                }
+                .joinToString(separator = "")
+
+        if (mnemonics != actualMnemonics) {
+            failWithActual(
+                Fact.simpleFact(
+                    "expected pin input to be '$mnemonics' but is '$actualMnemonics' instead"
+                )
+            )
+        }
+    }
+
+    companion object {
+        fun assertThat(input: PinInputViewModel): PinInputSubject =
+            assertAbout(Factory(::PinInputSubject)).that(input)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
index 2a4c0eb..7628be4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
@@ -126,7 +126,8 @@
         assertEquals(Intent.ACTION_CHOOSER, intent.getAction());
         assertFlags(intent, EXTERNAL_INTENT_FLAGS);
         Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
-        assertEquals(uri, target.getData());
+        assertEquals(uri, target.getParcelableExtra(Intent.EXTRA_STREAM, Uri.class));
+        assertEquals(uri, target.getClipData().getItemAt(0).getUri());
         assertEquals("image/png", target.getType());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
index 872c079..2b98214 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
@@ -61,10 +61,8 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces),
+        mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), mShadeViewController,
                 TOUCH_HEIGHT);
-        when(mCentralSurfaces.getShadeViewController())
-                .thenReturn(mShadeViewController);
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java
deleted file mode 100644
index eb998cc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.hdmi;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.settings.SecureSettings;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Locale;
-import java.util.concurrent.Executor;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class HdmiCecSetMenuLanguageHelperTest extends SysuiTestCase {
-
-    private HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper;
-
-    @Mock
-    private Executor mExecutor;
-
-    @Mock
-    private SecureSettings mSecureSettings;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mSecureSettings.getStringForUser(
-                Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST,
-                UserHandle.USER_CURRENT)).thenReturn(null);
-        mHdmiCecSetMenuLanguageHelper =
-                new HdmiCecSetMenuLanguageHelper(mExecutor, mSecureSettings);
-    }
-
-    @Test
-    public void testSetGetLocale() {
-        mHdmiCecSetMenuLanguageHelper.setLocale("en");
-        assertThat(mHdmiCecSetMenuLanguageHelper.getLocale()).isEqualTo(Locale.ENGLISH);
-    }
-
-    @Test
-    public void testIsLocaleDenylisted_EmptyByDefault() {
-        mHdmiCecSetMenuLanguageHelper.setLocale("en");
-        assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false);
-    }
-
-    @Test
-    public void testIsLocaleDenylisted_AcceptLanguage() {
-        mHdmiCecSetMenuLanguageHelper.setLocale("de");
-        mHdmiCecSetMenuLanguageHelper.acceptLocale();
-        assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false);
-        verify(mExecutor).execute(any());
-    }
-
-    @Test
-    public void testIsLocaleDenylisted_DeclineLanguage() {
-        mHdmiCecSetMenuLanguageHelper.setLocale("de");
-        mHdmiCecSetMenuLanguageHelper.declineLocale();
-        assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
-        verify(mSecureSettings).putStringForUser(
-                Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de",
-                UserHandle.USER_CURRENT);
-    }
-
-    @Test
-    public void testIsLocaleDenylisted_DeclineTwoLanguages() {
-        mHdmiCecSetMenuLanguageHelper.setLocale("de");
-        mHdmiCecSetMenuLanguageHelper.declineLocale();
-        assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
-        verify(mSecureSettings).putStringForUser(
-                Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de",
-                UserHandle.USER_CURRENT);
-        mHdmiCecSetMenuLanguageHelper.setLocale("pl");
-        mHdmiCecSetMenuLanguageHelper.declineLocale();
-        assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
-        verify(mSecureSettings).putStringForUser(
-                Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de,pl",
-                UserHandle.USER_CURRENT);
-    }
-}
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 00f67a3..1f66e5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -38,8 +38,10 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.inOrder;
@@ -386,6 +388,21 @@
         mViewMediator.setKeyguardEnabled(false);
         TestableLooper.get(this).processAllMessages();
 
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(true,
+                mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
+        final ArgumentCaptor<Runnable> animationRunnableCaptor =
+                ArgumentCaptor.forClass(Runnable.class);
+        verify(mStatusBarKeyguardViewManager).startPreHideAnimation(
+                animationRunnableCaptor.capture());
+
+        when(mStatusBarStateController.isDreaming()).thenReturn(true);
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        animationRunnableCaptor.getValue().run();
+
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        mViewMediator.mViewMediatorCallback.keyguardGone();
+
         // Then dream should wake up
         verify(mPowerManager).wakeUp(anyLong(), anyInt(),
                 eq("com.android.systemui:UNLOCK_DREAMING"));
@@ -705,6 +722,67 @@
     }
 
     @Test
+    public void testWakeAndUnlockingOverDream() {
+        // Send signal to wake
+        mViewMediator.onWakeAndUnlocking();
+
+        // Ensure not woken up yet
+        verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+
+        // Verify keyguard told of authentication
+        verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(true,
+                mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
+        final ArgumentCaptor<Runnable> animationRunnableCaptor =
+                ArgumentCaptor.forClass(Runnable.class);
+        verify(mStatusBarKeyguardViewManager).startPreHideAnimation(
+                animationRunnableCaptor.capture());
+
+        when(mStatusBarStateController.isDreaming()).thenReturn(true);
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        animationRunnableCaptor.getValue().run();
+
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        mViewMediator.mViewMediatorCallback.keyguardGone();
+
+        // Verify woken up now.
+        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
+    }
+
+    @Test
+    public void testWakeAndUnlockingOverDream_signalAuthenticateIfStillShowing() {
+        // Send signal to wake
+        mViewMediator.onWakeAndUnlocking();
+
+        // Ensure not woken up yet
+        verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+
+        // Verify keyguard told of authentication
+        verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
+        clearInvocations(mStatusBarKeyguardViewManager);
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(true,
+                mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
+        final ArgumentCaptor<Runnable> animationRunnableCaptor =
+                ArgumentCaptor.forClass(Runnable.class);
+        verify(mStatusBarKeyguardViewManager).startPreHideAnimation(
+                animationRunnableCaptor.capture());
+
+        when(mStatusBarStateController.isDreaming()).thenReturn(true);
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        animationRunnableCaptor.getValue().run();
+
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+        mViewMediator.mViewMediatorCallback.keyguardGone();
+
+
+        // Verify keyguard view controller informed of authentication again
+        verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
+    }
+
+    @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void testDoKeyguardWhileInteractive_resets() {
         mViewMediator.setShowingLocked(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 925ac30..05d6b99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -16,6 +16,7 @@
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.util.mockito.any
 import com.android.systemui.utils.GlobalWindowManager
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -225,6 +226,9 @@
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
             )
-            verifyNoMoreInteractions(globalWindowManager)
+            // Memory hidden should still be called.
+            verify(globalWindowManager, times(1))
+                .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+            verify(globalWindowManager, times(0)).trimCaches(any())
         }
 }
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 47c662c..b3f8000 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
@@ -44,6 +44,7 @@
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
@@ -264,7 +265,8 @@
             val successResult = successResult()
             authenticationCallback.value.onAuthenticationSucceeded(successResult)
 
-            assertThat(authStatus()).isEqualTo(SuccessFaceAuthenticationStatus(successResult))
+            val response = authStatus() as SuccessFaceAuthenticationStatus
+            assertThat(response.successResult).isEqualTo(successResult)
             assertThat(authenticated()).isTrue()
             assertThat(authRunning()).isFalse()
             assertThat(canFaceAuthRun()).isFalse()
@@ -385,7 +387,10 @@
 
             detectionCallback.value.onFaceDetected(1, 1, true)
 
-            assertThat(detectStatus()).isEqualTo(FaceDetectionStatus(1, 1, true))
+            val status = detectStatus()!!
+            assertThat(status.sensorId).isEqualTo(1)
+            assertThat(status.userId).isEqualTo(1)
+            assertThat(status.isStrongBiometric).isEqualTo(true)
         }
 
     @Test
@@ -451,6 +456,29 @@
         }
 
     @Test
+    fun multipleCancelCallsShouldNotCauseMultipleCancellationStatusBeingEmitted() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+            val emittedValues by collectValues(underTest.authenticationStatus)
+
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.cancel()
+            advanceTimeBy(100)
+            underTest.cancel()
+
+            advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT)
+            runCurrent()
+            advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT)
+            runCurrent()
+
+            assertThat(emittedValues.size).isEqualTo(1)
+            assertThat(emittedValues.first())
+                .isInstanceOf(ErrorFaceAuthenticationStatus::class.java)
+            assertThat((emittedValues.first() as ErrorFaceAuthenticationStatus).msgId).isEqualTo(-1)
+        }
+
+    @Test
     fun faceHelpMessagesAreIgnoredBasedOnConfig() =
         testScope.runTest {
             overrideResource(
@@ -467,7 +495,9 @@
             authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
             authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
 
-            assertThat(authStatus()).isEqualTo(HelpFaceAuthenticationStatus(9, "help msg"))
+            val response = authStatus() as HelpFaceAuthenticationStatus
+            assertThat(response.msg).isEqualTo("help msg")
+            assertThat(response.msgId).isEqualTo(response.msgId)
         }
 
     @Test
@@ -523,10 +553,8 @@
         }
 
     @Test
-    fun authenticateDoesNotRunWhenFpIsLockedOut() =
-        testScope.runTest {
-            testGatingCheckForFaceAuth { deviceEntryFingerprintAuthRepository.setLockedOut(true) }
-        }
+    fun authenticateDoesNotRunWhenFaceIsDisabled() =
+        testScope.runTest { testGatingCheckForFaceAuth { underTest.lockoutFaceAuth() } }
 
     @Test
     fun authenticateDoesNotRunWhenUserIsCurrentlyTrusted() =
@@ -831,6 +859,19 @@
         }
 
     @Test
+    fun disableFaceUnlockLocksOutFaceUnlock() =
+        testScope.runTest {
+            runCurrent()
+            initCollectors()
+            assertThat(underTest.isLockedOut.value).isFalse()
+
+            underTest.lockoutFaceAuth()
+            runCurrent()
+
+            assertThat(underTest.isLockedOut.value).isTrue()
+        }
+
+    @Test
     fun detectDoesNotRunWhenFaceAuthNotSupportedInCurrentPosture() =
         testScope.runTest {
             testGatingCheckForDetect {
@@ -1043,10 +1084,11 @@
     }
 
     private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
+        verify(faceManager, atLeastOnce())
+            .addLockoutResetCallback(faceLockoutResetCallback.capture())
         biometricSettingsRepository.setFaceEnrolled(true)
         biometricSettingsRepository.setIsFaceAuthEnabled(true)
         fakeUserRepository.setUserSwitching(false)
-        deviceEntryFingerprintAuthRepository.setLockedOut(false)
         trustRepository.setCurrentUserTrusted(false)
         keyguardRepository.setKeyguardGoingAway(false)
         keyguardRepository.setWakefulnessModel(
@@ -1060,6 +1102,7 @@
         biometricSettingsRepository.setIsUserInLockdown(false)
         fakeUserRepository.setSelectedUserInfo(primaryUser)
         biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
+        faceLockoutResetCallback.value.onLockoutReset(0)
         bouncerRepository.setAlternateVisible(true)
         keyguardRepository.setKeyguardShowing(true)
         runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index f974577..ec30732 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -17,31 +17,43 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.graphics.Point
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.core.animation.AnimatorTestRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealEffect
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RoboPilotTest
-@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
 class LightRevealScrimRepositoryTest : SysuiTestCase() {
     private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
     private lateinit var underTest: LightRevealScrimRepositoryImpl
 
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -50,112 +62,127 @@
     }
 
     @Test
-    fun nextRevealEffect_effectSwitchesBetweenDefaultAndBiometricWithNoDupes() =
-        runTest {
-            val values = mutableListOf<LightRevealEffect>()
-            val job = launch { underTest.revealEffect.collect { values.add(it) } }
+    fun nextRevealEffect_effectSwitchesBetweenDefaultAndBiometricWithNoDupes() = runTest {
+        val values = mutableListOf<LightRevealEffect>()
+        val job = launch { underTest.revealEffect.collect { values.add(it) } }
 
-            // We should initially emit the default reveal effect.
-            runCurrent()
-            values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT })
-
-            // The source and sensor locations are still null, so we should still be using the
-            // default reveal despite a biometric unlock.
-            fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
-
-            runCurrent()
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
+        fakeKeyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                WakefulnessState.STARTING_TO_WAKE,
+                WakeSleepReason.OTHER,
+                WakeSleepReason.OTHER
             )
+        )
+        // We should initially emit the default reveal effect.
+        runCurrent()
+        values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT })
 
-            // We got a source but still have no sensor locations, so should be sticking with
-            // the default effect.
-            fakeKeyguardRepository.setBiometricUnlockSource(
-                BiometricUnlockSource.FINGERPRINT_SENSOR
-            )
+        // The source and sensor locations are still null, so we should still be using the
+        // default reveal despite a biometric unlock.
+        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
 
-            runCurrent()
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
-            )
+        runCurrent()
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+        )
 
-            // We got a location for the face sensor, but we unlocked with fingerprint.
-            val faceLocation = Point(250, 0)
-            fakeKeyguardRepository.setFaceSensorLocation(faceLocation)
+        // We got a source but still have no sensor locations, so should be sticking with
+        // the default effect.
+        fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
 
-            runCurrent()
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
-            )
+        runCurrent()
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+        )
 
-            // Now we have fingerprint sensor locations, and wake and unlock via fingerprint.
-            val fingerprintLocation = Point(500, 500)
-            fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation)
-            fakeKeyguardRepository.setBiometricUnlockSource(
-                BiometricUnlockSource.FINGERPRINT_SENSOR
-            )
-            fakeKeyguardRepository.setBiometricUnlockState(
-                BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
-            )
+        // We got a location for the face sensor, but we unlocked with fingerprint.
+        val faceLocation = Point(250, 0)
+        fakeKeyguardRepository.setFaceSensorLocation(faceLocation)
 
-            // We should now have switched to the circle reveal, at the fingerprint location.
-            runCurrent()
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
-                {
-                    it is CircleReveal &&
-                        it.centerX == fingerprintLocation.x &&
-                        it.centerY == fingerprintLocation.y
-                },
-            )
+        runCurrent()
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+        )
 
-            // Subsequent wake and unlocks should not emit duplicate, identical CircleReveals.
-            val valuesPrevSize = values.size
-            fakeKeyguardRepository.setBiometricUnlockState(
-                BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
-            )
-            fakeKeyguardRepository.setBiometricUnlockState(
-                BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
-            )
-            assertEquals(valuesPrevSize, values.size)
+        // Now we have fingerprint sensor locations, and wake and unlock via fingerprint.
+        val fingerprintLocation = Point(500, 500)
+        fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation)
+        fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING)
 
-            // Non-biometric unlock, we should return to the default reveal.
-            fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+        // We should now have switched to the circle reveal, at the fingerprint location.
+        runCurrent()
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+            {
+                it is CircleReveal &&
+                    it.centerX == fingerprintLocation.x &&
+                    it.centerY == fingerprintLocation.y
+            },
+        )
 
-            runCurrent()
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
-                {
-                    it is CircleReveal &&
-                        it.centerX == fingerprintLocation.x &&
-                        it.centerY == fingerprintLocation.y
-                },
-                { it == DEFAULT_REVEAL_EFFECT },
-            )
+        // Subsequent wake and unlocks should not emit duplicate, identical CircleReveals.
+        val valuesPrevSize = values.size
+        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING)
+        fakeKeyguardRepository.setBiometricUnlockState(
+            BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+        )
+        assertEquals(valuesPrevSize, values.size)
 
-            // We already have a face location, so switching to face source should update the
-            // CircleReveal.
-            fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
-            runCurrent()
-            fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
-            runCurrent()
+        // Non-biometric unlock, we should return to the default reveal.
+        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
 
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
-                {
-                    it is CircleReveal &&
-                        it.centerX == fingerprintLocation.x &&
-                        it.centerY == fingerprintLocation.y
-                },
-                { it == DEFAULT_REVEAL_EFFECT },
-                {
-                    it is CircleReveal &&
-                        it.centerX == faceLocation.x &&
-                        it.centerY == faceLocation.y
-                },
-            )
+        runCurrent()
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+            {
+                it is CircleReveal &&
+                    it.centerX == fingerprintLocation.x &&
+                    it.centerY == fingerprintLocation.y
+            },
+            { it == DEFAULT_REVEAL_EFFECT },
+        )
 
-            job.cancel()
+        // We already have a face location, so switching to face source should update the
+        // CircleReveal.
+        fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+        runCurrent()
+        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+        runCurrent()
+
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+            {
+                it is CircleReveal &&
+                    it.centerX == fingerprintLocation.x &&
+                    it.centerY == fingerprintLocation.y
+            },
+            { it == DEFAULT_REVEAL_EFFECT },
+            { it is CircleReveal && it.centerX == faceLocation.x && it.centerY == faceLocation.y },
+        )
+
+        job.cancel()
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    fun revealAmount_emitsTo1AfterAnimationStarted() =
+        runTest(UnconfinedTestDispatcher()) {
+            val value by collectLastValue(underTest.revealAmount)
+            underTest.startRevealAmountAnimator(true)
+            assertEquals(0.0f, value)
+            animatorTestRule.advanceTimeBy(500L)
+            assertEquals(1.0f, value)
+        }
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    fun revealAmount_emitsTo0AfterAnimationStartedReversed() =
+        runTest(UnconfinedTestDispatcher()) {
+            val value by collectLastValue(underTest.revealAmount)
+            underTest.startRevealAmountAnimator(false)
+            assertEquals(1.0f, value)
+            animatorTestRule.advanceTimeBy(500L)
+            assertEquals(0.0f, value)
         }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index ced0a21..8c9ed5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
@@ -73,6 +74,8 @@
     private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
     private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository
+    private lateinit var fakeDeviceEntryFingerprintAuthRepository:
+        FakeDeviceEntryFingerprintAuthRepository
 
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
@@ -94,6 +97,7 @@
                 )
                 .keyguardTransitionInteractor
 
+        fakeDeviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
         underTest =
             SystemUIKeyguardFaceAuthInteractor(
                 mContext,
@@ -127,6 +131,7 @@
                 featureFlags,
                 FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
                 keyguardUpdateMonitor,
+                fakeDeviceEntryFingerprintAuthRepository
             )
     }
 
@@ -335,4 +340,14 @@
             assertThat(faceAuthRepository.runningAuthRequest.value)
                 .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false))
         }
+
+    @Test
+    fun faceUnlockIsDisabledWhenFpIsLockedOut() = testScope.runTest {
+        underTest.start()
+
+        fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+        runCurrent()
+
+        assertThat(faceAuthRepository.wasDisabled).isTrue()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 6e7ba6d..906d948 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -27,27 +27,37 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.statusbar.LightRevealScrim
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.Spy
 
 @SmallTest
 @RoboPilotTest
+@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class LightRevealScrimInteractorTest : SysuiTestCase() {
     private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
-    private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
+
+    @Spy private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
+
+    private val testScope = TestScope()
 
     private val keyguardTransitionInteractor =
         KeyguardTransitionInteractorFactory.create(
-                scope = TestScope().backgroundScope,
+                scope = testScope.backgroundScope,
                 repository = fakeKeyguardTransitionRepository,
             )
             .keyguardTransitionInteractor
@@ -69,9 +79,9 @@
         MockitoAnnotations.initMocks(this)
         underTest =
             LightRevealScrimInteractor(
-                fakeKeyguardTransitionRepository,
                 keyguardTransitionInteractor,
-                fakeLightRevealScrimRepository
+                fakeLightRevealScrimRepository,
+                testScope.backgroundScope
             )
     }
 
@@ -110,52 +120,36 @@
         }
 
     @Test
-    fun revealAmount_invertedWhenAppropriate() =
-        runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<Float>()
-            val job = underTest.revealAmount.onEach(values::add).launchIn(this)
-
+    fun lightRevealEffect_startsAnimationOnlyForDifferentStateTargets() =
+        testScope.runTest {
             fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.AOD,
-                    to = KeyguardState.LOCKSCREEN,
-                    value = 0.3f
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.OFF,
+                    to = KeyguardState.OFF
                 )
             )
-
-            assertEquals(values, listOf(0.3f))
+            runCurrent()
+            verify(fakeLightRevealScrimRepository, never()).startRevealAmountAnimator(anyBoolean())
 
             fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.LOCKSCREEN
+                )
+            )
+            runCurrent()
+            verify(fakeLightRevealScrimRepository).startRevealAmountAnimator(true)
+
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
                     from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.AOD,
-                    value = 0.3f
+                    to = KeyguardState.DOZING
                 )
             )
-
-            assertEquals(values, listOf(0.3f, 0.7f))
-
-            job.cancel()
-        }
-
-    @Test
-    fun revealAmount_ignoresTransitionsThatDoNotAffectRevealAmount() =
-        runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<Float>()
-            val job = underTest.revealAmount.onEach(values::add).launchIn(this)
-
-            fakeKeyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.AOD, value = 0.3f)
-            )
-
-            assertEquals(values, emptyList<Float>())
-
-            fakeKeyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(from = KeyguardState.AOD, to = KeyguardState.DOZING, value = 0.3f)
-            )
-
-            assertEquals(values, emptyList<Float>())
-
-            job.cancel()
+            runCurrent()
+            verify(fakeLightRevealScrimRepository).startRevealAmountAnimator(false)
         }
 }
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
new file mode 100644
index 0000000..6e52d1a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -0,0 +1,299 @@
+/*
+ *  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 android.content.Context
+import android.content.Intent
+import android.hardware.biometrics.BiometricSourceType
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.util.IndicationHelper
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+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
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: OccludingAppDeviceEntryInteractor
+    private lateinit var testScope: TestScope
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var configurationRepository: FakeConfigurationRepository
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var trustRepository: FakeTrustRepository
+
+    @Mock private lateinit var indicationHelper: IndicationHelper
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mockedContext: Context
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+        fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        configurationRepository = FakeConfigurationRepository()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.FACE_AUTH_REFACTOR, false)
+                set(Flags.DELAY_BOUNCER, false)
+            }
+        trustRepository = FakeTrustRepository()
+        underTest =
+            OccludingAppDeviceEntryInteractor(
+                BiometricMessageInteractor(
+                    mContext.resources,
+                    fingerprintAuthRepository,
+                    fingerprintPropertyRepository,
+                    indicationHelper,
+                    keyguardUpdateMonitor,
+                ),
+                fingerprintAuthRepository,
+                KeyguardInteractor(
+                    keyguardRepository,
+                    commandQueue = mock(),
+                    featureFlags,
+                    bouncerRepository,
+                    configurationRepository,
+                ),
+                PrimaryBouncerInteractor(
+                    bouncerRepository,
+                    primaryBouncerView = mock(),
+                    mainHandler = mock(),
+                    keyguardStateController = mock(),
+                    keyguardSecurityModel = mock(),
+                    primaryBouncerCallbackInteractor = mock(),
+                    falsingCollector = mock(),
+                    dismissCallbackRegistry = mock(),
+                    context,
+                    keyguardUpdateMonitor,
+                    trustRepository,
+                    featureFlags,
+                    testScope.backgroundScope,
+                ),
+                AlternateBouncerInteractor(
+                    statusBarStateController = mock(),
+                    keyguardStateController = mock(),
+                    bouncerRepository,
+                    biometricSettingsRepository,
+                    FakeSystemClock(),
+                    keyguardUpdateMonitor,
+                ),
+                testScope.backgroundScope,
+                mockedContext,
+                activityStarter,
+            )
+    }
+
+    @Test
+    fun fingerprintSuccess_goToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(true)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            verifyGoToHomeScreen()
+        }
+
+    @Test
+    fun fingerprintSuccess_notOnOccludingApp_doesNotGoToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(false)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            verifyNeverGoToHomeScreen()
+        }
+
+    @Test
+    fun lockout_goToHomeScreenOnDismissAction() =
+        testScope.runTest {
+            givenOnOccludingApp(true)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    "lockoutTest"
+                )
+            )
+            runCurrent()
+            verifyGoToHomeScreenOnDismiss()
+        }
+
+    @Test
+    fun lockout_notOnOccludingApp_neverGoToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(false)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    "lockoutTest"
+                )
+            )
+            runCurrent()
+            verifyNeverGoToHomeScreen()
+        }
+
+    @Test
+    fun message_fpFailOnOccludingApp_thenNotOnOccludingApp() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+
+            givenOnOccludingApp(true)
+            givenPrimaryAuthRequired(false)
+            runCurrent()
+            // WHEN a fp failure come in
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            // THEN message set to failure
+            assertThat(message?.type).isEqualTo(BiometricMessageType.FAIL)
+
+            // GIVEN fingerprint shouldn't run
+            givenOnOccludingApp(false)
+            runCurrent()
+            // WHEN another fp failure arrives
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+            // THEN message set to null
+            assertThat(message).isNull()
+        }
+
+    @Test
+    fun message_fpErrorHelpFailOnOccludingApp() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+
+            givenOnOccludingApp(true)
+            givenPrimaryAuthRequired(false)
+            runCurrent()
+
+            // ERROR message
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    "testError",
+                )
+            )
+            assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
+            assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)
+            assertThat(message?.message).isEqualTo("testError")
+            assertThat(message?.type).isEqualTo(BiometricMessageType.ERROR)
+
+            // HELP message
+            fingerprintAuthRepository.setAuthenticationStatus(
+                HelpFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
+                    "testHelp",
+                )
+            )
+            assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
+            assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL)
+            assertThat(message?.message).isEqualTo("testHelp")
+            assertThat(message?.type).isEqualTo(BiometricMessageType.HELP)
+
+            // FAIL message
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
+            assertThat(message?.id)
+                .isEqualTo(KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
+            assertThat(message?.type).isEqualTo(BiometricMessageType.FAIL)
+        }
+
+    private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+        keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
+        keyguardRepository.setKeyguardShowing(isOnOccludingApp)
+        bouncerRepository.setPrimaryShow(!isOnOccludingApp)
+        bouncerRepository.setAlternateVisible(!isOnOccludingApp)
+    }
+
+    private fun givenPrimaryAuthRequired(required: Boolean) {
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+            .thenReturn(!required)
+    }
+
+    private fun verifyGoToHomeScreen() {
+        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(mockedContext).startActivity(intentCaptor.capture())
+
+        assertThat(intentCaptor.value.hasCategory(Intent.CATEGORY_HOME)).isTrue()
+        assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_MAIN)
+    }
+
+    private fun verifyNeverGoToHomeScreen() {
+        verify(mockedContext, never()).startActivity(any())
+        verify(activityStarter, never())
+            .dismissKeyguardThenExecute(any(OnDismissAction::class.java), isNull(), eq(false))
+    }
+
+    private fun verifyGoToHomeScreenOnDismiss() {
+        val onDimissActionCaptor = ArgumentCaptor.forClass(OnDismissAction::class.java)
+        verify(activityStarter)
+            .dismissKeyguardThenExecute(onDimissActionCaptor.capture(), isNull(), eq(false))
+        onDimissActionCaptor.value.onDismiss()
+
+        verifyGoToHomeScreen()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 1baca21..b019a21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -33,6 +33,9 @@
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
@@ -46,6 +49,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @ExperimentalCoroutinesApi
@@ -63,19 +67,21 @@
     private lateinit var fakeCommandQueue: FakeCommandQueue
     private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var burnInInteractor: BurnInInteractor
+    private lateinit var shadeRepository: FakeShadeRepository
 
     @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+    @Mock private lateinit var dialogManager: SystemUIDialogManager
 
     private lateinit var underTest: UdfpsKeyguardInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
         testScope = TestScope()
         configRepository = FakeConfigurationRepository()
         keyguardRepository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
+        shadeRepository = FakeShadeRepository()
         fakeCommandQueue = FakeCommandQueue()
         featureFlags =
             FakeFeatureFlags().apply {
@@ -102,6 +108,8 @@
                     bouncerRepository,
                     configRepository,
                 ),
+                shadeRepository,
+                dialogManager,
             )
     }
 
@@ -142,6 +150,61 @@
             assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset)
         }
 
+    @Test
+    fun dialogHideAffordances() =
+        testScope.runTest {
+            val dialogHideAffordancesRequest by
+                collectLastValue(underTest.dialogHideAffordancesRequest)
+            runCurrent()
+            val captor = argumentCaptor<SystemUIDialogManager.Listener>()
+            verify(dialogManager).registerListener(captor.capture())
+
+            captor.value.shouldHideAffordances(false)
+            assertThat(dialogHideAffordancesRequest).isEqualTo(false)
+
+            captor.value.shouldHideAffordances(true)
+            assertThat(dialogHideAffordancesRequest).isEqualTo(true)
+
+            captor.value.shouldHideAffordances(false)
+            assertThat(dialogHideAffordancesRequest).isEqualTo(false)
+        }
+
+    @Test
+    fun shadeExpansion_updates() =
+        testScope.runTest {
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            val shadeExpansion by collectLastValue(underTest.shadeExpansion)
+            assertThat(shadeExpansion).isEqualTo(0f)
+
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(.5f)
+            assertThat(shadeExpansion).isEqualTo(.5f)
+
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(.7f)
+            assertThat(shadeExpansion).isEqualTo(.7f)
+
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(.22f)
+            assertThat(shadeExpansion).isEqualTo(.22f)
+
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+            assertThat(shadeExpansion).isEqualTo(1f)
+        }
+
+    @Test
+    fun qsProgress_updates() =
+        testScope.runTest {
+            val qsProgress by collectLastValue(underTest.qsProgress)
+            assertThat(qsProgress).isEqualTo(0f)
+
+            shadeRepository.setQsExpansion(.22f)
+            assertThat(qsProgress).isEqualTo(.44f)
+
+            shadeRepository.setQsExpansion(.5f)
+            assertThat(qsProgress).isEqualTo(1f)
+
+            shadeRepository.setQsExpansion(.7f)
+            assertThat(qsProgress).isEqualTo(1f)
+        }
+
     private fun initializeBurnInOffsets() {
         whenever(burnInHelper.burnInProgressOffset()).thenReturn(burnInProgress)
         whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(true)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
index 436c09c..b985b3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
@@ -32,6 +32,8 @@
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -58,7 +60,9 @@
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var fakeCommandQueue: FakeCommandQueue
     private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var shadeRepository: FakeShadeRepository
 
+    @Mock private lateinit var dialogManager: SystemUIDialogManager
     @Mock private lateinit var burnInHelper: BurnInHelperWrapper
 
     @Before
@@ -70,6 +74,7 @@
         keyguardRepository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
         fakeCommandQueue = FakeCommandQueue()
+        shadeRepository = FakeShadeRepository()
         featureFlags =
             FakeFeatureFlags().apply {
                 set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
@@ -93,6 +98,8 @@
                     bouncerRepository,
                     configRepository,
                 ),
+                shadeRepository,
+                dialogManager,
             )
 
         underTest =
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 a30e2a6..0fbcec2 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
@@ -33,6 +33,8 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,8 +62,10 @@
     private lateinit var fakeCommandQueue: FakeCommandQueue
     private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var shadeRepository: FakeShadeRepository
 
     @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+    @Mock private lateinit var dialogManager: SystemUIDialogManager
 
     @Before
     fun setUp() {
@@ -79,35 +83,39 @@
             }
         bouncerRepository = FakeKeyguardBouncerRepository()
         transitionRepository = FakeKeyguardTransitionRepository()
+        shadeRepository = FakeShadeRepository()
         val transitionInteractor =
             KeyguardTransitionInteractor(
                 transitionRepository,
                 testScope.backgroundScope,
             )
-        val udfpsKeyguardInteractor =
-            UdfpsKeyguardInteractor(
+        val keyguardInteractor =
+            KeyguardInteractor(
+                keyguardRepository,
+                fakeCommandQueue,
+                featureFlags,
+                bouncerRepository,
                 configRepository,
-                BurnInInteractor(
-                    context,
-                    burnInHelper,
-                    testScope.backgroundScope,
-                    configRepository,
-                    FakeSystemClock(),
-                ),
-                KeyguardInteractor(
-                    keyguardRepository,
-                    fakeCommandQueue,
-                    featureFlags,
-                    bouncerRepository,
-                    configRepository,
-                ),
             )
 
         underTest =
             FingerprintViewModel(
                 context,
                 transitionInteractor,
-                udfpsKeyguardInteractor,
+                UdfpsKeyguardInteractor(
+                    configRepository,
+                    BurnInInteractor(
+                        context,
+                        burnInHelper,
+                        testScope.backgroundScope,
+                        configRepository,
+                        FakeSystemClock(),
+                    ),
+                    keyguardInteractor,
+                    shadeRepository,
+                    dialogManager,
+                ),
+                keyguardInteractor,
             )
     }
 
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 d58ceee..41ae931 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
@@ -20,12 +20,27 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.Utils
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
 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 com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.animation.Interpolators
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,6 +50,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
 
 /** Tests UDFPS lockscreen view model transitions. */
@@ -48,26 +65,63 @@
     private val alternateBouncerColor =
         Utils.getColorAttrDefaultColor(context, alternateBouncerResId)
 
+    @Mock private lateinit var dialogManager: SystemUIDialogManager
+
     private lateinit var underTest: UdfpsLockscreenViewModel
     private lateinit var testScope: TestScope
     private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var configRepository: FakeConfigurationRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var shadeRepository: FakeShadeRepository
+    private lateinit var featureFlags: FakeFeatureFlags
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testScope = TestScope()
         transitionRepository = FakeKeyguardTransitionRepository()
-        val transitionInteractor =
-            KeyguardTransitionInteractor(
-                transitionRepository,
-                testScope.backgroundScope,
+        configRepository = FakeConfigurationRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        shadeRepository = FakeShadeRepository()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+                set(Flags.FACE_AUTH_REFACTOR, false)
+            }
+        val keyguardInteractor =
+            KeyguardInteractor(
+                keyguardRepository,
+                commandQueue = mock(),
+                featureFlags,
+                bouncerRepository,
+                configRepository,
             )
+
         underTest =
             UdfpsLockscreenViewModel(
                 context,
                 lockscreenColorResId,
                 alternateBouncerResId,
-                transitionInteractor,
+                KeyguardTransitionInteractor(
+                    transitionRepository,
+                    testScope.backgroundScope,
+                ),
+                UdfpsKeyguardInteractor(
+                    configRepository,
+                    BurnInInteractor(
+                        context,
+                        burnInHelperWrapper = mock(),
+                        testScope.backgroundScope,
+                        configRepository,
+                        FakeSystemClock(),
+                    ),
+                    keyguardInteractor,
+                    shadeRepository,
+                    dialogManager,
+                ),
+                keyguardInteractor,
             )
     }
 
@@ -125,6 +179,7 @@
         testScope.runTest {
             val transition by collectLastValue(underTest.transition)
             val visible by collectLastValue(underTest.visible)
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
 
             // TransitionState.STARTED: lockscreen -> AOD
             transitionRepository.sendTransitionStep(
@@ -176,6 +231,56 @@
         }
 
     @Test
+    fun lockscreenShadeLockedToAod() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+
+            // TransitionState.STARTED: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            // TransitionState.RUNNING: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            // TransitionState.FINISHED: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
     fun aodToLockscreen() =
         testScope.runTest {
             val transition by collectLastValue(underTest.transition)
@@ -235,6 +340,7 @@
         testScope.runTest {
             val transition by collectLastValue(underTest.transition)
             val visible by collectLastValue(underTest.visible)
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
 
             // TransitionState.STARTED: lockscreen -> alternate bouncer
             transitionRepository.sendTransitionStep(
@@ -398,6 +504,7 @@
         testScope.runTest {
             val transition by collectLastValue(underTest.transition)
             val visible by collectLastValue(underTest.visible)
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
 
             // TransitionState.STARTED: lockscreen -> occluded
             transitionRepository.sendTransitionStep(
@@ -502,4 +609,152 @@
             assertThat(transition?.color).isEqualTo(lockscreenColor)
             assertThat(visible).isTrue()
         }
+
+    @Test
+    fun qsProgressChange() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+            givenTransitionToLockscreenFinished()
+
+            // qsExpansion = 0f
+            shadeRepository.setQsExpansion(0f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(visible).isEqualTo(true)
+
+            // qsExpansion = .25
+            shadeRepository.setQsExpansion(.2f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(.6f)
+            assertThat(visible).isEqualTo(true)
+
+            // qsExpansion = .5
+            shadeRepository.setQsExpansion(.5f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isEqualTo(false)
+
+            // qsExpansion = 1
+            shadeRepository.setQsExpansion(1f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isEqualTo(false)
+        }
+
+    @Test
+    fun shadeExpansionChanged() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+            givenTransitionToLockscreenFinished()
+
+            // shadeExpansion = 0f
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(0f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(visible).isEqualTo(true)
+
+            // shadeExpansion = .2
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(.2f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(.8f)
+            assertThat(visible).isEqualTo(true)
+
+            // shadeExpansion = .5
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(.5f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(.5f)
+            assertThat(visible).isEqualTo(true)
+
+            // shadeExpansion = 1
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(1f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isEqualTo(false)
+        }
+
+    @Test
+    fun dialogHideAffordancesRequestChanged() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            givenTransitionToLockscreenFinished()
+            runCurrent()
+            val captor = argumentCaptor<SystemUIDialogManager.Listener>()
+            Mockito.verify(dialogManager).registerListener(captor.capture())
+
+            captor.value.shouldHideAffordances(true)
+            assertThat(transition?.alpha).isEqualTo(0f)
+
+            captor.value.shouldHideAffordances(false)
+            assertThat(transition?.alpha).isEqualTo(1f)
+        }
+
+    @Test
+    fun occludedToAlternateBouncer() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: occluded -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "occludedToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(0f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: occluded -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "occludedToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale)
+                .isEqualTo(Interpolators.FAST_OUT_SLOW_IN.getInterpolation(.6f))
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: occluded -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "occludedToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+        }
+
+    private suspend fun givenTransitionToLockscreenFinished() {
+        transitionRepository.sendTransitionStep(
+            TransitionStep(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                value = 1f,
+                transitionState = TransitionState.FINISHED,
+                ownerName = "givenTransitionToLockscreenFinished",
+            )
+        )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index f79c53d..ab24c46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -63,7 +63,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -124,7 +123,7 @@
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
                 mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index f8971fd..45e8e27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -70,7 +70,6 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -126,7 +125,7 @@
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 9f06b5f..708bb55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -93,7 +93,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -197,7 +196,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
@@ -279,7 +278,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         mMediaOutputController.start(mCb);
@@ -309,7 +308,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         mMediaOutputController.start(mCb);
@@ -530,7 +529,7 @@
                 "",
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         testMediaOutputController.start(mCb);
         reset(mCb);
@@ -553,7 +552,7 @@
                 "",
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         testMediaOutputController.start(mCb);
         reset(mCb);
@@ -589,7 +588,7 @@
                 null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
@@ -606,7 +605,7 @@
                 null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
@@ -783,33 +782,20 @@
     }
 
     @Test
-    public void getActiveRemoteMediaDevice_isSystemSession_returnSession() {
+    public void getActiveRemoteMediaDevices() {
         when(mRemoteSessionInfo.getId()).thenReturn(TEST_SESSION_ID);
         when(mRemoteSessionInfo.getName()).thenReturn(TEST_SESSION_NAME);
         when(mRemoteSessionInfo.getVolumeMax()).thenReturn(100);
         when(mRemoteSessionInfo.getVolume()).thenReturn(10);
         when(mRemoteSessionInfo.isSystemSession()).thenReturn(false);
         mRoutingSessionInfos.add(mRemoteSessionInfo);
-        when(mLocalMediaManager.getActiveMediaSession()).thenReturn(mRoutingSessionInfos);
+        when(mLocalMediaManager.getRemoteRoutingSessions()).thenReturn(mRoutingSessionInfos);
 
         assertThat(mMediaOutputController.getActiveRemoteMediaDevices()).containsExactly(
                 mRemoteSessionInfo);
     }
 
     @Test
-    public void getActiveRemoteMediaDevice_notSystemSession_returnEmpty() {
-        when(mRemoteSessionInfo.getId()).thenReturn(TEST_SESSION_ID);
-        when(mRemoteSessionInfo.getName()).thenReturn(TEST_SESSION_NAME);
-        when(mRemoteSessionInfo.getVolumeMax()).thenReturn(100);
-        when(mRemoteSessionInfo.getVolume()).thenReturn(10);
-        when(mRemoteSessionInfo.isSystemSession()).thenReturn(true);
-        mRoutingSessionInfos.add(mRemoteSessionInfo);
-        when(mLocalMediaManager.getActiveMediaSession()).thenReturn(mRoutingSessionInfos);
-
-        assertThat(mMediaOutputController.getActiveRemoteMediaDevices()).isEmpty();
-    }
-
-    @Test
     public void getGroupMediaDevices_differentDeviceOrder_showingSameOrder() {
         final MediaDevice selectedMediaDevice1 = mock(MediaDevice.class);
         final MediaDevice selectedMediaDevice2 = mock(MediaDevice.class);
@@ -888,7 +874,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
@@ -1080,7 +1066,7 @@
                 null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index a14ff2f..3e69a29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -67,7 +67,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 import java.util.function.Consumer;
 
 @MediumTest
@@ -132,7 +131,7 @@
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = makeTestDialog(mMediaOutputController);
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 01ffdcd..ee3b80a 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
@@ -26,6 +26,7 @@
 import android.view.WindowManager
 import android.view.WindowMetrics
 import androidx.core.view.WindowInsetsCompat.Type
+import androidx.lifecycle.LifecycleOwner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
@@ -41,9 +42,10 @@
 @SmallTest
 class TaskPreviewSizeProviderTest : SysuiTestCase() {
 
-    private val mockContext: Context = mock()
-    private val resources: Resources = mock()
-    private val windowManager: WindowManager = mock()
+    private val lifecycleOwner = mock<LifecycleOwner>()
+    private val mockContext = mock<Context>()
+    private val resources = mock<Resources>()
+    private val windowManager = mock<WindowManager>()
     private val sizeUpdates = arrayListOf<Rect>()
     private val testConfigurationController = FakeConfigurationController()
 
@@ -76,7 +78,7 @@
     @Test
     fun size_phoneDisplayAndRotate_emitsSizeUpdate() {
         givenDisplay(width = 400, height = 600, isTablet = false)
-        createSizeProvider()
+        createSizeProvider().also { it.onCreate(lifecycleOwner) }
 
         givenDisplay(width = 600, height = 400, isTablet = false)
         testConfigurationController.onConfigurationChanged(Configuration())
@@ -87,7 +89,7 @@
     @Test
     fun size_phoneDisplayAndRotateConfigurationChange_returnsUpdatedSize() {
         givenDisplay(width = 400, height = 600, isTablet = false)
-        val sizeProvider = createSizeProvider()
+        val sizeProvider = createSizeProvider().also { it.onCreate(lifecycleOwner) }
 
         givenDisplay(width = 600, height = 400, isTablet = false)
         testConfigurationController.onConfigurationChanged(Configuration())
@@ -95,6 +97,20 @@
         assertThat(sizeProvider.size).isEqualTo(Rect(0, 0, 150, 100))
     }
 
+    @Test
+    fun size_phoneDisplayAndRotateConfigurationChange_afterChooserDestroyed_doesNotUpdate() {
+        givenDisplay(width = 400, height = 600, isTablet = false)
+        val sizeProvider = createSizeProvider()
+        val previousSize = Rect(sizeProvider.size)
+
+        sizeProvider.onCreate(lifecycleOwner)
+        sizeProvider.onDestroy(lifecycleOwner)
+        givenDisplay(width = 600, height = 400, isTablet = false)
+        testConfigurationController.onConfigurationChanged(Configuration())
+
+        assertThat(sizeProvider.size).isEqualTo(previousSize)
+    }
+
     private fun givenTaskbarSize(size: Int) {
         val windowInsets =
             WindowInsets.Builder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
new file mode 100644
index 0000000..f5a70f0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.model
+
+import android.view.Display
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeDisplayTracker
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SysUiStateExtTest : SysuiTestCase() {
+
+    private val underTest = SysUiState(FakeDisplayTracker(context))
+
+    @Test
+    fun updateFlags() {
+        underTest.updateFlags(
+            Display.DEFAULT_DISPLAY,
+            1 to true,
+            2 to false,
+            3 to true,
+        )
+
+        assertThat(underTest.flags and 1).isNotEqualTo(0)
+        assertThat(underTest.flags and 2).isEqualTo(0)
+        assertThat(underTest.flags and 3).isNotEqualTo(0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
deleted file mode 100644
index ceacaf9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * 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.multishade.data.repository
-
-import android.content.Context
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeConfig
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class MultiShadeRepositoryTest : SysuiTestCase() {
-
-    private lateinit var inputProxy: MultiShadeInputProxy
-
-    @Before
-    fun setUp() {
-        inputProxy = MultiShadeInputProxy()
-    }
-
-    @Test
-    fun proxiedInput() = runTest {
-        val underTest = create()
-        val latest: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
-
-        assertWithMessage("proxiedInput should start with null").that(latest).isNull()
-
-        inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
-        assertThat(latest).isEqualTo(ProxiedInputModel.OnTap)
-
-        inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 100f))
-        assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 100f))
-
-        inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 120f))
-        assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 120f))
-
-        inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
-        assertThat(latest).isEqualTo(ProxiedInputModel.OnDragEnd)
-    }
-
-    @Test
-    fun shadeConfig_dualShadeEnabled() = runTest {
-        overrideResource(R.bool.dual_shade_enabled, true)
-        val underTest = create()
-        val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
-
-        assertThat(shadeConfig).isInstanceOf(ShadeConfig.DualShadeConfig::class.java)
-    }
-
-    @Test
-    fun shadeConfig_dualShadeNotEnabled() = runTest {
-        overrideResource(R.bool.dual_shade_enabled, false)
-        val underTest = create()
-        val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
-
-        assertThat(shadeConfig).isInstanceOf(ShadeConfig.SingleShadeConfig::class.java)
-    }
-
-    @Test
-    fun forceCollapseAll() = runTest {
-        val underTest = create()
-        val forceCollapseAll: Boolean? by collectLastValue(underTest.forceCollapseAll)
-
-        assertWithMessage("forceCollapseAll should start as false!")
-            .that(forceCollapseAll)
-            .isFalse()
-
-        underTest.setForceCollapseAll(true)
-        assertThat(forceCollapseAll).isTrue()
-
-        underTest.setForceCollapseAll(false)
-        assertThat(forceCollapseAll).isFalse()
-    }
-
-    @Test
-    fun shadeInteraction() = runTest {
-        val underTest = create()
-        val shadeInteraction: MultiShadeInteractionModel? by
-            collectLastValue(underTest.shadeInteraction)
-
-        assertWithMessage("shadeInteraction should start as null!").that(shadeInteraction).isNull()
-
-        underTest.setShadeInteraction(
-            MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false)
-        )
-        assertThat(shadeInteraction)
-            .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false))
-
-        underTest.setShadeInteraction(
-            MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true)
-        )
-        assertThat(shadeInteraction)
-            .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true))
-
-        underTest.setShadeInteraction(null)
-        assertThat(shadeInteraction).isNull()
-    }
-
-    @Test
-    fun expansion() = runTest {
-        val underTest = create()
-        val leftExpansion: Float? by
-            collectLastValue(underTest.getShade(ShadeId.LEFT).map { it.expansion })
-        val rightExpansion: Float? by
-            collectLastValue(underTest.getShade(ShadeId.RIGHT).map { it.expansion })
-        val singleExpansion: Float? by
-            collectLastValue(underTest.getShade(ShadeId.SINGLE).map { it.expansion })
-
-        assertWithMessage("expansion should start as 0!").that(leftExpansion).isZero()
-        assertWithMessage("expansion should start as 0!").that(rightExpansion).isZero()
-        assertWithMessage("expansion should start as 0!").that(singleExpansion).isZero()
-
-        underTest.setExpansion(
-            shadeId = ShadeId.LEFT,
-            0.4f,
-        )
-        assertThat(leftExpansion).isEqualTo(0.4f)
-        assertThat(rightExpansion).isEqualTo(0f)
-        assertThat(singleExpansion).isEqualTo(0f)
-
-        underTest.setExpansion(
-            shadeId = ShadeId.RIGHT,
-            0.73f,
-        )
-        assertThat(leftExpansion).isEqualTo(0.4f)
-        assertThat(rightExpansion).isEqualTo(0.73f)
-        assertThat(singleExpansion).isEqualTo(0f)
-
-        underTest.setExpansion(
-            shadeId = ShadeId.LEFT,
-            0.1f,
-        )
-        underTest.setExpansion(
-            shadeId = ShadeId.SINGLE,
-            0.88f,
-        )
-        assertThat(leftExpansion).isEqualTo(0.1f)
-        assertThat(rightExpansion).isEqualTo(0.73f)
-        assertThat(singleExpansion).isEqualTo(0.88f)
-    }
-
-    private fun create(): MultiShadeRepository {
-        return create(
-            context = context,
-            inputProxy = inputProxy,
-        )
-    }
-
-    companion object {
-        fun create(
-            context: Context,
-            inputProxy: MultiShadeInputProxy,
-        ): MultiShadeRepository {
-            return MultiShadeRepository(
-                applicationContext = context,
-                inputProxy = inputProxy,
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
deleted file mode 100644
index bcc99bc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * 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.multishade.domain.interactor
-
-import android.content.Context
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.data.repository.MultiShadeRepositoryTest
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class MultiShadeInteractorTest : SysuiTestCase() {
-
-    private lateinit var testScope: TestScope
-    private lateinit var inputProxy: MultiShadeInputProxy
-
-    @Before
-    fun setUp() {
-        testScope = TestScope()
-        inputProxy = MultiShadeInputProxy()
-    }
-
-    @Test
-    fun maxShadeExpansion() =
-        testScope.runTest {
-            val underTest = create()
-            val maxShadeExpansion: Float? by collectLastValue(underTest.maxShadeExpansion)
-            assertWithMessage("maxShadeExpansion must start with 0.0!")
-                .that(maxShadeExpansion)
-                .isEqualTo(0f)
-
-            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0.441f)
-            assertThat(maxShadeExpansion).isEqualTo(0.441f)
-
-            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0.442f)
-            assertThat(maxShadeExpansion).isEqualTo(0.442f)
-
-            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0f)
-            assertThat(maxShadeExpansion).isEqualTo(0.441f)
-
-            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0f)
-            assertThat(maxShadeExpansion).isEqualTo(0f)
-        }
-
-    @Test
-    fun isAnyShadeExpanded() =
-        testScope.runTest {
-            val underTest = create()
-            val isAnyShadeExpanded: Boolean? by collectLastValue(underTest.isAnyShadeExpanded)
-            assertWithMessage("isAnyShadeExpanded must start with false!")
-                .that(isAnyShadeExpanded)
-                .isFalse()
-
-            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0.441f)
-            assertThat(isAnyShadeExpanded).isTrue()
-
-            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0.442f)
-            assertThat(isAnyShadeExpanded).isTrue()
-
-            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0f)
-            assertThat(isAnyShadeExpanded).isTrue()
-
-            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0f)
-            assertThat(isAnyShadeExpanded).isFalse()
-        }
-
-    @Test
-    fun isVisible_dualShadeConfig() =
-        testScope.runTest {
-            overrideResource(R.bool.dual_shade_enabled, true)
-            val underTest = create()
-            val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
-            val isRightShadeVisible: Boolean? by
-                collectLastValue(underTest.isVisible(ShadeId.RIGHT))
-            val isSingleShadeVisible: Boolean? by
-                collectLastValue(underTest.isVisible(ShadeId.SINGLE))
-
-            assertThat(isLeftShadeVisible).isTrue()
-            assertThat(isRightShadeVisible).isTrue()
-            assertThat(isSingleShadeVisible).isFalse()
-        }
-
-    @Test
-    fun isVisible_singleShadeConfig() =
-        testScope.runTest {
-            overrideResource(R.bool.dual_shade_enabled, false)
-            val underTest = create()
-            val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
-            val isRightShadeVisible: Boolean? by
-                collectLastValue(underTest.isVisible(ShadeId.RIGHT))
-            val isSingleShadeVisible: Boolean? by
-                collectLastValue(underTest.isVisible(ShadeId.SINGLE))
-
-            assertThat(isLeftShadeVisible).isFalse()
-            assertThat(isRightShadeVisible).isFalse()
-            assertThat(isSingleShadeVisible).isTrue()
-        }
-
-    @Test
-    fun isNonProxiedInputAllowed() =
-        testScope.runTest {
-            val underTest = create()
-            val isLeftShadeNonProxiedInputAllowed: Boolean? by
-                collectLastValue(underTest.isNonProxiedInputAllowed(ShadeId.LEFT))
-            assertWithMessage("isNonProxiedInputAllowed should start as true!")
-                .that(isLeftShadeNonProxiedInputAllowed)
-                .isTrue()
-
-            // Need to collect proxied input so the flows become hot as the gesture cancelation code
-            // logic sits in side the proxiedInput flow for each shade.
-            collectLastValue(underTest.proxiedInput(ShadeId.LEFT))
-            collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
-
-            // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
-            // the
-            // same shade.
-            inputProxy.onProxiedInput(
-                ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
-            )
-            assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
-
-            // Registering the end of the proxied interaction re-allows it.
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
-            assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
-
-            // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
-            // disallowing non-proxied input on the LEFT shade.
-            inputProxy.onProxiedInput(
-                ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
-            )
-            assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
-
-            // Registering the end of the interaction on the RIGHT shade re-allows it.
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
-            assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
-        }
-
-    @Test
-    fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
-        testScope.runTest {
-            val underTest = create()
-            val isLeftShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
-            val isRightShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
-            val isSingleShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
-
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isLeftShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isRightShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isSingleShadeForceCollapsed)
-                .isFalse()
-
-            // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
-            // shade.
-            underTest.onUserInteractionStarted(ShadeId.RIGHT)
-            assertThat(isLeftShadeForceCollapsed).isTrue()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the end of the interaction on the RIGHT shade re-allows it.
-            underTest.onUserInteractionEnded(ShadeId.RIGHT)
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
-            // shade.
-            underTest.onUserInteractionStarted(ShadeId.LEFT)
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the end of the interaction on the LEFT shade re-allows it.
-            underTest.onUserInteractionEnded(ShadeId.LEFT)
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-        }
-
-    @Test
-    fun collapseAll() =
-        testScope.runTest {
-            val underTest = create()
-            val isLeftShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
-            val isRightShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
-            val isSingleShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
-
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isLeftShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isRightShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isSingleShadeForceCollapsed)
-                .isFalse()
-
-            underTest.collapseAll()
-            assertThat(isLeftShadeForceCollapsed).isTrue()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isTrue()
-
-            // Receiving proxied input on that's not a tap gesture, on the left-hand side resets the
-            // "collapse all". Note that now the RIGHT shade is force-collapsed because we're
-            // interacting with the LEFT shade.
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 0f))
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-        }
-
-    @Test
-    fun onTapOutside_collapsesAll() =
-        testScope.runTest {
-            val underTest = create()
-            val isLeftShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
-            val isRightShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
-            val isSingleShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
-
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isLeftShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isRightShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isSingleShadeForceCollapsed)
-                .isFalse()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
-            assertThat(isLeftShadeForceCollapsed).isTrue()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isTrue()
-        }
-
-    @Test
-    fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
-        testScope.runTest {
-            val underTest = create()
-            val proxiedInput: ProxiedInputModel? by
-                collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
-            underTest.onUserInteractionStarted(shadeId = ShadeId.RIGHT)
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
-            assertThat(proxiedInput).isNull()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
-            assertThat(proxiedInput).isNull()
-
-            underTest.onUserInteractionEnded(shadeId = ShadeId.RIGHT)
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
-            assertThat(proxiedInput).isNotNull()
-        }
-
-    private fun create(): MultiShadeInteractor {
-        return create(
-            testScope = testScope,
-            context = context,
-            inputProxy = inputProxy,
-        )
-    }
-
-    companion object {
-        fun create(
-            testScope: TestScope,
-            context: Context,
-            inputProxy: MultiShadeInputProxy,
-        ): MultiShadeInteractor {
-            return MultiShadeInteractor(
-                applicationScope = testScope.backgroundScope,
-                repository =
-                    MultiShadeRepositoryTest.create(
-                        context = context,
-                        inputProxy = inputProxy,
-                    ),
-                inputProxy = inputProxy,
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
deleted file mode 100644
index 5890cbd..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
+++ /dev/null
@@ -1,530 +0,0 @@
-/*
- * 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.multishade.domain.interactor
-
-import android.view.MotionEvent
-import android.view.ViewConfiguration
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-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
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.data.repository.MultiShadeRepository
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.android.systemui.shade.ShadeController
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.currentTime
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
-
-    private lateinit var underTest: MultiShadeMotionEventInteractor
-
-    private lateinit var testScope: TestScope
-    private lateinit var motionEvents: MutableSet<MotionEvent>
-    private lateinit var repository: MultiShadeRepository
-    private lateinit var interactor: MultiShadeInteractor
-    private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
-    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
-    private lateinit var falsingManager: FalsingManagerFake
-    @Mock private lateinit var shadeController: ShadeController
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        testScope = TestScope()
-        motionEvents = mutableSetOf()
-
-        val inputProxy = MultiShadeInputProxy()
-        repository =
-            MultiShadeRepository(
-                applicationContext = context,
-                inputProxy = inputProxy,
-            )
-        interactor =
-            MultiShadeInteractor(
-                applicationScope = testScope.backgroundScope,
-                repository = repository,
-                inputProxy = inputProxy,
-            )
-        val featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.DUAL_SHADE, true)
-        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
-        falsingManager = FalsingManagerFake()
-
-        underTest =
-            MultiShadeMotionEventInteractor(
-                applicationContext = context,
-                applicationScope = testScope.backgroundScope,
-                multiShadeInteractor = interactor,
-                featureFlags = featureFlags,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractorFactory.create(
-                            scope = TestScope().backgroundScope,
-                            repository = keyguardTransitionRepository,
-                        )
-                        .keyguardTransitionInteractor,
-                falsingManager = falsingManager,
-                shadeController = shadeController,
-            )
-    }
-
-    @After
-    fun tearDown() {
-        motionEvents.forEach { motionEvent -> motionEvent.recycle() }
-    }
-
-    @Test
-    fun listenForIsAnyShadeExpanded_expanded_makesWindowViewVisible() =
-        testScope.runTest {
-            whenever(shadeController.isKeyguard).thenReturn(false)
-            repository.setExpansion(ShadeId.LEFT, 0.1f)
-            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
-            assertThat(expanded).isTrue()
-
-            verify(shadeController).makeExpandedVisible(anyBoolean())
-        }
-
-    @Test
-    fun listenForIsAnyShadeExpanded_collapsed_makesWindowViewInvisible() =
-        testScope.runTest {
-            whenever(shadeController.isKeyguard).thenReturn(false)
-            repository.setForceCollapseAll(true)
-            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
-            assertThat(expanded).isFalse()
-
-            verify(shadeController).makeExpandedInvisible()
-        }
-
-    @Test
-    fun listenForIsAnyShadeExpanded_collapsedOnKeyguard_makesWindowViewVisible() =
-        testScope.runTest {
-            whenever(shadeController.isKeyguard).thenReturn(true)
-            repository.setForceCollapseAll(true)
-            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
-            assertThat(expanded).isFalse()
-
-            verify(shadeController).makeExpandedVisible(anyBoolean())
-        }
-
-    @Test
-    fun shouldIntercept_initialDown_returnsFalse() =
-        testScope.runTest {
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))).isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_moveBelowTouchSlop_returnsFalse() =
-        testScope.runTest {
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop - 1f,
-                        )
-                    )
-                )
-                .isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlop_returnsTrue() =
-        testScope.runTest {
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isTrue()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlop_butHorizontalFirst_returnsFalse() =
-        testScope.runTest {
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            x = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_up_afterMovedAboveTouchSlop_returnsTrue() =
-        testScope.runTest {
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop + 1f))
-
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))).isTrue()
-        }
-
-    @Test
-    fun shouldIntercept_cancel_afterMovedAboveTouchSlop_returnsTrue() =
-        testScope.runTest {
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop + 1f))
-
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isTrue()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlopAndUp_butShadeExpanded_returnsFalse() =
-        testScope.runTest {
-            repository.setExpansion(ShadeId.LEFT, 0.1f)
-            runCurrent()
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))).isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlopAndCancel_butShadeExpanded_returnsFalse() =
-        testScope.runTest {
-            repository.setExpansion(ShadeId.LEFT, 0.1f)
-            runCurrent()
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlopAndUp_butBouncerShowing_returnsFalse() =
-        testScope.runTest {
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.PRIMARY_BOUNCER,
-                    value = 0.1f,
-                    transitionState = TransitionState.STARTED,
-                )
-            )
-            runCurrent()
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))).isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlopAndCancel_butBouncerShowing_returnsFalse() =
-        testScope.runTest {
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.PRIMARY_BOUNCER,
-                    value = 0.1f,
-                    transitionState = TransitionState.STARTED,
-                )
-            )
-            runCurrent()
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isFalse()
-        }
-
-    @Test
-    fun tap_doesNotSendProxiedInput() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))
-
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    @Test
-    fun dragBelowTouchSlop_doesNotSendProxiedInput() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop - 1f))
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))
-
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    @Test
-    fun dragShadeAboveTouchSlopAndUp() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(
-                motionEvent(
-                    MotionEvent.ACTION_DOWN,
-                    x = 100f, // left shade
-                )
-            )
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val yDragAmountPx = touchSlop + 1f
-            val moveEvent =
-                motionEvent(
-                    MotionEvent.ACTION_MOVE,
-                    x = 100f, // left shade
-                    y = yDragAmountPx,
-                )
-            assertThat(underTest.shouldIntercept(moveEvent)).isTrue()
-            underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput)
-                .isEqualTo(
-                    ProxiedInputModel.OnDrag(
-                        xFraction = 0.1f,
-                        yDragAmountPx = yDragAmountPx,
-                    )
-                )
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val upEvent = motionEvent(MotionEvent.ACTION_UP)
-            assertThat(underTest.shouldIntercept(upEvent)).isTrue()
-            underTest.onTouchEvent(upEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    @Test
-    fun dragShadeAboveTouchSlopAndCancel() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(
-                motionEvent(
-                    MotionEvent.ACTION_DOWN,
-                    x = 900f, // right shade
-                )
-            )
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val yDragAmountPx = touchSlop + 1f
-            val moveEvent =
-                motionEvent(
-                    MotionEvent.ACTION_MOVE,
-                    x = 900f, // right shade
-                    y = yDragAmountPx,
-                )
-            assertThat(underTest.shouldIntercept(moveEvent)).isTrue()
-            underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput)
-                .isEqualTo(
-                    ProxiedInputModel.OnDrag(
-                        xFraction = 0.9f,
-                        yDragAmountPx = yDragAmountPx,
-                    )
-                )
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val cancelEvent = motionEvent(MotionEvent.ACTION_CANCEL)
-            assertThat(underTest.shouldIntercept(cancelEvent)).isTrue()
-            underTest.onTouchEvent(cancelEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    @Test
-    fun dragUp_withUp_doesNotShowShade() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(
-                motionEvent(
-                    MotionEvent.ACTION_DOWN,
-                    x = 100f, // left shade
-                )
-            )
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val yDragAmountPx = -(touchSlop + 1f) // dragging up
-            val moveEvent =
-                motionEvent(
-                    MotionEvent.ACTION_MOVE,
-                    x = 100f, // left shade
-                    y = yDragAmountPx,
-                )
-            assertThat(underTest.shouldIntercept(moveEvent)).isFalse()
-            underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val upEvent = motionEvent(MotionEvent.ACTION_UP)
-            assertThat(underTest.shouldIntercept(upEvent)).isFalse()
-            underTest.onTouchEvent(upEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    @Test
-    fun dragUp_withCancel_falseTouch_showsThenHidesBouncer() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(
-                motionEvent(
-                    MotionEvent.ACTION_DOWN,
-                    x = 900f, // right shade
-                )
-            )
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val yDragAmountPx = -(touchSlop + 1f) // drag up
-            val moveEvent =
-                motionEvent(
-                    MotionEvent.ACTION_MOVE,
-                    x = 900f, // right shade
-                    y = yDragAmountPx,
-                )
-            assertThat(underTest.shouldIntercept(moveEvent)).isFalse()
-            underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            falsingManager.setIsFalseTouch(true)
-            val cancelEvent = motionEvent(MotionEvent.ACTION_CANCEL)
-            assertThat(underTest.shouldIntercept(cancelEvent)).isFalse()
-            underTest.onTouchEvent(cancelEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    private fun TestScope.motionEvent(
-        action: Int,
-        downTime: Long = currentTime,
-        eventTime: Long = currentTime,
-        x: Float = 0f,
-        y: Float = 0f,
-    ): MotionEvent {
-        val motionEvent = MotionEvent.obtain(downTime, eventTime, action, x, y, 0)
-        motionEvents.add(motionEvent)
-        return motionEvent
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt
deleted file mode 100644
index 8935309..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.multishade.shared.math
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@SmallTest
-@RunWith(JUnit4::class)
-class MathTest : SysuiTestCase() {
-
-    @Test
-    fun isZero_zero_true() {
-        assertThat(0f.isZero(epsilon = EPSILON)).isTrue()
-    }
-
-    @Test
-    fun isZero_belowPositiveEpsilon_true() {
-        assertThat((EPSILON * 0.999999f).isZero(epsilon = EPSILON)).isTrue()
-    }
-
-    @Test
-    fun isZero_aboveNegativeEpsilon_true() {
-        assertThat((EPSILON * -0.999999f).isZero(epsilon = EPSILON)).isTrue()
-    }
-
-    @Test
-    fun isZero_positiveEpsilon_false() {
-        assertThat(EPSILON.isZero(epsilon = EPSILON)).isFalse()
-    }
-
-    @Test
-    fun isZero_negativeEpsilon_false() {
-        assertThat((-EPSILON).isZero(epsilon = EPSILON)).isFalse()
-    }
-
-    @Test
-    fun isZero_positive_false() {
-        assertThat(1f.isZero(epsilon = EPSILON)).isFalse()
-    }
-
-    @Test
-    fun isZero_negative_false() {
-        assertThat((-1f).isZero(epsilon = EPSILON)).isFalse()
-    }
-
-    companion object {
-        private const val EPSILON = 0.0001f
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
deleted file mode 100644
index 0484515..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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.multishade.ui.viewmodel
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class MultiShadeViewModelTest : SysuiTestCase() {
-
-    private lateinit var testScope: TestScope
-    private lateinit var inputProxy: MultiShadeInputProxy
-
-    @Before
-    fun setUp() {
-        testScope = TestScope()
-        inputProxy = MultiShadeInputProxy()
-    }
-
-    @Test
-    fun scrim_whenDualShadeCollapsed() =
-        testScope.runTest {
-            val alpha = 0.5f
-            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
-            overrideResource(R.bool.dual_shade_enabled, true)
-
-            val underTest = create()
-            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
-            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
-
-            assertThat(scrimAlpha).isZero()
-            assertThat(isScrimEnabled).isFalse()
-        }
-
-    @Test
-    fun scrim_whenDualShadeExpanded() =
-        testScope.runTest {
-            val alpha = 0.5f
-            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
-            overrideResource(R.bool.dual_shade_enabled, true)
-            val underTest = create()
-            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
-            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
-            assertThat(scrimAlpha).isZero()
-            assertThat(isScrimEnabled).isFalse()
-
-            underTest.leftShade.onExpansionChanged(0.5f)
-            assertThat(scrimAlpha).isEqualTo(alpha * 0.5f)
-            assertThat(isScrimEnabled).isTrue()
-
-            underTest.rightShade.onExpansionChanged(1f)
-            assertThat(scrimAlpha).isEqualTo(alpha * 1f)
-            assertThat(isScrimEnabled).isTrue()
-        }
-
-    @Test
-    fun scrim_whenSingleShadeCollapsed() =
-        testScope.runTest {
-            val alpha = 0.5f
-            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
-            overrideResource(R.bool.dual_shade_enabled, false)
-
-            val underTest = create()
-            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
-            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
-
-            assertThat(scrimAlpha).isZero()
-            assertThat(isScrimEnabled).isFalse()
-        }
-
-    @Test
-    fun scrim_whenSingleShadeExpanded() =
-        testScope.runTest {
-            val alpha = 0.5f
-            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
-            overrideResource(R.bool.dual_shade_enabled, false)
-            val underTest = create()
-            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
-            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
-
-            underTest.singleShade.onExpansionChanged(0.95f)
-
-            assertThat(scrimAlpha).isZero()
-            assertThat(isScrimEnabled).isFalse()
-        }
-
-    private fun create(): MultiShadeViewModel {
-        return MultiShadeViewModel(
-            viewModelScope = testScope.backgroundScope,
-            interactor =
-                MultiShadeInteractorTest.create(
-                    testScope = testScope,
-                    context = context,
-                    inputProxy = inputProxy,
-                ),
-        )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
deleted file mode 100644
index e32aac5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 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.multishade.ui.viewmodel
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class ShadeViewModelTest : SysuiTestCase() {
-
-    private lateinit var testScope: TestScope
-    private lateinit var inputProxy: MultiShadeInputProxy
-    private var interactor: MultiShadeInteractor? = null
-
-    @Before
-    fun setUp() {
-        testScope = TestScope()
-        inputProxy = MultiShadeInputProxy()
-    }
-
-    @Test
-    fun isVisible_dualShadeConfig() =
-        testScope.runTest {
-            overrideResource(R.bool.dual_shade_enabled, true)
-            val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
-            val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
-            val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
-
-            assertThat(isLeftShadeVisible).isTrue()
-            assertThat(isRightShadeVisible).isTrue()
-            assertThat(isSingleShadeVisible).isFalse()
-        }
-
-    @Test
-    fun isVisible_singleShadeConfig() =
-        testScope.runTest {
-            overrideResource(R.bool.dual_shade_enabled, false)
-            val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
-            val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
-            val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
-
-            assertThat(isLeftShadeVisible).isFalse()
-            assertThat(isRightShadeVisible).isFalse()
-            assertThat(isSingleShadeVisible).isTrue()
-        }
-
-    @Test
-    fun isSwipingEnabled() =
-        testScope.runTest {
-            val underTest = create(ShadeId.LEFT)
-            val isSwipingEnabled: Boolean? by collectLastValue(underTest.isSwipingEnabled)
-            assertWithMessage("isSwipingEnabled should start as true!")
-                .that(isSwipingEnabled)
-                .isTrue()
-
-            // Need to collect proxied input so the flows become hot as the gesture cancelation code
-            // logic sits in side the proxiedInput flow for each shade.
-            collectLastValue(underTest.proxiedInput)
-            collectLastValue(create(ShadeId.RIGHT).proxiedInput)
-
-            // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
-            // the
-            // same shade.
-            inputProxy.onProxiedInput(
-                ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
-            )
-            assertThat(isSwipingEnabled).isFalse()
-
-            // Registering the end of the proxied interaction re-allows it.
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
-            assertThat(isSwipingEnabled).isTrue()
-
-            // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
-            // disallowing non-proxied input on the LEFT shade.
-            inputProxy.onProxiedInput(
-                ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
-            )
-            assertThat(isSwipingEnabled).isFalse()
-
-            // Registering the end of the interaction on the RIGHT shade re-allows it.
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
-            assertThat(isSwipingEnabled).isTrue()
-        }
-
-    @Test
-    fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
-        testScope.runTest {
-            val leftShade = create(ShadeId.LEFT)
-            val rightShade = create(ShadeId.RIGHT)
-            val isLeftShadeForceCollapsed: Boolean? by collectLastValue(leftShade.isForceCollapsed)
-            val isRightShadeForceCollapsed: Boolean? by
-                collectLastValue(rightShade.isForceCollapsed)
-            val isSingleShadeForceCollapsed: Boolean? by
-                collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
-
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isLeftShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isRightShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isSingleShadeForceCollapsed)
-                .isFalse()
-
-            // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
-            // shade.
-            rightShade.onDragStarted()
-            assertThat(isLeftShadeForceCollapsed).isTrue()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the end of the interaction on the RIGHT shade re-allows it.
-            rightShade.onDragEnded()
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
-            // shade.
-            leftShade.onDragStarted()
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the end of the interaction on the LEFT shade re-allows it.
-            leftShade.onDragEnded()
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-        }
-
-    @Test
-    fun onTapOutside_collapsesAll() =
-        testScope.runTest {
-            val isLeftShadeForceCollapsed: Boolean? by
-                collectLastValue(create(ShadeId.LEFT).isForceCollapsed)
-            val isRightShadeForceCollapsed: Boolean? by
-                collectLastValue(create(ShadeId.RIGHT).isForceCollapsed)
-            val isSingleShadeForceCollapsed: Boolean? by
-                collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
-
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isLeftShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isRightShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isSingleShadeForceCollapsed)
-                .isFalse()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
-            assertThat(isLeftShadeForceCollapsed).isTrue()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isTrue()
-        }
-
-    @Test
-    fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
-        testScope.runTest {
-            val underTest = create(ShadeId.RIGHT)
-            val proxiedInput: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
-            underTest.onDragStarted()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
-            assertThat(proxiedInput).isNull()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
-            assertThat(proxiedInput).isNull()
-
-            underTest.onDragEnded()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
-            assertThat(proxiedInput).isNotNull()
-        }
-
-    private fun create(
-        shadeId: ShadeId,
-    ): ShadeViewModel {
-        return ShadeViewModel(
-            viewModelScope = testScope.backgroundScope,
-            shadeId = shadeId,
-            interactor = interactor
-                    ?: MultiShadeInteractorTest.create(
-                            testScope = testScope,
-                            context = context,
-                            inputProxy = inputProxy,
-                        )
-                        .also { interactor = it },
-        )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 25d494c..cbfad56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -94,6 +94,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
@@ -467,6 +468,7 @@
         when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
         return spy(new NavigationBar(
                 mNavigationBarView,
+                mock(ShadeController.class),
                 mNavigationBarFrame,
                 null,
                 context,
@@ -485,7 +487,7 @@
                 Optional.of(mock(Pip.class)),
                 Optional.of(mock(Recents.class)),
                 () -> Optional.of(mCentralSurfaces),
-                mock(ShadeController.class),
+                mock(ShadeViewController.class),
                 mock(NotificationRemoteInputManager.class),
                 mock(NotificationShadeDepthController.class),
                 mHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
new file mode 100644
index 0000000..0a8c0ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
@@ -0,0 +1,825 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.content.pm.UserInfo
+import android.os.Process.SYSTEM_UID
+import android.os.UserHandle
+import android.permission.PermissionGroupUsage
+import android.permission.PermissionManager
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.appops.AppOpsController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.privacy.logging.PrivacyLogger
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class PrivacyDialogControllerV2Test : SysuiTestCase() {
+
+    companion object {
+        private const val USER_ID = 0
+        private const val ENT_USER_ID = 10
+
+        private const val TEST_PACKAGE_NAME = "test package name"
+        private const val TEST_ATTRIBUTION_TAG = "test attribution tag"
+        private const val TEST_PROXY_LABEL = "test proxy label"
+
+        private const val PERM_CAMERA = android.Manifest.permission_group.CAMERA
+        private const val PERM_MICROPHONE = android.Manifest.permission_group.MICROPHONE
+        private const val PERM_LOCATION = android.Manifest.permission_group.LOCATION
+
+        private val TEST_INTENT = Intent("test_intent_action")
+    }
+
+    @Mock
+    private lateinit var dialog: PrivacyDialogV2
+    @Mock
+    private lateinit var permissionManager: PermissionManager
+    @Mock
+    private lateinit var packageManager: PackageManager
+    @Mock
+    private lateinit var privacyItemController: PrivacyItemController
+    @Mock
+    private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
+    @Mock
+    private lateinit var privacyLogger: PrivacyLogger
+    @Mock
+    private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var appOpsController: AppOpsController
+    @Captor
+    private lateinit var dialogDismissedCaptor: ArgumentCaptor<PrivacyDialogV2.OnDialogDismissed>
+    @Captor
+    private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback>
+    @Captor
+    private lateinit var intentCaptor: ArgumentCaptor<Intent>
+    @Mock
+    private lateinit var uiEventLogger: UiEventLogger
+    @Mock
+    private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+
+    private val backgroundExecutor = FakeExecutor(FakeSystemClock())
+    private val uiExecutor = FakeExecutor(FakeSystemClock())
+    private lateinit var controller: PrivacyDialogControllerV2
+    private var nextUid: Int = 0
+
+    private val dialogProvider = object : PrivacyDialogControllerV2.DialogProvider {
+        var list: List<PrivacyDialogV2.PrivacyElement>? = null
+        var manageApp: ((String, Int, Intent) -> Unit)? = null
+        var closeApp: ((String, Int) -> Unit)? = null
+        var openPrivacyDashboard: (() -> Unit)? = null
+
+        override fun makeDialog(
+            context: Context,
+            list: List<PrivacyDialogV2.PrivacyElement>,
+            manageApp: (String, Int, Intent) -> Unit,
+            closeApp: (String, Int) -> Unit,
+            openPrivacyDashboard: () -> Unit
+        ): PrivacyDialogV2 {
+            this.list = list
+            this.manageApp = manageApp
+            this.closeApp = closeApp
+            this.openPrivacyDashboard = openPrivacyDashboard
+            return dialog
+        }
+    }
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        nextUid = 0
+        setUpDefaultMockResponses()
+
+        controller = PrivacyDialogControllerV2(
+                permissionManager,
+                packageManager,
+                privacyItemController,
+                userTracker,
+                activityStarter,
+                backgroundExecutor,
+                uiExecutor,
+                privacyLogger,
+                keyguardStateController,
+                appOpsController,
+                uiEventLogger,
+                dialogLaunchAnimator,
+                dialogProvider
+        )
+    }
+
+    @After
+    fun tearDown() {
+        FakeExecutor.exhaustExecutors(uiExecutor, backgroundExecutor)
+        dialogProvider.list = null
+        dialogProvider.manageApp = null
+        dialogProvider.closeApp = null
+        dialogProvider.openPrivacyDashboard = null
+    }
+
+    @Test
+    fun testMicMutedParameter() {
+        `when`(appOpsController.isMicMuted).thenReturn(true)
+        controller.showDialog(context)
+        backgroundExecutor.runAllReady()
+
+        verify(permissionManager).getIndicatorAppOpUsageData(true)
+    }
+
+    @Test
+    fun testPermissionManagerOnlyCalledInBackgroundThread() {
+        controller.showDialog(context)
+        verify(permissionManager, never()).getIndicatorAppOpUsageData(anyBoolean())
+        backgroundExecutor.runAllReady()
+        verify(permissionManager).getIndicatorAppOpUsageData(anyBoolean())
+    }
+
+    @Test
+    fun testPackageManagerOnlyCalledInBackgroundThread() {
+        val usage = createMockPermGroupUsage()
+        `when`(usage.isPhoneCall).thenReturn(false)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
+        backgroundExecutor.runAllReady()
+        verify(packageManager, atLeastOnce())
+                .getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
+    }
+
+    @Test
+    fun testShowDialogShowsDialogWithoutView() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialogLaunchAnimator, never()).showFromView(any(), any(), any(), anyBoolean())
+        verify(dialog).show()
+    }
+
+    @Test
+    fun testShowDialogShowsDialogWithView() {
+        val view = View(context)
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context, view)
+        exhaustExecutors()
+
+        verify(dialogLaunchAnimator).showFromView(dialog, view)
+        verify(dialog, never()).show()
+    }
+
+    @Test
+    fun testDontShowEmptyDialog() {
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog, never()).show()
+    }
+
+    @Test
+    fun testHideDialogDismissesDialogIfShown() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        controller.dismissDialog()
+        verify(dialog).dismiss()
+    }
+
+    @Test
+    fun testHideDialogNoopIfNotShown() {
+        controller.dismissDialog()
+        verify(dialog, never()).dismiss()
+    }
+
+    @Test
+    fun testHideDialogNoopAfterDismissed() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog).addOnDismissListener(capture(dialogDismissedCaptor))
+
+        dialogDismissedCaptor.value.onDialogDismissed()
+        controller.dismissDialog()
+        verify(dialog, never()).dismiss()
+    }
+
+    @Test
+    fun testShowForAllUsers() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+
+        exhaustExecutors()
+        verify(dialog).setShowForAllUsers(true)
+    }
+
+    @Test
+    fun testSingleElementInList() {
+        val usage = createMockPermGroupUsage(
+                packageName = TEST_PACKAGE_NAME,
+                uid = generateUidForUser(USER_ID),
+                permissionGroupName = PERM_CAMERA,
+                lastAccessTimeMillis = 5L,
+                isActive = true,
+                isPhoneCall = false,
+                attributionTag = null,
+                proxyLabel = TEST_PROXY_LABEL
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(list.get(0).type).isEqualTo(PrivacyType.TYPE_CAMERA)
+            assertThat(list.get(0).packageName).isEqualTo(TEST_PACKAGE_NAME)
+            assertThat(list.get(0).userId).isEqualTo(USER_ID)
+            assertThat(list.get(0).applicationName).isEqualTo(TEST_PACKAGE_NAME)
+            assertThat(list.get(0).attributionTag).isNull()
+            assertThat(list.get(0).attributionLabel).isNull()
+            assertThat(list.get(0).proxyLabel).isEqualTo(TEST_PROXY_LABEL)
+            assertThat(list.get(0).lastActiveTimestamp).isEqualTo(5L)
+            assertThat(list.get(0).isActive).isTrue()
+            assertThat(list.get(0).isPhoneCall).isFalse()
+            assertThat(list.get(0).isService).isFalse()
+            assertThat(list.get(0).permGroupName).isEqualTo(PERM_CAMERA)
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+                    .isTrue()
+        }
+    }
+
+    private fun isIntentEqual(actual: Intent, expected: Intent): Boolean {
+        return actual.action == expected.action &&
+                actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME) ==
+                expected.getStringExtra(Intent.EXTRA_PACKAGE_NAME) &&
+                actual.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle ==
+                expected.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle
+    }
+
+    @Test
+    fun testTwoElementsDifferentType_sorted() {
+        val usage_camera = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_camera",
+                permissionGroupName = PERM_CAMERA
+        )
+        val usage_microphone = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_microphone",
+                permissionGroupName = PERM_MICROPHONE
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_microphone, usage_camera)
+        )
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(list).hasSize(2)
+            assertThat(list.get(0).type.compareTo(list.get(1).type)).isLessThan(0)
+        }
+    }
+
+    @Test
+    fun testTwoElementsSameType_oneActive() {
+        val usage_active = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_active",
+                isActive = true
+        )
+        val usage_recent = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_recent",
+                isActive = false
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_recent, usage_active)
+        )
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        assertThat(dialogProvider.list).hasSize(1)
+        assertThat(dialogProvider.list?.get(0)?.isActive).isTrue()
+    }
+
+    @Test
+    fun testTwoElementsSameType_twoActive() {
+        val usage_active = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_active",
+                isActive = true,
+                lastAccessTimeMillis = 0L
+        )
+        val usage_active_moreRecent = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_active_recent",
+                isActive = true,
+                lastAccessTimeMillis = 1L
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_active, usage_active_moreRecent)
+        )
+        controller.showDialog(context)
+        exhaustExecutors()
+        assertThat(dialogProvider.list).hasSize(2)
+        assertThat(dialogProvider.list?.get(0)?.lastActiveTimestamp).isEqualTo(1L)
+        assertThat(dialogProvider.list?.get(1)?.lastActiveTimestamp).isEqualTo(0L)
+    }
+
+    @Test
+    fun testManyElementsSameType_bothRecent() {
+        val usage_recent = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_recent",
+                isActive = false,
+                lastAccessTimeMillis = 0L
+        )
+        val usage_moreRecent = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_moreRecent",
+                isActive = false,
+                lastAccessTimeMillis = 1L
+        )
+        val usage_mostRecent = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_mostRecent",
+                isActive = false,
+                lastAccessTimeMillis = 2L
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_recent, usage_mostRecent, usage_moreRecent)
+        )
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        assertThat(dialogProvider.list).hasSize(1)
+        assertThat(dialogProvider.list?.get(0)?.lastActiveTimestamp).isEqualTo(2L)
+    }
+
+    @Test
+    fun testMicAndCameraDisabled() {
+        val usage_camera = createMockPermGroupUsage(
+                permissionGroupName = PERM_CAMERA
+        )
+        val usage_microphone = createMockPermGroupUsage(
+                permissionGroupName = PERM_MICROPHONE
+        )
+        val usage_location = createMockPermGroupUsage(
+                permissionGroupName = PERM_LOCATION
+        )
+
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_camera, usage_location, usage_microphone)
+        )
+        `when`(privacyItemController.micCameraAvailable).thenReturn(false)
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        assertThat(dialogProvider.list).hasSize(1)
+        assertThat(dialogProvider.list?.get(0)?.type).isEqualTo(PrivacyType.TYPE_LOCATION)
+    }
+
+    @Test
+    fun testLocationDisabled() {
+        val usage_camera = createMockPermGroupUsage(
+                permissionGroupName = PERM_CAMERA
+        )
+        val usage_microphone = createMockPermGroupUsage(
+                permissionGroupName = PERM_MICROPHONE
+        )
+        val usage_location = createMockPermGroupUsage(
+                permissionGroupName = PERM_LOCATION
+        )
+
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_camera, usage_location, usage_microphone)
+        )
+        `when`(privacyItemController.locationAvailable).thenReturn(false)
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        assertThat(dialogProvider.list).hasSize(2)
+        dialogProvider.list?.forEach {
+            assertThat(it.type).isNotEqualTo(PrivacyType.TYPE_LOCATION)
+        }
+    }
+
+    @Test
+    fun testAllIndicatorsAvailable() {
+        val usage_camera = createMockPermGroupUsage(
+                permissionGroupName = PERM_CAMERA
+        )
+        val usage_microphone = createMockPermGroupUsage(
+                permissionGroupName = PERM_MICROPHONE
+        )
+        val usage_location = createMockPermGroupUsage(
+                permissionGroupName = PERM_LOCATION
+        )
+
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_camera, usage_location, usage_microphone)
+        )
+        `when`(privacyItemController.micCameraAvailable).thenReturn(true)
+        `when`(privacyItemController.locationAvailable).thenReturn(true)
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        assertThat(dialogProvider.list).hasSize(3)
+    }
+
+    @Test
+    fun testNoIndicatorsAvailable() {
+        val usage_camera = createMockPermGroupUsage(
+                permissionGroupName = PERM_CAMERA
+        )
+        val usage_microphone = createMockPermGroupUsage(
+                permissionGroupName = PERM_MICROPHONE
+        )
+        val usage_location = createMockPermGroupUsage(
+                permissionGroupName = PERM_LOCATION
+        )
+
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_camera, usage_location, usage_microphone)
+        )
+        `when`(privacyItemController.micCameraAvailable).thenReturn(false)
+        `when`(privacyItemController.locationAvailable).thenReturn(false)
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog, never()).show()
+    }
+
+    @Test
+    fun testNotCurrentUser() {
+        val usage_other = createMockPermGroupUsage(
+                uid = generateUidForUser(ENT_USER_ID + 1)
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+                .thenReturn(listOf(usage_other))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog, never()).show()
+    }
+
+    @Test
+    fun testStartActivitySuccess() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.manageApp?.invoke(TEST_PACKAGE_NAME, USER_ID, TEST_INTENT)
+        verify(activityStarter).startActivity(any(), eq(true), capture(activityStartedCaptor))
+
+        activityStartedCaptor.value.onActivityStarted(ActivityManager.START_DELIVERED_TO_TOP)
+
+        verify(dialog).dismiss()
+    }
+
+    @Test
+    fun testStartActivityFailure() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.manageApp?.invoke(TEST_PACKAGE_NAME, USER_ID, TEST_INTENT)
+        verify(activityStarter).startActivity(any(), eq(true), capture(activityStartedCaptor))
+
+        activityStartedCaptor.value.onActivityStarted(ActivityManager.START_ABORTED)
+
+        verify(dialog, never()).dismiss()
+    }
+
+    @Test
+    fun testCallOnSecondaryUser() {
+        // Calls happen in
+        val usage = createMockPermGroupUsage(uid = SYSTEM_UID, isPhoneCall = true)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        `when`(userTracker.userProfiles).thenReturn(listOf(
+                UserInfo(ENT_USER_ID, "", 0)
+        ))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog).show()
+    }
+
+    @Test
+    fun testManageAppLogs() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.manageApp?.invoke(TEST_PACKAGE_NAME, USER_ID, TEST_INTENT)
+        verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+                USER_ID, TEST_PACKAGE_NAME)
+    }
+
+    @Test
+    fun testCloseAppLogs() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.closeApp?.invoke(TEST_PACKAGE_NAME, USER_ID)
+        verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP,
+                USER_ID, TEST_PACKAGE_NAME)
+    }
+
+    @Test
+    fun testOpenPrivacyDashboardLogs() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.openPrivacyDashboard?.invoke()
+        verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_CLICK_TO_PRIVACY_DASHBOARD)
+    }
+
+    @Test
+    fun testDismissedDialogLogs() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog).addOnDismissListener(capture(dialogDismissedCaptor))
+
+        dialogDismissedCaptor.value.onDialogDismissed()
+
+        controller.dismissDialog()
+
+        verify(uiEventLogger, times(1)).log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
+    }
+
+    @Test
+    fun testDefaultIntent() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+                    .isTrue()
+            assertThat(list.get(0).isService).isFalse()
+        }
+    }
+
+    @Test
+    fun testDefaultIntentOnEnterpriseUser() {
+        val usage =
+            createMockPermGroupUsage(
+                uid = generateUidForUser(ENT_USER_ID),
+            )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(
+                        TEST_PACKAGE_NAME, ENT_USER_ID)))
+                        .isTrue()
+            assertThat(list.get(0).isService).isFalse()
+        }
+    }
+
+    @Test
+    fun testDefaultIntentOnInvalidAttributionTag() {
+        val usage = createMockPermGroupUsage(
+                attributionTag = "INVALID_ATTRIBUTION_TAG",
+                proxyLabel = TEST_PROXY_LABEL
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+                    .isTrue()
+            assertThat(list.get(0).isService).isFalse()
+        }
+    }
+
+    @Test
+    fun testServiceIntentOnCorrectSubAttribution() {
+        val usage = createMockPermGroupUsage(
+                attributionTag = TEST_ATTRIBUTION_TAG,
+                attributionLabel = "TEST_LABEL"
+        )
+
+        val activityInfo = createMockActivityInfo()
+        val resolveInfo = createMockResolveInfo(activityInfo)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
+                .thenAnswer { resolveInfo }
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            val navigationIntent = list.get(0).navigationIntent!!
+            assertThat(navigationIntent.action).isEqualTo(Intent.ACTION_MANAGE_PERMISSION_USAGE)
+            assertThat(navigationIntent.getStringExtra(Intent.EXTRA_PERMISSION_GROUP_NAME))
+                    .isEqualTo(PERM_CAMERA)
+            assertThat(navigationIntent.getStringArrayExtra(Intent.EXTRA_ATTRIBUTION_TAGS))
+                    .isEqualTo(arrayOf(TEST_ATTRIBUTION_TAG.toString()))
+            assertThat(navigationIntent.getBooleanExtra(Intent.EXTRA_SHOWING_ATTRIBUTION, false))
+                    .isTrue()
+            assertThat(list.get(0).isService).isTrue()
+        }
+    }
+
+    @Test
+    fun testDefaultIntentOnMissingAttributionLabel() {
+        val usage = createMockPermGroupUsage(
+                attributionTag = TEST_ATTRIBUTION_TAG
+        )
+
+        val activityInfo = createMockActivityInfo()
+        val resolveInfo = createMockResolveInfo(activityInfo)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
+                .thenAnswer { resolveInfo }
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+                    .isTrue()
+            assertThat(list.get(0).isService).isFalse()
+        }
+    }
+
+    @Test
+    fun testDefaultIntentOnIncorrectPermission() {
+        val usage = createMockPermGroupUsage(
+                attributionTag = TEST_ATTRIBUTION_TAG
+        )
+
+        val activityInfo = createMockActivityInfo(
+                permission = "INCORRECT_PERMISSION"
+        )
+        val resolveInfo = createMockResolveInfo(activityInfo)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
+                .thenAnswer { resolveInfo }
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+                    .isTrue()
+            assertThat(list.get(0).isService).isFalse()
+        }
+    }
+
+    private fun exhaustExecutors() {
+        FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
+    }
+
+    private fun setUpDefaultMockResponses() {
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(emptyList())
+        `when`(appOpsController.isMicMuted).thenReturn(false)
+
+        `when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenAnswer { FakeApplicationInfo(it.getArgument(0)) }
+
+        `when`(privacyItemController.locationAvailable).thenReturn(true)
+        `when`(privacyItemController.micCameraAvailable).thenReturn(true)
+
+        `when`(userTracker.userProfiles).thenReturn(listOf(
+                UserInfo(USER_ID, "", 0),
+                UserInfo(ENT_USER_ID, "", UserInfo.FLAG_MANAGED_PROFILE)
+        ))
+
+        `when`(keyguardStateController.isUnlocked).thenReturn(true)
+    }
+
+    private class FakeApplicationInfo(val label: CharSequence) : ApplicationInfo() {
+        override fun loadLabel(pm: PackageManager): CharSequence {
+            return label
+        }
+    }
+
+    private fun generateUidForUser(user: Int): Int {
+        return user * UserHandle.PER_USER_RANGE + nextUid++
+    }
+
+    private fun createMockResolveInfo(
+        activityInfo: ActivityInfo? = null
+    ): ResolveInfo {
+        val resolveInfo = mock(ResolveInfo::class.java)
+        resolveInfo.activityInfo = activityInfo
+        return resolveInfo
+    }
+
+    private fun createMockActivityInfo(
+        permission: String = android.Manifest.permission.START_VIEW_PERMISSION_USAGE,
+        className: String = "TEST_CLASS_NAME"
+    ): ActivityInfo {
+        val activityInfo = mock(ActivityInfo::class.java)
+        activityInfo.permission = permission
+        activityInfo.name = className
+        return activityInfo
+    }
+
+    private fun createMockPermGroupUsage(
+        packageName: String = TEST_PACKAGE_NAME,
+        uid: Int = generateUidForUser(USER_ID),
+        permissionGroupName: String = PERM_CAMERA,
+        lastAccessTimeMillis: Long = 0L,
+        isActive: Boolean = false,
+        isPhoneCall: Boolean = false,
+        attributionTag: CharSequence? = null,
+        attributionLabel: CharSequence? = null,
+        proxyLabel: CharSequence? = null
+    ): PermissionGroupUsage {
+        val usage = mock(PermissionGroupUsage::class.java)
+        `when`(usage.packageName).thenReturn(packageName)
+        `when`(usage.uid).thenReturn(uid)
+        `when`(usage.permissionGroupName).thenReturn(permissionGroupName)
+        `when`(usage.lastAccessTimeMillis).thenReturn(lastAccessTimeMillis)
+        `when`(usage.isActive).thenReturn(isActive)
+        `when`(usage.isPhoneCall).thenReturn(isPhoneCall)
+        `when`(usage.attributionTag).thenReturn(attributionTag)
+        `when`(usage.attributionLabel).thenReturn(attributionLabel)
+        `when`(usage.proxyLabel).thenReturn(proxyLabel)
+        return usage
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt
new file mode 100644
index 0000000..f4644a5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class PrivacyDialogV2Test : SysuiTestCase() {
+
+    companion object {
+        private const val TEST_PACKAGE_NAME = "test_pkg"
+        private const val TEST_USER_ID = 0
+        private const val TEST_PERM_GROUP = "test_perm_group"
+
+        private val TEST_INTENT = Intent("test_intent_action")
+
+        private fun createPrivacyElement(
+            type: PrivacyType = PrivacyType.TYPE_MICROPHONE,
+            packageName: String = TEST_PACKAGE_NAME,
+            userId: Int = TEST_USER_ID,
+            applicationName: CharSequence = "App",
+            attributionTag: CharSequence? = null,
+            attributionLabel: CharSequence? = null,
+            proxyLabel: CharSequence? = null,
+            lastActiveTimestamp: Long = 0L,
+            isActive: Boolean = false,
+            isPhoneCall: Boolean = false,
+            isService: Boolean = false,
+            permGroupName: String = TEST_PERM_GROUP,
+            navigationIntent: Intent = TEST_INTENT
+        ) =
+            PrivacyDialogV2.PrivacyElement(
+                type,
+                packageName,
+                userId,
+                applicationName,
+                attributionTag,
+                attributionLabel,
+                proxyLabel,
+                lastActiveTimestamp,
+                isActive,
+                isPhoneCall,
+                isService,
+                permGroupName,
+                navigationIntent
+            )
+    }
+
+    @Mock private lateinit var manageApp: (String, Int, Intent) -> Unit
+    @Mock private lateinit var closeApp: (String, Int) -> Unit
+    @Mock private lateinit var openPrivacyDashboard: () -> Unit
+    private lateinit var dialog: PrivacyDialogV2
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @After
+    fun teardown() {
+        if (this::dialog.isInitialized) {
+            dialog.dismiss()
+        }
+    }
+
+    @Test
+    fun testManageAppCalledWithCorrectParams() {
+        val list = listOf(createPrivacyElement())
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+
+        dialog.requireViewById<View>(R.id.privacy_dialog_manage_app_button).callOnClick()
+
+        verify(manageApp).invoke(TEST_PACKAGE_NAME, TEST_USER_ID, TEST_INTENT)
+    }
+
+    @Test
+    fun testCloseAppCalledWithCorrectParams() {
+        val list = listOf(createPrivacyElement(isActive = true))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+
+        dialog.requireViewById<View>(R.id.privacy_dialog_close_app_button).callOnClick()
+
+        verify(closeApp).invoke(TEST_PACKAGE_NAME, TEST_USER_ID)
+    }
+
+    @Test
+    fun testCloseAppMissingForService() {
+        val list = listOf(createPrivacyElement(isActive = true, isService = true))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.findViewById<View>(R.id.privacy_dialog_manage_app_button)).isNotNull()
+        assertThat(dialog.findViewById<View>(R.id.privacy_dialog_close_app_button)).isNull()
+    }
+
+    @Test
+    fun testMoreButton() {
+        dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+
+        dialog.requireViewById<View>(R.id.privacy_dialog_more_button).callOnClick()
+
+        verify(openPrivacyDashboard).invoke()
+    }
+
+    @Test
+    fun testCloseButton() {
+        dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard)
+        val dismissListener = mock(PrivacyDialogV2.OnDialogDismissed::class.java)
+        dialog.addOnDismissListener(dismissListener)
+        dialog.show()
+        verify(dismissListener, never()).onDialogDismissed()
+
+        dialog.requireViewById<View>(R.id.privacy_dialog_close_button).callOnClick()
+
+        verify(dismissListener).onDialogDismissed()
+    }
+
+    @Test
+    fun testDismissListenerCalledOnDismiss() {
+        dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard)
+        val dismissListener = mock(PrivacyDialogV2.OnDialogDismissed::class.java)
+        dialog.addOnDismissListener(dismissListener)
+        dialog.show()
+        verify(dismissListener, never()).onDialogDismissed()
+
+        dialog.dismiss()
+
+        verify(dismissListener).onDialogDismissed()
+    }
+
+    @Test
+    fun testDismissListenerCalledImmediatelyIfDialogAlreadyDismissed() {
+        dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard)
+        val dismissListener = mock(PrivacyDialogV2.OnDialogDismissed::class.java)
+        dialog.show()
+        dialog.dismiss()
+
+        dialog.addOnDismissListener(dismissListener)
+
+        verify(dismissListener).onDialogDismissed()
+    }
+
+    @Test
+    fun testCorrectNumElements() {
+        val list =
+            listOf(
+                createPrivacyElement(type = PrivacyType.TYPE_CAMERA, isActive = true),
+                createPrivacyElement()
+            )
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(
+                dialog.requireViewById<ViewGroup>(R.id.privacy_dialog_items_container).childCount
+            )
+            .isEqualTo(2)
+    }
+
+    @Test
+    fun testHeaderText() {
+        val list = listOf(createPrivacyElement())
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_title).text)
+            .isEqualTo(TEST_PERM_GROUP)
+    }
+
+    @Test
+    fun testUsingText() {
+        val list = listOf(createPrivacyElement(type = PrivacyType.TYPE_CAMERA, isActive = true))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("In use by App")
+    }
+
+    @Test
+    fun testRecentText() {
+        val list = listOf(createPrivacyElement())
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("Recently used by App")
+    }
+
+    @Test
+    fun testPhoneCall() {
+        val list = listOf(createPrivacyElement(isPhoneCall = true))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("Recently used in phone call")
+    }
+
+    @Test
+    fun testPhoneCallNotClickable() {
+        val list = listOf(createPrivacyElement(isPhoneCall = true))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<View>(R.id.privacy_dialog_item_card).isClickable)
+            .isFalse()
+        assertThat(
+                dialog
+                    .requireViewById<View>(R.id.privacy_dialog_item_header_expand_toggle)
+                    .visibility
+            )
+            .isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun testProxyLabel() {
+        val list = listOf(createPrivacyElement(proxyLabel = "proxy label"))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("Recently used by App (proxy label)")
+    }
+
+    @Test
+    fun testSubattribution() {
+        val list =
+            listOf(
+                createPrivacyElement(
+                    attributionLabel = "For subattribution",
+                    isActive = true,
+                    isService = true
+                )
+            )
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("In use by App (For subattribution)")
+    }
+
+    @Test
+    fun testSubattributionAndProxyLabel() {
+        val list =
+            listOf(
+                createPrivacyElement(
+                    attributionLabel = "For subattribution",
+                    proxyLabel = "proxy label",
+                    isActive = true
+                )
+            )
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("In use by App (For subattribution \u2022 proxy label)")
+    }
+
+    @Test
+    fun testDialogHasTitle() {
+        val list = listOf(createPrivacyElement())
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+
+        assertThat(dialog.window?.attributes?.title).isEqualTo("Microphone & Camera")
+    }
+
+    @Test
+    fun testDialogIsFullscreen() {
+        dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+
+        assertThat(dialog.window?.attributes?.width).isEqualTo(MATCH_PARENT)
+        assertThat(dialog.window?.attributes?.height).isEqualTo(MATCH_PARENT)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
index 3620233..fa02e8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
@@ -13,9 +13,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.privacy.PrivacyDialogController
+import com.android.systemui.privacy.PrivacyDialogControllerV2
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.statusbar.phone.StatusIconContainer
@@ -24,6 +27,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Before
@@ -54,6 +58,8 @@
     @Mock
     private lateinit var privacyDialogController: PrivacyDialogController
     @Mock
+    private lateinit var privacyDialogControllerV2: PrivacyDialogControllerV2
+    @Mock
     private lateinit var privacyLogger: PrivacyLogger
     @Mock
     private lateinit var iconContainer: StatusIconContainer
@@ -69,6 +75,8 @@
     private lateinit var safetyCenterManager: SafetyCenterManager
     @Mock
     private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
 
     private val uiExecutor = FakeExecutor(FakeSystemClock())
     private val backgroundExecutor = FakeExecutor(FakeSystemClock())
@@ -94,6 +102,7 @@
                 uiEventLogger,
                 privacyChip,
                 privacyDialogController,
+                privacyDialogControllerV2,
                 privacyLogger,
                 iconContainer,
                 permissionManager,
@@ -103,7 +112,8 @@
                 appOpsController,
                 broadcastDispatcher,
                 safetyCenterManager,
-                deviceProvisionedController
+                deviceProvisionedController,
+                featureFlags
         )
 
         backgroundExecutor.runAllReady()
@@ -154,17 +164,34 @@
     }
 
     @Test
-    fun testPrivacyChipClicked() {
+    fun testPrivacyChipClickedWhenNewDialogDisabledAndSafetyCenterDisabled() {
+        whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(false)
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
         controller.onParentVisible()
         val captor = argumentCaptor<View.OnClickListener>()
         verify(privacyChip).setOnClickListener(capture(captor))
         captor.value.onClick(privacyChip)
         verify(privacyDialogController).showDialog(any(Context::class.java))
+        verify(privacyDialogControllerV2, never())
+            .showDialog(any(Context::class.java), any(View::class.java))
     }
 
     @Test
-    fun testSafetyCenterFlag() {
+    fun testPrivacyChipClickedWhenNewDialogEnabledAndSafetyCenterDisabled() {
+        whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(true)
+        whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
+        controller.onParentVisible()
+        val captor = argumentCaptor<View.OnClickListener>()
+        verify(privacyChip).setOnClickListener(capture(captor))
+        captor.value.onClick(privacyChip)
+        verify(privacyDialogController).showDialog(any(Context::class.java))
+        verify(privacyDialogControllerV2, never())
+                .showDialog(any(Context::class.java), any(View::class.java))
+    }
+
+    @Test
+    fun testPrivacyChipClickedWhenNewDialogDisabledAndSafetyCenterEnabled() {
+        whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(false)
         val receiverCaptor = argumentCaptor<BroadcastReceiver>()
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
         verify(broadcastDispatcher).registerReceiver(capture(receiverCaptor),
@@ -179,6 +206,28 @@
         verify(privacyChip).setOnClickListener(capture(captor))
         captor.value.onClick(privacyChip)
         verify(privacyDialogController, never()).showDialog(any(Context::class.java))
+        verify(privacyDialogControllerV2, never())
+            .showDialog(any(Context::class.java), any(View::class.java))
+    }
+
+    @Test
+    fun testPrivacyChipClickedWhenNewDialogEnabledAndSafetyCenterEnabled() {
+        whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(true)
+        val receiverCaptor = argumentCaptor<BroadcastReceiver>()
+        whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
+        verify(broadcastDispatcher).registerReceiver(capture(receiverCaptor),
+                any(), any(), nullable(), anyInt(), nullable())
+        receiverCaptor.value.onReceive(
+                context,
+                Intent(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED)
+        )
+        backgroundExecutor.runAllReady()
+        controller.onParentVisible()
+        val captor = argumentCaptor<View.OnClickListener>()
+        verify(privacyChip).setOnClickListener(capture(captor))
+        captor.value.onClick(privacyChip)
+        verify(privacyDialogControllerV2).showDialog(any(Context::class.java), eq(privacyChip))
+        verify(privacyDialogController, never()).showDialog(any(Context::class.java))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 5b30687..3e20511 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -31,6 +31,7 @@
 
 import android.animation.Animator;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
@@ -90,6 +91,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 @SmallTest
@@ -182,6 +184,8 @@
     private List<WifiEntry> mAccessPoints = new ArrayList<>();
     private List<WifiEntry> mWifiEntries = new ArrayList<>();
 
+    private Configuration mConfig;
+
     @Before
     public void setUp() {
         mStaticMockSession = mockitoSession()
@@ -226,11 +230,17 @@
         mInternetDialogController.mActivityStarter = mActivityStarter;
         mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
         mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, false);
+
+        mConfig = new Configuration(mContext.getResources().getConfiguration());
+        Configuration c2 = new Configuration(mConfig);
+        c2.setLocale(Locale.US);
+        mContext.getResources().updateConfiguration(c2, null);
     }
 
     @After
     public void tearDown() {
         mStaticMockSession.finishMocking();
+        mContext.getResources().updateConfiguration(mConfig, null);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 355c4b6..6bb13ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.recents.OverviewProxyService.ACTION_QUICKSTEP
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shared.recents.IOverviewProxy
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK
 import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_ASLEEP
@@ -93,6 +94,7 @@
     @Mock private lateinit var shellInterface: ShellInterface
     @Mock private lateinit var navBarController: NavigationBarController
     @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var navModeController: NavigationModeController
     @Mock private lateinit var statusBarWinController: NotificationShadeWindowController
     @Mock private lateinit var userTracker: UserTracker
@@ -132,6 +134,7 @@
                 shellInterface,
                 Lazy { navBarController },
                 Lazy { Optional.of(centralSurfaces) },
+                Lazy { shadeViewController },
                 navModeController,
                 statusBarWinController,
                 sysUiState,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
index 5638d70..6f6c5a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.scene.domain.startable
 
+import android.view.Display
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -23,17 +26,24 @@
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 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.shared.model.SceneContainerNames
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.map
+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
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 
 @SmallTest
 @RunWith(JUnit4::class)
@@ -53,6 +63,7 @@
         utils.keyguardInteractor(
             repository = keyguardRepository,
         )
+    private val sysUiState: SysUiState = mock()
 
     private val underTest =
         SystemUiDefaultSceneContainerStartable(
@@ -61,6 +72,8 @@
             authenticationInteractor = authenticationInteractor,
             keyguardInteractor = keyguardInteractor,
             featureFlags = featureFlags,
+            sysUiState = sysUiState,
+            displayId = Display.DEFAULT_DISPLAY,
         )
 
     @Before
@@ -375,6 +388,31 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
         }
 
+    @Test
+    fun hydrateSystemUiState() =
+        testScope.runTest {
+            underTest.start()
+            runCurrent()
+            clearInvocations(sysUiState)
+
+            listOf(
+                    SceneKey.Gone,
+                    SceneKey.Lockscreen,
+                    SceneKey.Bouncer,
+                    SceneKey.Shade,
+                    SceneKey.QuickSettings,
+                )
+                .forEachIndexed { index, sceneKey ->
+                    sceneInteractor.setCurrentScene(
+                        SceneContainerNames.SYSTEM_UI_DEFAULT,
+                        SceneModel(sceneKey),
+                    )
+                    runCurrent()
+
+                    verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY)
+                }
+        }
+
     private fun prepareState(
         isFeatureEnabled: Boolean = true,
         isDeviceUnlocked: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 9188293..202c7bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -108,7 +108,6 @@
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter;
@@ -299,7 +298,6 @@
     @Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
 
     @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
-    @Mock protected MultiShadeInteractor mMultiShadeInteractor;
     @Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel;
     @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock protected MotionEvent mDownMotionEvent;
@@ -370,7 +368,8 @@
                 mScreenOffAnimationController,
                 mKeyguardLogger,
                 mFeatureFlags,
-                mInteractionJankMonitor));
+                mInteractionJankMonitor,
+                mDumpManager));
 
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
         when(mHeadsUpCallback.getContext()).thenReturn(mContext);
@@ -614,7 +613,6 @@
                 mLockscreenToOccludedTransitionViewModel,
                 mMainDispatcher,
                 mKeyguardTransitionInteractor,
-                () -> mMultiShadeInteractor,
                 mDumpManager,
                 mKeyuardLongPressViewModel,
                 mKeyguardInteractor,
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 2a398c55..893123d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -34,21 +34,15 @@
 import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollectorFake
-import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.log.BouncerLogger
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.data.repository.MultiShadeRepository
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -67,6 +61,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.TestScope
@@ -80,9 +75,8 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.Optional
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -117,8 +111,9 @@
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
     @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
-    @Mock private lateinit var unfoldTransitionProgressProvider:
-            Optional<UnfoldTransitionProgressProvider>
+    @Mock
+    private lateinit var unfoldTransitionProgressProvider:
+        Optional<UnfoldTransitionProgressProvider>
     @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock
     lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
@@ -146,23 +141,11 @@
         val featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
         featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
-        featureFlags.set(Flags.DUAL_SHADE, false)
         featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
         featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
 
-        val inputProxy = MultiShadeInputProxy()
         testScope = TestScope()
-        val multiShadeInteractor =
-            MultiShadeInteractor(
-                applicationScope = testScope.backgroundScope,
-                repository =
-                    MultiShadeRepository(
-                        applicationContext = context,
-                        inputProxy = inputProxy,
-                    ),
-                inputProxy = inputProxy,
-            )
         underTest =
             NotificationShadeWindowViewController(
                 lockscreenShadeTransitionController,
@@ -193,25 +176,14 @@
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
                 featureFlags,
-                { multiShadeInteractor },
                 FakeSystemClock(),
-                {
-                    MultiShadeMotionEventInteractor(
-                        applicationContext = context,
-                        applicationScope = testScope.backgroundScope,
-                        multiShadeInteractor = multiShadeInteractor,
-                        featureFlags = featureFlags,
-                        keyguardTransitionInteractor =
-                            KeyguardTransitionInteractorFactory.create(
-                                    scope = TestScope().backgroundScope,
-                            ).keyguardTransitionInteractor,
-                        falsingManager = FalsingManagerFake(),
-                        shadeController = shadeController,
-                    )
-                },
-                BouncerMessageInteractor(FakeBouncerMessageRepository(),
-                        mock(BouncerMessageFactory::class.java),
-                        FakeUserRepository(), CountDownTimerUtil(), featureFlags),
+                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 d9eb9b9..ed4ac35c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -34,20 +34,14 @@
 import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollectorFake
-import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.log.BouncerLogger
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.data.repository.MultiShadeRepository
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.DragDownHelper
@@ -70,7 +64,6 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
@@ -85,7 +78,6 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -160,22 +152,10 @@
         val featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
         featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
-        featureFlags.set(Flags.DUAL_SHADE, false)
         featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
         featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
-        val inputProxy = MultiShadeInputProxy()
         testScope = TestScope()
-        val multiShadeInteractor =
-            MultiShadeInteractor(
-                applicationScope = testScope.backgroundScope,
-                repository =
-                    MultiShadeRepository(
-                        applicationContext = context,
-                        inputProxy = inputProxy,
-                    ),
-                inputProxy = inputProxy,
-            )
         controller =
             NotificationShadeWindowViewController(
                 lockscreenShadeTransitionController,
@@ -206,23 +186,7 @@
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
                 featureFlags,
-                { multiShadeInteractor },
                 FakeSystemClock(),
-                {
-                    MultiShadeMotionEventInteractor(
-                        applicationContext = context,
-                        applicationScope = testScope.backgroundScope,
-                        multiShadeInteractor = multiShadeInteractor,
-                        featureFlags = featureFlags,
-                        keyguardTransitionInteractor =
-                            KeyguardTransitionInteractorFactory.create(
-                                    scope = TestScope().backgroundScope,
-                                )
-                                .keyguardTransitionInteractor,
-                        falsingManager = FalsingManagerFake(),
-                        shadeController = shadeController,
-                    )
-                },
                 BouncerMessageInteractor(
                     FakeBouncerMessageRepository(),
                     Mockito.mock(BouncerMessageFactory::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 77a22ac..29bc64e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.FakePowerRepository
@@ -73,6 +74,8 @@
     @Mock
     private lateinit var userTracker: UserTracker
     @Mock
+    private lateinit var dozeInteractor: DozeInteractor
+    @Mock
     private lateinit var screenOffAnimationController: ScreenOffAnimationController
 
     private lateinit var powerRepository: FakePowerRepository
@@ -98,6 +101,7 @@
                 ambientDisplayConfiguration,
                 statusBarStateController,
                 shadeLogger,
+                dozeInteractor,
                 userTracker,
                 tunerService,
                 dumpManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
index ad908e7..aab4bc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
@@ -6,6 +6,8 @@
 import android.os.VibrationEffect
 import android.os.Vibrator
 import android.testing.AndroidTestingRunner
+import android.view.HapticFeedbackConstants
+import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.eq
@@ -33,6 +35,7 @@
 
     @Mock lateinit var vibrator: Vibrator
     @Mock lateinit var executor: Executor
+    @Mock lateinit var view: View
     @Captor lateinit var backgroundTaskCaptor: ArgumentCaptor<Runnable>
     lateinit var vibratorHelper: VibratorHelper
 
@@ -72,6 +75,21 @@
     }
 
     @Test
+    fun testPerformHapticFeedback() {
+        val constant = HapticFeedbackConstants.CONFIRM
+        vibratorHelper.performHapticFeedback(view, constant)
+        verify(view).performHapticFeedback(eq(constant))
+    }
+
+    @Test
+    fun testPerformHapticFeedback_withFlags() {
+        val constant = HapticFeedbackConstants.CONFIRM
+        val flag = HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
+        vibratorHelper.performHapticFeedback(view, constant, flag)
+        verify(view).performHapticFeedback(eq(constant), eq(flag))
+    }
+
+    @Test
     fun testHasVibrator() {
         assertThat(vibratorHelper.hasVibrator()).isTrue()
         verify(vibrator).hasVibrator()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
index 55b6be9..0b2da8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import android.util.Pair
 import android.view.Gravity
 import android.view.View
@@ -37,11 +39,14 @@
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
 class SystemEventChipAnimationControllerTest : SysuiTestCase() {
     private lateinit var controller: SystemEventChipAnimationController
 
@@ -159,7 +164,7 @@
         assertThat(chipRect).isEqualTo(Rect(890, 25, 990, 75))
     }
 
-    class TestView(context: Context) : View(context), BackgroundAnimatableView {
+    private class TestView(context: Context) : View(context), BackgroundAnimatableView {
         override val view: View
             get() = this
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
index ed24947..f91e5a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
@@ -21,7 +21,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -47,14 +46,11 @@
     private lateinit var onBeforeRenderListListener: OnBeforeRenderListListener
     private val keyguardStateController: KeyguardStateController = mock()
     private val pipeline: NotifPipeline = mock()
-    private val flags: NotifPipelineFlags = mock()
     private val dumpManager: DumpManager = mock()
 
     @Before
     fun setUp() {
-        whenever(flags.allowDismissOngoing()).thenReturn(true)
-
-        dismissibilityProvider = NotificationDismissibilityProviderImpl(flags, dumpManager)
+        dismissibilityProvider = NotificationDismissibilityProviderImpl(dumpManager)
         coordinator = DismissibilityCoordinator(keyguardStateController, dismissibilityProvider)
         coordinator.attach(pipeline)
         onBeforeRenderListListener = withArgCaptor {
@@ -309,57 +305,4 @@
             dismissibilityProvider.isDismissable(summary)
         )
     }
-
-    @Test
-    fun testFeatureToggleOffNonDismissibleEntry() {
-        whenever(flags.allowDismissOngoing()).thenReturn(false)
-        val entry =
-            NotificationEntryBuilder()
-                .setTag("entry")
-                .setFlag(mContext, Notification.FLAG_NO_DISMISS, true)
-                .build()
-
-        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
-        assertTrue(
-            "FLAG_NO_DISMISS should be ignored, if the feature is off",
-            dismissibilityProvider.isDismissable(entry)
-        )
-    }
-
-    @Test
-    fun testFeatureToggleOffOngoingNotifWhenPhoneIsLocked() {
-        whenever(flags.allowDismissOngoing()).thenReturn(false)
-        whenever(keyguardStateController.isUnlocked).thenReturn(false)
-        val entry =
-            NotificationEntryBuilder()
-                .setTag("entry")
-                .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
-                .build()
-
-        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
-        assertFalse(
-            "Ongoing Notifs should NOT be dismissible, if the feature is off",
-            dismissibilityProvider.isDismissable(entry)
-        )
-    }
-
-    @Test
-    fun testFeatureToggleOffOngoingNotifWhenPhoneIsUnLocked() {
-        whenever(flags.allowDismissOngoing()).thenReturn(false)
-        whenever(keyguardStateController.isUnlocked).thenReturn(true)
-        val entry =
-            NotificationEntryBuilder()
-                .setTag("entry")
-                .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
-                .build()
-
-        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
-        assertFalse(
-            "Ongoing Notifs should NOT be dismissible, if the feature is off",
-            dismissibilityProvider.isDismissable(entry)
-        )
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index 6d687a6..cf1d2ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -41,6 +41,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
@@ -60,12 +61,15 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Locale;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @UiThreadTest
@@ -87,6 +91,8 @@
     @Mock
     private NotificationGutsManager mNotificationGutsManager;
 
+    private Configuration mConfig;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -111,7 +117,15 @@
 
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
                 new Notification(), UserHandle.CURRENT, null, 0);
+        mConfig = new Configuration(mContext.getResources().getConfiguration());
+        Configuration c2 = new Configuration(mConfig);
+        c2.setLocale(Locale.US);
+        mContext.getResources().updateConfiguration(c2, null);
+    }
 
+    @After
+    public void tearDown() {
+        mContext.getResources().updateConfiguration(mConfig, null);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 5e0e140..68f2728 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -65,6 +66,7 @@
     @Mock private lateinit var biometricUnlockController: BiometricUnlockController
     @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
     @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
     @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
@@ -91,6 +93,7 @@
                 Lazy { biometricUnlockController },
                 Lazy { keyguardViewMediator },
                 Lazy { shadeController },
+                Lazy { shadeViewController },
                 Lazy { statusBarKeyguardViewManager },
                 Lazy { notifShadeWindowController },
                 activityLaunchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 479803e..4f8de3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -142,8 +142,7 @@
                 mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
                 mAuthController, mStatusBarStateController,
                 mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
-                mSystemClock,
-                mStatusBarKeyguardViewManager
+                mSystemClock
         );
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.addListener(mBiometricUnlockEventsListener);
@@ -465,69 +464,6 @@
     }
 
     @Test
-    public void onSideFingerprintSuccess_dreaming_unlockThenWake() {
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-        final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor =
-                ArgumentCaptor.forClass(Runnable.class);
-        givenDreamingLocked();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true);
-
-        // Make sure the BiometricUnlockController has registered a callback for when the keyguard
-        // is gone
-        verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(
-                afterKeyguardGoneRunnableCaptor.capture());
-        // Ensure that the power hasn't been told to wake up yet.
-        verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
-        // Check that the keyguard has been told to unlock.
-        verify(mKeyguardViewMediator).onWakeAndUnlocking();
-
-        // Simulate the keyguard disappearing.
-        afterKeyguardGoneRunnableCaptor.getValue().run();
-        // Verify that the power manager has been told to wake up now.
-        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
-    }
-
-    @Test
-    public void onSideFingerprintSuccess_dreaming_unlockIfStillLockedNotDreaming() {
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-        final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor =
-                ArgumentCaptor.forClass(Runnable.class);
-        givenDreamingLocked();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true);
-
-        // Make sure the BiometricUnlockController has registered a callback for when the keyguard
-        // is gone
-        verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(
-                afterKeyguardGoneRunnableCaptor.capture());
-        // Ensure that the power hasn't been told to wake up yet.
-        verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
-        // Check that the keyguard has been told to unlock.
-        verify(mKeyguardViewMediator).onWakeAndUnlocking();
-
-        when(mUpdateMonitor.isDreaming()).thenReturn(false);
-        when(mKeyguardStateController.isUnlocked()).thenReturn(false);
-
-        // Simulate the keyguard disappearing.
-        afterKeyguardGoneRunnableCaptor.getValue().run();
-
-        final ArgumentCaptor<Runnable> dismissKeyguardRunnableCaptor =
-                ArgumentCaptor.forClass(Runnable.class);
-        verify(mHandler).post(dismissKeyguardRunnableCaptor.capture());
-
-        // Verify that the power manager was not told to wake up.
-        verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
-
-        dismissKeyguardRunnableCaptor.getValue().run();
-        // Verify that the keyguard controller is told to unlock.
-        verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
-    }
-
-
-    @Test
     public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() {
         // GIVEN side fingerprint enrolled, last wake reason was power button
         when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
@@ -601,14 +537,25 @@
         verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
     }
 
-    private void givenDreamingLocked() {
-        when(mUpdateMonitor.isDreaming()).thenReturn(true);
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-    }
-
     private void givenFingerprintModeUnlockCollapsing() {
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
     }
+
+    private void givenDreamingLocked() {
+        when(mUpdateMonitor.isDreaming()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+    }
+    @Test
+    public void onSideFingerprintSuccess_dreaming_unlockNoWake() {
+        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+        when(mWakefulnessLifecycle.getLastWakeReason())
+                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+        givenDreamingLocked();
+        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true);
+        verify(mKeyguardViewMediator).onWakeAndUnlocking();
+        // Ensure that the power hasn't been told to wake up yet.
+        verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 85fbef0..9795b9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.AlarmManager
 import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResourcesManager
 import android.content.SharedPreferences
 import android.os.UserManager
 import android.telecom.TelecomManager
@@ -49,6 +50,7 @@
 import com.android.systemui.util.RingerModeTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.DateFormatUtil
 import com.android.systemui.util.time.FakeSystemClock
@@ -67,6 +69,7 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.never
@@ -83,6 +86,7 @@
     companion object {
         private const val ALARM_SLOT = "alarm"
         private const val CONNECTED_DISPLAY_SLOT = "connected_display"
+        private const val MANAGED_PROFILE_SLOT = "managed_profile"
     }
 
     @Mock private lateinit var iconController: StatusBarIconController
@@ -104,6 +108,7 @@
     @Mock private lateinit var userManager: UserManager
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var devicePolicyManagerResources: DevicePolicyResourcesManager
     @Mock private lateinit var recordingController: RecordingController
     @Mock private lateinit var telecomManager: TelecomManager
     @Mock private lateinit var sharedPreferences: SharedPreferences
@@ -132,6 +137,12 @@
             com.android.internal.R.string.status_bar_alarm_clock,
             ALARM_SLOT
         )
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.string.status_bar_managed_profile,
+            MANAGED_PROFILE_SLOT
+        )
+        whenever(devicePolicyManager.resources).thenReturn(devicePolicyManagerResources)
+        whenever(devicePolicyManagerResources.getString(anyString(), any())).thenReturn("")
         statusBarPolicy = createStatusBarPolicy()
     }
 
@@ -182,6 +193,32 @@
     }
 
     @Test
+    fun testAppTransitionFinished_doesNotShowManagedProfileIcon() {
+        whenever(userManager.getUserStatusBarIconResId(anyInt())).thenReturn(0 /* ID_NULL */)
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+        statusBarPolicy.appTransitionFinished(0)
+        // The above call posts to bgExecutor and then back to mainExecutor
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        verify(iconController, never()).setIconVisibility(MANAGED_PROFILE_SLOT, true)
+    }
+
+    @Test
+    fun testAppTransitionFinished_showsManagedProfileIcon() {
+        whenever(userManager.getUserStatusBarIconResId(anyInt())).thenReturn(100)
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+        statusBarPolicy.appTransitionFinished(0)
+        // The above call posts to bgExecutor and then back to mainExecutor
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        verify(iconController).setIconVisibility(MANAGED_PROFILE_SLOT, true)
+    }
+
+    @Test
     fun connectedDisplay_connected_iconShown() =
         testScope.runTest {
             statusBarPolicy.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index c8ec1bf..28193db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -140,8 +140,6 @@
     @Test
     fun handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
-        `when`(centralSurfacesImpl.shadeViewController)
-                .thenReturn(shadeViewController)
         `when`(shadeViewController.isViewEnabled).thenReturn(false)
         val returnVal = view.onTouchEvent(
                 MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
@@ -152,8 +150,6 @@
     @Test
     fun handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
-        `when`(centralSurfacesImpl.shadeViewController)
-                .thenReturn(shadeViewController)
         `when`(shadeViewController.isViewEnabled).thenReturn(false)
         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
 
@@ -165,8 +161,6 @@
     @Test
     fun handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
-        `when`(centralSurfacesImpl.shadeViewController)
-                .thenReturn(shadeViewController)
         `when`(shadeViewController.isViewEnabled).thenReturn(true)
         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)
 
@@ -178,8 +172,6 @@
     @Test
     fun handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
-        `when`(centralSurfacesImpl.shadeViewController)
-                .thenReturn(shadeViewController)
         `when`(shadeViewController.isFullyCollapsed).thenReturn(true)
         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 9157cd9..085ec27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -15,7 +15,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
 
 import static junit.framework.Assert.assertTrue;
 
@@ -39,14 +38,13 @@
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
 import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
@@ -57,23 +55,27 @@
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidTestingRunner.class)
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class StatusBarIconControllerTest extends LeakCheckedTest {
 
     private MobileContextProvider mMobileContextProvider = mock(MobileContextProvider.class);
+    private MobileUiAdapter mMobileUiAdapter = mock(MobileUiAdapter.class);
+    private MobileIconsViewModel mMobileIconsViewModel = mock(MobileIconsViewModel.class);
 
     @Before
     public void setup() {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         // For testing, ignore context overrides
         when(mMobileContextProvider.getMobileContextForSub(anyInt(), any())).thenReturn(mContext);
+        when(mMobileUiAdapter.getMobileIconsViewModel()).thenReturn(mMobileIconsViewModel);
     }
 
     @Test
     public void testSetCalledOnAdd_IconManager() {
         LinearLayout layout = new LinearLayout(mContext);
-        TestIconManager manager = new TestIconManager(layout, mMobileContextProvider);
+        TestIconManager manager =
+                new TestIconManager(layout, mMobileUiAdapter, mMobileContextProvider);
         testCallOnAdd_forManager(manager);
     }
 
@@ -83,9 +85,8 @@
         TestDarkIconManager manager = new TestDarkIconManager(
                 layout,
                 StatusBarLocation.HOME,
-                mock(StatusBarPipelineFlags.class),
                 mock(WifiUiAdapter.class),
-                mock(MobileUiAdapter.class),
+                mMobileUiAdapter,
                 mMobileContextProvider,
                 mock(DarkIconDispatcher.class));
         testCallOnAdd_forManager(manager);
@@ -153,15 +154,10 @@
         assertTrue("Expected StatusBarIconView",
                 (manager.getViewAt(0) instanceof StatusBarIconView));
 
-        holder = holderForType(TYPE_MOBILE);
-        manager.onIconAdded(1, "test_mobile", false, holder);
-        assertTrue(manager.getViewAt(1) instanceof StatusBarMobileView);
     }
 
     private StatusBarIconHolder holderForType(int type) {
         switch (type) {
-            case TYPE_MOBILE:
-                return StatusBarIconHolder.fromMobileIconState(mock(MobileIconState.class));
 
             case TYPE_ICON:
             default:
@@ -175,14 +171,12 @@
         TestDarkIconManager(
                 LinearLayout group,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider contextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(group,
                     location,
-                    statusBarPipelineFlags,
                     wifiUiAdapter,
                     mobileUiAdapter,
                     contextProvider,
@@ -202,23 +196,18 @@
 
             return mock;
         }
-
-        @Override
-        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
-            StatusBarMobileView mock = mock(StatusBarMobileView.class);
-            mGroup.addView(mock, index);
-
-            return mock;
-        }
     }
 
     private static class TestIconManager extends IconManager implements TestableIconManager {
-        TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
+        TestIconManager(
+                ViewGroup group,
+                MobileUiAdapter adapter,
+                MobileContextProvider contextProvider
+        ) {
             super(group,
                     StatusBarLocation.HOME,
-                    mock(StatusBarPipelineFlags.class),
                     mock(WifiUiAdapter.class),
-                    mock(MobileUiAdapter.class),
+                    adapter,
                     contextProvider);
         }
 
@@ -235,14 +224,6 @@
 
             return mock;
         }
-
-        @Override
-        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
-            StatusBarMobileView mock = mock(StatusBarMobileView.class);
-            mGroup.addView(mock, index);
-
-            return mock;
-        }
     }
 
     private interface TestableIconManager {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 2e9a690..e76f26d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -97,9 +97,7 @@
                 powerManager,
                 handler = handler
         )
-        controller.initialize(centralSurfaces, lightRevealScrim)
-        `when`(centralSurfaces.shadeViewController).thenReturn(
-            shadeViewController)
+        controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
 
         // Screen off does not run if the panel is expanded, so we should say it's collapsed to test
         // screen off.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
index 4aa48d6..755aaa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
@@ -54,7 +54,7 @@
     @Test
     fun collectionStarted_dumpHasInfo() {
         val view = TextView(context)
-        val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+        val viewModel = QsMobileIconViewModel(commonViewModel)
 
         underTest.logCollectionStarted(view, viewModel)
 
@@ -66,8 +66,8 @@
     fun collectionStarted_multipleViews_dumpHasInfo() {
         val view = TextView(context)
         val view2 = TextView(context)
-        val viewModel = QsMobileIconViewModel(commonViewModel, flags)
-        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+        val viewModel = QsMobileIconViewModel(commonViewModel)
+        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel)
 
         underTest.logCollectionStarted(view, viewModel)
         underTest.logCollectionStarted(view2, viewModel2)
@@ -81,8 +81,8 @@
     fun collectionStopped_dumpHasInfo() {
         val view = TextView(context)
         val view2 = TextView(context)
-        val viewModel = QsMobileIconViewModel(commonViewModel, flags)
-        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+        val viewModel = QsMobileIconViewModel(commonViewModel)
+        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel)
 
         underTest.logCollectionStarted(view, viewModel)
         underTest.logCollectionStarted(view2, viewModel2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index 7420db2..59fc0ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -235,7 +235,6 @@
 
     @Test
     fun onDarkChanged_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view =
             ModernStatusBarMobileView.constructAndBind(
                 context,
@@ -257,7 +256,6 @@
 
     @Test
     fun setStaticDrawableColor_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view =
             ModernStatusBarMobileView.constructAndBind(
                 context,
@@ -298,7 +296,7 @@
                 constants,
                 testScope.backgroundScope,
             )
-        viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
+        viewModel = QsMobileIconViewModel(viewModelCommon)
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index d5fb577..e59d90f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -86,9 +86,9 @@
                 testScope.backgroundScope,
             )
 
-        homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags, mock())
-        qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
-        keyguardIcon = KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+        homeIcon = HomeMobileIconViewModel(commonImpl, mock())
+        qsIcon = QsMobileIconViewModel(commonImpl)
+        keyguardIcon = KeyguardMobileIconViewModel(commonImpl)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index 5bc98e0..dbaa29b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -18,7 +18,9 @@
 
 import android.net.ConnectivityManager
 import android.net.wifi.WifiManager
+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.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
@@ -43,6 +45,7 @@
 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.Mockito
 import org.mockito.MockitoAnnotations
@@ -50,6 +53,8 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class WifiRepositorySwitcherTest : SysuiTestCase() {
     private lateinit var underTest: WifiRepositorySwitcher
     private lateinit var realImpl: WifiRepositoryImpl
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
index 9cf08c0..206ac1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
@@ -16,15 +16,20 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
 
+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.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 
 @SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class DisabledWifiRepositoryTest : SysuiTestCase() {
 
     private lateinit var underTest: DisabledWifiRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 7007345..3cf5f52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -32,7 +32,9 @@
 import android.net.wifi.WifiManager.TrafficStateCallback
 import android.net.wifi.WifiManager.UNKNOWN_SSID
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+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.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
@@ -57,6 +59,7 @@
 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.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -65,6 +68,8 @@
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class WifiRepositoryImplTest : SysuiTestCase() {
 
     private lateinit var underTest: WifiRepositoryImpl
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 0d51af2..3f49935 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -46,7 +45,6 @@
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel.Companion.viewModelForLocation
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -64,7 +62,6 @@
 
     private lateinit var testableLooper: TestableLooper
 
-    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
@@ -110,7 +107,6 @@
         viewModel =
             viewModelForLocation(
                 viewModelCommon,
-                statusBarPipelineFlags,
                 StatusBarLocation.HOME,
             )
     }
@@ -199,7 +195,6 @@
 
     @Test
     fun onDarkChanged_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
@@ -215,7 +210,6 @@
 
     @Test
     fun setStaticDrawableColor_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 0e303b2..cb469ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -58,7 +57,6 @@
 
     private lateinit var underTest: WifiViewModel
 
-    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
@@ -107,11 +105,9 @@
     @Test
     fun wifiIcon_allLocationViewModelsReceiveSameData() =
         runBlocking(IMMEDIATE) {
-            val home =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.HOME)
-            val keyguard =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.KEYGUARD)
-            val qs = viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.QS)
+            val home = viewModelForLocation(underTest, StatusBarLocation.HOME)
+            val keyguard = viewModelForLocation(underTest, StatusBarLocation.KEYGUARD)
+            val qs = viewModelForLocation(underTest, StatusBarLocation.QS)
 
             var latestHome: WifiIcon? = null
             val jobHome = home.wifiIcon.onEach { latestHome = it }.launchIn(this)
@@ -249,11 +245,9 @@
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            val home =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.HOME)
-            val keyguard =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.KEYGUARD)
-            val qs = viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.QS)
+            val home = viewModelForLocation(underTest, StatusBarLocation.HOME)
+            val keyguard = viewModelForLocation(underTest, StatusBarLocation.KEYGUARD)
+            val qs = viewModelForLocation(underTest, StatusBarLocation.QS)
 
             var latestHome: Boolean? = null
             val jobHome = home.isActivityInViewVisible.onEach { latestHome = it }.launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index 813597a..7f990a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -101,7 +101,6 @@
         whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
         whenever(wakefulnessLifecycle.lastSleepReason)
             .thenReturn(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
-        whenever(centralSurfaces.shadeViewController).thenReturn(shadeViewController)
         whenever(shadeFoldAnimator.startFoldToAodAnimation(any(), any(), any())).then {
             val onActionStarted = it.arguments[0] as Runnable
             onActionStarted.run()
@@ -124,7 +123,7 @@
                         latencyTracker,
                         { keyguardInteractor },
                     )
-                    .apply { initialize(centralSurfaces, lightRevealScrim) }
+                    .apply { initialize(centralSurfaces, shadeViewController, lightRevealScrim) }
 
             verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 17bb73b..820e2a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -1846,8 +1846,7 @@
     }
 
     @Test
-    public void testCreateBubbleFromOngoingNotification_OngoingDismissalEnabled() {
-        when(mNotifPipelineFlags.allowDismissOngoing()).thenReturn(true);
+    public void testCreateBubbleFromOngoingNotification() {
         NotificationEntry notif = new NotificationEntryBuilder()
                 .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
                 .setCanBubble(true)
@@ -1860,8 +1859,7 @@
 
 
     @Test
-    public void testCreateBubbleFromNoDismissNotification_OngoingDismissalEnabled() {
-        when(mNotifPipelineFlags.allowDismissOngoing()).thenReturn(true);
+    public void testCreateBubbleFromNoDismissNotification() {
         NotificationEntry notif = new NotificationEntryBuilder()
                 .setFlag(mContext, Notification.FLAG_NO_DISMISS, true)
                 .setCanBubble(true)
@@ -1873,37 +1871,6 @@
     }
 
     @Test
-    public void testCreateBubbleFromOngoingNotification_OngoingDismissalDisabled() {
-        NotificationEntry notif = new NotificationEntryBuilder()
-                .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
-                .setCanBubble(true)
-                .build();
-
-        BubbleEntry bubble = mBubblesManager.notifToBubbleEntry(notif);
-
-        assertFalse(
-                "Ongoing Notifis should be dismissable, if the feature is off",
-                bubble.isDismissable()
-        );
-    }
-
-
-    @Test
-    public void testCreateBubbleFromNoDismissNotification_OngoingDismissalDisabled() {
-        NotificationEntry notif = new NotificationEntryBuilder()
-                .setFlag(mContext, Notification.FLAG_NO_DISMISS, true)
-                .setCanBubble(true)
-                .build();
-
-        BubbleEntry bubble = mBubblesManager.notifToBubbleEntry(notif);
-
-        assertTrue(
-                "FLAG_NO_DISMISS should be ignored, if the feature is off",
-                bubble.isDismissable()
-        );
-    }
-
-    @Test
     public void registerBubbleBarListener_barDisabled_largeScreen_shouldBeIgnored() {
         mBubbleProperties.mIsBubbleBarEnabled = false;
         mPositioner.setIsLargeScreen(true);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index 0c5e438..2362a52 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -20,12 +20,16 @@
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
 
+    private val _isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val isInitialized = _isInitialized.asStateFlow()
+
     private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
-    override val sensorId = _sensorId.asStateFlow()
+    override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
 
     private val _strength: MutableStateFlow<SensorStrength> =
         MutableStateFlow(SensorStrength.CONVENIENCE)
@@ -33,11 +37,12 @@
 
     private val _sensorType: MutableStateFlow<FingerprintSensorType> =
         MutableStateFlow(FingerprintSensorType.UNKNOWN)
-    override val sensorType = _sensorType.asStateFlow()
+    override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow()
 
     private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
         MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
-    override val sensorLocations = _sensorLocations.asStateFlow()
+    override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
+        _sensorLocations.asStateFlow()
 
     fun setProperties(
         sensorId: Int,
@@ -49,5 +54,6 @@
         _strength.value = strength
         _sensorType.value = sensorType
         _sensorLocations.value = sensorLocations
+        _isInitialized.value = true
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index 548169e..f4c2db1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -27,6 +27,11 @@
 
 class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
 
+    private var _wasDisabled: Boolean = false
+
+    val wasDisabled: Boolean
+        get() = _wasDisabled
+
     override val isAuthenticated = MutableStateFlow(false)
     override val canRunFaceAuth = MutableStateFlow(false)
     private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
@@ -52,6 +57,9 @@
     override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
 
     override val isBypassEnabled = MutableStateFlow(false)
+    override fun lockoutFaceAuth() {
+        _wasDisabled = true
+    }
 
     override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
         _runningAuthRequest.value = uiEvent to fallbackToDetection
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
index 7c22604..b24b95e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.systemui.statusbar.LightRevealEffect
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 /** Fake implementation of [LightRevealScrimRepository] */
@@ -30,4 +31,15 @@
     fun setRevealEffect(effect: LightRevealEffect) {
         _revealEffect.tryEmit(effect)
     }
+
+    private val _revealAmount: MutableStateFlow<Float> = MutableStateFlow(0.0f)
+    override val revealAmount: Flow<Float> = _revealAmount
+
+    override fun startRevealAmountAnimator(reveal: Boolean) {
+        if (reveal) {
+            _revealAmount.value = 1.0f
+        } else {
+            _revealAmount.value = 0.0f
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 56837e8..03e3423 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -20,7 +20,6 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 
 import java.util.List;
 
@@ -65,10 +64,6 @@
     }
 
     @Override
-    public void setMobileIcons(String slot, List<MobileIconState> states) {
-    }
-
-    @Override
     public void setNewMobileIconSubIds(List<Integer> subIds) {
     }
 
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 2e0a946..1f0181f 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -33,7 +33,7 @@
         "dagger2",
         "jsr330",
     ],
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: ["-Xjvm-default=all"],
     java_version: "1.8",
     sdk_version: "current",
     min_sdk_version: "current",
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
index fee485d..896444d 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
@@ -35,14 +35,12 @@
 
     interface TransitionProgressListener {
         /** Called when transition is started */
-        @JvmDefault
         fun onTransitionStarted() {}
 
         /**
          * Called whenever transition progress is updated, [progress] is a value of the animation
          * where 0 is fully folded, 1 is fully unfolded
          */
-        @JvmDefault
         fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {}
 
         /**
@@ -51,11 +49,9 @@
          * For example, in [PhysicsBasedUnfoldTransitionProgressProvider] this could happen when the
          * animation is not tied to the hinge angle anymore and it is about to run fixed animation.
          */
-        @JvmDefault
         fun onTransitionFinishing() {}
 
         /** Called when transition is completely finished */
-        @JvmDefault
         fun onTransitionFinished() {}
     }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
index 0af372f..bce7e88 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
@@ -31,9 +31,9 @@
     val isFinishedOpening: Boolean
 
     interface FoldUpdatesListener {
-        @JvmDefault fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float) {}
-        @JvmDefault fun onFoldUpdate(@FoldUpdate update: Int) {}
-        @JvmDefault fun onUnfoldedScreenAvailable() {}
+        fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float) {}
+        fun onFoldUpdate(@FoldUpdate update: Int) {}
+        fun onUnfoldedScreenAvailable() {}
     }
 
     @IntDef(
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 3e7d759..631b453 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -47,7 +47,6 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.Signature;
 import android.net.MacAddress;
 import android.os.Binder;
 import android.os.Bundle;
@@ -55,17 +54,11 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
-import android.util.Log;
-import android.util.PackageUtils;
 import android.util.Slog;
 
 import com.android.internal.R;
-import com.android.internal.util.ArrayUtils;
 
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Class responsible for handling incoming {@link AssociationRequest}s.
@@ -449,31 +442,6 @@
     };
 
     private boolean mayAssociateWithoutPrompt(@NonNull String packageName, @UserIdInt int userId) {
-        // Below we check if the requesting package is allowlisted (usually by the OEM) for creating
-        // CDM associations without user confirmation (prompt).
-        // For this we'll check to config arrays:
-        // - com.android.internal.R.array.config_companionDevicePackages
-        // and
-        // - com.android.internal.R.array.config_companionDeviceCerts.
-        // Both arrays are expected to contain similar number of entries.
-        // config_companionDevicePackages contains package names of the allowlisted packages.
-        // config_companionDeviceCerts contains SHA256 digests of the signatures of the
-        // corresponding packages.
-        // If a package may be signed with one of several certificates, its package name would
-        // appear multiple times in the config_companionDevicePackages, with different entries
-        // (one for each of the valid signing certificates) at the corresponding positions in
-        // config_companionDeviceCerts.
-        final String[] allowlistedPackages = mContext.getResources()
-                .getStringArray(com.android.internal.R.array.config_companionDevicePackages);
-        if (!ArrayUtils.contains(allowlistedPackages, packageName)) {
-            if (DEBUG) {
-                Log.d(TAG, packageName + " is not allowlisted for creating associations "
-                        + "without user confirmation (prompt)");
-                Log.v(TAG, "Allowlisted packages=" + Arrays.toString(allowlistedPackages));
-            }
-            return false;
-        }
-
         // Throttle frequent associations
         final long now = System.currentTimeMillis();
         final List<AssociationInfo> associationForPackage =
@@ -493,40 +461,6 @@
             }
         }
 
-        final String[] allowlistedPackagesSignatureDigests = mContext.getResources()
-                .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
-        final Set<String> allowlistedSignatureDigestsForRequestingPackage = new HashSet<>();
-        for (int i = 0; i < allowlistedPackages.length; i++) {
-            if (allowlistedPackages[i].equals(packageName)) {
-                final String digest = allowlistedPackagesSignatureDigests[i].replaceAll(":", "");
-                allowlistedSignatureDigestsForRequestingPackage.add(digest);
-            }
-        }
-
-        final Signature[] requestingPackageSignatures = mPackageManager.getPackage(packageName)
-                .getSigningDetails().getSignatures();
-        final String[] requestingPackageSignatureDigests =
-                PackageUtils.computeSignaturesSha256Digests(requestingPackageSignatures);
-
-        boolean requestingPackageSignatureAllowlisted = false;
-        for (String signatureDigest : requestingPackageSignatureDigests) {
-            if (allowlistedSignatureDigestsForRequestingPackage.contains(signatureDigest)) {
-                requestingPackageSignatureAllowlisted = true;
-                break;
-            }
-        }
-
-        if (!requestingPackageSignatureAllowlisted) {
-            Slog.w(TAG, "Certificate mismatch for allowlisted package " + packageName);
-            if (DEBUG) {
-                Log.d(TAG, "  > allowlisted signatures for " + packageName + ": ["
-                        + String.join(", ", allowlistedSignatureDigestsForRequestingPackage)
-                        + "]");
-                Log.d(TAG, "  > actual signatures for " + packageName + ": "
-                        + Arrays.toString(requestingPackageSignatureDigests));
-            }
-        }
-
-        return requestingPackageSignatureAllowlisted;
+        return PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName);
     }
 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 89e8a18..4d4328d 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -247,7 +247,8 @@
         mCompanionAppController = new CompanionApplicationController(
                 context, mAssociationStore, mDevicePresenceMonitor);
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
-        mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore,
+        mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
+                mPackageManagerInternal, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
         // TODO(b/279663946): move context sync to a dedicated system service
         mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
index 3ab4aa8..db40fc4 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -20,6 +20,7 @@
 import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 
+import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
 import static com.android.server.companion.CompanionDeviceManagerService.TAG;
 
 import android.Manifest;
@@ -35,20 +36,28 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.PackageInfoFlags;
 import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
 import android.os.Binder;
+import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Utility methods for working with {@link PackageInfo}-s.
  */
-final class PackageUtils {
+public final class PackageUtils {
     private static final Intent COMPANION_SERVICE_INTENT =
             new Intent(CompanionDeviceService.SERVICE_INTERFACE);
     private static final String PROPERTY_PRIMARY_TAG =
@@ -141,4 +150,69 @@
             return false;
         }
     }
+
+    /**
+     * Check if the package is allowlisted in the overlay config.
+     * For this we'll check to config arrays:
+     *   - com.android.internal.R.array.config_companionDevicePackages
+     * and
+     *   - com.android.internal.R.array.config_companionDeviceCerts.
+     * Both arrays are expected to contain similar number of entries.
+     * config_companionDevicePackages contains package names of the allowlisted packages.
+     * config_companionDeviceCerts contains SHA256 digests of the signatures of the
+     * corresponding packages.
+     * If a package is signed with one of several certificates, its package name would
+     * appear multiple times in the config_companionDevicePackages, with different entries
+     * (one for each of the valid signing certificates) at the corresponding positions in
+     * config_companionDeviceCerts.
+     */
+    public static boolean isPackageAllowlisted(Context context,
+            PackageManagerInternal packageManagerInternal, @NonNull String packageName) {
+        final String[] allowlistedPackages = context.getResources()
+                .getStringArray(com.android.internal.R.array.config_companionDevicePackages);
+        if (!ArrayUtils.contains(allowlistedPackages, packageName)) {
+            if (DEBUG) {
+                Log.d(TAG, packageName + " is not allowlisted.");
+            }
+            return false;
+        }
+
+        final String[] allowlistedPackagesSignatureDigests = context.getResources()
+                .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
+        final Set<String> allowlistedSignatureDigestsForRequestingPackage = new HashSet<>();
+        for (int i = 0; i < allowlistedPackages.length; i++) {
+            if (allowlistedPackages[i].equals(packageName)) {
+                final String digest = allowlistedPackagesSignatureDigests[i].replaceAll(":", "");
+                allowlistedSignatureDigestsForRequestingPackage.add(digest);
+            }
+        }
+
+        final Signature[] requestingPackageSignatures = packageManagerInternal.getPackage(
+                        packageName)
+                .getSigningDetails().getSignatures();
+        final String[] requestingPackageSignatureDigests =
+                android.util.PackageUtils.computeSignaturesSha256Digests(
+                        requestingPackageSignatures);
+
+        boolean requestingPackageSignatureAllowlisted = false;
+        for (String signatureDigest : requestingPackageSignatureDigests) {
+            if (allowlistedSignatureDigestsForRequestingPackage.contains(signatureDigest)) {
+                requestingPackageSignatureAllowlisted = true;
+                break;
+            }
+        }
+
+        if (!requestingPackageSignatureAllowlisted) {
+            Slog.w(TAG, "Certificate mismatch for allowlisted package " + packageName);
+            if (DEBUG) {
+                Log.d(TAG, "  > allowlisted signatures for " + packageName + ": ["
+                        + String.join(", ", allowlistedSignatureDigestsForRequestingPackage)
+                        + "]");
+                Log.d(TAG, "  > actual signatures for " + packageName + ": "
+                        + Arrays.toString(requestingPackageSignatureDigests));
+            }
+        }
+
+        return requestingPackageSignatureAllowlisted;
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 82387a1..7af4957 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -37,6 +37,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -50,6 +51,7 @@
 import com.android.internal.R;
 import com.android.server.companion.AssociationStore;
 import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.PackageUtils;
 import com.android.server.companion.PermissionsUtils;
 import com.android.server.companion.transport.CompanionTransportManager;
 
@@ -77,6 +79,7 @@
             "system_data_transfer_result_receiver";
 
     private final Context mContext;
+    private final PackageManagerInternal mPackageManager;
     private final AssociationStore mAssociationStore;
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     private final CompanionTransportManager mTransportManager;
@@ -85,10 +88,12 @@
     private final ComponentName mCompanionDeviceDataTransferActivity;
 
     public SystemDataTransferProcessor(CompanionDeviceManagerService service,
+            PackageManagerInternal packageManager,
             AssociationStore associationStore,
             SystemDataTransferRequestStore systemDataTransferRequestStore,
             CompanionTransportManager transportManager) {
         mContext = service.getContext();
+        mPackageManager = packageManager;
         mAssociationStore = associationStore;
         mSystemDataTransferRequestStore = systemDataTransferRequestStore;
         mTransportManager = transportManager;
@@ -132,6 +137,11 @@
      */
     public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
             @UserIdInt int userId, int associationId) {
+        if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
+            Slog.i(LOG_TAG, "User consent Intent should be skipped. Returning null.");
+            return null;
+        }
+
         final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
 
         Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
@@ -175,23 +185,29 @@
         final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
 
         // Check if the request has been consented by the user.
-        List<SystemDataTransferRequest> storedRequests =
-                mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
-                        associationId);
-        boolean hasConsented = false;
-        for (SystemDataTransferRequest storedRequest : storedRequests) {
-            if (storedRequest instanceof PermissionSyncRequest && storedRequest.isUserConsented()) {
-                hasConsented = true;
-                break;
+        if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
+            Slog.i(LOG_TAG, "Skip user consent check due to the same OEM package.");
+        } else {
+            List<SystemDataTransferRequest> storedRequests =
+                    mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
+                            associationId);
+            boolean hasConsented = false;
+            for (SystemDataTransferRequest storedRequest : storedRequests) {
+                if (storedRequest instanceof PermissionSyncRequest
+                        && storedRequest.isUserConsented()) {
+                    hasConsented = true;
+                    break;
+                }
             }
-        }
-        if (!hasConsented) {
-            String message = "User " + userId + " hasn't consented permission sync.";
-            Slog.e(LOG_TAG, message);
-            try {
-                callback.onError(message);
-            } catch (RemoteException ignored) { }
-            return;
+            if (!hasConsented) {
+                String message = "User " + userId + " hasn't consented permission sync.";
+                Slog.e(LOG_TAG, message);
+                try {
+                    callback.onError(message);
+                } catch (RemoteException ignored) {
+                }
+                return;
+            }
         }
 
         // Start permission sync
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 279981b..d862a54 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -9,6 +9,30 @@
       ]
     },
     {
+      "name": "CtsVirtualDevicesAudioTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsVirtualDevicesSensorTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsVirtualDevicesAppLaunchTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
       "name": "CtsVirtualDevicesTestCases",
       "options": [
         {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index ca1ab9b..315972c 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -42,6 +42,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.assist.ActivityId;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
@@ -94,10 +95,12 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AbstractRemoteService;
 import com.android.internal.infra.GlobalWhitelistState;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
 import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionConsentManager;
 import com.android.server.contentprotection.ContentProtectionPackageManager;
 import com.android.server.contentprotection.RemoteContentProtectionService;
 import com.android.server.infra.AbstractMasterSystemService;
@@ -216,6 +219,8 @@
 
     @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
 
+    @Nullable private final ContentProtectionConsentManager mContentProtectionConsentManager;
+
     public ContentCaptureManagerService(@NonNull Context context) {
         super(context, new FrameworkResourcesServiceNameResolver(context,
                 com.android.internal.R.string.config_defaultContentCaptureService),
@@ -260,12 +265,15 @@
                 mContentProtectionBlocklistManager = createContentProtectionBlocklistManager();
                 mContentProtectionBlocklistManager.updateBlocklist(
                         mDevCfgContentProtectionAppsBlocklistSize);
+                mContentProtectionConsentManager = createContentProtectionConsentManager();
             } else {
                 mContentProtectionBlocklistManager = null;
+                mContentProtectionConsentManager = null;
             }
         } else {
             mContentProtectionServiceComponentName = null;
             mContentProtectionBlocklistManager = null;
+            mContentProtectionConsentManager = null;
         }
     }
 
@@ -802,6 +810,17 @@
                 new ContentProtectionPackageManager(getContext()));
     }
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected ContentProtectionConsentManager createContentProtectionConsentManager() {
+        // Same handler as used by AbstractMasterSystemService
+        return new ContentProtectionConsentManager(
+                BackgroundThread.getHandler(),
+                getContext().getContentResolver(),
+                LocalServices.getService(DevicePolicyManagerInternal.class));
+    }
+
     @Nullable
     private ComponentName getContentProtectionServiceComponentName() {
         String flatComponentName = getContentProtectionServiceFlatComponentName();
@@ -1213,7 +1232,7 @@
                 isContentCaptureReceiverEnabled =
                         isContentCaptureReceiverEnabled(userId, packageName);
                 isContentProtectionReceiverEnabled =
-                        isContentProtectionReceiverEnabled(packageName);
+                        isContentProtectionReceiverEnabled(userId, packageName);
 
                 if (!isContentCaptureReceiverEnabled) {
                     // Full package is not allowlisted: check individual components next
@@ -1284,13 +1303,13 @@
         @Override // from GlobalWhitelistState
         public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) {
             return isContentCaptureReceiverEnabled(userId, packageName)
-                    || isContentProtectionReceiverEnabled(packageName);
+                    || isContentProtectionReceiverEnabled(userId, packageName);
         }
 
         @Override // from GlobalWhitelistState
         public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
             return super.isWhitelisted(userId, componentName)
-                    || isContentProtectionReceiverEnabled(componentName.getPackageName());
+                    || isContentProtectionReceiverEnabled(userId, componentName.getPackageName());
         }
 
         private boolean isContentCaptureReceiverEnabled(
@@ -1298,9 +1317,11 @@
             return super.isWhitelisted(userId, packageName);
         }
 
-        private boolean isContentProtectionReceiverEnabled(@NonNull String packageName) {
+        private boolean isContentProtectionReceiverEnabled(
+                @UserIdInt int userId, @NonNull String packageName) {
             if (mContentProtectionServiceComponentName == null
-                    || mContentProtectionBlocklistManager == null) {
+                    || mContentProtectionBlocklistManager == null
+                    || mContentProtectionConsentManager == null) {
                 return false;
             }
             synchronized (mLock) {
@@ -1308,7 +1329,8 @@
                     return false;
                 }
             }
-            return mContentProtectionBlocklistManager.isAllowed(packageName);
+            return mContentProtectionConsentManager.isConsentGranted(userId)
+                    && mContentProtectionBlocklistManager.isAllowed(packageName);
         }
     }
 
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java
new file mode 100644
index 0000000..2eb758c
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java
@@ -0,0 +1,109 @@
+/*
+ * 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.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Manages consent for content protection.
+ *
+ * @hide
+ */
+public class ContentProtectionConsentManager {
+
+    private static final String TAG = "ContentProtectionConsentManager";
+
+    private static final String KEY_PACKAGE_VERIFIER_USER_CONSENT = "package_verifier_user_consent";
+
+    @NonNull private final ContentResolver mContentResolver;
+
+    @NonNull private final DevicePolicyManagerInternal mDevicePolicyManagerInternal;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    public final ContentObserver mContentObserver;
+
+    private volatile boolean mCachedPackageVerifierConsent;
+
+    public ContentProtectionConsentManager(
+            @NonNull Handler handler,
+            @NonNull ContentResolver contentResolver,
+            @NonNull DevicePolicyManagerInternal devicePolicyManagerInternal) {
+        mContentResolver = contentResolver;
+        mDevicePolicyManagerInternal = devicePolicyManagerInternal;
+        mContentObserver = new SettingsObserver(handler);
+
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(KEY_PACKAGE_VERIFIER_USER_CONSENT),
+                /* notifyForDescendants= */ false,
+                mContentObserver,
+                UserHandle.USER_ALL);
+        mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
+    }
+
+    /**
+     * Returns true if all the consents are granted
+     */
+    public boolean isConsentGranted(@UserIdInt int userId) {
+        return mCachedPackageVerifierConsent && !isUserOrganizationManaged(userId);
+    }
+
+    private boolean isPackageVerifierConsentGranted() {
+        // Not always cached internally
+        return Settings.Global.getInt(
+                        mContentResolver, KEY_PACKAGE_VERIFIER_USER_CONSENT, /* def= */ 0)
+                >= 1;
+    }
+
+    private boolean isUserOrganizationManaged(@UserIdInt int userId) {
+        // Cached internally
+        return mDevicePolicyManagerInternal.isUserOrganizationManaged(userId);
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) {
+            final String property = uri.getLastPathSegment();
+            if (property == null) {
+                return;
+            }
+            switch (property) {
+                case KEY_PACKAGE_VERIFIER_USER_CONSENT:
+                    mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
+                    return;
+                default:
+                    Slog.w(TAG, "Ignoring unexpected property: " + property);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 6fd6afe..754a7ed 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -159,9 +159,10 @@
 
     private int getAllowedUid() {
         final UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
-        final int mainUserId = umInternal.getMainUserId();
+        int mainUserId = umInternal.getMainUserId();
         if (mainUserId < 0) {
-            return -1;
+            // If main user is not defined. Use the SYSTEM user instead.
+            mainUserId = UserHandle.USER_SYSTEM;
         }
         String allowedPackage = mContext.getResources()
                 .getString(R.string.config_persistentDataPackageName);
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 1e3a5f4..7a95d77 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -17,7 +17,7 @@
             "name": "CtsWindowManagerDeviceTestCases",
             "options": [
                 {
-                    "include-filter": "android.server.wm.ToastWindowTest"
+                    "include-filter": "android.server.wm.window.ToastWindowTest"
                 }
             ],
             "file_patterns": ["NotificationManagerService\\.java"]
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 146215b..d398260 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4932,7 +4932,6 @@
             if (accountType == null) throw new IllegalArgumentException("accountType is null");
             mAccounts = accounts;
             mStripAuthTokenFromResult = stripAuthTokenFromResult;
-            mResponse = response;
             mAccountType = accountType;
             mExpectActivityLaunch = expectActivityLaunch;
             mCreationTime = SystemClock.elapsedRealtime();
@@ -4946,8 +4945,8 @@
             if (response != null) {
                 try {
                     response.asBinder().linkToDeath(this, 0 /* flags */);
+                    mResponse = response;
                 } catch (RemoteException e) {
-                    mResponse = null;
                     binderDied();
                 }
             }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d199006..dc83125 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -8169,7 +8169,13 @@
                 mAm.getUidStateLocked(r.mRecentCallingUid),
                 mAm.getUidProcessCapabilityLocked(r.mRecentCallingUid),
                 0,
-                0);
+                0,
+                r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
+                r.mAllowWIUInBindService,
+                r.mAllowWIUByBindings,
+                r.mAllowStartForegroundNoBinding,
+                r.mAllowStartInBindService,
+                r.mAllowStartByBindings);
 
         int event = 0;
         if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c9769b3..132d6a9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5083,6 +5083,10 @@
             // Tell anyone interested that we are done booting!
             SystemProperties.set("sys.boot_completed", "1");
             SystemProperties.set("dev.bootcomplete", "1");
+
+            // Start PSI monitoring in LMKD if it was skipped earlier.
+            ProcessList.startPsiMonitoringAfterBoot();
+
             mUserController.onBootComplete(
                     new IIntentReceiver.Stub() {
                         @Override
@@ -9009,7 +9013,9 @@
         final boolean isSystemApp = process == null ||
                 (process.info.flags & (ApplicationInfo.FLAG_SYSTEM |
                                        ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0;
-        final String processName = process == null ? "unknown" : process.processName;
+        final String processName = process != null && process.getPid() == MY_PID
+                ? "system_server"
+                : (process == null ? "unknown" : process.processName);
         final DropBoxManager dbox = (DropBoxManager)
                 mContext.getSystemService(Context.DROPBOX_SERVICE);
 
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 4f5b5e1..786e1cc 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -506,7 +506,13 @@
                 ActivityManager.PROCESS_STATE_UNKNOWN,
                 ActivityManager.PROCESS_CAPABILITY_NONE,
                 apiDurationBeforeFgsStart,
-                apiDurationAfterFgsEnd);
+                apiDurationAfterFgsEnd,
+                r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
+                r.mAllowWIUInBindService,
+                r.mAllowWIUByBindings,
+                r.mAllowStartForegroundNoBinding,
+                r.mAllowStartInBindService,
+                r.mAllowStartByBindings);
     }
 
     /**
@@ -557,7 +563,13 @@
                 ActivityManager.PROCESS_STATE_UNKNOWN,
                 ActivityManager.PROCESS_CAPABILITY_NONE,
                 0,
-                apiDurationAfterFgsEnd);
+                apiDurationAfterFgsEnd,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0);
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 79292fe..10d5fd3 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -42,7 +42,6 @@
 import android.os.IBinder;
 import android.os.PowerWhitelistManager;
 import android.os.PowerWhitelistManager.ReasonCode;
-import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.TransactionTooLargeException;
@@ -383,14 +382,6 @@
             })
     public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(
             int callingUid, @Nullable String callingPackage) {
-        if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
-            // We temporarily allow BAL for system processes, while we verify that all valid use
-            // cases are opted in explicitly to grant their BAL permission.
-            // Background: In many cases devices are running additional apps that share UID with
-            // the system. If one of these apps targets a lower SDK the change is not active, but
-            // as soon as that app is upgraded (or removed) BAL would be blocked. (b/283138430)
-            return BackgroundStartPrivileges.ALLOW_BAL;
-        }
         boolean isChangeEnabledForApp = callingPackage != null ? CompatChanges.isChangeEnabled(
                 DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingPackage,
                 UserHandle.getUserHandleForUid(callingUid)) : CompatChanges.isChangeEnabled(
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index acd9771..e484a6c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -357,6 +357,7 @@
     static final byte LMK_UPDATE_PROPS = 7;
     static final byte LMK_KILL_OCCURRED = 8; // Msg to subscribed clients on kill occurred event
     static final byte LMK_STATE_CHANGED = 9; // Msg to subscribed clients on state changed
+    static final byte LMK_START_MONITORING = 9; // Start monitoring if delayed earlier
 
     // Low Memory Killer Daemon command codes.
     // These must be kept in sync with async_event_type definitions in lmkd.h
@@ -1568,6 +1569,15 @@
         return true;
     }
 
+    /**
+     * {@hide}
+     */
+    public static void startPsiMonitoringAfterBoot() {
+        ByteBuffer buf = ByteBuffer.allocate(4);
+        buf.putInt(LMK_START_MONITORING);
+        writeLmkd(buf, null);
+    }
+
     private static boolean writeLmkd(ByteBuffer buf, ByteBuffer repl) {
         if (!sLmkdConnection.isConnected()) {
             // try to connect immediately and then keep retrying
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 72d3835..68062b5 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -1,7 +1,12 @@
 {
     "presubmit": [
         {
-            "name": "CtsAppOpsTestCases"
+            "name": "CtsAppOpsTestCases",
+            "options": [
+                {
+                  "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                }
+            ]
         },
         {
             "name": "CtsAppOps2TestCases"
@@ -26,6 +31,9 @@
             "name": "CtsPermissionTestCases",
             "options": [
                 {
+                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                },
+                {
                     "include-filter": "android.permission.cts.BackgroundPermissionsTest"
                 },
                 {
@@ -55,5 +63,27 @@
                 }
             ]
         }        
+    ],
+    "postsubmit": [
+        {
+            "name": "CtsAppOpsTestCases"
+        },
+        {
+            "name": "CtsPermissionTestCases",
+            "options": [
+                {
+                    "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.SplitPermissionTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.PermissionFlagsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.SharedUidPermissionsTest"
+                }
+            ]
+        }
     ]
 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 7404b19..9f958be 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -72,6 +72,8 @@
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.SensorPrivacyManagerInternal;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
 import android.hardware.hdmi.HdmiAudioSystemClient;
 import android.hardware.hdmi.HdmiClient;
 import android.hardware.hdmi.HdmiControlManager;
@@ -180,6 +182,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.view.Display;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
@@ -977,6 +980,36 @@
 
     private AtomicBoolean mMasterMute = new AtomicBoolean(false);
 
+    private DisplayManager mDisplayManager;
+
+    private DisplayListener mDisplayListener =
+      new DisplayListener() {
+        @Override
+        public void onDisplayAdded(int displayId) {}
+
+        @Override
+        public void onDisplayRemoved(int displayId) {}
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            if (displayId != Display.DEFAULT_DISPLAY) {
+                return;
+            }
+            int displayState = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState();
+            if (displayState == Display.STATE_ON) {
+                if (mMonitorRotation) {
+                    RotationHelper.enable();
+                }
+                AudioSystem.setParameters("screen_state=on");
+            } else {
+                if (mMonitorRotation) {
+                    //reduce wakeups (save current) by only listening when display is on
+                    RotationHelper.disable();
+                }
+                AudioSystem.setParameters("screen_state=off");
+            }
+        }
+      };
 
     ///////////////////////////////////////////////////////////////////////////
     // Construction
@@ -1255,6 +1288,8 @@
                 0 /* arg1 */,  0 /* arg2 */, null /* obj */,  0 /* delay */);
         queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER,
                 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
+
+        mDisplayManager = context.getSystemService(DisplayManager.class);
     }
 
     private void initVolumeStreamStates() {
@@ -1351,8 +1386,6 @@
                 new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
         intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
         intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
-        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
-        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
         intentFilter.addAction(Intent.ACTION_USER_BACKGROUND);
         intentFilter.addAction(Intent.ACTION_USER_FOREGROUND);
@@ -1382,6 +1415,8 @@
         } else {
             subscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener);
         }
+
+        mDisplayManager.registerDisplayListener(mDisplayListener, mAudioHandler);
     }
 
     public void systemReady() {
@@ -9552,17 +9587,6 @@
             } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)
                     || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                 mDeviceBroker.receiveBtEvent(intent);
-            } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
-                if (mMonitorRotation) {
-                    RotationHelper.enable();
-                }
-                AudioSystem.setParameters("screen_state=on");
-            } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-                if (mMonitorRotation) {
-                    //reduce wakeups (save current) by only listening when display is on
-                    RotationHelper.disable();
-                }
-                AudioSystem.setParameters("screen_state=off");
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                 sendMsg(mAudioHandler,
                         MSG_CONFIGURATION_CHANGED,
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 78c3808..8a54ae5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -580,7 +580,7 @@
         }
         final BiometricSchedulerOperation operation = mCurrentOperation;
         mHandler.postDelayed(() -> {
-            if (operation == mCurrentOperation) {
+            if (operation == mCurrentOperation && !operation.isFinished()) {
                 Counter.logIncrement("biometric.value_scheduler_watchdog_triggered_count");
                 clearScheduler();
             }
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 5b11cfe..4bfc090 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -36,16 +36,18 @@
 
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
-            Runnable modeChangeCallback) {
-        this(hbmController, modeChangeCallback,
+            Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig) {
+        this(hbmController, modeChangeCallback, displayDeviceConfig,
                 new DeviceConfigParameterProvider(DeviceConfigInterface.REAL));
     }
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
-            Runnable modeChangeCallback, DeviceConfigParameterProvider configParameterProvider) {
+            Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
+            DeviceConfigParameterProvider configParameterProvider) {
         mHbmController = hbmController;
         mModeChangeCallback = modeChangeCallback;
         mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled();
+        mNormalBrightnessModeController.resetNbmData(displayDeviceConfig.getLuxThrottlingData());
     }
 
     void dump(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index dd5afa2..da51569 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -33,12 +33,15 @@
     private final String mDisplayBrightnessStrategyName;
     private final boolean mShouldUseAutoBrightness;
 
+    private final boolean mIsSlowChange;
+
     private DisplayBrightnessState(Builder builder) {
         mBrightness = builder.getBrightness();
         mSdrBrightness = builder.getSdrBrightness();
         mBrightnessReason = builder.getBrightnessReason();
         mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
         mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
+        mIsSlowChange = builder.isSlowChange();
     }
 
     /**
@@ -77,6 +80,13 @@
         return mShouldUseAutoBrightness;
     }
 
+    /**
+     * @return {@code true} if the should transit to new state slowly
+     */
+    public boolean isSlowChange() {
+        return mIsSlowChange;
+    }
+
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -88,6 +98,8 @@
         stringBuilder.append(getBrightnessReason());
         stringBuilder.append("\n    shouldUseAutoBrightness:");
         stringBuilder.append(getShouldUseAutoBrightness());
+        stringBuilder.append("\n    isSlowChange:");
+        stringBuilder.append(mIsSlowChange);
         return stringBuilder.toString();
     }
 
@@ -111,13 +123,14 @@
                 && mBrightnessReason.equals(otherState.getBrightnessReason())
                 && TextUtils.equals(mDisplayBrightnessStrategyName,
                         otherState.getDisplayBrightnessStrategyName())
-                && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness();
+                && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
+                && mIsSlowChange == otherState.isSlowChange();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(
-                mBrightness, mSdrBrightness, mBrightnessReason, mShouldUseAutoBrightness);
+        return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
+                mShouldUseAutoBrightness, mIsSlowChange);
     }
 
     /**
@@ -129,6 +142,7 @@
         private BrightnessReason mBrightnessReason = new BrightnessReason();
         private String mDisplayBrightnessStrategyName;
         private boolean mShouldUseAutoBrightness;
+        private boolean mIsSlowChange;
 
         /**
          * Create a builder starting with the values from the specified {@link
@@ -143,6 +157,7 @@
             builder.setBrightnessReason(state.getBrightnessReason());
             builder.setDisplayBrightnessStrategyName(state.getDisplayBrightnessStrategyName());
             builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
+            builder.setIsSlowChange(state.isSlowChange());
             return builder;
         }
 
@@ -237,6 +252,21 @@
         }
 
         /**
+         * See {@link DisplayBrightnessState#isSlowChange()}.
+         */
+        public Builder setIsSlowChange(boolean shouldUseAutoBrightness) {
+            this.mIsSlowChange = shouldUseAutoBrightness;
+            return this;
+        }
+
+        /**
+         * See {@link DisplayBrightnessState#isSlowChange()}.
+         */
+        public boolean isSlowChange() {
+            return mIsSlowChange;
+        }
+
+        /**
          * This is used to construct an immutable DisplayBrightnessState object from its builder
          */
         public DisplayBrightnessState build() {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d19e78d..75c15eb 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -673,7 +673,7 @@
         HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback);
 
         mBrightnessRangeController = new BrightnessRangeController(hbmController,
-                modeChangeCallback);
+                modeChangeCallback, mDisplayDeviceConfig);
 
         mBrightnessThrottler = createBrightnessThrottlerLocked();
 
@@ -722,7 +722,9 @@
 
         setUpAutoBrightness(resources, handler);
 
-        mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+        mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic()
+                && !resources.getBoolean(
+                  com.android.internal.R.bool.config_displayColorFadeDisabled);
         mColorFadeFadesConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_animateScreenLights);
 
@@ -2286,8 +2288,17 @@
             if (!reportOnly && mPowerState.getScreenState() != state
                     && readyToUpdateDisplayState()) {
                 Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
-                // TODO(b/153319140) remove when we can get this from the above trace invocation
-                SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
+
+                String propertyKey = "debug.tracing.screen_state";
+                String propertyValue = String.valueOf(state);
+                try {
+                    // TODO(b/153319140) remove when we can get this from the above trace invocation
+                    SystemProperties.set(propertyKey, propertyValue);
+                } catch (RuntimeException e) {
+                    Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                            + " value=" + propertyValue + " " + e.getMessage());
+                }
+
                 mPowerState.setScreenState(state);
                 // Tell battery stats about the transition.
                 noteScreenState(state);
@@ -2380,8 +2391,17 @@
         }
         if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) {
             Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
-            // TODO(b/153319140) remove when we can get this from the above trace invocation
-            SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
+
+            String propertyKey = "debug.tracing.screen_brightness";
+            String propertyValue = String.valueOf(target);
+            try {
+                // TODO(b/153319140) remove when we can get this from the above trace invocation
+                SystemProperties.set(propertyKey, propertyValue);
+            } catch (RuntimeException e) {
+                Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                        + " value=" + propertyValue + " " + e.getMessage());
+            }
+
             noteScreenBrightness(target);
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 2694f55..42683d8 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -444,9 +444,6 @@
     @Nullable
     private BrightnessMappingStrategy mIdleModeBrightnessMapper;
 
-    // Indicates whether we should ramp slowly to the brightness value to follow.
-    private boolean mBrightnessToFollowSlowChange;
-
     private boolean mIsRbcActive;
 
     // Animators.
@@ -555,7 +552,7 @@
         mBrightnessThrottler = createBrightnessThrottlerLocked();
 
         mBrightnessRangeController = new BrightnessRangeController(hbmController,
-                modeChangeCallback);
+                modeChangeCallback, mDisplayDeviceConfig);
 
         mDisplayBrightnessController =
                 new DisplayBrightnessController(context, null,
@@ -616,7 +613,7 @@
 
         setUpAutoBrightness(resources, handler);
 
-        mColorFadeEnabled = mInjector.isColorFadeEnabled();
+        mColorFadeEnabled = mInjector.isColorFadeEnabled(resources);
         mColorFadeFadesConfig = resources.getBoolean(
                 R.bool.config_animateScreenLights);
 
@@ -1291,7 +1288,7 @@
         // actual state instead of the desired one.
         animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
         state = mPowerState.getScreenState();
-        boolean slowChange = false;
+
         final boolean userSetBrightnessChanged = mDisplayBrightnessController
                 .updateUserSetScreenBrightness();
 
@@ -1300,11 +1297,7 @@
         float brightnessState = displayBrightnessState.getBrightness();
         float rawBrightnessState = displayBrightnessState.getBrightness();
         mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
-
-        if (displayBrightnessState.getBrightnessReason().getReason()
-                == BrightnessReason.REASON_FOLLOWER) {
-            slowChange = mBrightnessToFollowSlowChange;
-        }
+        boolean slowChange = displayBrightnessState.isSlowChange();
 
         // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
         // doesn't yet have a valid lux value to use with auto-brightness.
@@ -1354,6 +1347,7 @@
                             .getRawAutomaticScreenBrightness();
                     brightnessState = clampScreenBrightness(brightnessState);
                     // slowly adapt to auto-brightness
+                    // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness
                     slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
                             && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
                     brightnessAdjustmentFlags =
@@ -1955,8 +1949,17 @@
             if (!reportOnly && mPowerState.getScreenState() != state
                     && readyToUpdateDisplayState()) {
                 Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
-                // TODO(b/153319140) remove when we can get this from the above trace invocation
-                SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
+
+                String propertyKey = "debug.tracing.screen_state";
+                String propertyValue = String.valueOf(state);
+                try {
+                    // TODO(b/153319140) remove when we can get this from the above trace invocation
+                    SystemProperties.set(propertyKey, propertyValue);
+                } catch (RuntimeException e) {
+                    Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                            + " value=" + propertyValue + " " + e.getMessage());
+                }
+
                 mPowerState.setScreenState(state);
                 // Tell battery stats about the transition.
                 noteScreenState(state);
@@ -2031,8 +2034,17 @@
         }
         if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) {
             Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
-            // TODO(b/153319140) remove when we can get this from the above trace invocation
-            SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
+
+            String propertyKey = "debug.tracing.screen_brightness";
+            String propertyValue = String.valueOf(target);
+            try {
+                // TODO(b/153319140) remove when we can get this from the above trace invocation
+                SystemProperties.set(propertyKey, propertyValue);
+            } catch (RuntimeException e) {
+                Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                        + " value=" + propertyValue + " " + e.getMessage());
+            }
+
             noteScreenBrightness(target);
         }
     }
@@ -2259,17 +2271,17 @@
             boolean slowChange) {
         mBrightnessRangeController.onAmbientLuxChange(ambientLux);
         if (nits < 0) {
-            mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
+            mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange);
         } else {
             float brightness = mDisplayBrightnessController.convertToFloatScale(nits);
             if (BrightnessUtils.isValidBrightnessValue(brightness)) {
-                mDisplayBrightnessController.setBrightnessToFollow(brightness);
+                mDisplayBrightnessController.setBrightnessToFollow(brightness, slowChange);
             } else {
                 // The device does not support nits
-                mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
+                mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness,
+                        slowChange);
             }
         }
-        mBrightnessToFollowSlowChange = slowChange;
         sendUpdatePowerState();
     }
 
@@ -2409,7 +2421,6 @@
         pw.println("  mReportedToPolicy="
                 + reportedToPolicyToString(mReportedScreenStateToPolicy));
         pw.println("  mIsRbcActive=" + mIsRbcActive);
-        pw.println("  mBrightnessToFollowSlowChange=" + mBrightnessToFollowSlowChange);
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
         mAutomaticBrightnessStrategy.dump(ipw);
 
@@ -2994,8 +3005,10 @@
                     sensorManager, resources);
         }
 
-        boolean isColorFadeEnabled() {
-            return !ActivityManager.isLowRamDeviceStatic();
+        boolean isColorFadeEnabled(Resources resources) {
+            return !ActivityManager.isLowRamDeviceStatic()
+                && !resources.getBoolean(
+                  com.android.internal.R.bool.config_displayColorFadeDisabled);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING
index 57c2e01..6f243e1 100644
--- a/services/core/java/com/android/server/display/TEST_MAPPING
+++ b/services/core/java/com/android/server/display/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
     "presubmit": [
         {
-            "name": "FrameworksMockingServicesTests",
-            "options": [
-                {"include-filter": "com.android.server.display"},
-                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-                {"exclude-annotation": "androidx.test.filters.FlakyTest"}
-            ]
-        },
-        {
-            "name": "FrameworksServicesTests",
+            "name": "DisplayServiceTests",
             "options": [
                 {"include-filter": "com.android.server.display"},
                 {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
@@ -17,5 +9,14 @@
                 {"exclude-annotation": "org.junit.Ignore"}
             ]
         }
+    ],
+    "postsubmit": [
+        {
+            "name": "DisplayServiceTests",
+            "options": [
+                {"include-filter": "com.android.server.display"},
+                {"exclude-annotation": "org.junit.Ignore"}
+            ]
+        }
     ]
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index a3bca9a..d4d1bae 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -19,10 +19,12 @@
 import android.hardware.display.BrightnessInfo;
 import android.os.PowerManager;
 import android.os.SystemClock;
-import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
 /**
  * Represents a particular brightness change event.
  */
@@ -34,6 +36,8 @@
     public static final int FLAG_IDLE_CURVE = 0x10;
     public static final int FLAG_LOW_POWER_MODE = 0x20;
 
+    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+
     private BrightnessReason mReason = new BrightnessReason();
     private int mDisplayId;
     private String mPhysicalDisplayId;
@@ -169,7 +173,7 @@
      * @return A stringified BrightnessEvent
      */
     public String toString(boolean includeTime) {
-        return (includeTime ? TimeUtils.formatForLogging(mTime) + " - " : "")
+        return (includeTime ? FORMAT.format(new Date(mTime)) + " - " : "")
                 + "BrightnessEvent: "
                 + "disp=" + mDisplayId
                 + ", physDisp=" + mPhysicalDisplayId
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
index 3fae224..8bf675c 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
@@ -54,6 +54,16 @@
     public static DisplayBrightnessState constructDisplayBrightnessState(
             int brightnessChangeReason, float brightness, float sdrBrightness,
             String displayBrightnessStrategyName) {
+        return constructDisplayBrightnessState(brightnessChangeReason, brightness, sdrBrightness,
+                displayBrightnessStrategyName, /* slowChange= */ false);
+    }
+
+    /**
+     * A utility to construct the DisplayBrightnessState
+     */
+    public static DisplayBrightnessState constructDisplayBrightnessState(
+            int brightnessChangeReason, float brightness, float sdrBrightness,
+            String displayBrightnessStrategyName, boolean slowChange) {
         BrightnessReason brightnessReason = new BrightnessReason();
         brightnessReason.setReason(brightnessChangeReason);
         return new DisplayBrightnessState.Builder()
@@ -61,6 +71,7 @@
                 .setSdrBrightness(sdrBrightness)
                 .setBrightnessReason(brightnessReason)
                 .setDisplayBrightnessStrategyName(displayBrightnessStrategyName)
+                .setIsSlowChange(slowChange)
                 .build();
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index ffd62a3..d6f0098 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -164,10 +164,10 @@
     /**
      * Sets the brightness to follow
      */
-    public void setBrightnessToFollow(Float brightnessToFollow) {
+    public void setBrightnessToFollow(float brightnessToFollow, boolean slowChange) {
         synchronized (mLock) {
             mDisplayBrightnessStrategySelector.getFollowerDisplayBrightnessStrategy()
-                    .setBrightnessToFollow(brightnessToFollow);
+                    .setBrightnessToFollow(brightnessToFollow, slowChange);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
index 090ec13..585f576 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
@@ -37,9 +37,13 @@
     // Set to PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no brightness to follow set.
     private float mBrightnessToFollow;
 
+    // Indicates whether we should ramp slowly to the brightness value to follow.
+    private boolean mBrightnessToFollowSlowChange;
+
     public FollowerBrightnessStrategy(int displayId) {
         mDisplayId = displayId;
         mBrightnessToFollow = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mBrightnessToFollowSlowChange = false;
     }
 
     @Override
@@ -48,7 +52,7 @@
         // Todo(b/241308599): Introduce a validator class and add validations before setting
         // the brightness
         return BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_FOLLOWER,
-                mBrightnessToFollow, mBrightnessToFollow, getName());
+                mBrightnessToFollow, mBrightnessToFollow, getName(), mBrightnessToFollowSlowChange);
     }
 
     @Override
@@ -60,8 +64,12 @@
         return mBrightnessToFollow;
     }
 
-    public void setBrightnessToFollow(float brightnessToFollow) {
+    /**
+     * Updates brightness value and brightness slowChange flag
+     **/
+    public void setBrightnessToFollow(float brightnessToFollow, boolean slowChange) {
         mBrightnessToFollow = brightnessToFollow;
+        mBrightnessToFollowSlowChange = slowChange;
     }
 
     /**
@@ -71,5 +79,6 @@
         writer.println("FollowerBrightnessStrategy:");
         writer.println("  mDisplayId=" + mDisplayId);
         writer.println("  mBrightnessToFollow:" + mBrightnessToFollow);
+        writer.println("  mBrightnessToFollowSlowChange:" + mBrightnessToFollowSlowChange);
     }
 }
diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
index feebdf1..dfb5f62 100644
--- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -60,6 +60,11 @@
                 DisplayManager.DeviceConfig.KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER, false);
     }
 
+    public boolean isDisableScreenWakeLocksWhileCachedFeatureEnabled() {
+        return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_DISABLE_SCREEN_WAKE_LOCKS_WHILE_CACHED, true);
+    }
+
     // feature: smooth_display_feature
     // parameter: peak_refresh_rate_default
     public float getPeakRefreshRateDefault() {
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 11e35ce..44524e2 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -2322,8 +2322,7 @@
         private final SparseBooleanArray mAuthenticationPossible = new SparseBooleanArray();
 
         public void observe() {
-            StatusBarManagerInternal statusBar =
-                    LocalServices.getService(StatusBarManagerInternal.class);
+            StatusBarManagerInternal statusBar = mInjector.getStatusBarManagerInternal();
             if (statusBar == null) {
                 return;
             }
@@ -2427,10 +2426,9 @@
 
         public void observe() {
             mDisplayManager = mContext.getSystemService(DisplayManager.class);
-            mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+            mDisplayManagerInternal = mInjector.getDisplayManagerInternal();
 
-            final SensorManagerInternal sensorManager =
-                    LocalServices.getService(SensorManagerInternal.class);
+            final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal();
             sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this);
 
             synchronized (mSensorObserverLock) {
@@ -2547,7 +2545,7 @@
             synchronized (mLock) {
                 setupHdrRefreshRates(mDefaultDisplayDeviceConfig);
             }
-            mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+            mDisplayManagerInternal = mInjector.getDisplayManagerInternal();
             mInjector.registerDisplayListener(this, mHandler,
                     DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
                     | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
@@ -2788,6 +2786,12 @@
         boolean registerThermalServiceListener(IThermalEventListener listener);
 
         boolean supportsFrameRateOverride();
+
+        DisplayManagerInternal getDisplayManagerInternal();
+
+        StatusBarManagerInternal getStatusBarManagerInternal();
+
+        SensorManagerInternal getSensorManagerInternal();
     }
 
     @VisibleForTesting
@@ -2885,6 +2889,21 @@
             return SurfaceFlingerProperties.enable_frame_rate_override().orElse(true);
         }
 
+        @Override
+        public DisplayManagerInternal getDisplayManagerInternal() {
+            return LocalServices.getService(DisplayManagerInternal.class);
+        }
+
+        @Override
+        public StatusBarManagerInternal getStatusBarManagerInternal() {
+            return LocalServices.getService(StatusBarManagerInternal.class);
+        }
+
+        @Override
+        public SensorManagerInternal getSensorManagerInternal() {
+            return LocalServices.getService(SensorManagerInternal.class);
+        }
+
         private DisplayManager getDisplayManager() {
             if (mDisplayManager == null) {
                 mDisplayManager = mContext.getSystemService(DisplayManager.class);
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 633bf731..0e8a5fb 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -17,6 +17,8 @@
 package com.android.server.dreams;
 
 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER;
+import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS;
 
 import android.app.ActivityTaskManager;
 import android.app.BroadcastOptions;
@@ -72,6 +74,7 @@
     private final Handler mHandler;
     private final Listener mListener;
     private final ActivityTaskManager mActivityTaskManager;
+    private final PowerManager mPowerManager;
 
     private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
             .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | FLAG_RECEIVER_FOREGROUND);
@@ -84,6 +87,15 @@
     private final Intent mCloseNotificationShadeIntent;
     private final Bundle mCloseNotificationShadeOptions;
 
+    /**
+     * If this flag is on, we report user activity to {@link PowerManager} so that the screen
+     * doesn't shut off immediately when a dream quits unexpectedly. The device will instead go to
+     * keyguard and time out back to dreaming shortly.
+     *
+     * This allows the dream a second chance to relaunch in case of an app update or other crash.
+     */
+    private final boolean mResetScreenTimeoutOnUnexpectedDreamExit;
+
     private DreamRecord mCurrentDream;
 
     // Whether a dreaming started intent has been broadcast.
@@ -101,6 +113,7 @@
         mHandler = handler;
         mListener = listener;
         mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
+        mPowerManager = mContext.getSystemService(PowerManager.class);
         mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         mCloseNotificationShadeIntent.putExtra(EXTRA_REASON_KEY, EXTRA_REASON_VALUE);
         mCloseNotificationShadeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -110,6 +123,8 @@
                         EXTRA_REASON_VALUE)
                 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                 .toBundle();
+        mResetScreenTimeoutOnUnexpectedDreamExit = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_resetScreenTimeoutOnUnexpectedDreamExit);
     }
 
     /**
@@ -235,6 +250,17 @@
     }
 
     /**
+     * Sends a user activity signal to PowerManager to stop the screen from turning off immediately
+     * if there hasn't been any user interaction in a while.
+     */
+    private void resetScreenTimeout() {
+        Slog.i(TAG, "Resetting screen timeout");
+        long time = SystemClock.uptimeMillis();
+        mPowerManager.userActivity(time, USER_ACTIVITY_EVENT_OTHER,
+                USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS);
+    }
+
+    /**
      * Stops dreaming.
      *
      * The current dream, if any, and any unstopped previous dreams are stopped. The device stops
@@ -448,6 +474,9 @@
             mHandler.post(() -> {
                 mService = null;
                 if (mCurrentDream == DreamRecord.this) {
+                    if (mResetScreenTimeoutOnUnexpectedDreamExit) {
+                        resetScreenTimeout();
+                    }
                     stopDream(true /*immediate*/, "binder died");
                 }
             });
@@ -473,6 +502,9 @@
             mHandler.post(() -> {
                 mService = null;
                 if (mCurrentDream == DreamRecord.this) {
+                    if (mResetScreenTimeoutOnUnexpectedDreamExit) {
+                        resetScreenTimeout();
+                    }
                     stopDream(true /*immediate*/, "service disconnected");
                 }
             });
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index eb2da34..4b30ae5 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -21,12 +21,16 @@
 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.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InputDevice;
+import android.view.KeyEvent;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -36,8 +40,12 @@
 
 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;
 
 /**
  * Collect Keyboard metrics
@@ -50,13 +58,14 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     @Retention(SOURCE)
-    @IntDef(prefix = { "LAYOUT_SELECTION_CRITERIA_" }, value = {
+    @IntDef(prefix = {"LAYOUT_SELECTION_CRITERIA_"}, value = {
             LAYOUT_SELECTION_CRITERIA_USER,
             LAYOUT_SELECTION_CRITERIA_DEVICE,
             LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
             LAYOUT_SELECTION_CRITERIA_DEFAULT
     })
-    public @interface LayoutSelectionCriteria {}
+    public @interface LayoutSelectionCriteria {
+    }
 
     /** Manual selection by user */
     public static final int LAYOUT_SELECTION_CRITERIA_USER = 0;
@@ -76,17 +85,301 @@
     @VisibleForTesting
     static final String DEFAULT_LANGUAGE_TAG = "None";
 
+    public enum KeyboardLogEvent {
+        UNSPECIFIED(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED,
+                "INVALID_KEYBOARD_EVENT"),
+        HOME(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME,
+                "HOME"),
+        RECENT_APPS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS,
+                "RECENT_APPS"),
+        BACK(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK,
+                "BACK"),
+        APP_SWITCH(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH,
+                "APP_SWITCH"),
+        LAUNCH_ASSISTANT(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT,
+                "LAUNCH_ASSISTANT"),
+        LAUNCH_VOICE_ASSISTANT(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT,
+                "LAUNCH_VOICE_ASSISTANT"),
+        LAUNCH_SYSTEM_SETTINGS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS,
+                "LAUNCH_SYSTEM_SETTINGS"),
+        TOGGLE_NOTIFICATION_PANEL(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL,
+                "TOGGLE_NOTIFICATION_PANEL"),
+        TOGGLE_TASKBAR(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR,
+                "TOGGLE_TASKBAR"),
+        TAKE_SCREENSHOT(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT,
+                "TAKE_SCREENSHOT"),
+        OPEN_SHORTCUT_HELPER(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER,
+                "OPEN_SHORTCUT_HELPER"),
+        BRIGHTNESS_UP(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP,
+                "BRIGHTNESS_UP"),
+        BRIGHTNESS_DOWN(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN,
+                "BRIGHTNESS_DOWN"),
+        KEYBOARD_BACKLIGHT_UP(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP,
+                "KEYBOARD_BACKLIGHT_UP"),
+        KEYBOARD_BACKLIGHT_DOWN(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN,
+                "KEYBOARD_BACKLIGHT_DOWN"),
+        KEYBOARD_BACKLIGHT_TOGGLE(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE,
+                "KEYBOARD_BACKLIGHT_TOGGLE"),
+        VOLUME_UP(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP,
+                "VOLUME_UP"),
+        VOLUME_DOWN(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN,
+                "VOLUME_DOWN"),
+        VOLUME_MUTE(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE,
+                "VOLUME_MUTE"),
+        ALL_APPS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS,
+                "ALL_APPS"),
+        LAUNCH_SEARCH(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH,
+                "LAUNCH_SEARCH"),
+        LANGUAGE_SWITCH(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH,
+                "LANGUAGE_SWITCH"),
+        ACCESSIBILITY_ALL_APPS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS,
+                "ACCESSIBILITY_ALL_APPS"),
+        TOGGLE_CAPS_LOCK(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK,
+                "TOGGLE_CAPS_LOCK"),
+        SYSTEM_MUTE(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE,
+                "SYSTEM_MUTE"),
+        SPLIT_SCREEN_NAVIGATION(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION,
+                "SPLIT_SCREEN_NAVIGATION"),
+        TRIGGER_BUG_REPORT(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT,
+                "TRIGGER_BUG_REPORT"),
+        LOCK_SCREEN(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN,
+                "LOCK_SCREEN"),
+        OPEN_NOTES(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES,
+                "OPEN_NOTES"),
+        TOGGLE_POWER(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER,
+                "TOGGLE_POWER"),
+        SYSTEM_NAVIGATION(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION,
+                "SYSTEM_NAVIGATION"),
+        SLEEP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP,
+                "SLEEP"),
+        WAKEUP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP,
+                "WAKEUP"),
+        MEDIA_KEY(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY,
+                "MEDIA_KEY"),
+        LAUNCH_DEFAULT_BROWSER(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER,
+                "LAUNCH_DEFAULT_BROWSER"),
+        LAUNCH_DEFAULT_EMAIL(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL,
+                "LAUNCH_DEFAULT_EMAIL"),
+        LAUNCH_DEFAULT_CONTACTS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS,
+                "LAUNCH_DEFAULT_CONTACTS"),
+        LAUNCH_DEFAULT_CALENDAR(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR,
+                "LAUNCH_DEFAULT_CALENDAR"),
+        LAUNCH_DEFAULT_CALCULATOR(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR,
+                "LAUNCH_DEFAULT_CALCULATOR"),
+        LAUNCH_DEFAULT_MUSIC(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC,
+                "LAUNCH_DEFAULT_MUSIC"),
+        LAUNCH_DEFAULT_MAPS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS,
+                "LAUNCH_DEFAULT_MAPS"),
+        LAUNCH_DEFAULT_MESSAGING(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING,
+                "LAUNCH_DEFAULT_MESSAGING"),
+        LAUNCH_DEFAULT_GALLERY(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY,
+                "LAUNCH_DEFAULT_GALLERY"),
+        LAUNCH_DEFAULT_FILES(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES,
+                "LAUNCH_DEFAULT_FILES"),
+        LAUNCH_DEFAULT_WEATHER(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER,
+                "LAUNCH_DEFAULT_WEATHER"),
+        LAUNCH_DEFAULT_FITNESS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS,
+                "LAUNCH_DEFAULT_FITNESS"),
+        LAUNCH_APPLICATION_BY_PACKAGE_NAME(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME,
+                "LAUNCH_APPLICATION_BY_PACKAGE_NAME");
+
+        private final int mValue;
+        private final String mName;
+
+        private static final SparseArray<KeyboardLogEvent> VALUE_TO_ENUM_MAP = new SparseArray<>();
+
+        static {
+            for (KeyboardLogEvent type : KeyboardLogEvent.values()) {
+                VALUE_TO_ENUM_MAP.put(type.mValue, type);
+            }
+        }
+
+        KeyboardLogEvent(int enumValue, String enumName) {
+            mValue = enumValue;
+            mName = enumName;
+        }
+
+        public int getIntValue() {
+            return mValue;
+        }
+
+        /**
+         * Convert int value to corresponding KeyboardLogEvent enum. If can't find any matching
+         * value will return {@code null}
+         */
+        @Nullable
+        public static KeyboardLogEvent from(int value) {
+            return VALUE_TO_ENUM_MAP.get(value);
+        }
+
+        /**
+         * Find KeyboardLogEvent corresponding to volume up/down/mute key events.
+         */
+        @Nullable
+        public static KeyboardLogEvent getVolumeEvent(int keycode) {
+            switch (keycode) {
+                case KeyEvent.KEYCODE_VOLUME_DOWN:
+                    return VOLUME_DOWN;
+                case KeyEvent.KEYCODE_VOLUME_UP:
+                    return VOLUME_UP;
+                case KeyEvent.KEYCODE_VOLUME_MUTE:
+                    return VOLUME_MUTE;
+                default:
+                    return null;
+            }
+        }
+
+        /**
+         * Find KeyboardLogEvent corresponding to brightness up/down key events.
+         */
+        @Nullable
+        public static KeyboardLogEvent getBrightnessEvent(int keycode) {
+            switch (keycode) {
+                case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
+                    return BRIGHTNESS_DOWN;
+                case KeyEvent.KEYCODE_BRIGHTNESS_UP:
+                    return BRIGHTNESS_UP;
+                default:
+                    return null;
+            }
+        }
+
+        /**
+         * Find KeyboardLogEvent corresponding to intent filter category. Returns
+         * {@code null if no matching event found}
+         */
+        @Nullable
+        public static KeyboardLogEvent getLogEventFromIntent(Intent intent) {
+            Intent selectorIntent = intent.getSelector();
+            if (selectorIntent != null) {
+                Set<String> selectorCategories = selectorIntent.getCategories();
+                if (selectorCategories != null && !selectorCategories.isEmpty()) {
+                    for (String intentCategory : selectorCategories) {
+                        KeyboardLogEvent logEvent = getEventFromSelectorCategory(intentCategory);
+                        if (logEvent == null) {
+                            continue;
+                        }
+                        return logEvent;
+                    }
+                }
+            }
+
+            Set<String> intentCategories = intent.getCategories();
+            if (intentCategories == null || intentCategories.isEmpty()
+                    || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
+                return null;
+            }
+            if (intent.getComponent() == null) {
+                return null;
+            }
+
+            // TODO(b/280423320): Add new field package name associated in the
+            //  KeyboardShortcutEvent atom and log it accordingly.
+            return LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+        }
+
+        @Nullable
+        private static KeyboardLogEvent getEventFromSelectorCategory(String category) {
+            switch (category) {
+                case Intent.CATEGORY_APP_BROWSER:
+                    return LAUNCH_DEFAULT_BROWSER;
+                case Intent.CATEGORY_APP_EMAIL:
+                    return LAUNCH_DEFAULT_EMAIL;
+                case Intent.CATEGORY_APP_CONTACTS:
+                    return LAUNCH_DEFAULT_CONTACTS;
+                case Intent.CATEGORY_APP_CALENDAR:
+                    return LAUNCH_DEFAULT_CALENDAR;
+                case Intent.CATEGORY_APP_CALCULATOR:
+                    return LAUNCH_DEFAULT_CALCULATOR;
+                case Intent.CATEGORY_APP_MUSIC:
+                    return LAUNCH_DEFAULT_MUSIC;
+                case Intent.CATEGORY_APP_MAPS:
+                    return LAUNCH_DEFAULT_MAPS;
+                case Intent.CATEGORY_APP_MESSAGING:
+                    return LAUNCH_DEFAULT_MESSAGING;
+                case Intent.CATEGORY_APP_GALLERY:
+                    return LAUNCH_DEFAULT_GALLERY;
+                case Intent.CATEGORY_APP_FILES:
+                    return LAUNCH_DEFAULT_FILES;
+                case Intent.CATEGORY_APP_WEATHER:
+                    return LAUNCH_DEFAULT_WEATHER;
+                case Intent.CATEGORY_APP_FITNESS:
+                    return LAUNCH_DEFAULT_FITNESS;
+                default:
+                    return null;
+            }
+        }
+    }
+
     /**
      * Log keyboard system shortcuts for the proto
      * {@link com.android.os.input.KeyboardSystemsEventReported}
      * defined in "stats/atoms/input/input_extension_atoms.proto"
      */
-    public static void logKeyboardSystemsEventReportedAtom(InputDevice inputDevice,
-            int keyboardSystemEvent, int[] keyCode, int modifierState) {
+    public static void logKeyboardSystemsEventReportedAtom(@Nullable InputDevice inputDevice,
+            @Nullable KeyboardLogEvent keyboardSystemEvent, int modifierState, int... keyCodes) {
+        // Logging Keyboard system event only for an external HW keyboard. We should not log events
+        // for virtual keyboards or internal Key events.
+        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
+            return;
+        }
         int vendorId = inputDevice.getVendorId();
         int productId = inputDevice.getProductId();
+        if (keyboardSystemEvent == null) {
+            Slog.w(TAG, "Invalid keyboard event logging, keycode = " + Arrays.toString(keyCodes)
+                    + ", modifier state = " + modifierState);
+            return;
+        }
         FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
-                vendorId, productId, keyboardSystemEvent, keyCode, modifierState);
+                vendorId, productId, keyboardSystemEvent.getIntValue(), keyCodes, modifierState);
+
+        if (DEBUG) {
+            Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemEvent.mName);
+        }
     }
 
     /**
@@ -94,8 +387,8 @@
      * {@link com.android.os.input.KeyboardConfigured} atom
      *
      * @param event {@link KeyboardConfigurationEvent} contains information about keyboard
-     *               configuration. Use {@link KeyboardConfigurationEvent.Builder} to create the
-     *               configuration event to log.
+     *              configuration. Use {@link KeyboardConfigurationEvent.Builder} to create the
+     *              configuration event to log.
      */
     public static void logKeyboardConfiguredAtom(KeyboardConfigurationEvent event) {
         // Creating proto to log nested field KeyboardLayoutConfig in atom
@@ -241,7 +534,7 @@
                     KeyboardLayout selectedLayout = mSelectedLayoutList.get(i);
                     @LayoutSelectionCriteria int layoutSelectionCriteria =
                             mLayoutSelectionCriteriaList.get(i);
-                    InputMethodSubtype imeSubtype =  mImeSubtypeList.get(i);
+                    InputMethodSubtype imeSubtype = mImeSubtypeList.get(i);
                     String keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
                     keyboardLanguageTag = keyboardLanguageTag == null ? DEFAULT_LANGUAGE_TAG
                             : keyboardLanguageTag;
@@ -328,4 +621,3 @@
                 || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT;
     }
 }
-
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index a1b67e1..f1698dd 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -37,6 +37,7 @@
 import android.util.EventLog;
 import android.util.Slog;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.GuardedBy;
@@ -75,7 +76,8 @@
     @GuardedBy("ImfLock.class")
     @Override
     public void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
-            int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
         if (curMethod != null) {
             if (DEBUG) {
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index c53f1a5..b12a816 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -30,6 +30,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodSubtype;
 import android.window.ImeOnBackInvokedDispatcher;
 
@@ -198,8 +199,8 @@
 
     // TODO(b/192412909): Convert this back to void method
     @AnyThread
-    boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, int flags,
-            ResultReceiver resultReceiver) {
+    boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
+            @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
         try {
             mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
index 27f6a89..29fa369 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
@@ -21,6 +21,7 @@
 import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethod;
 
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 
@@ -34,13 +35,13 @@
      *
      * @param showInputToken A token that represents the requester to show IME.
      * @param statsToken     A token that tracks the progress of an IME request.
-     * @param showFlags      Provides additional operating flags to show IME.
      * @param resultReceiver If non-null, this will be called back to the caller when
      *                       it has processed request to tell what it has done.
      * @param reason         The reason for requesting to show IME.
      */
     default void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
-            int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {}
+            @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {}
 
     /**
      * Performs hiding IME to the given window
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index f012d91..9ad4628 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -221,17 +221,21 @@
 
     /**
      * Called when {@link InputMethodManagerService} is processing the show IME request.
-     * @param statsToken The token for tracking this show request
-     * @param showFlags The additional operation flags to indicate whether this show request mode is
-     *                  implicit or explicit.
-     * @return {@code true} when the computer has proceed this show request operation.
+     *
+     * @param statsToken The token for tracking this show request.
+     * @return {@code true} when the show request can proceed.
      */
-    boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, int showFlags) {
+    boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken,
+            @InputMethodManager.ShowFlags int showFlags) {
         if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) {
             ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
             return false;
         }
         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
+        // We only "set" the state corresponding to the flags, as this will be reset
+        // in clearImeShowFlags during a hide request.
+        // Thus, we keep the strongest values set (e.g. an implicit show right after
+        // an explicit show will still be considered explicit, likewise for forced).
         if ((showFlags & InputMethodManager.SHOW_FORCED) != 0) {
             mRequestedShowExplicitly = true;
             mShowForced = true;
@@ -243,12 +247,12 @@
 
     /**
      * Called when {@link InputMethodManagerService} is processing the hide IME request.
-     * @param statsToken The token for tracking this hide request
-     * @param hideFlags The additional operation flags to indicate whether this hide request mode is
-     *                  implicit or explicit.
-     * @return {@code true} when the computer has proceed this hide request operations.
+     *
+     * @param statsToken The token for tracking this hide request.
+     * @return {@code true} when the hide request can proceed.
      */
-    boolean canHideIme(@NonNull ImeTracker.Token statsToken, int hideFlags) {
+    boolean canHideIme(@NonNull ImeTracker.Token statsToken,
+            @InputMethodManager.HideFlags int hideFlags) {
         if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
                 && (mRequestedShowExplicitly || mShowForced)) {
             if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
@@ -264,13 +268,31 @@
         return true;
     }
 
-    int getImeShowFlags() {
+    /**
+     * Returns the show flags for IME. This translates from {@link InputMethodManager.ShowFlags}
+     * to {@link InputMethod.ShowFlags}.
+     */
+    @InputMethod.ShowFlags
+    int getShowFlagsForInputMethodServiceOnly() {
         int flags = 0;
         if (mShowForced) {
             flags |= InputMethod.SHOW_FORCED | InputMethod.SHOW_EXPLICIT;
         } else if (mRequestedShowExplicitly) {
             flags |= InputMethod.SHOW_EXPLICIT;
-        } else {
+        }
+        return flags;
+    }
+
+    /**
+     * Returns the show flags for IMM. This translates from {@link InputMethod.ShowFlags}
+     * to {@link InputMethodManager.ShowFlags}.
+     */
+    @InputMethodManager.ShowFlags
+    int getShowFlags() {
+        int flags = 0;
+        if (mShowForced) {
+            flags |= InputMethodManager.SHOW_FORCED;
+        } else if (!mRequestedShowExplicitly) {
             flags |= InputMethodManager.SHOW_IMPLICIT;
         }
         return flags;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7bda2c1..c5fbcb9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2468,7 +2468,7 @@
             final ImeTracker.Token statsToken = mCurStatsToken;
             mCurStatsToken = null;
             showCurrentInputLocked(mCurFocusedWindow, statsToken,
-                    mVisibilityStateComputer.getImeShowFlags(),
+                    mVisibilityStateComputer.getShowFlags(),
                     null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
         }
 
@@ -3404,8 +3404,9 @@
 
     @Override
     public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, int lastClickTooType,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            int lastClickTooType, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
@@ -3578,15 +3579,17 @@
 
     @GuardedBy("ImfLock.class")
     boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
-            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @InputMethodManager.ShowFlags int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         return showCurrentInputLocked(windowToken, statsToken, flags,
                 MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
     }
 
     @GuardedBy("ImfLock.class")
     private boolean showCurrentInputLocked(IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            int lastClickToolType, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
             statsToken = createStatsTokenForFocusedClient(true /* show */,
@@ -3617,7 +3620,8 @@
                 curMethod.updateEditorToolType(lastClickToolType);
             }
             mVisibilityApplier.performShowIme(windowToken, statsToken,
-                    mVisibilityStateComputer.getImeShowFlags(), resultReceiver, reason);
+                    mVisibilityStateComputer.getShowFlagsForInputMethodServiceOnly(),
+                    resultReceiver, reason);
             mVisibilityStateComputer.setInputShown(true);
             return true;
         } else {
@@ -3629,8 +3633,8 @@
 
     @Override
     public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#hideSoftInput");
@@ -3660,7 +3664,8 @@
 
     @GuardedBy("ImfLock.class")
     boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
-            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
             statsToken = createStatsTokenForFocusedClient(false /* show */,
@@ -4847,7 +4852,7 @@
     }
 
     @BinderThread
-    private void hideMySoftInput(@NonNull IBinder token, int flags,
+    private void hideMySoftInput(@NonNull IBinder token, @InputMethodManager.HideFlags int flags,
             @SoftInputShowHideReason int reason) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
@@ -4869,7 +4874,7 @@
     }
 
     @BinderThread
-    private void showMySoftInput(@NonNull IBinder token, int flags) {
+    private void showMySoftInput(@NonNull IBinder token, @InputMethodManager.ShowFlags int flags) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
             synchronized (ImfLock.class) {
@@ -6828,8 +6833,8 @@
 
         @BinderThread
         @Override
-        public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason,
-                AndroidFuture future /* T=Void */) {
+        public void hideMySoftInput(@InputMethodManager.HideFlags int flags,
+                @SoftInputShowHideReason int reason, AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked")
             final AndroidFuture<Void> typedFuture = future;
             try {
@@ -6842,7 +6847,8 @@
 
         @BinderThread
         @Override
-        public void showMySoftInput(int flags, AndroidFuture future /* T=Void */) {
+        public void showMySoftInput(@InputMethodManager.ShowFlags int flags,
+                AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked")
             final AndroidFuture<Void> typedFuture = future;
             try {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 8e1ad65..7552e93 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -118,7 +118,6 @@
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
@@ -1021,6 +1020,7 @@
         }
 
         mAssistants.resetDefaultAssistantsIfNecessary();
+        mPreferencesHelper.syncChannelsBypassingDnd();
     }
 
     @VisibleForTesting
@@ -1221,8 +1221,7 @@
                 }
             }
 
-            int mustNotHaveFlags = mFlagResolver.isEnabled(ALLOW_DISMISS_ONGOING)
-                    ? FLAG_NO_DISMISS : FLAG_ONGOING_EVENT;
+            int mustNotHaveFlags = FLAG_NO_DISMISS;
             cancelNotification(callingUid, callingPid, pkg, tag, id,
                     /* mustHaveFlags= */ 0,
                     /* mustNotHaveFlags= */ mustNotHaveFlags,
@@ -1861,6 +1860,7 @@
                     mConditionProviders.onUserSwitched(userId);
                     mListeners.onUserSwitched(userId);
                     mZenModeHelper.onUserSwitched(userId);
+                    mPreferencesHelper.syncChannelsBypassingDnd();
                 }
                 // assistant is the only thing that cares about managed profiles specifically
                 mAssistants.onUserSwitched(userId);
@@ -2491,6 +2491,16 @@
         getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
                 ReviewNotificationPermissionsReceiver.getFilter(),
                 Context.RECEIVER_NOT_EXPORTED);
+
+        mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null,
+                new AppOpsManager.OnOpChangedInternalListener() {
+                    @Override
+                    public void onOpChanged(@NonNull String op, @NonNull String packageName,
+                            int userId) {
+                        mHandler.post(
+                                () -> handleNotificationPermissionChange(packageName, userId));
+                    }
+                });
     }
 
     /**
@@ -3557,13 +3567,9 @@
                     .setPackageName(pkg)
                     .setSubtype(enabled ? 1 : 0));
             mNotificationChannelLogger.logAppNotificationsAllowed(uid, pkg, enabled);
-            // Now, cancel any outstanding notifications that are part of a just-disabled app
-            if (!enabled) {
-                cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0,
-                        UserHandle.getUserId(uid), REASON_PACKAGE_BANNED);
-            }
 
-            handleSavePolicyFile();
+            // Outstanding notifications from this package will be cancelled as soon as we get the
+            // callback from AppOpsManager.
         }
 
         /**
@@ -5883,6 +5889,23 @@
         }
     };
 
+    private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) {
+        if (!mUmInternal.isUserInitialized(userId)) {
+            return; // App-op "updates" are sent when starting a new user the first time.
+        }
+        int uid = mPackageManagerInternal.getPackageUid(pkg, 0, userId);
+        if (uid == INVALID_UID) {
+            Log.e(TAG, String.format("No uid found for %s, %s!", pkg, userId));
+            return;
+        }
+        boolean hasPermission = mPermissionHelper.hasPermission(uid);
+        if (!hasPermission) {
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, /* channelId= */ null,
+                    /* mustHaveFlags= */ 0, /* mustNotHaveFlags= */ 0, userId,
+                    REASON_PACKAGE_BANNED);
+        }
+    }
+
     protected void checkNotificationListenerAccess() {
         if (!isCallerSystemOrPhone()) {
             getContext().enforceCallingPermission(
@@ -6862,13 +6885,11 @@
         }
 
         // Only notifications that can be non-dismissible can have the flag FLAG_NO_DISMISS
-        if (mFlagResolver.isEnabled(ALLOW_DISMISS_ONGOING)) {
-            if (((notification.flags & FLAG_ONGOING_EVENT) > 0)
-                    && canBeNonDismissible(ai, notification)) {
-                notification.flags |= FLAG_NO_DISMISS;
-            } else {
-                notification.flags &= ~FLAG_NO_DISMISS;
-            }
+        if (((notification.flags & FLAG_ONGOING_EVENT) > 0)
+                && canBeNonDismissible(ai, notification)) {
+            notification.flags |= FLAG_NO_DISMISS;
+        } else {
+            notification.flags &= ~FLAG_NO_DISMISS;
         }
 
         int canColorize = getContext().checkPermission(
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1818d25..1bb1092 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -202,7 +202,7 @@
     private SparseBooleanArray mLockScreenShowNotifications;
     private SparseBooleanArray mLockScreenPrivateNotifications;
     private boolean mIsMediaNotificationFilteringEnabled = DEFAULT_MEDIA_NOTIFICATION_FILTERING;
-    private boolean mAreChannelsBypassingDnd;
+    private boolean mCurrentUserHasChannelsBypassingDnd;
     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
     private boolean mShowReviewPermissionsNotification;
 
@@ -230,7 +230,6 @@
         updateBadgingEnabled();
         updateBubblesEnabled();
         updateMediaNotificationFilteringEnabled();
-        syncChannelsBypassingDnd(Process.SYSTEM_UID, true);  // init comes from system
     }
 
     public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
@@ -893,7 +892,7 @@
             r.groups.put(group.getId(), group);
         }
         if (needsDndChange) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
     }
 
@@ -972,7 +971,7 @@
                         existing.setBypassDnd(bypassDnd);
                         needsPolicyFileChange = true;
 
-                        if (bypassDnd != mAreChannelsBypassingDnd
+                        if (bypassDnd != mCurrentUserHasChannelsBypassingDnd
                                 || previousExistingImportance != existing.getImportance()) {
                             needsDndChange = true;
                         }
@@ -1031,7 +1030,7 @@
                 }
 
                 r.channels.put(channel.getId(), channel);
-                if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
+                if (channel.canBypassDnd() != mCurrentUserHasChannelsBypassingDnd) {
                     needsDndChange = true;
                 }
                 MetricsLogger.action(getChannelLog(channel, pkg).setType(
@@ -1041,7 +1040,7 @@
         }
 
         if (needsDndChange) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
 
         return needsPolicyFileChange;
@@ -1127,14 +1126,14 @@
                 // relevantly affected without the parent channel already having been.
             }
 
-            if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
+            if (updatedChannel.canBypassDnd() != mCurrentUserHasChannelsBypassingDnd
                     || channel.getImportance() != updatedChannel.getImportance()) {
                 needsDndChange = true;
                 changed = true;
             }
         }
         if (needsDndChange) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
         if (changed) {
             updateConfig();
@@ -1321,7 +1320,7 @@
             }
         }
         if (channelBypassedDnd) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
         return deletedChannel;
     }
@@ -1538,7 +1537,7 @@
             }
         }
         if (groupBypassedDnd) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
         return deletedChannels;
     }
@@ -1685,8 +1684,8 @@
                 }
             }
         }
-        if (!deletedChannelIds.isEmpty() && mAreChannelsBypassingDnd) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+        if (!deletedChannelIds.isEmpty() && mCurrentUserHasChannelsBypassingDnd) {
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
         return deletedChannelIds;
     }
@@ -1788,21 +1787,28 @@
     }
 
     /**
-     * Syncs {@link #mAreChannelsBypassingDnd} with the current user's notification policy before
-     * updating
+     * Syncs {@link #mCurrentUserHasChannelsBypassingDnd} with the current user's notification
+     * policy before updating. Must be called:
+     * <ul>
+     *     <li>On system init, after channels and DND configurations are loaded.</li>
+     *     <li>When the current user changes, after the corresponding DND config is loaded.</li>
+     * </ul>
      */
-    private void syncChannelsBypassingDnd(int callingUid, boolean fromSystemOrSystemUi) {
-        mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
-                & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
+    void syncChannelsBypassingDnd() {
+        mCurrentUserHasChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
+                & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
 
-        updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+        updateCurrentUserHasChannelsBypassingDnd(/* callingUid= */ Process.SYSTEM_UID,
+                /* fromSystemOrSystemUi= */ true);
     }
 
     /**
-     * Updates the user's NotificationPolicy based on whether the current userId
-     * has channels bypassing DND
+     * Updates the user's NotificationPolicy based on whether the current userId has channels
+     * bypassing DND. It should be called whenever a channel is created, updated, or deleted, or
+     * when the current user is switched.
      */
-    private void updateChannelsBypassingDnd(int callingUid, boolean fromSystemOrSystemUi) {
+    private void updateCurrentUserHasChannelsBypassingDnd(int callingUid,
+            boolean fromSystemOrSystemUi) {
         ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>();
 
         final int currentUserId = getCurrentUser();
@@ -1817,7 +1823,7 @@
 
                 for (NotificationChannel channel : r.channels.values()) {
                     if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
-                        candidatePkgs.add(new Pair(r.pkg, r.uid));
+                        candidatePkgs.add(new Pair<>(r.pkg, r.uid));
                         break;
                     }
                 }
@@ -1830,9 +1836,9 @@
             }
         }
         boolean haveBypassingApps = candidatePkgs.size() > 0;
-        if (mAreChannelsBypassingDnd != haveBypassingApps) {
-            mAreChannelsBypassingDnd = haveBypassingApps;
-            updateZenPolicy(mAreChannelsBypassingDnd, callingUid, fromSystemOrSystemUi);
+        if (mCurrentUserHasChannelsBypassingDnd != haveBypassingApps) {
+            mCurrentUserHasChannelsBypassingDnd = haveBypassingApps;
+            updateZenPolicy(mCurrentUserHasChannelsBypassingDnd, callingUid, fromSystemOrSystemUi);
         }
     }
 
@@ -1869,7 +1875,7 @@
     }
 
     public boolean areChannelsBypassingDnd() {
-        return mAreChannelsBypassingDnd;
+        return mCurrentUserHasChannelsBypassingDnd;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 2206eac..26e70c0 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -356,9 +356,13 @@
     /**
      * Performs a non-staged install of the given {@code apexFile}.
      *
+     * If {@code force} is {@code true}, then  update is forced even for APEXes that do not support
+     * non-staged update. This feature is only available on debuggable builds to improve development
+     * velocity of the teams that have their code packaged in an APEX.
+     *
      * @return {@code ApeInfo} about the newly installed APEX package.
      */
-    abstract ApexInfo installPackage(File apexFile) throws PackageManagerException;
+    abstract ApexInfo installPackage(File apexFile, boolean force) throws PackageManagerException;
 
     /**
      * Get a list of apex system services implemented in an apex.
@@ -903,10 +907,11 @@
         }
 
         @Override
-        ApexInfo installPackage(File apexFile)
+        ApexInfo installPackage(File apexFile, boolean force)
                 throws PackageManagerException {
             try {
-                return waitForApexService().installAndActivatePackage(apexFile.getAbsolutePath());
+                return waitForApexService().installAndActivatePackage(apexFile.getAbsolutePath(),
+                        force);
             } catch (RemoteException e) {
                 throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
                         "apexservice not available");
diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java
index 6de7f07..dd96a2b 100644
--- a/services/core/java/com/android/server/pm/InstallArgs.java
+++ b/services/core/java/com/android/server/pm/InstallArgs.java
@@ -43,6 +43,7 @@
     final IPackageInstallObserver2 mObserver;
     // Always refers to PackageManager flags only
     final int mInstallFlags;
+    final int mDevelopmentInstallFlags;
     @NonNull
     final InstallSource mInstallSource;
     final String mVolumeUuid;
@@ -69,8 +70,8 @@
     @Nullable String[] mInstructionSets;
 
     InstallArgs(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
-            int installFlags, InstallSource installSource, String volumeUuid,
-            UserHandle user, String[] instructionSets, String abiOverride,
+            int installFlags, int developmentInstallFlags, InstallSource installSource,
+            String volumeUuid,  UserHandle user, String[] instructionSets, String abiOverride,
             @NonNull ArrayMap<String, Integer> permissionStates,
             List<String> allowlistedRestrictedPermissions,
             int autoRevokePermissionsMode, String traceMethod, int traceCookie,
@@ -80,6 +81,7 @@
         mOriginInfo = originInfo;
         mMoveInfo = moveInfo;
         mInstallFlags = installFlags;
+        mDevelopmentInstallFlags = developmentInstallFlags;
         mObserver = observer;
         mInstallSource = Preconditions.checkNotNull(installSource);
         mVolumeUuid = volumeUuid;
@@ -105,7 +107,7 @@
      * when cleaning up old installs, or used as a move source.
      */
     InstallArgs(String codePath, String[] instructionSets) {
-        this(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY, null, null,
+        this(OriginInfo.fromNothing(), null, null, 0, 0, InstallSource.EMPTY, null, null,
                 instructionSets, null, new ArrayMap<>(), null, MODE_DEFAULT, null, 0,
                 SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN,
                 PackageManager.INSTALL_SCENARIO_DEFAULT, false, DataLoaderType.NONE,
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 3464874..6fc14e8 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -134,12 +134,13 @@
     InstallRequest(InstallingSession params) {
         mUserId = params.getUser().getIdentifier();
         mInstallArgs = new InstallArgs(params.mOriginInfo, params.mMoveInfo, params.mObserver,
-                params.mInstallFlags, params.mInstallSource, params.mVolumeUuid,
-                params.getUser(), null /*instructionSets*/, params.mPackageAbiOverride,
-                params.mPermissionStates, params.mAllowlistedRestrictedPermissions,
-                params.mAutoRevokePermissionsMode, params.mTraceMethod, params.mTraceCookie,
-                params.mSigningDetails, params.mInstallReason, params.mInstallScenario,
-                params.mForceQueryableOverride, params.mDataLoaderType, params.mPackageSource,
+                params.mInstallFlags, params.mDevelopmentInstallFlags, params.mInstallSource,
+                params.mVolumeUuid,  params.getUser(), null /*instructionSets*/,
+                params.mPackageAbiOverride, params.mPermissionStates,
+                params.mAllowlistedRestrictedPermissions, params.mAutoRevokePermissionsMode,
+                params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
+                params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
+                params.mDataLoaderType, params.mPackageSource,
                 params.mApplicationEnabledSettingPersistent);
         mPackageMetrics = new PackageMetrics(this);
         mIsInstallInherit = params.mIsInherit;
@@ -286,6 +287,10 @@
         return mInstallArgs == null ? 0 : mInstallArgs.mInstallFlags;
     }
 
+    public int getDevelopmentInstallFlags() {
+        return mInstallArgs == null ? 0 : mInstallArgs.mDevelopmentInstallFlags;
+    }
+
     public int getInstallReason() {
         return mInstallArgs == null ? INSTALL_REASON_UNKNOWN : mInstallArgs.mInstallReason;
     }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 35862db..30a23bf 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -68,6 +68,7 @@
     final MoveInfo mMoveInfo;
     final IPackageInstallObserver2 mObserver;
     int mInstallFlags;
+    int mDevelopmentInstallFlags;
     @NonNull
     final InstallSource mInstallSource;
     final String mVolumeUuid;
@@ -102,8 +103,8 @@
 
     // For move install
     InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
-            int installFlags, InstallSource installSource, String volumeUuid,
-            UserHandle user, String packageAbiOverride, int packageSource,
+            int installFlags, int developmentInstallFlags, InstallSource installSource,
+            String volumeUuid, UserHandle user, String packageAbiOverride, int packageSource,
             PackageLite packageLite, PackageManagerService pm) {
         mPm = pm;
         mUser = user;
@@ -113,6 +114,7 @@
         mMoveInfo = moveInfo;
         mObserver = observer;
         mInstallFlags = installFlags;
+        mDevelopmentInstallFlags = developmentInstallFlags;
         mInstallSource = Preconditions.checkNotNull(installSource);
         mVolumeUuid = volumeUuid;
         mPackageAbiOverride = packageAbiOverride;
@@ -149,6 +151,7 @@
         mInstallScenario = sessionParams.installScenario;
         mObserver = observer;
         mInstallFlags = sessionParams.installFlags;
+        mDevelopmentInstallFlags = sessionParams.developmentInstallFlags;
         mInstallSource = installSource;
         mVolumeUuid = sessionParams.volumeUuid;
         mPackageAbiOverride = sessionParams.abiOverride;
@@ -592,6 +595,10 @@
                     "Only a non-staged install of a single APEX is supported");
         }
         InstallRequest request = requests.get(0);
+        boolean force =
+                (request.getDevelopmentInstallFlags()
+                        & PackageManager.INSTALL_DEVELOPMENT_FORCE_NON_STAGED_APEX_UPDATE)
+                        != 0;
         try {
             // Should directory scanning logic be moved to ApexManager for better test coverage?
             final File dir = request.getOriginInfo().mResolvedFile;
@@ -608,7 +615,7 @@
                         PackageManagerException.INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE);
             }
             try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) {
-                ApexInfo apexInfo = mPm.mApexManager.installPackage(apexes[0]);
+                ApexInfo apexInfo = mPm.mApexManager.installPackage(apexes[0], force);
                 // APEX has been handled successfully by apexd. Let's continue the install flow
                 // so it will be scanned and registered with the system.
                 // TODO(b/225756739): Improve atomicity of rebootless APEX install.
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 15c10aa..1a5591c 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -302,8 +302,9 @@
                 new File(origin.mResolvedPath), /* flags */ 0);
         final PackageLite lite = ret.isSuccess() ? ret.getResult() : null;
         final InstallingSession installingSession = new InstallingSession(origin, move,
-                installObserver, installFlags, installSource, volumeUuid, user, packageAbiOverride,
-                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm);
+                installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource,
+                volumeUuid, user, packageAbiOverride,  PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED,
+                lite, mPm);
         installingSession.movePackage();
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2c0e74d..80e07f4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -709,6 +709,9 @@
                     != PackageManager.PERMISSION_GRANTED) {
                 params.installFlags &= ~PackageManager.INSTALL_ALLOW_TEST;
             }
+
+            // developmentInstallFlags can ony be set by shell or root.
+            params.developmentInstallFlags = 0;
         }
 
         String originatingPackageName = null;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 6136197..303f321 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2029,8 +2029,8 @@
                 mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
             }
         } catch (PackageManagerException e) {
-            destroy();
             String msg = ExceptionUtils.getCompleteMessage(e);
+            destroy(msg);
             dispatchSessionFinished(e.error, msg, null);
             maybeFinishChildSessions(e.error, msg);
         }
@@ -2283,7 +2283,7 @@
 
     private void onSessionValidationFailure(int error, String detailMessage) {
         // Session is sealed but could not be validated, we need to destroy it.
-        destroyInternal();
+        destroyInternal("Failed to validate session, error: " + error + ", " + detailMessage);
         // Dispatch message to remove session from PackageInstallerService.
         dispatchSessionFinished(error, detailMessage, null);
     }
@@ -4013,7 +4013,7 @@
             root.mHandler.obtainMessage(
                     isCommitted() ? MSG_INSTALL : MSG_PRE_APPROVAL_REQUEST).sendToTarget();
         } else {
-            root.destroy();
+            root.destroy("User rejected permissions");
             root.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
             root.maybeFinishChildSessions(INSTALL_FAILED_ABORTED, "User rejected permissions");
         }
@@ -4128,7 +4128,7 @@
                 if (isStaged() && isCommitted()) {
                     mStagingManager.abortCommittedSession(mStagedSession);
                 }
-                destroy();
+                destroy("Session was abandoned");
                 dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
                 maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
                         "Session was abandoned because the parent session is abandoned");
@@ -4750,7 +4750,7 @@
             mSessionErrorMessage = errorMessage;
             Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage);
         }
-        destroy();
+        destroy("Session marked as failed: " + errorMessage);
         mCallback.onSessionChanged(this);
     }
 
@@ -4765,7 +4765,7 @@
             mSessionErrorMessage = "";
             Slog.d(TAG, "Marking session " + sessionId + " as applied");
         }
-        destroy();
+        destroy(null);
         mCallback.onSessionChanged(this);
     }
 
@@ -4808,7 +4808,7 @@
      * Free up storage used by this session and its children.
      * Must not be called on a child session.
      */
-    private void destroy() {
+    private void destroy(String reason) {
         // TODO(b/173194203): destroy() is called indirectly by
         //  PackageInstallerService#restoreAndApplyStagedSessionIfNeeded on an orphan child session.
         //  Enable this assertion when we figure out a better way to clean up orphan sessions.
@@ -4817,16 +4817,20 @@
         // TODO(b/173194203): destroyInternal() should be used by destroy() only.
         //  For the sake of consistency, a session should be destroyed as a whole. The caller
         //  should always call destroy() for cleanup without knowing it has child sessions or not.
-        destroyInternal();
+        destroyInternal(reason);
         for (PackageInstallerSession child : getChildSessions()) {
-            child.destroyInternal();
+            child.destroyInternal(reason);
         }
     }
 
     /**
      * Free up storage used by this session.
      */
-    private void destroyInternal() {
+    private void destroyInternal(String reason) {
+        if (reason != null) {
+            Slog.i(TAG,
+                    "Session [" + this.sessionId + "] was destroyed because of [" + reason + "]");
+        }
         final IncrementalFileStorages incrementalFileStorages;
         synchronized (mLock) {
             mSealed = true;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9137d44..10fe65df 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2121,8 +2121,8 @@
                 // Save the names of pre-existing packages prior to scanning, so we can determine
                 // which system packages are completely new due to an upgrade.
                 mExistingPackages = new ArraySet<>(packageSettings.size());
-                for (PackageSetting ps : packageSettings.values()) {
-                    mExistingPackages.add(ps.getPackageName());
+                for (int i = 0; i < packageSettings.size(); i++) {
+                    mExistingPackages.add(packageSettings.valueAt(i).getPackageName());
                 }
 
                 // Triggering {@link com.android.server.pm.crossprofile.
@@ -2249,8 +2249,10 @@
             // If this is the first boot or an update from pre-M, then we need to initialize the
             // default preferred apps across all defined users.
             if (mPromoteSystemApps || mFirstBoot) {
-                for (UserInfo user : mInjector.getUserManagerInternal().getUsers(true)) {
-                    mSettings.applyDefaultPreferredAppsLPw(user.id);
+                final List<UserInfo> users = mInjector.getUserManagerInternal().getUsers(true);
+                for (int i = 0; i < users.size(); i++) {
+                    mSettings.applyDefaultPreferredAppsLPw(users.get(i).id);
+
                 }
             }
 
@@ -4278,8 +4280,9 @@
         final Computer snapshot = snapshotComputer();
         for (String packageName : apkList) {
             setSystemAppHiddenUntilInstalled(snapshot, packageName, true);
-            for (UserInfo user : mInjector.getUserManagerInternal().getUsers(false)) {
-                setSystemAppInstallState(snapshot, packageName, false, user.id);
+            final List<UserInfo> users = mInjector.getUserManagerInternal().getUsers(false);
+            for (int i = 0; i < users.size(); i++) {
+                setSystemAppInstallState(snapshot, packageName, false, users.get(i).id);
             }
         }
     }
@@ -4724,7 +4727,8 @@
 
                 ArraySet<CrossProfileIntentFilter> set =
                         new ArraySet<>(resolver.filterSet());
-                for (CrossProfileIntentFilter filter : set) {
+                for (int i = 0; i < set.size(); i++) {
+                    final CrossProfileIntentFilter filter = set.valueAt(i);
                     if (IntentFilter.filterEquals(filter.mFilter, intentFilter)
                             && filter.getOwnerPackage().equals(ownerPackage)
                             && filter.getTargetUserId() == targetUserId
@@ -6066,8 +6070,8 @@
             final Computer snapshot = snapshotComputer();
             enforceOwnerRights(snapshot, packageName, Binder.getCallingUid());
             mimeTypes = CollectionUtils.emptyIfNull(mimeTypes);
-            for (String mimeType : mimeTypes) {
-                if (mimeType.length() > 255) {
+            for (int i = 0; i < mimeTypes.size(); i++) {
+                if (mimeTypes.get(i).length() > 255) {
                     throw new IllegalArgumentException("MIME type length exceeds 255 characters");
                 }
             }
@@ -6941,7 +6945,9 @@
 
                 if (targetPkg.getLibraryNames() != null) {
                     // Set the overlay paths for dependencies of the shared library.
-                    for (final String libName : targetPkg.getLibraryNames()) {
+                    List<String> libraryNames = targetPkg.getLibraryNames();
+                    for (int j = 0; j < libraryNames.size(); j++) {
+                        final String libName = libraryNames.get(j);
                         ArraySet<String> modifiedDependents = null;
 
                         final SharedLibraryInfo info = computer.getSharedLibraryInfo(libName,
@@ -6955,7 +6961,8 @@
                         if (dependents == null) {
                             continue;
                         }
-                        for (final VersionedPackage dependent : dependents) {
+                        for (int k = 0; k < dependents.size(); k++) {
+                            final VersionedPackage dependent = dependents.get(k);
                             final PackageStateInternal dependentState =
                                     computer.getPackageStateInternal(dependent.getPackageName());
                             if (dependentState == null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 8d64bd9..ce0e7ad7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1641,8 +1641,8 @@
         // broadcast only if packageInstallerName is "android". We can't always force
         // "android" as packageIntallerName, e.g, rollback auto implies
         // "-i com.android.shell".
-        while (currentTime < endTime) {
-            if (si != null && (si.isStagedSessionReady() || si.isStagedSessionFailed())) {
+        while (si != null && currentTime < endTime) {
+            if (si.isStagedSessionReady() || si.isStagedSessionFailed()) {
                 break;
             }
             SystemClock.sleep(Math.min(endTime - currentTime, 100));
@@ -3308,6 +3308,13 @@
         // Set package source to other by default
         sessionParams.setPackageSource(PackageInstaller.PACKAGE_SOURCE_OTHER);
 
+        // Encodes one of the states:
+        //  1. Install request explicitly specified --staged, then value will be true.
+        //  2. Install request explicitly specified --non-staged, then value will be false.
+        //  3. Install request did not specify either --staged or --non-staged, then for APEX
+        //      installs the value will be true, and for apk installs it will be false.
+        Boolean staged = null;
+
         String opt;
         boolean replaceExisting = true;
         boolean forceNonStaged = false;
@@ -3416,7 +3423,6 @@
                     break;
                 case "--apex":
                     sessionParams.setInstallAsApex();
-                    sessionParams.setStaged();
                     break;
                 case "--force-non-staged":
                     forceNonStaged = true;
@@ -3425,7 +3431,10 @@
                     sessionParams.setMultiPackage();
                     break;
                 case "--staged":
-                    sessionParams.setStaged();
+                    staged = true;
+                    break;
+                case "--non-staged":
+                    staged = false;
                     break;
                 case "--force-queryable":
                     sessionParams.setForceQueryable();
@@ -3460,11 +3469,18 @@
                     throw new IllegalArgumentException("Unknown option " + opt);
             }
         }
+        if (staged == null) {
+            staged = (sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
+        }
         if (replaceExisting) {
             sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
         }
         if (forceNonStaged) {
             sessionParams.isStaged = false;
+            sessionParams.developmentInstallFlags |=
+                    PackageManager.INSTALL_DEVELOPMENT_FORCE_NON_STAGED_APEX_UPDATE;
+        } else if (staged) {
+            sessionParams.setStaged();
         }
         return params;
     }
@@ -4346,7 +4362,8 @@
         pw.println("       [--preload] [--instant] [--full] [--dont-kill]");
         pw.println("       [--enable-rollback]");
         pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
-        pw.println("       [--apex] [--force-non-staged] [--staged-ready-timeout TIMEOUT]");
+        pw.println("       [--apex] [--non-staged] [--force-non-staged]");
+        pw.println("       [--staged-ready-timeout TIMEOUT]");
         pw.println("       [PATH [SPLIT...]|-]");
         pw.println("    Install an application.  Must provide the apk data to install, either as");
         pw.println("    file path(s) or '-' to read from stdin.  Options are:");
@@ -4375,8 +4392,12 @@
         pw.println("      --update-ownership: request the update ownership enforcement");
         pw.println("      --force-uuid: force install on to disk volume with given UUID");
         pw.println("      --apex: install an .apex file, not an .apk");
+        pw.println("      --non-staged: explicitly set this installation to be non-staged.");
+        pw.println("          This flag is only useful for APEX installs that are implicitly");
+        pw.println("          assumed to be staged.");
         pw.println("      --force-non-staged: force the installation to run under a non-staged");
-        pw.println("          session, which may complete without requiring a reboot");
+        pw.println("          session, which may complete without requiring a reboot. This will");
+        pw.println("          force a rebootless update even for APEXes that don't support it");
         pw.println("      --staged-ready-timeout: By default, staged sessions wait "
                 + DEFAULT_STAGED_READY_TIMEOUT_MS);
         pw.println("          milliseconds for pre-reboot verification to complete when");
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 3c846da..e2bbaff 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -18,7 +18,12 @@
       "name": "CtsCompilationTestCases"
     },
     {
-      "name": "CtsAppEnumerationTestCases"
+      "name": "CtsAppEnumerationTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
     },
     {
       "name": "CtsMatchFlagTestCases"
@@ -97,20 +102,15 @@
           "include-filter": "android.appsecurity.cts.EphemeralTest#testGetSearchableInfo"
         }
       ]
-    }
-  ],
-  "presubmit-large": [
+    },
     {
-      "name": "CtsContentTestCases",
+      "name": "CtsPackageManagerTestCases",
       "options": [
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
           "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "include-filter": "android.content.pm.cts"
         }
       ]
     }
@@ -129,6 +129,9 @@
     },
     {
       "name": "PackageManagerServiceHostTests"
+    },
+    {
+      "name": "CtsAppEnumerationTestCases"
     }
   ],
   "imports": [
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index 579d4e3..b2dcf37 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -4,6 +4,9 @@
             "name": "CtsPermissionTestCases",
             "options": [
                 {
+                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                },
+                {
                     "include-filter": "android.permission.cts.BackgroundPermissionsTest"
                 },
                 {
@@ -29,6 +32,9 @@
             "name": "CtsPermissionPolicyTestCases",
             "options": [
                 {
+                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                },
+                {
                     "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
                 },
                 {
@@ -59,6 +65,29 @@
             "options": [
                 {
                     "include-filter": "android.permission.cts.PermissionUpdateListenerTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.SplitPermissionTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.PermissionFlagsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.SharedUidPermissionsTest"
+                }
+            ]
+        },
+        {
+            "name": "CtsPermissionPolicyTestCases",
+            "options": [
+                {
+                    "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
                 }
             ]
         }
diff --git a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
index a2177e8..0bb969f 100644
--- a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
+++ b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
@@ -17,13 +17,13 @@
 
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.os.Build;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
 
 import libcore.io.IoUtils;
 
@@ -82,8 +82,8 @@
         }
 
         AssetManager assets = new AssetManager();
-        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                Build.VERSION.RESOURCES_SDK_INT);
+        assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, Build.VERSION.RESOURCES_SDK_INT);
         assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
 
         mCachedAssetManager = assets;
diff --git a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
index 1a8c1996..56d92fb 100644
--- a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
+++ b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
@@ -80,8 +80,8 @@
 
     private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
         final AssetManager assets = new AssetManager();
-        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                Build.VERSION.RESOURCES_SDK_INT);
+        assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, Build.VERSION.RESOURCES_SDK_INT);
         assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
         return assets;
     }
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 44cc3e7..539bb6b 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -16,12 +16,15 @@
 
 package com.android.server.policy;
 
+import android.annotation.SuppressLint;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.XmlResourceParser;
+import android.hardware.input.InputManager;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -29,11 +32,14 @@
 import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 
 import com.android.internal.policy.IShortcutService;
 import com.android.internal.util.XmlUtils;
+import com.android.server.input.KeyboardMetricsCollector;
+import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -87,11 +93,13 @@
     }
 
     private final Context mContext;
+    private final Handler mHandler;
     private boolean mSearchKeyShortcutPending = false;
     private boolean mConsumeSearchKeyUp = true;
 
-    ModifierShortcutManager(Context context) {
+    ModifierShortcutManager(Context context, Handler handler) {
         mContext = context;
+        mHandler = handler;
         loadShortcuts();
     }
 
@@ -267,11 +275,13 @@
      * Handle the shortcut to {@link Intent}
      *
      * @param kcm the {@link KeyCharacterMap} associated with the keyboard device.
-     * @param keyCode The key code of the event.
+     * @param keyEvent The key event.
      * @param metaState The meta key modifier state.
      * @return True if invoked the shortcut, otherwise false.
      */
-    private boolean handleIntentShortcut(KeyCharacterMap kcm, int keyCode, int metaState) {
+    @SuppressLint("MissingPermission")
+    private boolean handleIntentShortcut(KeyCharacterMap kcm, KeyEvent keyEvent, int metaState) {
+        final int keyCode = keyEvent.getKeyCode();
         // Shortcuts are invoked through Search+key, so intercept those here
         // Any printing key that is chorded with Search should be consumed
         // even if no shortcut was invoked.  This prevents text from being
@@ -301,6 +311,7 @@
                             + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + ","
                             + " category=" + category);
                 }
+                logKeyboardShortcut(keyEvent, KeyboardLogEvent.getLogEventFromIntent(intent));
                 return true;
             } else {
                 return false;
@@ -317,11 +328,24 @@
                         + "the activity to which it is registered was not found: "
                         + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode));
             }
+            logKeyboardShortcut(keyEvent, KeyboardLogEvent.getLogEventFromIntent(shortcutIntent));
             return true;
         }
         return false;
     }
 
+    private void logKeyboardShortcut(KeyEvent event, KeyboardLogEvent logEvent) {
+        mHandler.post(() -> handleKeyboardLogging(event, logEvent));
+    }
+
+    private void handleKeyboardLogging(KeyEvent event, KeyboardLogEvent logEvent) {
+        final InputManager inputManager = mContext.getSystemService(InputManager.class);
+        final InputDevice inputDevice = inputManager != null
+                ? inputManager.getInputDevice(event.getDeviceId()) : null;
+        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(inputDevice,
+                logEvent, event.getMetaState(), event.getKeyCode());
+    }
+
     /**
      * Handle the shortcut from {@link KeyEvent}
      *
@@ -354,7 +378,7 @@
         }
 
         final KeyCharacterMap kcm = event.getKeyCharacterMap();
-        if (handleIntentShortcut(kcm, keyCode, metaState)) {
+        if (handleIntentShortcut(kcm, event, metaState)) {
             return true;
         }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5e01c49..7f86f1d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -220,6 +220,7 @@
 import com.android.server.display.BrightnessUtils;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.input.KeyboardMetricsCollector;
+import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.KeyCombinationManager.TwoKeysCombinationRule;
@@ -419,6 +420,7 @@
     ActivityManagerInternal mActivityManagerInternal;
     ActivityTaskManagerInternal mActivityTaskManagerInternal;
     AutofillManagerInternal mAutofillManagerInternal;
+    InputManager mInputManager;
     InputManagerInternal mInputManagerInternal;
     DreamManagerInternal mDreamManagerInternal;
     PowerManagerInternal mPowerManagerInternal;
@@ -769,7 +771,7 @@
                     handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
                     break;
                 case MSG_LOG_KEYBOARD_SYSTEM_EVENT:
-                    handleKeyboardSystemEvent(msg.arg2, (KeyEvent) msg.obj);
+                    handleKeyboardSystemEvent(KeyboardLogEvent.from(msg.arg1), (KeyEvent) msg.obj);
                     break;
             }
         }
@@ -1828,8 +1830,11 @@
     }
 
     private void launchAllAppsViaA11y() {
-        getAccessibilityManagerInternal().performSystemAction(
-                AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
+        AccessibilityManagerInternal accessibilityManager = getAccessibilityManagerInternal();
+        if (accessibilityManager != null) {
+            accessibilityManager.performSystemAction(
+                    AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
+        }
     }
 
     private void toggleNotificationPanel() {
@@ -1900,6 +1905,7 @@
             // If we have released the home key, and didn't do anything else
             // while it was pressed, then it is time to go home!
             if (!down) {
+                logKeyboardSystemsEvent(event, KeyboardLogEvent.HOME);
                 if (mDisplayId == DEFAULT_DISPLAY) {
                     cancelPreloadRecentApps();
                 }
@@ -2101,6 +2107,7 @@
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mInputManager = mContext.getSystemService(InputManager.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
@@ -2170,7 +2177,7 @@
         mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
         mSettingsObserver.observe();
-        mModifierShortcutManager = new ModifierShortcutManager(mContext);
+        mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler);
         mUiMode = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_defaultUiModeType);
         mHomeIntent =  new Intent(Intent.ACTION_MAIN, null);
@@ -3011,20 +3018,33 @@
      * We won't log keyboard events when the input device is null
      * or when it is virtual.
      */
-    private void handleKeyboardSystemEvent(int keyboardSystemEvent, KeyEvent event) {
-        final InputManager inputManager = mContext.getSystemService(InputManager.class);
-        final InputDevice inputDevice = inputManager != null
-                ? inputManager.getInputDevice(event.getDeviceId()) : null;
-        if (inputDevice != null && !inputDevice.isVirtual()) {
-            KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(
-                    inputDevice, keyboardSystemEvent,
-                    new int[]{event.getKeyCode()}, event.getMetaState());
-        }
+    private void handleKeyboardSystemEvent(KeyboardLogEvent keyboardLogEvent, KeyEvent event) {
+        final InputDevice inputDevice = mInputManager.getInputDevice(event.getDeviceId());
+        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(inputDevice,
+                keyboardLogEvent, event.getMetaState(), event.getKeyCode());
+        event.recycle();
     }
 
-    private void logKeyboardSystemsEvent(KeyEvent event, int keyboardSystemEvent) {
-        mHandler.obtainMessage(MSG_LOG_KEYBOARD_SYSTEM_EVENT, 0, keyboardSystemEvent, event)
-                .sendToTarget();
+    private void logKeyboardSystemsEventOnActionUp(KeyEvent event,
+            KeyboardLogEvent keyboardSystemEvent) {
+        if (event.getAction() != KeyEvent.ACTION_UP) {
+            return;
+        }
+        logKeyboardSystemsEvent(event, keyboardSystemEvent);
+    }
+
+    private void logKeyboardSystemsEventOnActionDown(KeyEvent event,
+            KeyboardLogEvent keyboardSystemEvent) {
+        if (event.getAction() != KeyEvent.ACTION_DOWN) {
+            return;
+        }
+        logKeyboardSystemsEvent(event, keyboardSystemEvent);
+    }
+
+    private void logKeyboardSystemsEvent(KeyEvent event, KeyboardLogEvent keyboardSystemEvent) {
+        KeyEvent eventToLog = KeyEvent.obtain(event);
+        mHandler.obtainMessage(MSG_LOG_KEYBOARD_SYSTEM_EVENT, keyboardSystemEvent.getIntValue(), 0,
+                eventToLog).sendToTarget();
     }
 
     // TODO(b/117479243): handle it in InputPolicy
@@ -3125,8 +3145,6 @@
 
         switch (keyCode) {
             case KeyEvent.KEYCODE_HOME:
-                logKeyboardSystemsEvent(event,
-                        FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME);
                 return handleHomeShortcuts(displayId, focusedToken, event);
             case KeyEvent.KEYCODE_MENU:
                 // Hijack modified menu keys for debugging features
@@ -3137,14 +3155,14 @@
                     Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
                     mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
                             null, null, null, 0, null, null);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TRIGGER_BUG_REPORT);
                     return true;
                 }
                 break;
             case KeyEvent.KEYCODE_RECENT_APPS:
                 if (firstDown) {
                     showRecentApps(false /* triggeredFromAltTab */);
-                    logKeyboardSystemsEvent(event,
-                            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
                 }
                 return true;
             case KeyEvent.KEYCODE_APP_SWITCH:
@@ -3153,6 +3171,7 @@
                         preloadRecentApps();
                     } else if (!down) {
                         toggleRecentApps();
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.APP_SWITCH);
                     }
                 }
                 return true;
@@ -3161,6 +3180,7 @@
                     launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
                             deviceId, event.getEventTime(),
                             AssistUtils.INVOCATION_TYPE_UNKNOWN);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
                     return true;
                 }
                 break;
@@ -3173,12 +3193,14 @@
             case KeyEvent.KEYCODE_I:
                 if (firstDown && event.isMetaPressed()) {
                     showSystemSettings();
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS);
                     return true;
                 }
                 break;
             case KeyEvent.KEYCODE_L:
                 if (firstDown && event.isMetaPressed()) {
                     lockNow(null /* options */);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LOCK_SCREEN);
                     return true;
                 }
                 break;
@@ -3186,8 +3208,10 @@
                 if (firstDown && event.isMetaPressed()) {
                     if (event.isCtrlPressed()) {
                         sendSystemKeyToStatusBarAsync(event);
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.OPEN_NOTES);
                     } else {
                         toggleNotificationPanel();
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
                     }
                     return true;
                 }
@@ -3195,12 +3219,14 @@
             case KeyEvent.KEYCODE_S:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TAKE_SCREENSHOT);
                     return true;
                 }
                 break;
             case KeyEvent.KEYCODE_T:
                 if (firstDown && event.isMetaPressed()) {
                     toggleTaskbar();
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_TASKBAR);
                     return true;
                 }
                 break;
@@ -3209,6 +3235,7 @@
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.goToFullscreenFromSplit();
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
                         return true;
                     }
                 }
@@ -3216,18 +3243,21 @@
             case KeyEvent.KEYCODE_DPAD_LEFT:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     enterStageSplitFromRunningApp(true /* leftOrTop */);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
                     return true;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     enterStageSplitFromRunningApp(false /* leftOrTop */);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
                     return true;
                 }
                 break;
             case KeyEvent.KEYCODE_SLASH:
                 if (firstDown && event.isMetaPressed() && !keyguardOn) {
                     toggleKeyboardShortcutsMenu(event.getDeviceId());
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.OPEN_SHORTCUT_HELPER);
                     return true;
                 }
                 break;
@@ -3296,20 +3326,26 @@
                     intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
                     intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
                     startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.getBrightnessEvent(keyCode));
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
                 if (down) {
                     mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_DOWN);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
                 if (down) {
                     mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_UP);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
                 // TODO: Add logic
+                if (!down) {
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_TOGGLE);
+                }
                 return true;
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -3335,6 +3371,7 @@
                 if (firstDown && !keyguardOn && isUserSetupComplete()) {
                     if (event.isMetaPressed()) {
                         showRecentApps(false);
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
                         return true;
                     } else if (mRecentAppsHeldModifiers == 0) {
                         final int shiftlessModifiers =
@@ -3343,6 +3380,7 @@
                                 shiftlessModifiers, KeyEvent.META_ALT_ON)) {
                             mRecentAppsHeldModifiers = shiftlessModifiers;
                             showRecentApps(true);
+                            logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
                             return true;
                         }
                     }
@@ -3354,11 +3392,13 @@
                     Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.ALL_APPS);
                 }
                 return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
                 if (!down) {
                     toggleNotificationPanel();
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
                 }
                 return true;
             case KeyEvent.KEYCODE_SEARCH:
@@ -3366,6 +3406,7 @@
                     switch (mSearchKeyBehavior) {
                         case SEARCH_BEHAVIOR_TARGET_ACTIVITY: {
                             launchTargetSearchActivity();
+                            logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SEARCH);
                             return true;
                         }
                         case SEARCH_BEHAVIOR_DEFAULT_SEARCH:
@@ -3378,6 +3419,7 @@
                 if (firstDown) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, direction);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH);
                     return true;
                 }
                 break;
@@ -3386,6 +3428,7 @@
                 if (firstDown && event.isMetaPressed()) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, direction);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH);
                     return true;
                 }
                 break;
@@ -3404,9 +3447,11 @@
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
                     } else if (mPendingMetaAction) {
                         if (!canceled) {
                             launchAllAppsViaA11y();
+                            logKeyboardSystemsEvent(event, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS);
                         }
                         mPendingMetaAction = false;
                     }
@@ -3434,10 +3479,16 @@
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
                         return true;
                     }
                 }
                 break;
+            case KeyEvent.KEYCODE_CAPS_LOCK:
+                if (!down) {
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+                }
+                break;
             case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
@@ -4277,6 +4328,7 @@
         // Handle special keys.
         switch (keyCode) {
             case KeyEvent.KEYCODE_BACK: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.BACK);
                 if (down) {
                     mBackKeyHandled = false;
                 } else {
@@ -4294,6 +4346,8 @@
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
+                logKeyboardSystemsEventOnActionDown(event,
+                        KeyboardLogEvent.getVolumeEvent(keyCode));
                 if (down) {
                     sendSystemKeyToStatusBarAsync(event);
 
@@ -4394,6 +4448,7 @@
             }
 
             case KeyEvent.KEYCODE_TV_POWER: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.TOGGLE_POWER);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false; // wake-up will be handled separately
                 if (down && hdmiControlManager != null) {
@@ -4403,6 +4458,7 @@
             }
 
             case KeyEvent.KEYCODE_POWER: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.TOGGLE_POWER);
                 EventLogTags.writeInterceptPower(
                         KeyEvent.actionToString(event.getAction()),
                         mPowerKeyHandled ? 1 : 0,
@@ -4425,12 +4481,14 @@
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
                 // fall through
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SYSTEM_NAVIGATION);
                 result &= ~ACTION_PASS_TO_USER;
                 interceptSystemNavigationKey(event);
                 break;
             }
 
             case KeyEvent.KEYCODE_SLEEP: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!mPowerManager.isInteractive()) {
@@ -4445,6 +4503,7 @@
             }
 
             case KeyEvent.KEYCODE_SOFT_SLEEP: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!down) {
@@ -4454,6 +4513,7 @@
             }
 
             case KeyEvent.KEYCODE_WAKEUP: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.WAKEUP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = true;
                 break;
@@ -4462,6 +4522,7 @@
             case KeyEvent.KEYCODE_MUTE:
                 result &= ~ACTION_PASS_TO_USER;
                 if (down && event.getRepeatCount() == 0) {
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SYSTEM_MUTE);
                     toggleMicrophoneMuteFromKey();
                 }
                 break;
@@ -4476,6 +4537,7 @@
             case KeyEvent.KEYCODE_MEDIA_RECORD:
             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
             case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.MEDIA_KEY);
                 if (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) {
                     // If the global session is active pass all media keys to it
                     // instead of the active window.
@@ -4520,6 +4582,7 @@
                             0 /* unused */, event.getEventTime() /* eventTime */);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
@@ -4530,6 +4593,7 @@
                     Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_VOICE_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index 9f1cb1a..819a82c 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -32,6 +32,9 @@
       "name": "CtsPermissionPolicyTestCases",
       "options": [
         {
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
           "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
         },
         {
@@ -46,6 +49,9 @@
       "name": "CtsPermissionTestCases",
       "options": [
         {
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
           "include-filter": "android.permission.cts.SplitPermissionTest"
         },
         {
@@ -78,6 +84,31 @@
           "include-filter": "com.android.server.policy."
         }
       ]
+    },
+    {
+      "name": "CtsPermissionPolicyTestCases",
+      "options": [
+        {
+          "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
+        },
+        {
+          "include-filter": "android.permissionpolicy.cts.RestrictedStoragePermissionSharedUidTest"
+        },
+        {
+          "include-filter": "android.permissionpolicy.cts.RestrictedStoragePermissionTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsPermissionTestCases",
+      "options": [
+        {
+          "include-filter": "android.permission.cts.SplitPermissionTest"
+        },
+        {
+          "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+        }
+      ]
     }
   ]
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index fc971e4..5a050ac 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -92,6 +92,7 @@
 import android.os.UserManager;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
+import android.provider.DeviceConfigInterface;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.dreams.DreamManagerInternal;
@@ -128,6 +129,7 @@
 import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
 import com.android.server.am.BatteryStatsService;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 import com.android.server.policy.WindowManagerPolicy;
@@ -323,6 +325,9 @@
     private final Injector mInjector;
     private final PermissionCheckerWrapper mPermissionCheckerWrapper;
     private final PowerPropertiesWrapper mPowerPropertiesWrapper;
+    private final DeviceConfigParameterProvider mDeviceConfigProvider;
+
+    private boolean mDisableScreenWakeLocksWhileCached;
 
     private LightsManager mLightsManager;
     private BatteryManagerInternal mBatteryManagerInternal;
@@ -1065,6 +1070,10 @@
                 }
             };
         }
+
+        DeviceConfigParameterProvider createDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+        }
     }
 
     /** Interface for checking an app op permission */
@@ -1161,6 +1170,7 @@
                 mInjector.createInattentiveSleepWarningController();
         mPermissionCheckerWrapper = mInjector.createPermissionCheckerWrapper();
         mPowerPropertiesWrapper = mInjector.createPowerPropertiesWrapper();
+        mDeviceConfigProvider = mInjector.createDeviceConfigParameterProvider();
 
         mPowerGroupWakefulnessChangeListener = new PowerGroupWakefulnessChangeListener();
 
@@ -1346,6 +1356,14 @@
 
             mLightsManager = getLocalService(LightsManager.class);
             mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
+            updateDeviceConfigLocked();
+            mDeviceConfigProvider.addOnPropertiesChangedListener(BackgroundThread.getExecutor(),
+                    properties -> {
+                        synchronized (mLock) {
+                            updateDeviceConfigLocked();
+                            updateWakeLockDisabledStatesLocked();
+                        }
+                    });
 
             // Initialize display power management.
             mDisplayManagerInternal.initPowerManagement(
@@ -1545,6 +1563,12 @@
         updatePowerStateLocked();
     }
 
+    @GuardedBy("mLock")
+    private void updateDeviceConfigLocked() {
+        mDisableScreenWakeLocksWhileCached = mDeviceConfigProvider
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+    }
+
     @RequiresPermission(value = android.Manifest.permission.TURN_SCREEN_ON, conditional = true)
     private void acquireWakeLockInternal(IBinder lock, int displayId, int flags, String tag,
             String packageName, WorkSource ws, String historyTag, int uid, int pid,
@@ -2760,13 +2784,13 @@
     /** Get wake lock summary flags that correspond to the given wake lock. */
     @SuppressWarnings("deprecation")
     private int getWakeLockSummaryFlags(WakeLock wakeLock) {
+        if (wakeLock.mDisabled) {
+            // We only respect this if the wake lock is not disabled.
+            return 0;
+        }
         switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
             case PowerManager.PARTIAL_WAKE_LOCK:
-                if (!wakeLock.mDisabled) {
-                    // We only respect this if the wake lock is not disabled.
-                    return WAKE_LOCK_CPU;
-                }
-                break;
+                return WAKE_LOCK_CPU;
             case PowerManager.FULL_WAKE_LOCK:
                 return WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT;
             case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
@@ -4151,7 +4175,7 @@
         for (int i = 0; i < numWakeLocks; i++) {
             final WakeLock wakeLock = mWakeLocks.get(i);
             if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK)
-                    == PowerManager.PARTIAL_WAKE_LOCK) {
+                    == PowerManager.PARTIAL_WAKE_LOCK || isScreenLock(wakeLock)) {
                 if (setWakeLockDisabledStateLocked(wakeLock)) {
                     changed = true;
                     if (wakeLock.mDisabled) {
@@ -4205,6 +4229,22 @@
                 }
             }
             return wakeLock.setDisabled(disabled);
+        } else if (mDisableScreenWakeLocksWhileCached && isScreenLock(wakeLock)) {
+            boolean disabled = false;
+            final int appid = UserHandle.getAppId(wakeLock.mOwnerUid);
+            final UidState state = wakeLock.mUidState;
+            // Cached inactive processes are never allowed to hold wake locks.
+            if (mConstants.NO_CACHED_WAKE_LOCKS
+                    && appid >= Process.FIRST_APPLICATION_UID
+                    && !state.mActive
+                    && state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT
+                    && state.mProcState >= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+                if (DEBUG_SPEW) {
+                    Slog.d(TAG, "disabling full wakelock " + wakeLock);
+                }
+                disabled = true;
+            }
+            return wakeLock.setDisabled(disabled);
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index fbfe291..8374997 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -24,6 +24,15 @@
         {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
+    },
+    {
+      "name": "PowerServiceTests",
+      "options": [
+        {"include-filter": "com.android.server.power"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
     }
   ],
   "postsubmit": [
@@ -42,6 +51,14 @@
         {"include-filter": "com.android.server.power"},
         {"exclude-filter": "com.android.server.power.BatteryStatsTests"}
       ]
+    },
+    {
+      "name": "PowerServiceTests",
+      "options": [
+        {"include-filter": "com.android.server.power"},
+        {"exclude-filter": "com.android.server.power.BatteryStatsTests"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
     }
   ]
 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index d8e6c26..d1d27f3 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -17,6 +17,7 @@
 package com.android.server.powerstats;
 
 import android.content.Context;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.util.FileRotator;
@@ -27,6 +28,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.util.Date;
 import java.util.concurrent.locks.ReentrantLock;
 
 /**
@@ -266,4 +268,51 @@
             mLock.unlock();
         }
     }
+
+    /**
+     * Dump stats about stored data.
+     */
+    public void dump(IndentingPrintWriter ipw) {
+        mLock.lock();
+        try {
+            final int versionDot = mDataStorageFilename.lastIndexOf('.');
+            final String beforeVersionDot = mDataStorageFilename.substring(0, versionDot);
+            final File[] files = mDataStorageDir.listFiles();
+
+            int number = 0;
+            int dataSize = 0;
+            long earliestLogEpochTime = Long.MAX_VALUE;
+            for (int i = 0; i < files.length; i++) {
+                // Check that the stems before the version match.
+                final File file = files[i];
+                final String fileName = file.getName();
+                if (files[i].getName().startsWith(beforeVersionDot)) {
+                    number++;
+                    dataSize += file.length();
+                    final int firstTimeChar = fileName.lastIndexOf('.') + 1;
+                    final int endChar = fileName.lastIndexOf('-');
+                    try {
+                        final Long startTime =
+                                Long.parseLong(fileName.substring(firstTimeChar, endChar));
+                        if (startTime != null && startTime < earliestLogEpochTime) {
+                            earliestLogEpochTime = startTime;
+                        }
+                    } catch (NumberFormatException nfe) {
+                        Slog.e(TAG,
+                            "Failed to extract start time from file : " + fileName, nfe);
+                    }
+                }
+            }
+
+            if (earliestLogEpochTime != Long.MAX_VALUE) {
+                ipw.println("Earliest data time : " + new Date(earliestLogEpochTime));
+            } else {
+                ipw.println("Failed to parse earliest data time!!!");
+            }
+            ipw.println("# files : " + number);
+            ipw.println("Total data size (B) : " + dataSize);
+        } finally {
+            mLock.unlock();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index 39ead13..e80a86d 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -30,6 +30,7 @@
 import android.os.Message;
 import android.os.SystemClock;
 import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
@@ -354,4 +355,23 @@
             updateCacheFile(residencyCacheFilename, powerEntityBytes);
         }
     }
+
+    /**
+     * Dump stats about stored data.
+     */
+    public void dump(IndentingPrintWriter ipw) {
+        ipw.println("PowerStats Meter Data:");
+        ipw.increaseIndent();
+        mPowerStatsMeterStorage.dump(ipw);
+        ipw.decreaseIndent();
+        ipw.println("PowerStats Model Data:");
+        ipw.increaseIndent();
+        mPowerStatsModelStorage.dump(ipw);
+        ipw.decreaseIndent();
+        ipw.println("PowerStats State Residency Data:");
+        ipw.increaseIndent();
+        mPowerStatsResidencyStorage.dump(ipw);
+        ipw.decreaseIndent();
+    }
+
 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 9832c49..5609f69 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -42,6 +42,7 @@
 import android.power.PowerStatsInternal;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfigInterface;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
 
@@ -232,18 +233,31 @@
                     } else if ("residency".equals(args[1])) {
                         mPowerStatsLogger.writeResidencyDataToFile(fd);
                     }
-                } else if (args.length == 0) {
-                    pw.println("PowerStatsService dumpsys: available PowerEntities");
+                } else {
+                    IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+                    ipw.println("PowerStatsService dumpsys: available PowerEntities");
                     PowerEntity[] powerEntity = getPowerStatsHal().getPowerEntityInfo();
-                    PowerEntityUtils.dumpsys(powerEntity, pw);
+                    ipw.increaseIndent();
+                    PowerEntityUtils.dumpsys(powerEntity, ipw);
+                    ipw.decreaseIndent();
 
-                    pw.println("PowerStatsService dumpsys: available Channels");
+                    ipw.println("PowerStatsService dumpsys: available Channels");
                     Channel[] channel = getPowerStatsHal().getEnergyMeterInfo();
-                    ChannelUtils.dumpsys(channel, pw);
+                    ipw.increaseIndent();
+                    ChannelUtils.dumpsys(channel, ipw);
+                    ipw.decreaseIndent();
 
-                    pw.println("PowerStatsService dumpsys: available EnergyConsumers");
+                    ipw.println("PowerStatsService dumpsys: available EnergyConsumers");
                     EnergyConsumer[] energyConsumer = getPowerStatsHal().getEnergyConsumerInfo();
-                    EnergyConsumerUtils.dumpsys(energyConsumer, pw);
+                    ipw.increaseIndent();
+                    EnergyConsumerUtils.dumpsys(energyConsumer, ipw);
+                    ipw.decreaseIndent();
+
+                    ipw.println("PowerStatsService dumpsys: PowerStatsLogger stats");
+                    ipw.increaseIndent();
+                    mPowerStatsLogger.dump(ipw);
+                    ipw.decreaseIndent();
+
                 }
             }
         }
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
new file mode 100644
index 0000000..8be3b2d
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.os.VibrationEffect;
+import android.os.vibrator.persistence.VibrationXmlParser;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.vibrator.persistence.XmlParserException;
+import com.android.internal.vibrator.persistence.XmlReader;
+import com.android.internal.vibrator.persistence.XmlValidator;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+
+/**
+ * Class that loads custom {@link VibrationEffect} to be performed for each
+ * {@link HapticFeedbackConstants} key.
+ *
+ * <p>The system has its default logic to get the {@link VibrationEffect} that will be played for a
+ * given haptic feedback constant. Devices may choose to override some or all of these supported
+ * haptic feedback vibrations via a customization XML.
+ *
+ * <p>The XML simply provides a mapping of a constant from {@link HapticFeedbackConstants} to its
+ * corresponding {@link VibrationEffect}. Its root tag should be `<haptic-feedback-constants>`. It
+ * should have one or more entries for customizing a haptic feedback constant. A customization is
+ * started by a `<constant id="X">` tag (where `X` is the haptic feedback constant being customized
+ * in this entry) and closed by </constant>. Between these two tags, there should be a valid XML
+ * serialization of a non-repeating {@link VibrationEffect}. Such a valid vibration serialization
+ * should be parse-able by {@link VibrationXmlParser}.
+ *
+ * The example below represents a valid customization for effect IDs 10 and 11.
+ *
+ * <pre>
+ *   {@code
+ *     <haptic-feedback-constants>
+ *          <constant id="10">
+ *              // Valid Vibration Serialization
+ *          </constant>
+ *          <constant id="11">
+ *              // Valid Vibration Serialization
+ *          </constant>
+ *     </haptic-feedback-constants>
+ *   }
+ * </pre>
+ *
+ * <p>After a successful parsing of the customization XML file, it returns a {@link SparseArray}
+ * that maps each customized haptic feedback effect ID to its respective {@link VibrationEffect}.
+ *
+ * @hide
+ */
+final class HapticFeedbackCustomization {
+    private static final String TAG = "HapticFeedbackCustomization";
+
+    /** The outer-most tag for haptic feedback customizations.  */
+    private static final String TAG_CONSTANTS = "haptic-feedback-constants";
+    /** The tag defining a customization for a single haptic feedback constant. */
+    private static final String TAG_CONSTANT = "constant";
+
+    /**
+     * Attribute for {@link TAG_CONSTANT}, specifying the haptic feedback constant to
+     * customize.
+     */
+    private static final String ATTRIBUTE_ID = "id";
+
+    /**
+     * Parses the haptic feedback vibration customization XML file for the device, and provides a
+     * mapping of the customized effect IDs to their respective {@link VibrationEffect}s.
+     *
+     * <p>This is potentially expensive, so avoid calling repeatedly. One call is enough, and the
+     * caller should process the returned mapping (if any) for further queries.
+     *
+     * @param res {@link Resources} object to be used for reading the device's resources.
+     * @return a {@link SparseArray} that maps each customized haptic feedback effect ID to its
+     *      respective {@link VibrationEffect}, or {@code null}, if the device has not configured
+     *      a file for haptic feedback constants customization.
+     * @throws {@link IOException} if an IO error occurs while parsing the customization XML.
+     * @throws {@link CustomizationParserException} for any non-IO error that occurs when parsing
+     *      the XML, like an invalid XML content or an invalid haptic feedback constant.
+     *
+     * @hide
+     */
+    @Nullable
+    static SparseArray<VibrationEffect> loadVibrations(Resources res)
+            throws CustomizationParserException, IOException {
+        try {
+            return loadVibrationsInternal(res);
+        } catch (VibrationXmlParser.VibrationXmlParserException
+                | XmlParserException
+                | XmlPullParserException e) {
+            throw new CustomizationParserException(
+                    "Error parsing haptic feedback customization file.", e);
+        }
+    }
+
+    @Nullable
+    private static SparseArray<VibrationEffect> loadVibrationsInternal(Resources res) throws
+            CustomizationParserException,
+            IOException,
+            VibrationXmlParser.VibrationXmlParserException,
+            XmlParserException,
+            XmlPullParserException {
+        String customizationFile =
+                res.getString(
+                        com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
+        if (TextUtils.isEmpty(customizationFile)) {
+            Slog.d(TAG, "Customization file not configured.");
+            return null;
+        }
+
+        FileReader fileReader;
+        try {
+            fileReader = new FileReader(customizationFile);
+        } catch (FileNotFoundException e) {
+            Slog.d(TAG, "Specified customization file not found.");
+            return  null;
+        }
+
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+        parser.setInput(fileReader);
+
+        XmlReader.readDocumentStartTag(parser, TAG_CONSTANTS);
+        XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+        int rootDepth = parser.getDepth();
+
+        SparseArray<VibrationEffect> mapping = new SparseArray<>();
+        while (XmlReader.readNextTagWithin(parser, rootDepth)) {
+            XmlValidator.checkStartTag(parser, TAG_CONSTANT);
+            int customizationDepth = parser.getDepth();
+
+            // Only attribute in tag is the `id` attribute.
+            XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_ID);
+            int effectId = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_ID);
+            if (mapping.contains(effectId)) {
+                throw new CustomizationParserException(
+                        "Multiple customizations found for effect " + effectId);
+            }
+
+            // Move the parser one step into the `<constant>` tag.
+            XmlValidator.checkParserCondition(
+                    XmlReader.readNextTagWithin(parser, customizationDepth),
+                    "Unsupported empty customization tag");
+
+            VibrationEffect effect = VibrationXmlParser.parseTag(
+                    parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
+            if (effect.getDuration() == Long.MAX_VALUE) {
+                throw new CustomizationParserException(String.format(
+                        "Vibration for effect ID %d is repeating, which is not allowed as a"
+                        + " haptic feedback: %s", effectId, effect));
+            }
+            mapping.put(effectId, effect);
+
+            XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth);
+        }
+
+        // Make checks that the XML ends well.
+        XmlReader.readEndTag(parser, TAG_CONSTANTS, rootDepth);
+        XmlReader.readDocumentEndTag(parser);
+
+        return mapping;
+    }
+
+    /**
+     * Represents an error while parsing a haptic feedback customization XML.
+     *
+     * @hide
+     */
+    static final class CustomizationParserException extends Exception {
+        private CustomizationParserException(String message) {
+            super(message);
+        }
+
+        private CustomizationParserException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 308ce4f..19dd0b2 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -21,9 +21,16 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.util.Slog;
+import android.util.SparseArray;
 import android.view.HapticFeedbackConstants;
 
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Provides the {@link VibrationEffect} and {@link VibrationAttributes} for haptic feedback.
@@ -31,6 +38,8 @@
  * @hide
  */
 public final class HapticFeedbackVibrationProvider {
+    private static final String TAG = "HapticFeedbackVibrationProvider";
+
     private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
     private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES =
@@ -42,15 +51,41 @@
     private final boolean mHapticTextHandleEnabled;
     // Vibrator effect for haptic feedback during boot when safe mode is enabled.
     private final VibrationEffect mSafeModeEnabledVibrationEffect;
+    // Haptic feedback vibration customizations specific to the device.
+    // If present and valid, a vibration here will be used for an effect.
+    // Otherwise, the system's default vibration will be used.
+    @Nullable private final SparseArray<VibrationEffect> mHapticCustomizations;
 
     /** @hide */
     public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
+        this(res, vibrator, loadHapticCustomizations(res));
+    }
+
+    /** @hide */
+    @VisibleForTesting HapticFeedbackVibrationProvider(
+            Resources res,
+            Vibrator vibrator,
+            @Nullable SparseArray<VibrationEffect> hapticCustomizations) {
         mVibrator = vibrator;
         mHapticTextHandleEnabled = res.getBoolean(
                 com.android.internal.R.bool.config_enableHapticTextHandle);
+
+        if (hapticCustomizations != null) {
+            // Clean up the customizations to remove vibrations which may not ever be used due to
+            // Vibrator properties or other device configurations.
+            removeUnsupportedVibrations(hapticCustomizations, vibrator);
+            if (hapticCustomizations.size() == 0) {
+                hapticCustomizations = null;
+            }
+        }
+        mHapticCustomizations = hapticCustomizations;
+
         mSafeModeEnabledVibrationEffect =
-                VibrationSettings.createEffectFromResource(
-                        res, com.android.internal.R.array.config_safeModeEnabledVibePattern);
+                effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED)
+                        ? mHapticCustomizations.get(HapticFeedbackConstants.SAFE_MODE_ENABLED)
+                        : VibrationSettings.createEffectFromResource(
+                                res,
+                                com.android.internal.R.array.config_safeModeEnabledVibePattern);
     }
 
     /**
@@ -68,7 +103,7 @@
             case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
             case HapticFeedbackConstants.SCROLL_TICK:
             case HapticFeedbackConstants.SEGMENT_TICK:
-                return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+                return getVibration(effectId, VibrationEffect.EFFECT_TICK);
 
             case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
                 if (!mHapticTextHandleEnabled) {
@@ -77,13 +112,16 @@
                 // fallthrough
             case HapticFeedbackConstants.CLOCK_TICK:
             case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
-                return VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
+                return getVibration(effectId, VibrationEffect.EFFECT_TEXTURE_TICK);
 
             case HapticFeedbackConstants.KEYBOARD_RELEASE:
             case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
             case HapticFeedbackConstants.ENTRY_BUMP:
             case HapticFeedbackConstants.DRAG_CROSSING:
-                return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
+                return getVibration(
+                        effectId,
+                        VibrationEffect.EFFECT_TICK,
+                        /* fallbackForPredefinedEffect= */ false);
 
             case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
             case HapticFeedbackConstants.VIRTUAL_KEY:
@@ -93,46 +131,42 @@
             case HapticFeedbackConstants.GESTURE_START:
             case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
             case HapticFeedbackConstants.SCROLL_LIMIT:
-                return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+                return getVibration(effectId, VibrationEffect.EFFECT_CLICK);
 
             case HapticFeedbackConstants.LONG_PRESS:
             case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
             case HapticFeedbackConstants.DRAG_START:
             case HapticFeedbackConstants.EDGE_SQUEEZE:
-                return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+                return getVibration(effectId, VibrationEffect.EFFECT_HEAVY_CLICK);
 
             case HapticFeedbackConstants.REJECT:
-                return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+                return getVibration(effectId, VibrationEffect.EFFECT_DOUBLE_CLICK);
 
             case HapticFeedbackConstants.SAFE_MODE_ENABLED:
                 return mSafeModeEnabledVibrationEffect;
 
             case HapticFeedbackConstants.ASSISTANT_BUTTON:
-                if (mVibrator.areAllPrimitivesSupported(
-                        VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
-                        VibrationEffect.Composition.PRIMITIVE_TICK)) {
-                    // quiet ramp, short pause, then sharp tick
-                    return VibrationEffect.startComposition()
-                            .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
-                            .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
-                            .compose();
-                }
-                // fallback for devices without composition support
-                return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+                return getAssistantButtonVibration();
 
             case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
-                return getScaledPrimitiveOrElseEffect(
-                        VibrationEffect.Composition.PRIMITIVE_TICK, 0.4f,
+                return getVibration(
+                        effectId,
+                        VibrationEffect.Composition.PRIMITIVE_TICK,
+                        /* primitiveScale= */ 0.4f,
                         VibrationEffect.EFFECT_TEXTURE_TICK);
 
             case HapticFeedbackConstants.TOGGLE_ON:
-                return getScaledPrimitiveOrElseEffect(
-                        VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f,
+                return getVibration(
+                        effectId,
+                        VibrationEffect.Composition.PRIMITIVE_TICK,
+                        /* primitiveScale= */ 0.5f,
                         VibrationEffect.EFFECT_TICK);
 
             case HapticFeedbackConstants.TOGGLE_OFF:
-                return getScaledPrimitiveOrElseEffect(
-                        VibrationEffect.Composition.PRIMITIVE_LOW_TICK, 0.2f,
+                return getVibration(
+                        effectId,
+                        VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+                        /* primitiveScale= */ 0.2f,
                         VibrationEffect.EFFECT_TEXTURE_TICK);
 
             case HapticFeedbackConstants.NO_HAPTICS:
@@ -181,14 +215,100 @@
         pw.print("mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled);
     }
 
-    private VibrationEffect getScaledPrimitiveOrElseEffect(
-            int primitiveId, float scale, int elseEffectId) {
+    private VibrationEffect getVibration(int effectId, int predefinedVibrationEffectId) {
+        return getVibration(
+                effectId, predefinedVibrationEffectId, /* fallbackForPredefinedEffect= */ true);
+    }
+
+    /**
+     * Returns the customized vibration for {@code hapticFeedbackId}, or
+     * {@code predefinedVibrationEffectId} if a customization does not exist for the haptic
+     * feedback.
+     *
+     * <p>If a customization does not exist and the default predefined effect is to be returned,
+     * {@code fallbackForPredefinedEffect} will be used to decide whether or not to fallback
+     * to a generic pattern if the predefined effect is not hardware supported.
+     *
+     * @see VibrationEffect#get(int, boolean)
+     */
+    private VibrationEffect getVibration(
+            int hapticFeedbackId,
+            int predefinedVibrationEffectId,
+            boolean fallbackForPredefinedEffect) {
+        if (effectHasCustomization(hapticFeedbackId)) {
+            return mHapticCustomizations.get(hapticFeedbackId);
+        }
+        return VibrationEffect.get(predefinedVibrationEffectId, fallbackForPredefinedEffect);
+    }
+
+    /**
+     * Returns the customized vibration for {@code hapticFeedbackId}, or some fallback vibration if
+     * a customization does not exist for the ID.
+     *
+     * <p>The fallback will be a primitive composition formed of {@code primitiveId} and
+     * {@code primitiveScale}, if the primitive is supported. Otherwise, it will be a predefined
+     * vibration of {@code elsePredefinedVibrationEffectId}.
+     */
+    private VibrationEffect getVibration(
+            int hapticFeedbackId,
+            int primitiveId,
+            float primitiveScale,
+            int elsePredefinedVibrationEffectId) {
+        if (effectHasCustomization(hapticFeedbackId)) {
+            return mHapticCustomizations.get(hapticFeedbackId);
+        }
         if (mVibrator.areAllPrimitivesSupported(primitiveId)) {
             return VibrationEffect.startComposition()
-                    .addPrimitive(primitiveId, scale)
+                    .addPrimitive(primitiveId, primitiveScale)
                     .compose();
         } else {
-            return VibrationEffect.get(elseEffectId);
+            return VibrationEffect.get(elsePredefinedVibrationEffectId);
+        }
+    }
+
+    private VibrationEffect getAssistantButtonVibration() {
+        if (effectHasCustomization(HapticFeedbackConstants.ASSISTANT_BUTTON)) {
+            return mHapticCustomizations.get(HapticFeedbackConstants.ASSISTANT_BUTTON);
+        }
+        if (mVibrator.areAllPrimitivesSupported(
+                VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
+                VibrationEffect.Composition.PRIMITIVE_TICK)) {
+            // quiet ramp, short pause, then sharp tick
+            return VibrationEffect.startComposition()
+                    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
+                    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
+                    .compose();
+        }
+        // fallback for devices without composition support
+        return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+    }
+
+    private boolean effectHasCustomization(int effectId) {
+        return mHapticCustomizations != null && mHapticCustomizations.contains(effectId);
+    }
+
+    @Nullable
+    private static SparseArray<VibrationEffect> loadHapticCustomizations(Resources res) {
+        try {
+            return HapticFeedbackCustomization.loadVibrations(res);
+        } catch (IOException | HapticFeedbackCustomization.CustomizationParserException e) {
+            Slog.e(TAG, "Unable to load haptic customizations.", e);
+            return null;
+        }
+    }
+
+    private static void removeUnsupportedVibrations(
+            SparseArray<VibrationEffect> customizations, Vibrator vibrator) {
+        Set<Integer> keysToRemove = new HashSet<>();
+        for (int i = 0; i < customizations.size(); i++) {
+            int key = customizations.keyAt(i);
+            if (!vibrator.areVibrationFeaturesSupported(customizations.get(key))) {
+                keysToRemove.add(key);
+            }
+        }
+
+        for (int key : keysToRemove) {
+            customizations.remove(key);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 0a62c88..0c1c1d4 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -3110,7 +3110,18 @@
         } else {
             TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null;
             if (candidateTf == null) {
-                final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */);
+                // Puts the activity on the top-most non-isolated navigation TF, unless the
+                // activity is launched from the same TF.
+                final TaskFragment sourceTaskFragment =
+                        mSourceRecord != null ? mSourceRecord.getTaskFragment() : null;
+                final ActivityRecord top = task.getActivity(r -> {
+                    if (!r.canBeTopRunning()) {
+                        return false;
+                    }
+                    final TaskFragment taskFragment = r.getTaskFragment();
+                    return !taskFragment.isIsolatedNav() || (sourceTaskFragment != null
+                            && sourceTaskFragment == taskFragment);
+                });
                 if (top != null) {
                     candidateTf = top.getTaskFragment();
                 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index aeaf783..2c866ab 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -311,6 +311,7 @@
  * {@hide}
  */
 public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
+    private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender";
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM;
     static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
     static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
@@ -928,6 +929,14 @@
             configuration.setLayoutDirection(configuration.locale);
         }
 
+        // Retrieve the grammatical gender from system property, set it into configuration which
+        // will get updated later if the grammatical gender raw value of current configuration is
+        // {@link Configuration#GRAMMATICAL_GENDER_UNDEFINED}.
+        if (configuration.getGrammaticalGenderRaw() == Configuration.GRAMMATICAL_GENDER_UNDEFINED) {
+            configuration.setGrammaticalGender(SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY,
+                    Configuration.GRAMMATICAL_GENDER_UNDEFINED));
+        }
+
         synchronized (mGlobalLock) {
             mForceResizableActivities = forceResizable;
             mDevEnableNonResizableMultiWindow = devEnableNonResizableMultiWindow;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 50948e1..5553600 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2201,7 +2201,8 @@
             displaySwapping |= s.isDisplaySleepingAndSwapping();
             ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
                     + "finishing=%s", s, s.nowVisible, animating, s.finishing);
-            if ((!animating && !displaySwapping) || mService.mShuttingDown) {
+            if ((!animating && !displaySwapping) || mService.mShuttingDown
+                    || s.getRootTask().isForceHiddenForPinnedTask()) {
                 if (!processPausingActivities && s.isState(PAUSING)) {
                     // Defer processing pausing activities in this iteration and reschedule
                     // a delayed idle to reprocess it again
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 349d115..467937c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2750,6 +2750,10 @@
     void onAppTransitionDone() {
         super.onAppTransitionDone();
         mWmService.mWindowsChanged = true;
+        onTransitionFinished();
+    }
+
+    void onTransitionFinished() {
         // If the transition finished callback cannot match the token for some reason, make sure the
         // rotated state is cleared if it is already invisible.
         if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.isVisibleRequested()
@@ -5418,8 +5422,10 @@
             // Attach the SystemUiContext to this DisplayContent the get latest configuration.
             // Note that the SystemUiContext will be removed automatically if this DisplayContent
             // is detached.
+            final WindowProcessController wpc = mAtmService.getProcessController(
+                    getDisplayUiContext().getIApplicationThread());
             mWmService.mWindowContextListenerController.registerWindowContainerListener(
-                    getDisplayUiContext().getWindowContextToken(), this, SYSTEM_UID,
+                    wpc, getDisplayUiContext().getWindowContextToken(), this, SYSTEM_UID,
                     INVALID_WINDOW_TYPE, null /* options */);
         }
     }
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 56edde0..271d71e 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -24,6 +24,7 @@
 
 import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
@@ -40,7 +41,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -48,7 +48,6 @@
 import android.util.Slog;
 import android.view.Display;
 import android.view.Gravity;
-import android.view.IWindowManager;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -56,7 +55,6 @@
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.Button;
@@ -95,6 +93,10 @@
      */
     @Nullable
     private Context mWindowContext;
+    /**
+     * The root display area feature id that the {@link #mWindowContext} is attaching to.
+     */
+    private int mWindowContextRootDisplayAreaId = FEATURE_UNDEFINED;
     // Local copy of vr mode enabled state, to avoid calling into VrManager with
     // the lock held.
     private boolean mVrModeEnabled;
@@ -206,12 +208,15 @@
     private void handleHide() {
         if (mClingWindow != null) {
             if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation");
-            // We don't care which root display area the window manager is specifying for removal.
-            try {
-                getWindowManager(FEATURE_UNDEFINED).removeView(mClingWindow);
-            } catch (WindowManager.InvalidDisplayException e) {
-                Slog.w(TAG, "Fail to hide the immersive confirmation window because of " + e);
-                return;
+            if (mWindowManager != null) {
+                try {
+                    mWindowManager.removeView(mClingWindow);
+                } catch (WindowManager.InvalidDisplayException e) {
+                    Slog.w(TAG, "Fail to hide the immersive confirmation window because of "
+                            + e);
+                }
+                mWindowManager = null;
+                mWindowContext = null;
             }
             mClingWindow = null;
         }
@@ -394,26 +399,18 @@
      * @return the WindowManager specifying with the {@code rootDisplayAreaId} to attach the
      *         confirmation window.
      */
-    private WindowManager getWindowManager(int rootDisplayAreaId) {
-        if (mWindowManager == null || mWindowContext == null) {
-            // Create window context to specify the RootDisplayArea
-            final Bundle options = getOptionsForWindowContext(rootDisplayAreaId);
-            mWindowContext = mContext.createWindowContext(
-                    IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options);
-            mWindowManager = mWindowContext.getSystemService(WindowManager.class);
-            return mWindowManager;
+    @NonNull
+    private WindowManager createWindowManager(int rootDisplayAreaId) {
+        if (mWindowManager != null) {
+            throw new IllegalStateException(
+                    "Must not create a new WindowManager while there is an existing one");
         }
-
-        // Update the window context and window manager to specify the RootDisplayArea
+        // Create window context to specify the RootDisplayArea
         final Bundle options = getOptionsForWindowContext(rootDisplayAreaId);
-        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
-        try {
-            wms.attachWindowContextToDisplayArea(mWindowContext.getWindowContextToken(),
-                    IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, mContext.getDisplayId(), options);
-        }  catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-
+        mWindowContextRootDisplayAreaId = rootDisplayAreaId;
+        mWindowContext = mContext.createWindowContext(
+                IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options);
+        mWindowManager = mWindowContext.getSystemService(WindowManager.class);
         return mWindowManager;
     }
 
@@ -434,14 +431,23 @@
     }
 
     private void handleShow(int rootDisplayAreaId) {
+        if (mClingWindow != null) {
+            if (rootDisplayAreaId == mWindowContextRootDisplayAreaId) {
+                if (DEBUG) Slog.d(TAG, "Immersive mode confirmation has already been shown");
+                return;
+            } else {
+                // Hide the existing confirmation before show a new one in the new root.
+                if (DEBUG) Slog.d(TAG, "Immersive mode confirmation was shown in a different root");
+                handleHide();
+            }
+        }
+
         if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation");
-
         mClingWindow = new ClingWindowView(mContext, mConfirm);
-
         // show the confirmation
-        WindowManager.LayoutParams lp = getClingWindowLayoutParams();
+        final WindowManager.LayoutParams lp = getClingWindowLayoutParams();
         try {
-            getWindowManager(rootDisplayAreaId).addView(mClingWindow, lp);
+            createWindowManager(rootDisplayAreaId).addView(mClingWindow, lp);
         } catch (WindowManager.InvalidDisplayException e) {
             Slog.w(TAG, "Fail to show the immersive confirmation window because of " + e);
         }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 2b8312c..5f3d517 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -81,6 +81,7 @@
     private boolean mIsLeashReadyForDispatching;
     private final Rect mSourceFrame = new Rect();
     private final Rect mLastSourceFrame = new Rect();
+    private final Rect mLastContainerBounds = new Rect();
     private @NonNull Insets mInsetsHint = Insets.NONE;
     private @Flags int mFlagsFromFrameProvider;
     private @Flags int mFlagsFromServer;
@@ -278,11 +279,31 @@
         // visible. (i.e. No surface, pending insets that were given during layout, etc..)
         if (mServerVisible) {
             mSource.setFrame(mSourceFrame);
+            updateInsetsHint();
         } else {
             mSource.setFrame(0, 0, 0, 0);
         }
     }
 
+    // To be called when mSourceFrame or the window container bounds is changed.
+    private void updateInsetsHint() {
+        if (!mControllable || !mServerVisible) {
+            return;
+        }
+        final Rect bounds = mWindowContainer.getBounds();
+        if (mSourceFrame.equals(mLastSourceFrame) && bounds.equals(mLastContainerBounds)) {
+            return;
+        }
+        mLastSourceFrame.set(mSourceFrame);
+        mLastContainerBounds.set(bounds);
+        mInsetsHint = mSource.calculateInsets(bounds, true /* ignoreVisibility */);
+    }
+
+    @VisibleForTesting
+    Insets getInsetsHint() {
+        return mInsetsHint;
+    }
+
     /** @return A new source computed by the specified window frame in the given display frames. */
     InsetsSource createSimulatedSource(DisplayFrames displayFrames, Rect frame) {
         final InsetsSource source = new InsetsSource(mSource);
@@ -338,15 +359,9 @@
                     mSetLeashPositionConsumer.accept(t);
                 }
             }
-            if (mServerVisible && !mLastSourceFrame.equals(mSource.getFrame())) {
-                final Insets insetsHint = mSource.calculateInsets(
-                        mWindowContainer.getBounds(), true /* ignoreVisibility */);
-                if (!insetsHint.equals(mControl.getInsetsHint())) {
-                    changed = true;
-                    mControl.setInsetsHint(insetsHint);
-                    mInsetsHint = insetsHint;
-                }
-                mLastSourceFrame.set(mSource.getFrame());
+            if (!mControl.getInsetsHint().equals(mInsetsHint)) {
+                mControl.setInsetsHint(mInsetsHint);
+                changed = true;
             }
             if (changed) {
                 mStateController.notifyControlChanged(mControlTarget);
@@ -587,6 +602,11 @@
             pw.print(prefix + "mControl=");
             mControl.dump("", pw);
         }
+        if (mControllable) {
+            pw.print(prefix + "mInsetsHint=");
+            pw.print(mInsetsHint);
+            pw.println();
+        }
         pw.print(prefix);
         pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
         pw.println();
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 7572a64..bbc35a3 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -63,6 +63,8 @@
  */
 class SurfaceAnimationRunner {
 
+    private static final String TAG = SurfaceAnimationRunner.class.getSimpleName();
+
     private final Object mLock = new Object();
 
     /**
@@ -186,6 +188,16 @@
                 // We must wait for t to be committed since otherwise the leash doesn't have the
                 // windows we want to screenshot and extend as children.
                 t.addTransactionCommittedListener(mEdgeExtensionExecutor, () -> {
+                    if (!animationLeash.isValid()) {
+                        Log.e(TAG, "Animation leash is not valid");
+                        synchronized (mEdgeExtensionLock) {
+                            mEdgeExtensions.remove(animationLeash);
+                        }
+                        synchronized (mLock) {
+                            mPreProcessingAnimations.remove(animationLeash);
+                        }
+                        return;
+                    }
                     final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
 
                     final Transaction edgeExtensionCreationTransaction = new Transaction();
@@ -450,8 +462,7 @@
             // The leash we are trying to screenshot may have been removed by this point, which is
             // likely the reason for ending up with a null edgeBuffer, in which case we just want to
             // return and do nothing.
-            Log.e("SurfaceAnimationRunner", "Failed to create edge extension - "
-                    + "edge buffer is null");
+            Log.e(TAG, "Failed to create edge extension - edge buffer is null");
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 92e90ae..4e95c84 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4504,6 +4504,10 @@
         return mForceHiddenFlags != 0;
     }
 
+    boolean isForceHiddenForPinnedTask() {
+        return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0;
+    }
+
     @Override
     protected boolean isForceTranslucent() {
         return mForceTranslucent;
@@ -5251,17 +5255,21 @@
             // Ensure that we do not trigger entering PiP an activity on the root pinned task.
             return;
         }
-        final boolean isTransient = opts != null && opts.getTransientLaunch();
-        final Task targetRootTask = toFrontTask != null
-                ? toFrontTask.getRootTask() : toFrontActivity.getRootTask();
-        if (targetRootTask != null && (targetRootTask.isActivityTypeAssistant() || isTransient)) {
-            // Ensure the task/activity being brought forward is not the assistant and is not
-            // transient. In the case of transient-launch, we want to wait until the end of the
-            // transition and only allow switch if the transient launch was committed.
+        final Task targetRootTask = toFrontTask != null ? toFrontTask.getRootTask()
+                : toFrontActivity != null ? toFrontActivity.getRootTask() : null;
+        if (targetRootTask == null) {
+            Slog.e(TAG, "No root task for enter pip, both to front task and activity are null?");
             return;
         }
-        pipCandidate.supportsEnterPipOnTaskSwitch = true;
+        final boolean isTransient = opts != null && opts.getTransientLaunch()
+                || (targetRootTask.mTransitionController.isTransientHide(targetRootTask));
 
+        // Ensure the task/activity being brought forward is not the assistant and is not transient
+        // nor transient hide target. In the case of transient-launch, we want to wait until the end
+        // of the transition and only allow to enter pip on task switch after the transient launch
+        // was committed.
+        pipCandidate.supportsEnterPipOnTaskSwitch = !targetRootTask.isActivityTypeAssistant()
+                && !isTransient;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 92c0987..57ce368 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -329,6 +329,15 @@
      */
     private boolean mDelayLastActivityRemoval;
 
+    /**
+     * Whether the activity navigation should be isolated. That is, Activities cannot be launched
+     * on an isolated TaskFragment, unless the activity is launched from an Activity in the same
+     * isolated TaskFragment, or explicitly requested to be launched to.
+     * <p>
+     * Note that only an embedded TaskFragment can be isolated.
+     */
+    private boolean mIsolatedNav;
+
     final Point mLastSurfaceSize = new Point();
 
     private final Rect mTmpBounds = new Rect();
@@ -481,6 +490,19 @@
         return mAnimationParams;
     }
 
+    /** @see #mIsolatedNav */
+    void setIsolatedNav(boolean isolatedNav) {
+        if (!isEmbedded()) {
+            return;
+        }
+        mIsolatedNav = isolatedNav;
+    }
+
+    /** @see #mIsolatedNav */
+    boolean isIsolatedNav() {
+        return isEmbedded() && mIsolatedNav;
+    }
+
     TaskFragment getAdjacentTaskFragment() {
         return mAdjacentTaskFragment;
     }
@@ -3034,7 +3056,8 @@
     @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         super.dump(pw, prefix, dumpAll);
-        pw.println(prefix + "bounds=" + getBounds().toShortString());
+        pw.println(prefix + "bounds=" + getBounds().toShortString()
+                + (mIsolatedNav ? ", isolatedNav" : ""));
         final String doublePrefix = prefix + "  ";
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final WindowContainer<?> child = mChildren.get(i);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index eaea53d..789e3d2 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1328,6 +1328,7 @@
             if (asyncRotationController != null && containsChangeFor(dc, mTargets)) {
                 asyncRotationController.onTransitionFinished();
             }
+            dc.onTransitionFinished();
             if (hasParticipatedDisplay && dc.mDisplayRotationCompatPolicy != null) {
                 final ChangeInfo changeInfo = mChanges.get(dc);
                 if (changeInfo != null
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index 43dc9c8..a59c230 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -47,7 +47,7 @@
  *
  * <ul>
  *   <li>When a {@link WindowContext} is created, it registers the listener via
- *     {@link WindowManagerService#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)}
+ *     {@link WindowManagerService#attachWindowContextToDisplayArea
  *     automatically.</li>
  *   <li>When the {@link WindowContext} adds the first window to the screen via
  *     {@link android.view.WindowManager#addView(View, android.view.ViewGroup.LayoutParams)},
@@ -55,7 +55,7 @@
  *     to corresponding {@link WindowToken} via this controller.</li>
  *   <li>When the {@link WindowContext} is GCed, it unregisters the previously
  *     registered listener via
- *     {@link WindowManagerService#detachWindowContextFromWindowContainer(IBinder)}.
+ *     {@link WindowManagerService#detachWindowContext(IBinder)}.
  *     {@link WindowManagerService} is also responsible for removing the
  *     {@link WindowContext} created {@link WindowToken}.</li>
  * </ul>
@@ -69,22 +69,24 @@
     final ArrayMap<IBinder, WindowContextListenerImpl> mListeners = new ArrayMap<>();
 
     /**
-     * @see #registerWindowContainerListener(IBinder, WindowContainer, int, int, Bundle, boolean)
+     * @see #registerWindowContainerListener(WindowProcessController, IBinder, WindowContainer, int,
+     * int, Bundle, boolean)
      */
-    void registerWindowContainerListener(@NonNull IBinder clientToken,
-            @NonNull WindowContainer<?> container, int ownerUid, @WindowType int type,
-            @Nullable Bundle options) {
-        registerWindowContainerListener(clientToken, container, ownerUid, type, options,
+    void registerWindowContainerListener(@NonNull WindowProcessController wpc,
+            @NonNull IBinder clientToken, @NonNull WindowContainer<?> container, int ownerUid,
+            @WindowType int type, @Nullable Bundle options) {
+        registerWindowContainerListener(wpc, clientToken, container, ownerUid, type, options,
                 true /* shouDispatchConfigWhenRegistering */);
     }
 
     /**
      * Registers the listener to a {@code container} which is associated with
-     * a {@code clientToken}, which is a {@link android.window.WindowContext} representation. If the
+     * a {@code clientToken}, which is a {@link WindowContext} representation. If the
      * listener associated with {@code clientToken} hasn't been initialized yet, create one
      * {@link WindowContextListenerImpl}. Otherwise, the listener associated with
      * {@code clientToken} switches to listen to the {@code container}.
      *
+     * @param wpc the process that we should send the window configuration change to
      * @param clientToken the token to associate with the listener
      * @param container the {@link WindowContainer} which the listener is going to listen to.
      * @param ownerUid the caller UID
@@ -94,19 +96,32 @@
      *                {@code container}'s config will dispatch to the client side when
      *                registering the {@link WindowContextListenerImpl}
      */
-    void registerWindowContainerListener(@NonNull IBinder clientToken,
-            @NonNull WindowContainer<?> container, int ownerUid, @WindowType int type,
-            @Nullable Bundle options, boolean shouDispatchConfigWhenRegistering) {
+    void registerWindowContainerListener(@NonNull WindowProcessController wpc,
+            @NonNull IBinder clientToken, @NonNull WindowContainer<?> container, int ownerUid,
+            @WindowType int type, @Nullable Bundle options,
+            boolean shouDispatchConfigWhenRegistering) {
         WindowContextListenerImpl listener = mListeners.get(clientToken);
         if (listener == null) {
-            listener = new WindowContextListenerImpl(clientToken, container, ownerUid, type,
+            listener = new WindowContextListenerImpl(wpc, clientToken, container, ownerUid, type,
                     options);
             listener.register(shouDispatchConfigWhenRegistering);
         } else {
-            listener.updateContainer(container);
+            updateContainerForWindowContextListener(clientToken, container);
         }
     }
 
+    /**
+     * Updates the {@link WindowContainer} that an existing {@link WindowContext} is listening to.
+     */
+    void updateContainerForWindowContextListener(@NonNull IBinder clientToken,
+            @NonNull WindowContainer<?> container) {
+        final WindowContextListenerImpl listener = mListeners.get(clientToken);
+        if (listener == null) {
+            throw new IllegalArgumentException("Can't find listener for " + clientToken);
+        }
+        listener.updateContainer(container);
+    }
+
     void unregisterWindowContainerListener(IBinder clientToken) {
         final WindowContextListenerImpl listener = mListeners.get(clientToken);
         // Listeners may be removed earlier. An example is the display where the listener is
@@ -189,9 +204,13 @@
 
     @VisibleForTesting
     class WindowContextListenerImpl implements WindowContainerListener {
-        @NonNull private final IWindowToken mClientToken;
+        @NonNull
+        private final WindowProcessController mWpc;
+        @NonNull
+        private final IWindowToken mClientToken;
         private final int mOwnerUid;
-        @NonNull private WindowContainer<?> mContainer;
+        @NonNull
+        private WindowContainer<?> mContainer;
         /**
          * The options from {@link Context#createWindowContext(int, Bundle)}.
          * <p>It can be used for choosing the {@link DisplayArea} where the window context
@@ -207,8 +226,10 @@
 
         private boolean mHasPendingConfiguration;
 
-        private WindowContextListenerImpl(IBinder clientToken, WindowContainer<?> container,
+        private WindowContextListenerImpl(@NonNull WindowProcessController wpc,
+                @NonNull IBinder clientToken, @NonNull WindowContainer<?> container,
                 int ownerUid, @WindowType int type, @Nullable Bundle options) {
+            mWpc = Objects.requireNonNull(wpc);
             mClientToken = IWindowToken.Stub.asInterface(clientToken);
             mContainer = Objects.requireNonNull(container);
             mOwnerUid = ownerUid;
@@ -308,6 +329,7 @@
             mLastReportedDisplay = displayId;
 
             try {
+                // TODO(b/290876897): migrate to dispatch through wpc
                 mClientToken.onConfigurationChanged(config, displayId);
             } catch (RemoteException e) {
                 ProtoLog.w(WM_ERROR, "Could not report config changes to the window token client.");
@@ -337,6 +359,7 @@
             }
             mDeathRecipient.unlinkToDeath();
             try {
+                // TODO(b/290876897): migrate to dispatch through wpc
                 mClientToken.onWindowTokenRemoved();
             } catch (RemoteException e) {
                 ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client.");
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index db92191..2a28010 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -167,6 +167,7 @@
 import android.app.ActivityThread;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
+import android.app.IApplicationThread;
 import android.app.IAssistDataReceiver;
 import android.app.WindowConfiguration;
 import android.content.BroadcastReceiver;
@@ -1697,8 +1698,8 @@
                         return WindowManagerGlobal.ADD_INVALID_TYPE;
                     }
                 } else {
-                    mWindowContextListenerController.registerWindowContainerListener(
-                            windowContextToken, token, callingUid, type, options);
+                    mWindowContextListenerController.updateContainerForWindowContextListener(
+                            windowContextToken, token);
                 }
             }
 
@@ -2734,18 +2735,26 @@
         }
     }
 
+    @Nullable
     @Override
-    public Configuration attachWindowContextToDisplayArea(IBinder clientToken, int
-            type, int displayId, Bundle options) {
-        if (clientToken == null) {
-            throw new IllegalArgumentException("clientToken must not be null!");
-        }
+    public Configuration attachWindowContextToDisplayArea(@NonNull IApplicationThread appThread,
+            @NonNull IBinder clientToken, @LayoutParams.WindowType int type, int displayId,
+            @Nullable Bundle options) {
+        Objects.requireNonNull(appThread);
+        Objects.requireNonNull(clientToken);
         final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
                 "attachWindowContextToDisplayArea", false /* printLog */);
+        final int callingPid = Binder.getCallingPid();
         final int callingUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
+                final WindowProcessController wpc = mAtmService.getProcessController(appThread);
+                if (wpc == null) {
+                    ProtoLog.w(WM_ERROR, "attachWindowContextToDisplayArea: calling from"
+                            + " non-existing process pid=%d uid=%d", callingPid, callingUid);
+                    return null;
+                }
                 final DisplayContent dc = mRoot.getDisplayContentOrCreate(displayId);
                 if (dc == null) {
                     ProtoLog.w(WM_ERROR, "attachWindowContextToDisplayArea: trying to attach"
@@ -2756,8 +2765,9 @@
                 // the feature b/155340867 is completed.
                 final DisplayArea<?> da = dc.findAreaForWindowType(type, options,
                         callerCanManageAppTokens, false /* roundedCornerOverlay */);
-                mWindowContextListenerController.registerWindowContainerListener(clientToken, da,
-                        callingUid, type, options, false /* shouDispatchConfigWhenRegistering */);
+                mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
+                        da, callingUid, type, options,
+                        false /* shouDispatchConfigWhenRegistering */);
                 return da.getConfiguration();
             }
         } finally {
@@ -2765,14 +2775,68 @@
         }
     }
 
+    @Nullable
     @Override
-    public void attachWindowContextToWindowToken(IBinder clientToken, IBinder token) {
-        final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
-                "attachWindowContextToWindowToken", false /* printLog */);
+    public Configuration attachWindowContextToDisplayContent(@NonNull IApplicationThread appThread,
+            @NonNull IBinder clientToken, int displayId) {
+        Objects.requireNonNull(appThread);
+        Objects.requireNonNull(clientToken);
+        final int callingPid = Binder.getCallingPid();
         final int callingUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
+                final WindowProcessController wpc = mAtmService.getProcessController(appThread);
+                if (wpc == null) {
+                    ProtoLog.w(WM_ERROR, "attachWindowContextToDisplayContent: calling from"
+                            + " non-existing process pid=%d uid=%d", callingPid, callingUid);
+                    return null;
+                }
+                // We use "getDisplayContent" instead of "getDisplayContentOrCreate" because
+                // this method may be called in DisplayPolicy's constructor and may cause
+                // infinite loop. In this scenario, we early return here and switch to do the
+                // registration in DisplayContent#onParentChanged at DisplayContent initialization.
+                final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc == null) {
+                    if (Binder.getCallingPid() != MY_PID) {
+                        throw new WindowManager.InvalidDisplayException(
+                                "attachWindowContextToDisplayContent: trying to attach to a"
+                                        + " non-existing display:" + displayId);
+                    }
+                    // Early return if this method is invoked from system process.
+                    // See above comments for more detail.
+                    return null;
+                }
+
+                mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
+                        dc, callingUid, INVALID_WINDOW_TYPE, null /* options */,
+                        false /* shouDispatchConfigWhenRegistering */);
+                return dc.getConfiguration();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public void attachWindowContextToWindowToken(@NonNull IApplicationThread appThread,
+            @NonNull IBinder clientToken, @NonNull IBinder token) {
+        Objects.requireNonNull(appThread);
+        Objects.requireNonNull(clientToken);
+        Objects.requireNonNull(token);
+        final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
+                "attachWindowContextToWindowToken", false /* printLog */);
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final WindowProcessController wpc = mAtmService.getProcessController(appThread);
+                if (wpc == null) {
+                    ProtoLog.w(WM_ERROR, "attachWindowContextToWindowToken: calling from"
+                            + " non-existing process pid=%d uid=%d", callingPid, callingUid);
+                    return;
+                }
                 final WindowToken windowToken = mRoot.getWindowToken(token);
                 if (windowToken == null) {
                     ProtoLog.w(WM_ERROR, "Then token:%s is invalid. It might be "
@@ -2793,7 +2857,7 @@
                         callerCanManageAppTokens, callingUid)) {
                     return;
                 }
-                mWindowContextListenerController.registerWindowContainerListener(clientToken,
+                mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
                         windowToken, callingUid, windowToken.windowType, windowToken.mOptions);
             }
         } finally {
@@ -2802,9 +2866,10 @@
     }
 
     @Override
-    public void detachWindowContextFromWindowContainer(IBinder clientToken) {
+    public void detachWindowContext(@NonNull IBinder clientToken) {
+        Objects.requireNonNull(clientToken);
         final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
-                "detachWindowContextFromWindowContainer", false /* printLog */);
+                "detachWindowContext", false /* printLog */);
         final int callingUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -2828,40 +2893,6 @@
         }
     }
 
-    @Override
-    public Configuration attachToDisplayContent(IBinder clientToken, int displayId) {
-        if (clientToken == null) {
-            throw new IllegalArgumentException("clientToken must not be null!");
-        }
-        final int callingUid = Binder.getCallingUid();
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                // We use "getDisplayContent" instead of "getDisplayContentOrCreate" because
-                // this method may be called in DisplayPolicy's constructor and may cause
-                // infinite loop. In this scenario, we early return here and switch to do the
-                // registration in DisplayContent#onParentChanged at DisplayContent initialization.
-                final DisplayContent dc = mRoot.getDisplayContent(displayId);
-                if (dc == null) {
-                    if (Binder.getCallingPid() != MY_PID) {
-                        throw new WindowManager.InvalidDisplayException("attachToDisplayContent: "
-                                + "trying to attach to a non-existing display:" + displayId);
-                    }
-                    // Early return if this method is invoked from system process.
-                    // See above comments for more detail.
-                    return null;
-                }
-
-                mWindowContextListenerController.registerWindowContainerListener(clientToken, dc,
-                        callingUid, INVALID_WINDOW_TYPE, null /* options */,
-                        false /* shouDispatchConfigWhenRegistering */);
-                return dc.getConfiguration();
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
     /** Returns {@code true} if this binder is a registered window token. */
     @Override
     public boolean isWindowToken(IBinder binder) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 164c8b0..a84749a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -30,6 +30,7 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
@@ -1356,6 +1357,11 @@
                 }
                 break;
             }
+            case OP_TYPE_SET_ISOLATED_NAVIGATION: {
+                final boolean isolatedNav = operation.isIsolatedNav();
+                taskFragment.setIsolatedNav(isolatedNav);
+                break;
+            }
         }
         return effects;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 77f8de5..c323a7f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -241,7 +241,6 @@
 import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
 import static android.provider.Telephony.Carriers.INVALID_APN_ID;
 import static android.security.keystore.AttestationUtils.USE_INDIVIDUAL_ATTESTATION;
-
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
@@ -540,6 +539,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
@@ -18825,10 +18826,16 @@
         });
     }
 
+    ThreadPoolExecutor calculateHasIncompatibleAccountsExecutor = new ThreadPoolExecutor(
+            1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
+
     @Override
     public void calculateHasIncompatibleAccounts() {
+        if (calculateHasIncompatibleAccountsExecutor.getQueue().size() > 1) {
+            return;
+        }
         new CalculateHasIncompatibleAccountsTask().executeOnExecutor(
-                AsyncTask.THREAD_POOL_EXECUTOR, null);
+                calculateHasIncompatibleAccountsExecutor, null);
     }
 
     @Nullable
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
index 24d0521..0265453 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
@@ -32,9 +32,8 @@
 
 // TODO(scottjonathan): Replace with generic set implementation
 final class StringSetPolicySerializer extends PolicySerializer<Set<String>> {
-    private static final String ATTR_VALUES = ":strings";
+    private static final String ATTR_VALUES = "strings";
     private static final String ATTR_VALUES_SEPARATOR = ";";
-
     @Override
     void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
             @NonNull Set<String> value) throws IOException {
diff --git a/services/incremental/TEST_MAPPING b/services/incremental/TEST_MAPPING
index 648b904..d32364d 100644
--- a/services/incremental/TEST_MAPPING
+++ b/services/incremental/TEST_MAPPING
@@ -22,14 +22,12 @@
     },
     {
       "name": "CtsInstalledLoadingProgressHostTests"
-    }
-  ],
-  "presubmit-large": [
+    },
     {
-      "name": "CtsContentTestCases",
+      "name": "CtsPackageManagerTestCases",
       "options": [
         {
-          "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest"
+          "include-filter": "android.content.pm.cts.PackageManagerShellCommandInstallTest"
         },
         {
           "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest"
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ee4bc12..6a2d4dc 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2447,8 +2447,9 @@
                 t.traceBegin("StartCompanionDeviceManager");
                 mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS);
                 t.traceEnd();
+            }
 
-                // VirtualDeviceManager depends on CDM to control the associations.
+            if (context.getResources().getBoolean(R.bool.config_enableVirtualDeviceManager)) {
                 t.traceBegin("StartVirtualDeviceManager");
                 mSystemServiceManager.startService(VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS);
                 t.traceEnd();
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 5e7dd59..c0cfa53 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.media.MediaMetrics;
 import android.media.midi.IBluetoothMidiService;
@@ -43,6 +44,7 @@
 import android.media.midi.MidiDeviceService;
 import android.media.midi.MidiDeviceStatus;
 import android.media.midi.MidiManager;
+import android.media.midi.MidiUmpDeviceService;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -580,10 +582,14 @@
                         intent.setComponent(new ComponentName(
                                 MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE,
                                 MidiManager.BLUETOOTH_MIDI_SERVICE_CLASS));
-                    } else {
+                    } else if (!isUmpDevice(mDeviceInfo)) {
                         intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
                         intent.setComponent(
                                 new ComponentName(mServiceInfo.packageName, mServiceInfo.name));
+                    } else {
+                        intent = new Intent(MidiUmpDeviceService.SERVICE_INTERFACE);
+                        intent.setComponent(
+                                new ComponentName(mServiceInfo.packageName, mServiceInfo.name));
                     }
 
                     if (!mContext.bindServiceAsUser(intent, mServiceConnection,
@@ -696,8 +702,8 @@
                             isDeviceDisconnected ? "true" : "false")
                     .set(MediaMetrics.Property.IS_SHARED,
                             !mDeviceInfo.isPrivate() ? "true" : "false")
-                    .set(MediaMetrics.Property.SUPPORTS_MIDI_UMP, mDeviceInfo.getDefaultProtocol()
-                             != MidiDeviceInfo.PROTOCOL_UNKNOWN ? "true" : "false")
+                    .set(MediaMetrics.Property.SUPPORTS_MIDI_UMP,
+                            isUmpDevice(mDeviceInfo) ? "true" : "false")
                     .set(MediaMetrics.Property.USING_ALSA, mDeviceInfo.getProperties().get(
                             MidiDeviceInfo.PROPERTY_ALSA_CARD) != null ? "true" : "false")
                     .set(MediaMetrics.Property.EVENT, "deviceClosed")
@@ -971,19 +977,37 @@
     private void onStartOrUnlockUser(TargetUser user, boolean matchDirectBootUnaware) {
         Log.d(TAG, "onStartOrUnlockUser " + user.getUserIdentifier() + " matchDirectBootUnaware: "
                 + matchDirectBootUnaware);
-        Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
         int resolveFlags = PackageManager.GET_META_DATA;
         if (matchDirectBootUnaware) {
             resolveFlags |= PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
         }
-        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServicesAsUser(intent,
-                resolveFlags, user.getUserIdentifier());
-        if (resolveInfos != null) {
-            int count = resolveInfos.size();
-            for (int i = 0; i < count; i++) {
-                ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
-                if (serviceInfo != null) {
-                    addPackageDeviceServer(serviceInfo, user.getUserIdentifier());
+
+        {
+            Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
+            List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServicesAsUser(intent,
+                    resolveFlags, user.getUserIdentifier());
+            if (resolveInfos != null) {
+                int count = resolveInfos.size();
+                for (int i = 0; i < count; i++) {
+                    ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
+                    if (serviceInfo != null) {
+                        addLegacyPackageDeviceServer(serviceInfo, user.getUserIdentifier());
+                    }
+                }
+            }
+        }
+
+        {
+            Intent intent = new Intent(MidiUmpDeviceService.SERVICE_INTERFACE);
+            List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServicesAsUser(intent,
+                    resolveFlags, user.getUserIdentifier());
+            if (resolveInfos != null) {
+                int count = resolveInfos.size();
+                for (int i = 0; i < count; i++) {
+                    ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
+                    if (serviceInfo != null) {
+                        addUmpPackageDeviceServer(serviceInfo, user.getUserIdentifier());
+                    }
                 }
             }
         }
@@ -1057,13 +1081,11 @@
                 if (device.isUidAllowed(uid) && device.isUserIdAllowed(userId)) {
                     // UMP devices have protocols that are not PROTOCOL_UNKNOWN
                     if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
-                        if (device.getDeviceInfo().getDefaultProtocol()
-                                != MidiDeviceInfo.PROTOCOL_UNKNOWN) {
+                        if (isUmpDevice(device.getDeviceInfo())) {
                             deviceInfos.add(device.getDeviceInfo());
                         }
                     } else if (transport == MidiManager.TRANSPORT_MIDI_BYTE_STREAM) {
-                        if (device.getDeviceInfo().getDefaultProtocol()
-                                == MidiDeviceInfo.PROTOCOL_UNKNOWN) {
+                        if (!isUmpDevice(device.getDeviceInfo())) {
                             deviceInfos.add(device.getDeviceInfo());
                         }
                     }
@@ -1364,14 +1386,15 @@
         ServiceInfo[] services = info.services;
         if (services == null) return;
         for (int i = 0; i < services.length; i++) {
-            addPackageDeviceServer(services[i], userId);
+            addLegacyPackageDeviceServer(services[i], userId);
+            addUmpPackageDeviceServer(services[i], userId);
         }
     }
 
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
-    private void addPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
-        Log.d(TAG, "addPackageDeviceServer()" + userId);
+    private void addLegacyPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
+        Log.d(TAG, "addLegacyPackageDeviceServer()" + userId);
         XmlResourceParser parser = null;
 
         try {
@@ -1507,6 +1530,128 @@
         }
     }
 
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    private void addUmpPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
+        Log.d(TAG, "addUmpPackageDeviceServer()" + userId);
+        XmlResourceParser parser = null;
+
+        try {
+            ComponentName componentName = new ComponentName(serviceInfo.packageName,
+                    serviceInfo.name);
+            int resId = mPackageManager.getProperty(MidiUmpDeviceService.SERVICE_INTERFACE,
+                    componentName).getResourceId();
+            Resources resources = mPackageManager.getResourcesForApplication(
+                    serviceInfo.packageName);
+            parser = resources.getXml(resId);
+            if (parser == null) return;
+
+            // ignore virtual device servers that do not require the correct permission
+            if (!android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE.equals(
+                    serviceInfo.permission)) {
+                Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName
+                        + ": it does not require the permission "
+                        + android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE);
+                return;
+            }
+
+            Bundle properties = null;
+            int numPorts = 0;
+            boolean isPrivate = false;
+            ArrayList<String> portNames = new ArrayList<String>();
+
+            while (true) {
+                int eventType = parser.next();
+                if (eventType == XmlPullParser.END_DOCUMENT) {
+                    break;
+                } else if (eventType == XmlPullParser.START_TAG) {
+                    String tagName = parser.getName();
+                    if ("device".equals(tagName)) {
+                        if (properties != null) {
+                            Log.w(TAG, "nested <device> elements in metadata for "
+                                    + serviceInfo.packageName);
+                            continue;
+                        }
+                        properties = new Bundle();
+                        properties.putParcelable(MidiDeviceInfo.PROPERTY_SERVICE_INFO, serviceInfo);
+                        numPorts = 0;
+                        isPrivate = false;
+
+                        int count = parser.getAttributeCount();
+                        for (int i = 0; i < count; i++) {
+                            String name = parser.getAttributeName(i);
+                            String value = parser.getAttributeValue(i);
+                            if ("private".equals(name)) {
+                                isPrivate = "true".equals(value);
+                            } else {
+                                properties.putString(name, value);
+                            }
+                        }
+                    } else if ("port".equals(tagName)) {
+                        if (properties == null) {
+                            Log.w(TAG, "<port> outside of <device> in metadata for "
+                                    + serviceInfo.packageName);
+                            continue;
+                        }
+                        numPorts++;
+
+                        String portName = null;
+                        int count = parser.getAttributeCount();
+                        for (int i = 0; i < count; i++) {
+                            String name = parser.getAttributeName(i);
+                            String value = parser.getAttributeValue(i);
+                            if ("name".equals(name)) {
+                                portName = value;
+                                break;
+                            }
+                        }
+                        portNames.add(portName);
+                    }
+                } else if (eventType == XmlPullParser.END_TAG) {
+                    String tagName = parser.getName();
+                    if ("device".equals(tagName)) {
+                        if (properties != null) {
+                            if (numPorts == 0) {
+                                Log.w(TAG, "<device> with no ports in metadata for "
+                                        + serviceInfo.packageName);
+                                continue;
+                            }
+
+                            int uid;
+                            try {
+                                ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
+                                        serviceInfo.packageName, 0, userId);
+                                uid = appInfo.uid;
+                            } catch (PackageManager.NameNotFoundException e) {
+                                Log.e(TAG, "could not fetch ApplicationInfo for "
+                                        + serviceInfo.packageName);
+                                continue;
+                            }
+
+                            synchronized (mDevicesByInfo) {
+                                addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL,
+                                        numPorts, numPorts,
+                                        portNames.toArray(EMPTY_STRING_ARRAY),
+                                        portNames.toArray(EMPTY_STRING_ARRAY),
+                                        properties, null, serviceInfo, isPrivate, uid,
+                                        MidiDeviceInfo.PROTOCOL_UMP_MIDI_2_0, userId);
+                            }
+                            // setting properties to null signals that we are no longer
+                            // processing a <device>
+                            properties = null;
+                            portNames.clear();
+                        }
+                    }
+                }
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+                // No such property
+        } catch (Exception e) {
+            Log.w(TAG, "Unable to load component info " + serviceInfo.toString(), e);
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
     private void removePackageDeviceServers(String packageName, int userId) {
         synchronized (mDevicesByInfo) {
             Iterator<Device> iterator = mDevicesByInfo.values().iterator();
@@ -1649,4 +1794,8 @@
     private int getCallingUserId() {
         return UserHandle.getUserId(Binder.getCallingUid());
     }
+
+    private boolean isUmpDevice(MidiDeviceInfo deviceInfo) {
+        return deviceInfo.getDefaultProtocol() != MidiDeviceInfo.PROTOCOL_UNKNOWN;
+    }
 }
diff --git a/services/permission/TEST_MAPPING b/services/permission/TEST_MAPPING
index 579d4e3..b2dcf37 100644
--- a/services/permission/TEST_MAPPING
+++ b/services/permission/TEST_MAPPING
@@ -4,6 +4,9 @@
             "name": "CtsPermissionTestCases",
             "options": [
                 {
+                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                },
+                {
                     "include-filter": "android.permission.cts.BackgroundPermissionsTest"
                 },
                 {
@@ -29,6 +32,9 @@
             "name": "CtsPermissionPolicyTestCases",
             "options": [
                 {
+                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                },
+                {
                     "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
                 },
                 {
@@ -59,6 +65,29 @@
             "options": [
                 {
                     "include-filter": "android.permission.cts.PermissionUpdateListenerTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.SplitPermissionTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.PermissionFlagsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.SharedUidPermissionsTest"
+                }
+            ]
+        },
+        {
+            "name": "CtsPermissionPolicyTestCases",
+            "options": [
+                {
+                    "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
                 }
             ]
         }
diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
index 212ec14..bef56ce 100644
--- a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
@@ -17,7 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.frameworks.inputmethodtests">
 
-    <uses-sdk android:targetSdkVersion="31" />
     <queries>
         <intent>
             <action android:name="android.view.InputMethod" />
diff --git a/services/tests/InputMethodSystemServerTests/TEST_MAPPING b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
index 77e32a7..cedbfd2b 100644
--- a/services/tests/InputMethodSystemServerTests/TEST_MAPPING
+++ b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
@@ -9,5 +9,16 @@
         {"exclude-annotation": "org.junit.Ignore"}
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksImeTests",
+      "options": [
+        {"include-filter": "com.android.inputmethodservice"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
   ]
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
index 0104f71..b7de749 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
@@ -18,8 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.inputmethod.imetests">
 
-    <uses-sdk android:targetSdkVersion="31" />
-
     <!-- Permissions required for granting and logging -->
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 898658e..e8acb06 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -20,6 +20,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.fail;
+
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -45,6 +47,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -56,9 +59,9 @@
     private static final String EDIT_TEXT_DESC = "Input box";
     private static final long TIMEOUT_IN_SECONDS = 3;
     private static final String ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
-            "settings put secure show_ime_with_hard_keyboard 1";
+            "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 1";
     private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
-            "settings put secure show_ime_with_hard_keyboard 0";
+            "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 0";
 
     private Instrumentation mInstrumentation;
     private UiDevice mUiDevice;
@@ -82,29 +85,19 @@
         mUiDevice.freezeRotation();
         mUiDevice.setOrientationNatural();
         // Waits for input binding ready.
-        eventually(
-                () -> {
-                    mInputMethodService =
-                            InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting();
-                    assertThat(mInputMethodService).isNotNull();
+        eventually(() -> {
+            mInputMethodService =
+                    InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting();
+            assertThat(mInputMethodService).isNotNull();
 
-                    // The editor won't bring up keyboard by default.
-                    assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
-                    assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
-                });
-        // Save the original value of show_ime_with_hard_keyboard in Settings.
+            // The editor won't bring up keyboard by default.
+            assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
+            assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
+        });
+        // Save the original value of show_ime_with_hard_keyboard from Settings.
         mShowImeWithHardKeyboardEnabled = Settings.Secure.getInt(
                 mInputMethodService.getContentResolver(),
                 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0;
-        // Disable showing Ime with hard keyboard because it is the precondition the for most test
-        // cases
-        if (mShowImeWithHardKeyboardEnabled) {
-            executeShellCommand(DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
-        }
-        mInputMethodService.getResources().getConfiguration().keyboard =
-                Configuration.KEYBOARD_NOKEYS;
-        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
-                Configuration.HARDKEYBOARDHIDDEN_YES;
     }
 
     @After
@@ -112,82 +105,141 @@
         mUiDevice.unfreezeRotation();
         executeShellCommand("ime disable " + mInputMethodId);
         // Change back the original value of show_ime_with_hard_keyboard in Settings.
-        executeShellCommand(mShowImeWithHardKeyboardEnabled ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
+        executeShellCommand(mShowImeWithHardKeyboardEnabled
+                ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
                 : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
     }
 
+    /**
+     * This checks that the IME can be shown and hidden by user actions
+     * (i.e. tapping on an EditText, tapping the Home button).
+     */
     @Test
-    public void testShowHideKeyboard_byUserAction() throws InterruptedException {
+    public void testShowHideKeyboard_byUserAction() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
         // Performs click on editor box to bring up the soft keyboard.
         Log.i(TAG, "Click on EditText.");
-        verifyInputViewStatus(() -> clickOnEditorText(), true /* inputViewStarted */);
-
-        // Press back key to hide soft keyboard.
-        Log.i(TAG, "Press back");
         verifyInputViewStatus(
-                () -> assertThat(mUiDevice.pressHome()).isTrue(), false /* inputViewStarted */);
+                () -> clickOnEditorText(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Press home key to hide soft keyboard.
+        Log.i(TAG, "Press home");
+        verifyInputViewStatus(
+                () -> assertThat(mUiDevice.pressHome()).isTrue(),
+                true /* expected */,
+                false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks that the IME can be shown and hidden using the WindowInsetsController APIs.
+     */
     @Test
-    public void testShowHideKeyboard_byApi() throws InterruptedException {
+    public void testShowHideKeyboard_byApi() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
         // Triggers to show IME via public API.
         verifyInputViewStatus(
                 () -> assertThat(mActivity.showImeWithWindowInsetsController()).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
         // Triggers to hide IME via public API.
         verifyInputViewStatusOnMainSync(
-                () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                () -> assertThat(mActivity.hideImeWithWindowInsetsController()).isTrue(),
+                true /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf.
+     */
     @Test
-    public void testShowHideSelf() throws InterruptedException {
-        // IME requests to show itself without any flags: expect shown.
+    public void testShowHideSelf() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // IME request to show itself without any flags, expect shown.
         Log.i(TAG, "Call IMS#requestShowSelf(0)");
         verifyInputViewStatusOnMainSync(
-                () -> mInputMethodService.requestShowSelf(0), true /* inputViewStarted */);
+                () -> mInputMethodService.requestShowSelf(0 /* flags */),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-        // IME requests to hide itself with flag: HIDE_IMPLICIT_ONLY, expect not hide (shown).
+        // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect not hide (shown).
         Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
         verifyInputViewStatusOnMainSync(
                 () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+                false /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-        // IME request to hide itself without any flags: expect hidden.
+        // IME request to hide itself without any flags, expect hidden.
         Log.i(TAG, "Call IMS#requestHideSelf(0)");
         verifyInputViewStatusOnMainSync(
-                () -> mInputMethodService.requestHideSelf(0), false /* inputViewStarted */);
+                () -> mInputMethodService.requestHideSelf(0 /* flags */),
+                true /* expected */,
+                false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
 
-        // IME request to show itself with flag SHOW_IMPLICIT: expect shown.
+        // IME request to show itself with flag SHOW_IMPLICIT, expect shown.
         Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)");
         verifyInputViewStatusOnMainSync(
                 () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-        // IME request to hide itself with flag: HIDE_IMPLICIT_ONLY, expect hidden.
+        // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect hidden.
         Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
         verifyInputViewStatusOnMainSync(
                 () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+                true /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks the return value of IMS#onEvaluateInputViewShown,
+     * when show_ime_with_hard_keyboard is enabled.
+     */
     @Test
     public void testOnEvaluateInputViewShown_showImeWithHardKeyboard() throws Exception {
-        executeShellCommand(ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
-        mInstrumentation.waitForIdleSync();
+        setShowImeWithHardKeyboard(true /* enabled */);
 
-        // Simulate connecting a hard keyboard
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
         mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
                 Configuration.HARDKEYBOARDHIDDEN_NO;
+        eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
 
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_NO;
+        eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
         eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
     }
 
+    /**
+     * This checks the return value of IMSonEvaluateInputViewShown,
+     * when show_ime_with_hard_keyboard is disabled.
+     */
     @Test
-    public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() {
+    public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
         mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
@@ -196,6 +248,8 @@
 
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_NO;
         eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
 
         mInputMethodService.getResources().getConfiguration().keyboard =
@@ -205,149 +259,386 @@
         eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
     }
 
+    /**
+     * This checks that any (implicit or explicit) show request,
+     * when IMS#onEvaluateInputViewShown returns false, results in the IME not being shown.
+     */
     @Test
     public void testShowSoftInput_disableShowImeWithHardKeyboard() throws Exception {
-        // Simulate connecting a hard keyboard
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
         mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
                 Configuration.HARDKEYBOARDHIDDEN_NO;
+
         // When InputMethodService#onEvaluateInputViewShown() returns false, the Ime should not be
         // shown no matter what the show flag is.
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
+
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                false /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks that an explicit show request results in the IME being shown.
+     */
     @Test
     public void testShowSoftInputExplicitly() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
         // When InputMethodService#onEvaluateInputViewShown() returns true and flag is EXPLICIT, the
         // Ime should be shown.
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that an implicit show request results in the IME being shown.
+     */
     @Test
     public void testShowSoftInputImplicitly() throws Exception {
-        // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT, the
-        // Ime should be shown.
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT,
+        // the IME should be shown.
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that an explicit show request when the IME is not previously shown,
+     * and it should be shown in fullscreen mode, results in the IME being shown.
+     */
     @Test
-    public void testShowSoftInputImplicitly_fullScreenMode() throws Exception {
-        // When keyboard is off, InputMethodService#onEvaluateInputViewShown returns true, flag is
-        // IMPLICIT and InputMethodService#onEvaluateFullScreenMode returns true, the Ime should not
-        // be shown.
+    public void testShowSoftInputExplicitly_fullScreenMode() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // Set orientation landscape to enable fullscreen mode.
         setOrientation(2);
         eventually(() -> assertThat(mUiDevice.isNaturalOrientation()).isFalse());
-        // Wait for the TestActivity to be recreated
+        // Wait for the TestActivity to be recreated.
         eventually(() ->
                 assertThat(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity));
-        // Get the new TestActivity
+        // Get the new TestActivity.
         mActivity = TestActivity.getLastCreatedInstance();
         assertThat(mActivity).isNotNull();
         InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
-        // Wait for the new EditText to be served by InputMethodManager
-        eventually(() ->
-                assertThat(imm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
+        // Wait for the new EditText to be served by InputMethodManager.
+        eventually(() -> assertThat(
+                imm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
+
         verifyInputViewStatusOnMainSync(() -> assertThat(
-                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
-                false /* inputViewStarted */);
+                        mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that an implicit show request when the IME is not previously shown,
+     * and it should be shown in fullscreen mode, results in the IME not being shown.
+     */
+    @Test
+    public void testShowSoftInputImplicitly_fullScreenMode() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // Set orientation landscape to enable fullscreen mode.
+        setOrientation(2);
+        eventually(() -> assertThat(mUiDevice.isNaturalOrientation()).isFalse());
+        // Wait for the TestActivity to be recreated.
+        eventually(() ->
+                assertThat(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity));
+        // Get the new TestActivity.
+        mActivity = TestActivity.getLastCreatedInstance();
+        assertThat(mActivity).isNotNull();
+        InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
+        // Wait for the new EditText to be served by InputMethodManager.
+        eventually(() -> assertThat(
+                imm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
+                false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
+    }
+
+    /**
+     * This checks that an explicit show request when a hard keyboard is connected,
+     * results in the IME being shown.
+     */
+    @Test
+    public void testShowSoftInputExplicitly_withHardKeyboard() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+    }
+
+    /**
+     * This checks that an implicit show request when a hard keyboard is connected,
+     * results in the IME not being shown.
+     */
     @Test
     public void testShowSoftInputImplicitly_withHardKeyboard() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
-        // When connecting to a hard keyboard and the flag is IMPLICIT, the Ime should not be shown.
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks that an explicit show request followed by connecting a hard keyboard
+     * and a configuration change, still results in the IME being shown.
+     */
     @Test
-    public void testConfigurationChanged_withKeyboardShownExplicitly() throws InterruptedException {
+    public void testShowSoftInputExplicitly_thenConfigurationChanged() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Start with no hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
-        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
-        mInputMethodService.getResources().getConfiguration().orientation =
-                Configuration.ORIENTATION_LANDSCAPE;
-        verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
-                mInputMethodService.getResources().getConfiguration()),
-                true /* inputViewStarted */);
-    }
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-    @Test
-    public void testConfigurationChanged_withKeyboardShownImplicitly() throws InterruptedException {
-        verifyInputViewStatusOnMainSync(() -> assertThat(
-                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
-                true /* inputViewStarted */);
-        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
-        mInputMethodService.getResources().getConfiguration().orientation =
-                Configuration.ORIENTATION_LANDSCAPE;
+        // Simulate connecting a hard keyboard.
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
+        mInputMethodService.getResources().getConfiguration().orientation =
+                Configuration.ORIENTATION_LANDSCAPE;
+
+        verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
+                mInputMethodService.getResources().getConfiguration()),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+    }
+
+    /**
+     * This checks that an implicit show request followed by connecting a hard keyboard
+     * and a configuration change, does not trigger IMS#onFinishInputView,
+     * but results in the IME being hidden.
+     */
+    @Test
+    public void testShowSoftInputImplicitly_thenConfigurationChanged() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Start with no hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Simulate connecting a hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
+        mInputMethodService.getResources().getConfiguration().orientation =
+                Configuration.ORIENTATION_LANDSCAPE;
 
         // Normally, IMS#onFinishInputView will be called when finishing the input view by the user.
         // But if IMS#hideWindow is called when receiving a new configuration change, we don't
         // expect that it's user-driven to finish the lifecycle of input view with
         // IMS#onFinishInputView, because the input view will be re-initialized according to the
-        // last mShowSoftRequested state. So in this case we treat the input view is still alive.
+        // last #mShowInputRequested state. So in this case we treat the input view as still alive.
         verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
-                                mInputMethodService.getResources().getConfiguration()),
+                mInputMethodService.getResources().getConfiguration()),
+                true /* expected */,
                 true /* inputViewStarted */);
         assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
-    private void verifyInputViewStatus(Runnable runnable, boolean inputViewStarted)
-            throws InterruptedException {
-        verifyInputViewStatusInternal(runnable, inputViewStarted, false /*runOnMainSync*/);
+    /**
+     * This checks that an explicit show request directly followed by an implicit show request,
+     * while a hardware keyboard is connected, still results in the IME being shown
+     * (i.e. the implicit show request is treated as explicit).
+     */
+    @Test
+    public void testShowSoftInputExplicitly_thenShowSoftInputImplicitly_withHardKeyboard()
+            throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        // Explicit show request.
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                        mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Implicit show request.
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
+        // This should now consider the implicit show request, but keep the state from the
+        // explicit show request, and thus not hide the keyboard.
+        verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
+                        mInputMethodService.getResources().getConfiguration()),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
-    private void verifyInputViewStatusOnMainSync(Runnable runnable, boolean inputViewStarted)
-            throws InterruptedException {
-        verifyInputViewStatusInternal(runnable, inputViewStarted, true /*runOnMainSync*/);
+    /**
+     * This checks that a forced show request directly followed by an explicit show request,
+     * and then a hide not always request, still results in the IME being shown
+     * (i.e. the explicit show request retains the forced state).
+     */
+    @Test
+    public void testShowSoftInputForced_testShowSoftInputExplicitly_thenHideSoftInputNotAlways()
+            throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_FORCED)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                        mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                false /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        verifyInputViewStatusOnMainSync(() ->
+                        mActivity.hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS),
+                false /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that the IME fullscreen mode state is updated after changing orientation.
+     */
+    @Test
+    public void testFullScreenMode() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        Log.i(TAG, "Set orientation natural");
+        verifyFullscreenMode(() -> setOrientation(0),
+                false /* expected */,
+                true /* orientationPortrait */);
+
+        Log.i(TAG, "Set orientation left");
+        verifyFullscreenMode(() -> setOrientation(1),
+                true /* expected */,
+                false /* orientationPortrait */);
+
+        Log.i(TAG, "Set orientation right");
+        verifyFullscreenMode(() -> setOrientation(2),
+                false /* expected */,
+                false /* orientationPortrait */);
+    }
+
+    private void verifyInputViewStatus(
+            Runnable runnable, boolean expected, boolean inputViewStarted)
+            throws InterruptedException {
+        verifyInputViewStatusInternal(runnable, expected, inputViewStarted,
+                false /* runOnMainSync */);
+    }
+
+    private void verifyInputViewStatusOnMainSync(
+            Runnable runnable, boolean expected, boolean inputViewStarted)
+            throws InterruptedException {
+        verifyInputViewStatusInternal(runnable, expected, inputViewStarted,
+                true /* runOnMainSync */);
+    }
+
+    /**
+     * Verifies the status of the Input View after executing the given runnable.
+     *
+     * @param runnable the runnable to execute for showing or hiding the IME.
+     * @param expected whether the runnable is expected to trigger the signal.
+     * @param inputViewStarted the expected state of the Input View after executing the runnable.
+     * @param runOnMainSync whether to execute the runnable on the main thread.
+     */
     private void verifyInputViewStatusInternal(
-            Runnable runnable, boolean inputViewStarted, boolean runOnMainSync)
+            Runnable runnable, boolean expected, boolean inputViewStarted, boolean runOnMainSync)
             throws InterruptedException {
         CountDownLatch signal = new CountDownLatch(1);
         mInputMethodService.setCountDownLatchForTesting(signal);
-        // Runnable to trigger onStartInputView()/ onFinishInputView()
+        // Runnable to trigger onStartInputView() / onFinishInputView() / onConfigurationChanged()
         if (runOnMainSync) {
             mInstrumentation.runOnMainSync(runnable);
         } else {
             runnable.run();
         }
-        // Waits for onStartInputView() to finish.
         mInstrumentation.waitForIdleSync();
-        signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        boolean completed = signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        if (expected && !completed) {
+            fail("Timed out waiting for"
+                    + " onStartInputView() / onFinishInputView() / onConfigurationChanged()");
+        } else if (!expected && completed) {
+            fail("Unexpected call"
+                    + " onStartInputView() / onFinishInputView() / onConfigurationChanged()");
+        }
         // Input is not finished.
         assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
         assertThat(mInputMethodService.getCurrentInputViewStarted()).isEqualTo(inputViewStarted);
     }
 
-    @Test
-    public void testFullScreenMode() throws Exception {
-        Log.i(TAG, "Set orientation natural");
-        verifyFullscreenMode(() -> setOrientation(0), true /* orientationPortrait */);
-
-        Log.i(TAG, "Set orientation left");
-        verifyFullscreenMode(() -> setOrientation(1), false /* orientationPortrait */);
-
-        Log.i(TAG, "Set orientation right");
-        verifyFullscreenMode(() -> setOrientation(2), false /* orientationPortrait */);
-    }
-
     private void setOrientation(int orientation) {
         // Simple wrapper for catching RemoteException.
         try {
@@ -366,7 +657,15 @@
         }
     }
 
-    private void verifyFullscreenMode(Runnable runnable, boolean orientationPortrait)
+    /**
+     * Verifies the IME fullscreen mode state after executing the given runnable.
+     *
+     * @param runnable the runnable to execute for setting the orientation.
+     * @param expected whether the runnable is expected to trigger the signal.
+     * @param orientationPortrait whether the orientation is expected to be portrait.
+     */
+    private void verifyFullscreenMode(
+            Runnable runnable, boolean expected, boolean orientationPortrait)
             throws InterruptedException {
         CountDownLatch signal = new CountDownLatch(1);
         mInputMethodService.setCountDownLatchForTesting(signal);
@@ -379,7 +678,12 @@
         }
         // Waits for onConfigurationChanged() to finish.
         mInstrumentation.waitForIdleSync();
-        signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        boolean completed = signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        if (expected && !completed) {
+            fail("Timed out waiting for onConfigurationChanged()");
+        } else if (!expected && completed) {
+            fail("Unexpected call onConfigurationChanged()");
+        }
 
         clickOnEditorText();
         eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isTrue());
@@ -416,7 +720,21 @@
         return mTargetPackageName + "/" + INPUT_METHOD_SERVICE_NAME;
     }
 
-    private String executeShellCommand(String cmd) throws Exception {
+    /**
+     * Sets the value of show_ime_with_hard_keyboard, only if it is different to the default value.
+     *
+     * @param enabled the value to be set.
+     */
+    private void setShowImeWithHardKeyboard(boolean enabled) throws IOException {
+        if (mShowImeWithHardKeyboardEnabled != enabled) {
+            executeShellCommand(enabled
+                    ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
+                    : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
+            mInstrumentation.waitForIdleSync();
+        }
+    }
+
+    private String executeShellCommand(String cmd) throws IOException {
         Log.i(TAG, "Run command: " + cmd);
         return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
                 .executeShellCommand(cmd);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 869497c..3199e06 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -40,7 +40,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.Display;
-import android.view.inputmethod.InputMethodManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
@@ -77,9 +76,9 @@
     public void testPerformShowIme() throws Exception {
         synchronized (ImfLock.class) {
             mVisibilityApplier.performShowIme(new Binder() /* showInputToken */,
-                    null /* statsToken */, InputMethodManager.SHOW_IMPLICIT, null, SHOW_SOFT_INPUT);
+                    null /* statsToken */, 0 /* showFlags */, null, SHOW_SOFT_INPUT);
         }
-        verifyShowSoftInput(false, true, InputMethodManager.SHOW_IMPLICIT);
+        verifyShowSoftInput(false, true, 0 /* showFlags */);
     }
 
     @Test
@@ -126,7 +125,7 @@
     @Test
     public void testApplyImeVisibility_showImeImplicit() throws Exception {
         mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT);
-        verifyShowSoftInput(true, true, InputMethodManager.SHOW_IMPLICIT);
+        verifyShowSoftInput(true, true, 0 /* showFlags */);
     }
 
     @Test
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index a38c162..fae5f86 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -106,7 +106,7 @@
     @Test
     public void testRequestImeVisibility_showExplicit() {
         initImeTargetWindowState(mWindowToken);
-        boolean res = mComputer.onImeShowFlags(null, 0 /* show explicit */);
+        boolean res = mComputer.onImeShowFlags(null, 0 /* showFlags */);
         mComputer.requestImeVisibility(mWindowToken, res);
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
@@ -118,6 +118,34 @@
         assertThat(mComputer.mRequestedShowExplicitly).isTrue();
     }
 
+    /**
+     * This checks that the state after an explicit show request does not get reset during
+     * a subsequent implicit show request, without an intermediary hide request.
+     */
+    @Test
+    public void testRequestImeVisibility_showExplicit_thenShowImplicit() {
+        initImeTargetWindowState(mWindowToken);
+        mComputer.onImeShowFlags(null, 0 /* showFlags */);
+        assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+
+        mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
+        assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+    }
+
+    /**
+     * This checks that the state after a forced show request does not get reset during
+     * a subsequent explicit show request, without an intermediary hide request.
+     */
+    @Test
+    public void testRequestImeVisibility_showForced_thenShowExplicit() {
+        initImeTargetWindowState(mWindowToken);
+        mComputer.onImeShowFlags(null, InputMethodManager.SHOW_FORCED);
+        assertThat(mComputer.mShowForced).isTrue();
+
+        mComputer.onImeShowFlags(null, 0 /* showFlags */);
+        assertThat(mComputer.mShowForced).isTrue();
+    }
+
     @Test
     public void testRequestImeVisibility_showImplicit_a11yNoImePolicy() {
         // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
index 42d373b..e87a34e 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -162,7 +163,10 @@
             assertThat(mBindingController.getCurToken()).isNotNull();
         }
         // Wait for onServiceConnected()
-        mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        boolean completed = mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        if (!completed) {
+            fail("Timed out waiting for onServiceConnected()");
+        }
 
         // Verify onServiceConnected() is called and bound successfully.
         synchronized (ImfLock.class) {
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
index 8d0e0c4..e1fd2b3 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
@@ -43,6 +43,8 @@
     },
     export_package_resources: true,
     sdk_version: "current",
+
+    certificate: "platform",
 }
 
 android_library {
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
index 996322d..cf7d660 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
@@ -18,8 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.apps.inputmethod.simpleime">
 
-    <uses-sdk android:targetSdkVersion="31" />
-
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application android:debuggable="true"
diff --git a/services/tests/displayservicetests/TEST_MAPPING b/services/tests/displayservicetests/TEST_MAPPING
index d865519..477860d 100644
--- a/services/tests/displayservicetests/TEST_MAPPING
+++ b/services/tests/displayservicetests/TEST_MAPPING
@@ -1,13 +1,7 @@
 {
-  "presubmit": [
+  "imports": [
     {
-      "name": "DisplayServiceTests",
-      "options": [
-        {"include-filter": "com.android.server.display"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "path": "frameworks/base/services/core/java/com/android/server/display"
     }
   ]
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index 95c62ae..7e69357 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -86,7 +86,9 @@
                 .append("\n    brightnessReason:")
                 .append(displayBrightnessState.getBrightnessReason())
                 .append("\n    shouldUseAutoBrightness:")
-                .append(displayBrightnessState.getShouldUseAutoBrightness());
+                .append(displayBrightnessState.getShouldUseAutoBrightness())
+                .append("\n    isSlowChange:")
+                .append(displayBrightnessState.isSlowChange());
         return sb.toString();
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
index 081f19d..d8569f7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
@@ -21,8 +21,8 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.view.Display;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
@@ -46,7 +46,8 @@
         DisplayManagerInternal.DisplayPowerRequest
                 displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
         float brightnessToFollow = 0.2f;
-        mFollowerBrightnessStrategy.setBrightnessToFollow(brightnessToFollow);
+        boolean slowChange = true;
+        mFollowerBrightnessStrategy.setBrightnessToFollow(brightnessToFollow, slowChange);
         BrightnessReason brightnessReason = new BrightnessReason();
         brightnessReason.setReason(BrightnessReason.REASON_FOLLOWER);
         DisplayBrightnessState expectedDisplayBrightnessState =
@@ -55,6 +56,7 @@
                         .setBrightnessReason(brightnessReason)
                         .setSdrBrightness(brightnessToFollow)
                         .setDisplayBrightnessStrategyName(mFollowerBrightnessStrategy.getName())
+                        .setIsSlowChange(slowChange)
                         .build();
         DisplayBrightnessState updatedDisplayBrightnessState =
                 mFollowerBrightnessStrategy.updateBrightness(displayPowerRequest);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 04273d6..a820d1b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -88,7 +88,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.LocalServices;
 import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.TestUtils;
 import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver;
@@ -149,15 +148,9 @@
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
         when(mContext.getContentResolver()).thenReturn(resolver);
-        mInjector = spy(new FakesInjector());
+        mInjector = spy(new FakesInjector(mDisplayManagerInternalMock, mStatusBarMock,
+                mSensorManagerInternalMock));
         mHandler = new Handler(Looper.getMainLooper());
-
-        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
-        LocalServices.addService(StatusBarManagerInternal.class, mStatusBarMock);
-        LocalServices.removeServiceForTest(SensorManagerInternal.class);
-        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
     }
 
     private DisplayModeDirector createDirectorFromRefreshRateArray(
@@ -2831,16 +2824,28 @@
         private final DisplayInfo mDisplayInfo;
         private final Display mDisplay;
         private boolean mDisplayInfoValid = true;
-        private ContentObserver mBrightnessObserver;
+        private final DisplayManagerInternal mDisplayManagerInternal;
+        private final StatusBarManagerInternal mStatusBarManagerInternal;
+        private final SensorManagerInternal mSensorManagerInternal;
+
         private ContentObserver mPeakRefreshRateObserver;
 
         FakesInjector() {
+            this(null, null, null);
+        }
+
+        FakesInjector(DisplayManagerInternal displayManagerInternal,
+                StatusBarManagerInternal statusBarManagerInternal,
+                SensorManagerInternal sensorManagerInternal) {
             mDeviceConfig = new FakeDeviceConfig();
             mDisplayInfo = new DisplayInfo();
             mDisplayInfo.defaultModeId = MODE_ID;
             mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
                     800, 600, /* refreshRate= */ 60)};
             mDisplay = createDisplay(DISPLAY_ID);
+            mDisplayManagerInternal = displayManagerInternal;
+            mStatusBarManagerInternal = statusBarManagerInternal;
+            mSensorManagerInternal = sensorManagerInternal;
         }
 
         @NonNull
@@ -2896,6 +2901,21 @@
             return true;
         }
 
+        @Override
+        public DisplayManagerInternal getDisplayManagerInternal() {
+            return mDisplayManagerInternal;
+        }
+
+        @Override
+        public StatusBarManagerInternal getStatusBarManagerInternal() {
+            return mStatusBarManagerInternal;
+        }
+
+        @Override
+        public SensorManagerInternal getSensorManagerInternal() {
+            return mSensorManagerInternal;
+        }
+
         protected Display createDisplay(int id) {
             return new Display(DisplayManagerGlobal.getInstance(), id, mDisplayInfo,
                     ApplicationProvider.getApplicationContext().getResources());
diff --git a/services/tests/dreamservicetests/Android.bp b/services/tests/dreamservicetests/Android.bp
new file mode 100644
index 0000000..b698a60
--- /dev/null
+++ b/services/tests/dreamservicetests/Android.bp
@@ -0,0 +1,35 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "DreamServiceTests",
+
+    // Include all test java files.
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    static_libs: [
+        "androidx.test.ext.truth",
+        "frameworks-base-testutils",
+        "mockito-target-minus-junit4",
+        "services.core",
+    ],
+
+    platform_apis: true,
+
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+
+    certificate: "platform",
+
+    dxflags: ["--multi-dex"],
+
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/dreamservicetests/AndroidManifest.xml b/services/tests/dreamservicetests/AndroidManifest.xml
new file mode 100644
index 0000000..fc3ad34
--- /dev/null
+++ b/services/tests/dreamservicetests/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.dreamservicetests">
+
+    <!--
+    Insert permissions here. eg:
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    -->
+
+    <application android:debuggable="true"
+                 android:testOnly="true">
+        <uses-library android:name="android.test.mock" android:required="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.frameworks.dreamservicetests"
+         android:label="Frameworks Services Tests"/>
+</manifest>
diff --git a/services/tests/dreamservicetests/AndroidTest.xml b/services/tests/dreamservicetests/AndroidTest.xml
new file mode 100644
index 0000000..ae89281
--- /dev/null
+++ b/services/tests/dreamservicetests/AndroidTest.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 Dream Service 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="install-arg" value="-t" />
+        <option name="test-file-name" value="DreamServiceTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="DreamServiceTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.dreamservicetests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+        <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
+    </test>
+</configuration>
diff --git a/services/tests/dreamservicetests/OWNERS b/services/tests/dreamservicetests/OWNERS
new file mode 100644
index 0000000..6dd64e7
--- /dev/null
+++ b/services/tests/dreamservicetests/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 78010
+
+include /core/java/android/service/dreams/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index 1ae9124..52044bf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doAnswer;
@@ -368,10 +369,11 @@
         File finalApex = extractResource("test.rebootles_apex_v2", "test.rebootless_apex_v2.apex");
         ApexInfo newApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true,
                 /* isFactory= */ false, finalApex);
-        when(mApexService.installAndActivatePackage(anyString())).thenReturn(newApexInfo);
+        when(mApexService.installAndActivatePackage(anyString(), anyBoolean())).thenReturn(
+                newApexInfo);
 
         File installedApex = extractResource("installed", "test.rebootless_apex_v2.apex");
-        newApexInfo = mApexManager.installPackage(installedApex);
+        newApexInfo = mApexManager.installPackage(installedApex, /* force= */ false);
 
         var newPkg = mockParsePackage(mPackageParser2, newApexInfo);
         assertThat(newPkg.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
@@ -398,10 +400,11 @@
         File finalApex = extractResource("test.rebootles_apex_v2", "test.rebootless_apex_v2.apex");
         ApexInfo newApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true,
                 /* isFactory= */ false, finalApex);
-        when(mApexService.installAndActivatePackage(anyString())).thenReturn(newApexInfo);
+        when(mApexService.installAndActivatePackage(anyString(), anyBoolean())).thenReturn(
+                newApexInfo);
 
         File installedApex = extractResource("installed", "test.rebootless_apex_v2.apex");
-        newApexInfo = mApexManager.installPackage(installedApex);
+        newApexInfo = mApexManager.installPackage(installedApex, /* force= */ false);
 
         var newPkg = mockParsePackage(mPackageParser2, newApexInfo);
         assertThat(newPkg.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
@@ -416,13 +419,13 @@
 
     @Test
     public void testInstallPackageBinderCallFails() throws Exception {
-        when(mApexService.installAndActivatePackage(anyString())).thenThrow(
+        when(mApexService.installAndActivatePackage(anyString(), anyBoolean())).thenThrow(
                 new RuntimeException("install failed :("));
 
         File installedApex = extractResource("test.apex_rebootless_v1",
                 "test.rebootless_apex_v1.apex");
         assertThrows(PackageManagerException.class,
-                () -> mApexManager.installPackage(installedApex));
+                () -> mApexManager.installPackage(installedApex, /* force= */ false));
     }
 
     @Test
diff --git a/services/tests/powerservicetests/TEST_MAPPING b/services/tests/powerservicetests/TEST_MAPPING
new file mode 100644
index 0000000..1b534a1
--- /dev/null
+++ b/services/tests/powerservicetests/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/services/core/java/com/android/server/power"
+    }
+  ]
+}
+
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index 9463b2d..9bce536 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -18,6 +18,8 @@
 
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 import static android.os.PowerManager.USER_ACTIVITY_EVENT_BUTTON;
 import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
 import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
@@ -80,6 +82,7 @@
 import android.os.PowerSaveState;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
 import android.sysprop.PowerProperties;
@@ -94,6 +97,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.lights.LightsManager;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.power.PowerManagerService.BatteryReceiver;
@@ -163,6 +167,7 @@
     @Mock private PowerManagerService.PermissionCheckerWrapper mPermissionCheckerWrapperMock;
     @Mock private PowerManagerService.PowerPropertiesWrapper mPowerPropertiesWrapper;
     @Mock private DeviceStateManager mDeviceStateManagerMock;
+    @Mock private DeviceConfigParameterProvider mDeviceParameterProvider;
 
     @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule();
 
@@ -344,6 +349,11 @@
             PowerManagerService.PowerPropertiesWrapper createPowerPropertiesWrapper() {
                 return mPowerPropertiesWrapper;
             }
+
+            @Override
+            DeviceConfigParameterProvider createDeviceConfigParameterProvider() {
+                return mDeviceParameterProvider;
+            }
         });
         return mService;
     }
@@ -2757,4 +2767,197 @@
         assertThat(mService.wasDeviceIdleForInternal(newTime)).isFalse();
     }
 
+    @Test
+    public void testFeatureEnabledProcStateUncachedToCached_fullWakeLockDisabled() {
+        doReturn(true).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK);
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+
+        setCachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isTrue();
+    }
+
+    @Test
+    public void testFeatureDisabledProcStateUncachedToCached_fullWakeLockEnabled() {
+        doReturn(false).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK);
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+
+        setCachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testFeatureEnabledProcStateUncachedToCached_screenBrightWakeLockDisabled() {
+        doReturn(true).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock",
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK);
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+
+        setCachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isTrue();
+    }
+
+    @Test
+    public void testFeatureDisabledProcStateUncachedToCached_screenBrightWakeLockEnabled() {
+        doReturn(false).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock",
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK);
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+
+        setCachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testFeatureEnabledProcStateUncachedToCached_screenDimWakeLockDisabled() {
+        doReturn(true).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("screenDimWakeLock",
+                PowerManager.SCREEN_DIM_WAKE_LOCK);
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+
+        setCachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isTrue();
+    }
+
+    @Test
+    public void testFeatureDisabledProcStateUncachedToCached_screenDimWakeLockEnabled() {
+        doReturn(false).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("screenDimWakeLock",
+                PowerManager.SCREEN_DIM_WAKE_LOCK);
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+
+        setCachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testFeatureEnabledProcStateCachedToUncached_fullWakeLockEnabled() {
+        doReturn(true).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK);
+        setCachedUidProcState(wakeLock.mOwnerUid);
+
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testFeatureDisabledProcStateCachedToUncached_fullWakeLockEnabled() {
+        doReturn(false).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK);
+        setCachedUidProcState(wakeLock.mOwnerUid);
+
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testFeatureEnabledProcStateCachedToUncached_screenBrightWakeLockEnabled() {
+        doReturn(true).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock",
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK);
+        setCachedUidProcState(wakeLock.mOwnerUid);
+
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testFeatureDisabledProcStateCachedToUncached_screenBrightWakeLockEnabled() {
+        doReturn(false).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock",
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK);
+        setCachedUidProcState(wakeLock.mOwnerUid);
+
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testFeatureEnabledProcStateCachedToUncached_screenDimWakeLockEnabled() {
+        doReturn(true).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("screenDimWakeLock",
+                PowerManager.SCREEN_DIM_WAKE_LOCK);
+        setCachedUidProcState(wakeLock.mOwnerUid);
+
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testFeatureDisabledProcStateCachedToUncached_screenDimWakeLockEnabled() {
+        doReturn(false).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("screenDimWakeLock",
+                PowerManager.SCREEN_DIM_WAKE_LOCK);
+        setCachedUidProcState(wakeLock.mOwnerUid);
+
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testFeatureDynamicallyDisabledProcStateUncachedToCached_fullWakeLockEnabled() {
+        doReturn(true).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        ArgumentCaptor<DeviceConfig.OnPropertiesChangedListener> listenerCaptor =
+                ArgumentCaptor.forClass(DeviceConfig.OnPropertiesChangedListener.class);
+        createService();
+        startSystem();
+        verify(mDeviceParameterProvider, times(1))
+                .addOnPropertiesChangedListener(any(), listenerCaptor.capture());
+        WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK);
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+        // dynamically disable the feature
+        doReturn(false).when(mDeviceParameterProvider)
+                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
+        listenerCaptor.getValue().onPropertiesChanged(
+                new DeviceConfig.Properties("ignored_namespace", null));
+
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    private void setCachedUidProcState(int uid) {
+        mService.updateUidProcStateInternal(uid, PROCESS_STATE_TOP_SLEEPING);
+    }
+
+    private void setUncachedUidProcState(int uid) {
+        mService.updateUidProcStateInternal(uid, PROCESS_STATE_RECEIVER);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index e457119..e7777f7 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -42,6 +42,7 @@
 
 import com.android.server.LocalServices;
 import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionConsentManager;
 import com.android.server.contentprotection.RemoteContentProtectionService;
 import com.android.server.pm.UserManagerInternal;
 
@@ -91,6 +92,8 @@
 
     @Mock private RemoteContentProtectionService mMockRemoteContentProtectionService;
 
+    @Mock private ContentProtectionConsentManager mMockContentProtectionConsentManager;
+
     private boolean mDevCfgEnableContentProtectionReceiver;
 
     private int mContentProtectionBlocklistManagersCreated;
@@ -99,6 +102,8 @@
 
     private int mRemoteContentProtectionServicesCreated;
 
+    private int mContentProtectionConsentManagersCreated;
+
     private String mConfigDefaultContentProtectionService = COMPONENT_NAME.flattenToString();
 
     private boolean mContentProtectionServiceInfoConstructorShouldThrow;
@@ -114,43 +119,51 @@
     }
 
     @Test
-    public void constructor_contentProtection_flagDisabled_noBlocklistManager() {
+    public void constructor_contentProtection_flagDisabled_noManagers() {
         assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
         verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
     @Test
-    public void constructor_contentProtection_componentNameNull_noBlocklistManager() {
+    public void constructor_contentProtection_componentNameNull_noManagers() {
         mConfigDefaultContentProtectionService = null;
 
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
         assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
         verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
     @Test
-    public void constructor_contentProtection_componentNameBlank_noBlocklistManager() {
+    public void constructor_contentProtection_componentNameBlank_noManagers() {
         mConfigDefaultContentProtectionService = "   ";
 
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
         assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
         verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
     @Test
-    public void constructor_contentProtection_enabled_createsBlocklistManager() {
+    public void constructor_contentProtection_enabled_createsManagers() {
         mDevCfgEnableContentProtectionReceiver = true;
 
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
         assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
         verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
     @Test
@@ -175,11 +188,13 @@
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isNull();
-        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
     @Test
     public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -211,11 +226,13 @@
         assertThat(actual.contentProtectionOptions).isNotNull();
         assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
         assertThat(actual.whitelistedComponents).isNull();
-        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
     @Test
     public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -234,7 +251,22 @@
     }
 
     @Test
+    public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionNotGranted() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
     public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionDisabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
@@ -248,6 +280,7 @@
 
     @Test
     public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -271,11 +304,27 @@
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isTrue();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionNotGranted() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
         verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
     @Test
     public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionDisabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
@@ -289,6 +338,7 @@
 
     @Test
     public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -312,16 +362,18 @@
                         USER_ID, COMPONENT_NAME);
 
         assertThat(actual).isTrue();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
         verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
     @Test
-    public void isContentProtectionReceiverEnabled_withoutBlocklistManager() {
+    public void isContentProtectionReceiverEnabled_withoutManagers() {
         boolean actual =
                 mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
         verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
@@ -336,6 +388,7 @@
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
         verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
@@ -423,5 +476,11 @@
             mRemoteContentProtectionServicesCreated++;
             return mMockRemoteContentProtectionService;
         }
+
+        @Override
+        protected ContentProtectionConsentManager createContentProtectionConsentManager() {
+            mContentProtectionConsentManagersCreated++;
+            return mMockContentProtectionConsentManager;
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
new file mode 100644
index 0000000..0e80bfd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.TestableContentResolver;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link ContentProtectionConsentManager}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentprotection.ContentProtectionConsentManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionConsentManagerTest {
+
+    private static final String KEY_PACKAGE_VERIFIER_USER_CONSENT = "package_verifier_user_consent";
+
+    private static final Uri URI_PACKAGE_VERIFIER_USER_CONSENT =
+            Settings.Global.getUriFor(KEY_PACKAGE_VERIFIER_USER_CONSENT);
+
+    private static final int VALUE_TRUE = 1;
+
+    private static final int VALUE_FALSE = -1;
+
+    private static final int VALUE_DEFAULT = 0;
+
+    private static final int TEST_USER_ID = 1234;
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Rule
+    public final TestableContext mTestableContext =
+            new TestableContext(ApplicationProvider.getApplicationContext());
+
+    private final TestableContentResolver mTestableContentResolver =
+            mTestableContext.getContentResolver();
+
+    @Mock private ContentResolver mMockContentResolver;
+
+    @Mock private DevicePolicyManagerInternal mMockDevicePolicyManagerInternal;
+
+    @Test
+    public void constructor_registersContentObserver() {
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(mMockContentResolver);
+
+        assertThat(manager.mContentObserver).isNotNull();
+        verify(mMockContentResolver)
+                .registerContentObserver(
+                        URI_PACKAGE_VERIFIER_USER_CONSENT,
+                        /* notifyForDescendants= */ false,
+                        manager.mContentObserver,
+                        UserHandle.USER_ALL);
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierNotGranted() {
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_FALSE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierGranted_userNotManaged() {
+        ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isTrue();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierGranted_userManaged() {
+        when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID))
+                .thenReturn(true);
+        ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierDefault() {
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_DEFAULT);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+    }
+
+    @Test
+    public void contentObserver() throws Exception {
+        ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
+        boolean firstActual = manager.isConsentGranted(TEST_USER_ID);
+
+        Settings.Global.putInt(
+                mTestableContentResolver, KEY_PACKAGE_VERIFIER_USER_CONSENT, VALUE_FALSE);
+        // Observer has to be called manually, mTestableContentResolver is not propagating
+        manager.mContentObserver.onChange(
+                /* selfChange= */ false, URI_PACKAGE_VERIFIER_USER_CONSENT, TEST_USER_ID);
+        boolean secondActual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(firstActual).isTrue();
+        assertThat(secondActual).isFalse();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+    }
+
+    private ContentProtectionConsentManager createContentProtectionConsentManager(
+            ContentResolver contentResolver) {
+        return new ContentProtectionConsentManager(
+                new Handler(Looper.getMainLooper()),
+                contentResolver,
+                mMockDevicePolicyManagerInternal);
+    }
+
+    private ContentProtectionConsentManager createContentProtectionConsentManager(
+            int valuePackageVerifierUserConsent) {
+        Settings.Global.putInt(
+                mTestableContentResolver,
+                KEY_PACKAGE_VERIFIER_USER_CONSENT,
+                valuePackageVerifierUserConsent);
+        return createContentProtectionConsentManager(mTestableContentResolver);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
index d5ad815..b5bf1ea 100644
--- a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
@@ -16,7 +16,11 @@
 
 package com.android.server.dreams;
 
+import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER;
+import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS;
+
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
@@ -32,7 +36,9 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IPowerManager;
 import android.os.IRemoteCallback;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.service.dreams.IDreamService;
@@ -58,6 +64,8 @@
 
     @Mock
     private ActivityTaskManager mActivityTaskManager;
+    @Mock
+    private IPowerManager mPowerManager;
 
     @Mock
     private IBinder mIBinder;
@@ -67,6 +75,8 @@
     @Captor
     private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor;
     @Captor
+    private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor;
+    @Captor
     private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor;
 
     private final TestLooper mLooper = new TestLooper();
@@ -90,6 +100,12 @@
         when(mContext.getSystemServiceName(ActivityTaskManager.class))
                 .thenReturn(Context.ACTIVITY_TASK_SERVICE);
 
+        final PowerManager powerManager = new PowerManager(mContext, mPowerManager, null, null);
+        when(mContext.getSystemService(Context.POWER_SERVICE))
+                .thenReturn(powerManager);
+        when(mContext.getSystemServiceName(PowerManager.class))
+                .thenReturn(Context.POWER_SERVICE);
+
         mToken = new Binder();
         mDreamName = ComponentName.unflattenFromString("dream");
         mOverlayName = ComponentName.unflattenFromString("dream_overlay");
@@ -209,9 +225,51 @@
         verify(mIDreamService).detach();
     }
 
+    @Test
+    public void serviceDisconnect_resetsScreenTimeout() throws RemoteException {
+        // Start dream.
+        mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+        ServiceConnection serviceConnection = captureServiceConnection();
+        serviceConnection.onServiceConnected(mDreamName, mIBinder);
+        mLooper.dispatchAll();
+
+        // Dream disconnects unexpectedly.
+        serviceConnection.onServiceDisconnected(mDreamName);
+        mLooper.dispatchAll();
+
+        // Power manager receives user activity signal.
+        verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(),
+                eq(USER_ACTIVITY_EVENT_OTHER),
+                eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS));
+    }
+
+    @Test
+    public void binderDied_resetsScreenTimeout() throws RemoteException {
+        // Start dream.
+        mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+        captureServiceConnection().onServiceConnected(mDreamName, mIBinder);
+        mLooper.dispatchAll();
+
+        // Dream binder dies.
+        captureDeathRecipient().binderDied();
+        mLooper.dispatchAll();
+
+        // Power manager receives user activity signal.
+        verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(),
+                eq(USER_ACTIVITY_EVENT_OTHER),
+                eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS));
+    }
+
     private ServiceConnection captureServiceConnection() {
         verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(),
                 any());
         return mServiceConnectionACaptor.getValue();
     }
+
+    private IBinder.DeathRecipient captureDeathRecipient() throws RemoteException {
+        verify(mIBinder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
+        return mDeathRecipientCaptor.getValue();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 0037970..8c07b6c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.res.Validator
 import android.os.Environment
+import android.os.SystemProperties.PROP_VALUE_MAX
 import android.platform.test.annotations.Postsubmit
 import com.android.internal.R
 import com.android.server.pm.PackageManagerService
@@ -28,7 +29,6 @@
 import org.junit.Assert.fail
 import org.junit.Test
 import org.xmlpull.v1.XmlPullParser
-import org.xmlpull.v1.XmlPullParserException
 import org.xmlpull.v1.XmlPullParserFactory
 import java.io.ByteArrayInputStream
 import java.io.File
@@ -75,548 +75,370 @@
     }
 
     @Test
-    fun parseBadManifests() {
+    fun parseManifestTag() {
         val tag = "manifest"
-        val prefix = "<manifest $ns>"
-        val suffix = "</manifest>"
-        parseTagBadAttr(tag, "package", 256, )
-        parseTagBadAttr(tag, "android:sharedUserId", 256)
-        parseTagBadAttr(tag, "android:versionName", 4000)
-        parseBadApplicationTags(100, prefix, suffix, tag)
-        parseBadOverlayTags(100, prefix, suffix, tag)
-        parseBadInstrumentationTags(100, prefix, suffix, tag)
-        parseBadPermissionGroupTags(100, prefix, suffix, tag)
-        parseBadPermissionTreeTags(100, prefix, suffix, tag)
-        parseBadSupportsGlTextureTags(100, prefix, suffix, tag)
-        parseBadSupportsScreensTags(100, prefix, suffix, tag)
-        parseBadUsesConfigurationTags(100, prefix, suffix, tag)
-        parseBadUsesPermissionSdk23Tags(100, prefix, suffix, tag)
-        parseBadUsesSdkTags(100, prefix, suffix, tag)
-        parseBadCompatibleScreensTags(200, prefix, suffix, tag)
-        parseBadQueriesTags(200, prefix, suffix, tag)
-        parseBadAttributionTags(400, prefix, suffix, tag)
-        parseBadUsesFeatureTags(400, prefix, suffix, tag)
-        parseBadPermissionTags(2000, prefix, suffix, tag)
-        parseBadUsesPermissionTags(20000, prefix, suffix, tag)
+        validateTagAttr(tag, "package", null, 256)
+        validateTagAttr(tag, "sharedUserId", null, 256)
+        validateTagAttr(tag, "versionName", null, 1024)
+        validateTagCount("application", 100, tag)
+        validateTagCount("overlay", 100, tag)
+        validateTagCount("instrumentation", 100, tag)
+        validateTagCount("permission-group", 100, tag)
+        validateTagCount("permission-tree", 100, tag)
+        validateTagCount("supports-gl-texture", 100, tag)
+        validateTagCount("supports-screens", 100, tag)
+        validateTagCount("uses-configuration", 100, tag)
+        validateTagCount("uses-sdk", 100, tag)
+        validateTagCount("compatible-screens", 200, tag)
+        validateTagCount("queries", 200, tag)
+        validateTagCount("attribution", 400, tag)
+        validateTagCount("uses-feature", 400, tag)
+        validateTagCount("permission", 2000, tag)
+        validateTagCount("uses-permission", 20000, tag)
     }
 
-    private fun parseBadApplicationTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseApplicationTag() {
         val tag = "application"
-        val newPrefix = "$prefix<$tag>"
-        val newSuffix = "</$tag>$suffix"
-
-        parseTagBadAttr(tag, "android:backupAgent", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:manageSpaceActivity", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:process", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:requiredAccountType", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:restrictedAccountType", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:taskAffinity", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-
-        parseBadProfileableTags(100, newPrefix, newSuffix, tag)
-        parseBadUsesNativeLibraryTags(100, newPrefix, newSuffix, tag)
-        parseBadReceiverTags(1000, newPrefix, newSuffix, tag)
-        parseBadServiceTags(1000, newPrefix, newSuffix, tag)
-        parseBadActivityAliasTags(4000, newPrefix, newSuffix, tag)
-        parseBadUsesLibraryTags(4000, newPrefix, newSuffix, tag)
-        parseBadProviderTags(8000, newPrefix, newSuffix, tag)
-        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
-        parseBadActivityTags(40000, newPrefix, newSuffix, tag)
+        validateTagAttr(tag, "backupAgent",
+            R.styleable.AndroidManifestApplication_backupAgent, 1024)
+        validateTagAttr(tag, "manageSpaceActivity",
+            R.styleable.AndroidManifestApplication_manageSpaceActivity, 1024)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestApplication_name, 1024)
+        validateTagAttr(tag, "permission", R.styleable.AndroidManifestApplication_permission, 1024)
+        validateTagAttr(tag, "process", R.styleable.AndroidManifestApplication_process, 1024)
+        validateTagAttr(tag, "requiredAccountType",
+            R.styleable.AndroidManifestApplication_requiredAccountType, 1024)
+        validateTagAttr(tag, "restrictedAccountType",
+            R.styleable.AndroidManifestApplication_restrictedAccountType, 1024)
+        validateTagAttr(tag, "taskAffinity",
+            R.styleable.AndroidManifestApplication_taskAffinity, 1024)
+        validateTagCount("profileable", 100, tag)
+        validateTagCount("uses-native-library", 100, tag)
+        validateTagCount("receiver", 1000, tag)
+        validateTagCount("service", 1000, tag)
+        validateTagCount("meta-data", 1000, tag)
+        validateTagCount("uses-library", 1000, tag)
+        validateTagCount("activity-alias", 4000, tag)
+        validateTagCount("provider", 8000, tag)
+        validateTagCount("activity", 40000, tag)
     }
 
-    private fun parseBadProfileableTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
-        val tag = "profileable"
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-    }
-
-    private fun parseBadUsesNativeLibraryTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseUsesNativeLibraryTag() {
         val tag = "uses-native-library"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesNativeLibrary_name, 1024)
     }
 
-    private fun parseBadReceiverTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseReceiverTag() {
         val tag = "receiver"
-        val newPrefix = "$prefix<$tag>"
-        val newSuffix = "</$tag>$suffix"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:process", 1024, prefix, suffix)
-
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
-        parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestReceiver_name, 1024)
+        validateTagAttr(tag, "permission", R.styleable.AndroidManifestReceiver_permission, 1024)
+        validateTagAttr(tag, "process", R.styleable.AndroidManifestReceiver_process, 1024)
+        validateTagCount("meta-data", 1000, tag)
+        validateTagCount("intent-filter", 20000, tag)
     }
 
-    private fun parseBadServiceTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseServiceTag() {
         val tag = "service"
-        val newPrefix = "$prefix<$tag>"
-        val newSuffix = "</$tag>$suffix"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:process", 1024, prefix, suffix)
-
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
-        parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestService_name, 1024)
+        validateTagAttr(tag, "permission", R.styleable.AndroidManifestService_permission, 1024)
+        validateTagAttr(tag, "process", R.styleable.AndroidManifestService_process, 1024)
+        validateTagCount("meta-data", 1000, tag)
+        validateTagCount("intent-filter", 20000, tag)
     }
 
-    private fun parseBadActivityAliasTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseActivityAliasTag() {
         val tag = "activity-alias"
-        val newPrefix = "$prefix<$tag>"
-        val newSuffix = "</$tag>$suffix"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:targetActivity", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
-        parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestActivityAlias_name, 1024)
+        validateTagAttr(tag, "permission",
+            R.styleable.AndroidManifestActivityAlias_permission, 1024)
+        validateTagAttr(tag, "targetActivity",
+            R.styleable.AndroidManifestActivityAlias_targetActivity, 1024)
+        validateTagCount("meta-data", 1000, tag)
+        validateTagCount("intent-filter", 20000, tag)
     }
 
-    private fun parseBadUsesLibraryTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseUsesLibraryTag() {
         val tag = "uses-library"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesLibrary_name, 1024)
     }
 
-    private fun parseBadActivityTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseActivityTag() {
         val tag = "activity"
-        val newPrefix = "$prefix<$tag>"
-        val newSuffix = "</$tag>$suffix"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:parentActivityName", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:process", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:taskAffinity", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-        parseBadLayoutTags(1000, newPrefix, newSuffix, tag)
-        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
-        parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestActivity_name, 1024)
+        validateTagAttr(tag, "parentActivityName",
+            R.styleable.AndroidManifestActivity_parentActivityName, 1024)
+        validateTagAttr(tag, "permission", R.styleable.AndroidManifestActivity_permission, 1024)
+        validateTagAttr(tag, "process", R.styleable.AndroidManifestActivity_process, 1024)
+        validateTagAttr(tag, "taskAffinity", R.styleable.AndroidManifestActivity_taskAffinity, 1024)
+        validateTagCount("layout", 1000, tag)
+        validateTagCount("meta-data", 1000, tag)
+        validateTagCount("intent-filter", 20000, tag)
     }
 
-    private fun parseBadLayoutTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
-        val tag = "layout"
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-    }
-
-    private fun parseBadOverlayTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseOverlayTag() {
         val tag = "overlay"
-        parseTagBadAttr(tag, "android:category", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:requiredSystemPropertyName", 32768, prefix, suffix)
-        parseTagBadAttr(tag, "android:requiredSystemPropertyValue", 256, prefix, suffix)
-        parseTagBadAttr(tag, "android:targetPackage", 256, prefix, suffix)
-        parseTagBadAttr(tag, "android:targetName", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "category", R.styleable.AndroidManifestResourceOverlay_category, 1024)
+        validateTagAttr(tag, "requiredSystemPropertyName",
+            R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName, 1024)
+        validateTagAttr(tag, "requiredSystemPropertyValue",
+            R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue, PROP_VALUE_MAX)
+        validateTagAttr(tag, "targetPackage",
+            R.styleable.AndroidManifestResourceOverlay_targetPackage, 256)
+        validateTagAttr(tag, "targetName",
+            R.styleable.AndroidManifestResourceOverlay_targetName, 1024)
     }
 
-    private fun parseBadInstrumentationTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseInstrumentationTag() {
         val tag = "instrumentation"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:targetPackage", 256, prefix, suffix)
-        parseTagBadAttr(tag, "android:targetProcesses", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestInstrumentation_name, 1024)
+        validateTagAttr(tag, "targetPackage",
+            R.styleable.AndroidManifestInstrumentation_targetPackage, 256)
+        validateTagAttr(tag, "targetProcesses",
+            R.styleable.AndroidManifestInstrumentation_targetProcesses, 1024)
     }
 
-    private fun parseBadPermissionGroupTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parsePermissionGroupTag() {
         val tag = "permission-group"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestPermissionGroup_name, 1024)
     }
 
-    private fun parseBadPermissionTreeTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parsePermissionTreeTag() {
         val tag = "permission-tree"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestPermissionTree_name, 1024)
     }
 
-    private fun parseBadSupportsGlTextureTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseSupportsGlTextureTag() {
         val tag = "supports-gl-texture"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", null, 1024)
     }
 
-    private fun parseBadSupportsScreensTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
-        val tag = "supports-screens"
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-    }
-
-    private fun parseBadUsesConfigurationTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
-        val tag = "uses-configuration"
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-    }
-
-    private fun parseBadUsesPermissionSdk23Tags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseUsesPermissionSdk23Tag() {
         val tag = "uses-permission-sdk-23"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesPermission_name, 1024)
     }
 
-    private fun parseBadUsesSdkTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
-        val tag = "uses-sdk"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-    }
-
-    private fun parseBadCompatibleScreensTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseCompatibleScreensTag() {
         val tag = "compatible-screens"
-        val newPrefix = "$prefix<$tag>"
-        val newSuffix = "</$tag>$suffix"
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-        parseBadScreenTags(4000, newPrefix, newSuffix, tag)
+        validateTagCount("screen", 4000, tag)
     }
 
-    private fun parseBadScreenTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
-        val tag = "screen"
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-    }
-
-    private fun parseBadQueriesTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseQueriesTag() {
         val tag = "queries"
-        val newPrefix = "$prefix<$tag>"
-        val newSuffix = "</$tag>$suffix"
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-        parseBadPackageTags(1000, newPrefix, newSuffix, tag)
-        parseBadIntentTags(2000, newPrefix, newSuffix, tag)
-        parseBadProviderTags(8000, newPrefix, newSuffix, tag)
+        validateTagCount("package", 1000, tag)
+        validateTagCount("intent", 2000, tag)
+        validateTagCount("provider", 8000, tag)
     }
 
-    private fun parseBadPackageTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parsePackageTag() {
         val tag = "package"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", null, 1024)
     }
 
-    private fun parseBadIntentTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseIntentTag() {
         val tag = "intent"
-        val newPrefix = "$prefix<$tag>"
-        val newSuffix = "</$tag>$suffix"
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-        parseBadActionTags(20000, newPrefix, newSuffix, tag)
-        parseBadCategoryTags(40000, newPrefix, newSuffix, tag)
-        parseBadDataTags(40000, newPrefix, newSuffix, tag)
+        validateTagCount("action", 20000, tag)
+        validateTagCount("category", 40000, tag)
+        validateTagCount("data", 40000, tag)
     }
 
-    private fun parseBadProviderTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseProviderTag() {
         val tag = "provider"
-        val newPrefix = "$prefix<$tag>"
-        val newSuffix = "</$tag>$suffix"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:process", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:readPermission", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:writePermission", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-        parseBadGrantUriPermissionTags(100, newPrefix, newSuffix, tag)
-        parseBadPathPermissionTags(100, newPrefix, newSuffix, tag)
-        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
-        parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestProvider_name, 1024)
+        validateTagAttr(tag, "permission", R.styleable.AndroidManifestProvider_permission, 1024)
+        validateTagAttr(tag, "process", R.styleable.AndroidManifestProvider_process, 1024)
+        validateTagAttr(tag, "readPermission",
+            R.styleable.AndroidManifestProvider_readPermission, 1024)
+        validateTagAttr(tag, "writePermission",
+            R.styleable.AndroidManifestProvider_writePermission, 1024)
+        validateTagCount("grant-uri-permission", 100, tag)
+        validateTagCount("path-permission", 100, tag)
+        validateTagCount("meta-data", 1000, tag)
+        validateTagCount("intent-filter", 20000, tag)
     }
 
-    private fun parseBadGrantUriPermissionTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseGrantUriPermissionTag() {
         val tag = "grant-uri-permission"
-        parseTagBadAttr(tag, "android:path", 4000, prefix, suffix)
-        parseTagBadAttr(tag, "android:pathPrefix", 4000, prefix, suffix)
-        parseTagBadAttr(tag, "android:pathPattern", 4000, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "path", R.styleable.AndroidManifestGrantUriPermission_path, 4000)
+        validateTagAttr(tag, "pathPrefix",
+            R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 4000)
+        validateTagAttr(tag, "pathPattern",
+            R.styleable.AndroidManifestGrantUriPermission_pathPattern, 4000)
     }
 
-    private fun parseBadPathPermissionTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parsePathPermissionTag() {
         val tag = "path-permission"
-        parseTagBadAttr(tag, "android:path", 4000, prefix, suffix)
-        parseTagBadAttr(tag, "android:pathPrefix", 4000, prefix, suffix)
-        parseTagBadAttr(tag, "android:pathPattern", 4000, prefix, suffix)
-        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:readPermission", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:writePermission", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "path", R.styleable.AndroidManifestPathPermission_path, 4000)
+        validateTagAttr(tag, "pathPrefix",
+            R.styleable.AndroidManifestPathPermission_pathPrefix, 4000)
+        validateTagAttr(tag, "pathPattern",
+            R.styleable.AndroidManifestPathPermission_pathPattern, 4000)
+        validateTagAttr(tag, "permission",
+            R.styleable.AndroidManifestPathPermission_permission, 1024)
+        validateTagAttr(tag, "readPermission",
+            R.styleable.AndroidManifestPathPermission_readPermission, 1024)
+        validateTagAttr(tag, "writePermission",
+            R.styleable.AndroidManifestPathPermission_writePermission, 1024)
     }
 
-    private fun parseBadMetaDataTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseMetaDataTag() {
         val tag = "meta-data"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:value", 32768, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestMetaData_name, 1024)
+        validateTagAttr(tag, "value", R.styleable.AndroidManifestMetaData_value, 4000)
     }
 
-    private fun parseBadIntentFilterTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseIntentFilterTag() {
         val tag = "intent-filter"
-        val newPrefix = "$prefix<$tag>"
-        val newSuffix = "</$tag>$suffix"
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-        parseBadActionTags(20000, newPrefix, newSuffix, tag)
-        parseBadCategoryTags(40000, newPrefix, newSuffix, tag)
-        parseBadDataTags(40000, newPrefix, newSuffix, tag)
+        validateTagCount("action", 20000, tag)
+        validateTagCount("category", 40000, tag)
+        validateTagCount("data", 40000, tag)
     }
 
-    private fun parseBadActionTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseActionTag() {
         val tag = "action"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestAction_name, 1024)
     }
 
-    private fun parseBadCategoryTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseCategoryTag() {
         val tag = "category"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestCategory_name, 1024)
     }
 
-    private fun parseBadDataTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseDataTag() {
         val tag = "data"
-        parseTagBadAttr(tag, "android:scheme", 256, prefix, suffix)
-        parseTagBadAttr(tag, "android:host", 256, prefix, suffix)
-        parseTagBadAttr(tag, "android:path", 4000, prefix, suffix)
-        parseTagBadAttr(tag, "android:pathPattern", 4000, prefix, suffix)
-        parseTagBadAttr(tag, "android:pathPrefix", 4000, prefix, suffix)
-        parseTagBadAttr(tag, "android:pathSuffix", 4000, prefix, suffix)
-        parseTagBadAttr(tag, "android:pathAdvancedPattern", 4000, prefix, suffix)
-        parseTagBadAttr(tag, "android:mimeType", 512, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "scheme", R.styleable.AndroidManifestData_scheme, 256)
+        validateTagAttr(tag, "host", R.styleable.AndroidManifestData_host, 256)
+        validateTagAttr(tag, "path", R.styleable.AndroidManifestData_path, 4000)
+        validateTagAttr(tag, "pathPattern", R.styleable.AndroidManifestData_pathPattern, 4000)
+        validateTagAttr(tag, "pathPrefix", R.styleable.AndroidManifestData_pathPrefix, 4000)
+        validateTagAttr(tag, "pathSuffix", R.styleable.AndroidManifestData_pathSuffix, 4000)
+        validateTagAttr(tag, "pathAdvancedPattern",
+            R.styleable.AndroidManifestData_pathAdvancedPattern, 4000)
+        validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 512)
     }
 
-    private fun parseBadAttributionTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
-        val tag = "attribution"
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
-    }
-
-    private fun parseBadUsesFeatureTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseUsesFeatureTag() {
         val tag = "uses-feature"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesFeature_name, 1024)
     }
 
-    private fun parseBadPermissionTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parsePermissionTag() {
         val tag = "permission"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseTagBadAttr(tag, "android:permissionGroup", 256, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestPermission_name, 1024)
+        validateTagAttr(tag, "permissionGroup",
+            R.styleable.AndroidManifestPermission_permissionGroup, 256)
     }
 
-    private fun parseBadUsesPermissionTags(
-            maxNum: Int,
-            prefix: String,
-            suffix: String,
-            parentTag: String
-    ) {
+    @Test
+    fun parseUsesPermissionTag() {
         val tag = "uses-permission"
-        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
-        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesPermission_name, 1024)
     }
 
-    private fun parseTagBadAttr(
-            tag: String,
-            attrName: String,
-            maxLength: Int,
-            prefix: String = "",
-            suffix: String = ""
+    private fun validateTagAttr(tag: String, name: String, index: Int?, maxLen: Int) {
+        validateTagAttr_shouldPass(tag, name, index, maxLen)
+        validateTagAttr_shouldFail(tag, name, index, maxLen)
+    }
+
+    private fun validateTagAttr_shouldPass(
+        tag: String,
+        name: String,
+        index: Int?,
+        maxLen: Int
     ) {
-        var attrValue = "x".repeat(maxLength)
-        var tagValue = if (tag.equals("manifest")) "$tag $ns" else tag
-        var manifestStr = "$prefix<$tagValue $attrName=\"$attrValue\" />$suffix"
+        val value = "x".repeat(maxLen)
+        val xml = "<$tag $name=\"$value\" />"
+        pullParser.setInput(ByteArrayInputStream(xml.toByteArray()), null)
+        val validator = Validator()
+        pullParser.nextTag()
+        validator.validate(pullParser)
         try {
-            parseManifestStr(manifestStr)
-        } catch (e: XmlPullParserException) {
-            fail("Failed to parse valid <$tag> attribute $attrName with max length of $maxLength:" +
+            validator.validateStrAttr(pullParser, name, value)
+        } catch (e: SecurityException) {
+            fail("Failed to parse valid <$tag> attribute $name with max length of $maxLen:" +
                     " ${e.message}")
         }
-        attrValue = "x".repeat(maxLength + 1)
-        manifestStr = "$prefix<$tagValue $attrName=\"$attrValue\" />$suffix"
-        val e = assertThrows(XmlPullParserException::class.java) {
-            parseManifestStr(manifestStr)
+        if (index != null) {
+            try {
+                validator.validateResStrAttr(pullParser, index, value)
+            } catch (e: SecurityException) {
+                fail("Failed to parse valid <$tag> resource string attribute $name with max" +
+                        " length of $maxLen: ${e.message}")
+            }
         }
-        assertEquals(expectedAttrLengthErrorMsg(attrName.split(":").last(), tag), e.message)
     }
 
-    private fun parseBadTagCount(
-            tag: String,
-            maxNum: Int,
-            parentTag: String,
-            prefix: String,
-            suffix: String
+    private fun validateTagAttr_shouldFail(
+        tag: String,
+        name: String,
+        index: Int?,
+        maxLen: Int
     ) {
-        var tags = "<$tag />".repeat(maxNum)
-        var manifestStr = "$prefix$tags$suffix"
+        val value = "x".repeat(maxLen + 1)
+        val xml = "<$tag $name=\"$value\" />"
+        pullParser.setInput(ByteArrayInputStream(xml.toByteArray()), null)
+        val validator = Validator()
+        pullParser.nextTag()
+        validator.validate(pullParser)
+        val e1 = assertThrows(SecurityException::class.java) {
+            validator.validateStrAttr(pullParser, name, value)
+        }
+        assertEquals(expectedAttrLengthErrorMsg(name, tag), e1.message)
+        if (index != null) {
+            val e2 = assertThrows(SecurityException::class.java) {
+                validator.validateResStrAttr(pullParser, index, value)
+            }
+            assertEquals(expectedResAttrLengthErrorMsg(tag), e2.message)
+        }
+    }
+
+    private fun validateTagCount(tag: String, maxNum: Int, parentTag: String) {
+        validateTagCount_shouldPass(tag, maxNum, parentTag)
+        validateTagCount_shouldFail(tag, maxNum, parentTag)
+    }
+
+    private fun validateTagCount_shouldPass(tag: String, maxNum: Int, parentTag: String) {
+        val tags = "<$tag />".repeat(maxNum)
+        val xml = "<$parentTag>$tags</$parentTag>"
         try {
-            parseManifestStr(manifestStr)
-        } catch (e: XmlPullParserException) {
+            parseXmlStr(xml)
+        } catch (e: SecurityException) {
             fail("Failed to parse <$tag> with max count limit of $maxNum under" +
                     " <$parentTag>: ${e.message}")
         }
-        tags = "<$tag />".repeat(maxNum + 1)
-        manifestStr = "$prefix$tags$suffix"
-        val e = assertThrows(XmlPullParserException::class.java) {
-            parseManifestStr(manifestStr)
+    }
+
+    private fun validateTagCount_shouldFail(tag: String, maxNum: Int, parentTag: String) {
+        val tags = "<$tag />".repeat(maxNum + 1)
+        val xml = "<$parentTag>$tags</$parentTag>"
+        val e = assertThrows(SecurityException::class.java) {
+            parseXmlStr(xml)
         }
         assertEquals(expectedCountErrorMsg(tag, parentTag), e.message)
     }
@@ -624,13 +446,12 @@
     @Test
     fun parseUnexpectedTag_shouldSkip() {
         val host = "x".repeat(256)
-        val dataTags = "<data android:host=\"$host\" />".repeat(2049)
-        val ns = "http://schemas.android.com/apk/res/android"
-        val manifestStr = "<manifest xmlns:android=\"$ns\" package=\"test\">$dataTags</manifest>"
-        parseManifestStr(manifestStr)
+        val dataTags = "<data host=\"$host\" />".repeat(2049)
+        val xml = "<manifest package=\"test\">$dataTags</manifest>"
+        parseXmlStr(xml)
     }
 
-    fun parseManifestStr(manifestStr: String) {
+    fun parseXmlStr(manifestStr: String) {
         pullParser.setInput(ByteArrayInputStream(manifestStr.toByteArray()), null)
         val validator = Validator()
         do {
@@ -647,39 +468,4 @@
 
     fun expectedResAttrLengthErrorMsg(tag: String) =
             "String length limit exceeded for attribute in $tag"
-
-    @Test
-    fun validateResAttrs() {
-        pullParser.setInput(ByteArrayInputStream("<manifest />".toByteArray()), null)
-        pullParser.next()
-        val validator = Validator()
-        validator.validate(pullParser)
-        validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_host,
-                "R.styleable.AndroidManifestData_host", 255)
-        validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_port,
-                "R.styleable.AndroidManifestData_port", 255)
-        validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_scheme,
-                "R.styleable.AndroidManifestData_scheme", 255)
-        validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_mimeType,
-                "R.styleable.AndroidManifestData_mimeType", 512)
-    }
-
-    fun validateResAttr(
-            parser: XmlPullParser,
-            validator: Validator,
-            resId: Int,
-            resIdStr: String,
-            maxLength: Int
-    ) {
-        try {
-            validator.validateAttr(parser, resId, "x".repeat(maxLength))
-        } catch (e: XmlPullParserException) {
-            fail("Failed to parse valid string resource attribute $resIdStr with max length of" +
-                    " $maxLength: ${e.message}")
-        }
-        val e = assertThrows(XmlPullParserException::class.java) {
-            validator.validateAttr(parser, resId, "x".repeat(maxLength + 1))
-        }
-        assertEquals(expectedResAttrLengthErrorMsg("manifest"), e.message)
-    }
-}
\ No newline at end of file
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f552ab2..c1f2c5f 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -81,7 +81,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
@@ -406,6 +405,7 @@
     UriGrantsManagerInternal mUgmInternal;
     @Mock
     AppOpsManager mAppOpsManager;
+    private AppOpsManager.OnOpChangedListener mOnPermissionChangeListener;
     @Mock
     private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
             mNotificationAssistantAccessGrantedCallback;
@@ -422,6 +422,7 @@
     @Mock
     MultiRateLimiter mToastRateLimiter;
     BroadcastReceiver mPackageIntentReceiver;
+    BroadcastReceiver mUserSwitchIntentReceiver;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;
 
@@ -605,6 +606,13 @@
         tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
                 SEARCH_SELECTOR_PKG);
 
+        doAnswer(invocation -> {
+            mOnPermissionChangeListener = invocation.getArgument(2);
+            return null;
+        }).when(mAppOpsManager).startWatchingMode(eq(AppOpsManager.OP_POST_NOTIFICATION), any(),
+                any());
+        when(mUmInternal.isUserInitialized(anyInt())).thenReturn(true);
+
         mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
         mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
                 mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
@@ -652,8 +660,12 @@
                     && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) {
                 mPackageIntentReceiver = broadcastReceivers.get(i);
             }
+            if (filter.hasAction(Intent.ACTION_USER_SWITCHED)) {
+                mUserSwitchIntentReceiver = broadcastReceivers.get(i);
+            }
         }
         assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
+        assertNotNull("User-switch receiver should exist", mUserSwitchIntentReceiver);
 
         // Pretend the shortcut exists
         List<ShortcutInfo> shortcutInfos = new ArrayList<>();
@@ -3217,6 +3229,108 @@
     }
 
     @Test
+    public void onOpChanged_permissionRevoked_cancelsAllNotificationsFromPackage()
+            throws RemoteException {
+        // Have preexisting posted notifications from revoked package and other packages.
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("revoked", 1001, 1, 0), mTestNotificationChannel));
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+        // Have preexisting enqueued notifications from revoked package and other packages.
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("revoked", 1001, 3, 0), mTestNotificationChannel));
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+        assertThat(mService.mNotificationList).hasSize(2);
+        assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+        when(mPackageManagerInternal.getPackageUid("revoked", 0, 0)).thenReturn(1001);
+        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
+
+        mOnPermissionChangeListener.onOpChanged(
+                AppOpsManager.OPSTR_POST_NOTIFICATION, "revoked", 0);
+        waitForIdle();
+
+        assertThat(mService.mNotificationList).hasSize(1);
+        assertThat(mService.mNotificationList.get(0).getSbn().getPackageName()).isEqualTo("other");
+        assertThat(mService.mEnqueuedNotifications).hasSize(1);
+        assertThat(mService.mEnqueuedNotifications.get(0).getSbn().getPackageName()).isEqualTo(
+                "other");
+    }
+
+    @Test
+    public void onOpChanged_permissionStillGranted_notificationsAreNotAffected()
+            throws RemoteException {
+        // NOTE: This combination (receiving the onOpChanged broadcast for a package, the permission
+        // being now granted, AND having previously posted notifications from said package) should
+        // never happen (if we trust the broadcasts are correct). So this test is for a what-if
+        // scenario, to verify we still handle it reasonably.
+
+        // Have preexisting posted notifications from specific package and other packages.
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("granted", 1001, 1, 0), mTestNotificationChannel));
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+        // Have preexisting enqueued notifications from specific package and other packages.
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("granted", 1001, 3, 0), mTestNotificationChannel));
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+        assertThat(mService.mNotificationList).hasSize(2);
+        assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+        when(mPackageManagerInternal.getPackageUid("granted", 0, 0)).thenReturn(1001);
+        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
+
+        mOnPermissionChangeListener.onOpChanged(
+                AppOpsManager.OPSTR_POST_NOTIFICATION, "granted", 0);
+        waitForIdle();
+
+        assertThat(mService.mNotificationList).hasSize(2);
+        assertThat(mService.mEnqueuedNotifications).hasSize(2);
+    }
+
+    @Test
+    public void onOpChanged_notInitializedUser_ignored() throws RemoteException {
+        when(mUmInternal.isUserInitialized(eq(0))).thenReturn(false);
+
+        mOnPermissionChangeListener.onOpChanged(
+                AppOpsManager.OPSTR_POST_NOTIFICATION, "package", 0);
+        waitForIdle();
+
+        // We early-exited and didn't even query PM for package details.
+        verify(mPackageManagerInternal, never()).getPackageUid(any(), anyLong(), anyInt());
+    }
+
+    @Test
+    public void setNotificationsEnabledForPackage_disabling_clearsNotifications() throws Exception {
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("package", 1001, 1, 0), mTestNotificationChannel));
+        assertThat(mService.mNotificationList).hasSize(1);
+        when(mPackageManagerInternal.getPackageUid("package", 0, 0)).thenReturn(1001);
+        when(mPermissionHelper.hasRequestedPermission(any(), eq("package"), anyInt())).thenReturn(
+                true);
+
+        // Start with granted permission and simulate effect of revoking it.
+        when(mPermissionHelper.hasPermission(1001)).thenReturn(true);
+        doAnswer(invocation -> {
+            when(mPermissionHelper.hasPermission(1001)).thenReturn(false);
+            mOnPermissionChangeListener.onOpChanged(
+                    AppOpsManager.OPSTR_POST_NOTIFICATION, "package", 0);
+            return null;
+        }).when(mPermissionHelper).setNotificationPermission("package", 0, false, true);
+
+        mBinderService.setNotificationsEnabledForPackage("package", 1001, false);
+        waitForIdle();
+
+        assertThat(mService.mNotificationList).hasSize(0);
+
+        mTestableLooper.moveTimeForward(500);
+        waitForIdle();
+        verify(mContext).sendBroadcastAsUser(any(), eq(UserHandle.of(0)), eq(null));
+    }
+
+    @Test
     public void testUpdateAppNotifyCreatorBlock() throws Exception {
         when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
 
@@ -4236,6 +4350,7 @@
     @Test
     public void testSetNASMigrationDoneAndResetDefault_enableNAS() throws Exception {
         int userId = 10;
+        setNASMigrationDone(false, userId);
         when(mUm.getProfileIds(userId, false)).thenReturn(new int[]{userId});
 
         mBinderService.setNASMigrationDoneAndResetDefault(userId, true);
@@ -4247,6 +4362,7 @@
     @Test
     public void testSetNASMigrationDoneAndResetDefault_disableNAS() throws Exception {
         int userId = 10;
+        setNASMigrationDone(false, userId);
         when(mUm.getProfileIds(userId, false)).thenReturn(new int[]{userId});
 
         mBinderService.setNASMigrationDoneAndResetDefault(userId, false);
@@ -4259,6 +4375,8 @@
     public void testSetNASMigrationDoneAndResetDefault_multiProfile() throws Exception {
         int userId1 = 11;
         int userId2 = 12; //work profile
+        setNASMigrationDone(false, userId1);
+        setNASMigrationDone(false, userId2);
         setUsers(new int[]{userId1, userId2});
         when(mUm.isManagedProfile(userId2)).thenReturn(true);
         when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1, userId2});
@@ -4272,6 +4390,8 @@
     public void testSetNASMigrationDoneAndResetDefault_multiUser() throws Exception {
         int userId1 = 11;
         int userId2 = 12;
+        setNASMigrationDone(false, userId1);
+        setNASMigrationDone(false, userId2);
         setUsers(new int[]{userId1, userId2});
         when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1});
         when(mUm.getProfileIds(userId2, false)).thenReturn(new int[]{userId2});
@@ -11204,8 +11324,6 @@
                 ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED);
         // Given: a notification from an app on the system partition has the flag
         // FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
@@ -11221,8 +11339,6 @@
     public void fixMediaNotification_withOnGoingFlag_shouldBeNonDismissible()
             throws Exception {
         // Given: a media notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .setStyle(new Notification.MediaStyle()
@@ -11251,8 +11367,6 @@
                 ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED);
         // Given: a notification from an app on the system partition has the flag
         // FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
@@ -11268,9 +11382,6 @@
     public void fixCallNotification_withOnGoingFlag_shouldNotBeNonDismissible()
             throws Exception {
         // Given: a call notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
-
         Person person = new Person.Builder()
                 .setName("caller")
                 .build();
@@ -11291,8 +11402,6 @@
     @Test
     public void fixNonExemptNotification_withOnGoingFlag_shouldBeDismissible() throws Exception {
         // Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
@@ -11309,8 +11418,6 @@
             throws Exception {
         // Given: a non-exempt notification has the flag FLAG_NO_DISMISS set (even though this is
         // not allowed)
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .build();
         n.flags |= Notification.FLAG_NO_DISMISS;
@@ -11325,8 +11432,6 @@
     @Test
     public void fixMediaNotification_withoutOnGoingFlag_shouldBeDismissible() throws Exception {
         // Given: a media notification doesn't have the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .setStyle(new Notification.MediaStyle()
@@ -11345,8 +11450,6 @@
             throws Exception {
         // Given: a media notification doesn't have the flag FLAG_ONGOING_EVENT set,
         // but has the flag FLAG_NO_DISMISS set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .setStyle(new Notification.MediaStyle()
@@ -11365,8 +11468,6 @@
     public void fixNonExempt_Notification_withoutOnGoingFlag_shouldBeDismissible()
             throws Exception {
         // Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .build();
@@ -11383,8 +11484,6 @@
             throws Exception {
         when(mDevicePolicyManager.isActiveDeviceOwner(mUid)).thenReturn(true);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         setDpmAppOppsExemptFromDismissal(false);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
@@ -11411,8 +11510,6 @@
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid,
                 PKG)).thenReturn(AppOpsManager.MODE_ALLOWED);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         setDpmAppOppsExemptFromDismissal(true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
@@ -11432,8 +11529,6 @@
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid,
                 PKG)).thenReturn(AppOpsManager.MODE_ALLOWED);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         setDpmAppOppsExemptFromDismissal(false);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
@@ -12174,6 +12269,21 @@
                 any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
     }
 
+    @Test
+    public void onUserSwitched_updatesZenModeAndChannelsBypassingDnd() {
+        Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, 20);
+        mService.mZenModeHelper = mock(ZenModeHelper.class);
+        mService.setPreferencesHelper(mPreferencesHelper);
+
+        mUserSwitchIntentReceiver.onReceive(mContext, intent);
+
+        InOrder inOrder = inOrder(mPreferencesHelper, mService.mZenModeHelper);
+        inOrder.verify(mService.mZenModeHelper).onUserSwitched(eq(20));
+        inOrder.verify(mPreferencesHelper).syncChannelsBypassingDnd();
+        inOrder.verifyNoMoreInteractions();
+    }
+
     private static <T extends Parcelable> T parcelAndUnparcel(T source,
             Parcelable.Creator<T> creator) {
         Parcel parcel = Parcel.obtain();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index ea670bd..c242554 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2478,7 +2478,7 @@
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
-
+        mHelper.syncChannelsBypassingDnd();
 
         // create notification channel that can bypass dnd, but app is blocked
         // expected result: areChannelsBypassingDnd = false
@@ -2509,6 +2509,7 @@
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
+        mHelper.syncChannelsBypassingDnd();
 
         // create notification channel that can bypass dnd, but app is blocked
         // expected result: areChannelsBypassingDnd = false
@@ -2533,6 +2534,7 @@
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
+        mHelper.syncChannelsBypassingDnd();
 
         // create notification channel that can bypass dnd, but app is blocked
         // expected result: areChannelsBypassingDnd = false
@@ -2587,6 +2589,7 @@
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
+        mHelper.syncChannelsBypassingDnd();
         assertFalse(mHelper.areChannelsBypassingDnd());
         verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
         resetZenModeHelper();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
new file mode 100644
index 0000000..a81898d
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -0,0 +1,295 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vibrator;
+
+
+import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
+import static android.os.VibrationEffect.EFFECT_CLICK;
+
+import static com.android.server.vibrator.HapticFeedbackCustomization.CustomizationParserException;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.os.VibrationEffect;
+import android.util.AtomicFile;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.R;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+public class HapticFeedbackCustomizationTest {
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    // Pairs of valid vibration XML along with their equivalent VibrationEffect.
+    private static final String COMPOSITION_VIBRATION_XML = "<vibration>"
+            + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>"
+            + "</vibration>";
+    private static final VibrationEffect COMPOSITION_VIBRATION =
+            VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
+
+    private static final String PREDEFINED_VIBRATION_XML =
+            "<vibration><predefined-effect name=\"click\"/></vibration>";
+    private static final VibrationEffect PREDEFINED_VIBRATION =
+            VibrationEffect.createPredefined(EFFECT_CLICK);
+
+    @Mock private Resources mResourcesMock;
+
+    @Test
+    public void testParseCustomizations_noCustomization_success() throws Exception {
+        assertParseCustomizationsSucceeds(
+                /* xml= */ "<haptic-feedback-constants></haptic-feedback-constants>",
+                /* expectedCustomizations= */ new SparseArray<>());
+    }
+
+    @Test
+    public void testParseCustomizations_oneCustomization_success() throws Exception {
+        String xml = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
+        expectedMapping.put(10, COMPOSITION_VIBRATION);
+
+        assertParseCustomizationsSucceeds(xml, expectedMapping);
+    }
+
+    @Test
+    public void testParseCustomizations_multipleCustomizations_success() throws Exception {
+        String xml = "<haptic-feedback-constants>"
+                + "<constant id=\"1\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "<constant id=\"12\">"
+                + PREDEFINED_VIBRATION_XML
+                + "</constant>"
+                + "<constant id=\"150\">"
+                + PREDEFINED_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
+        expectedMapping.put(1, COMPOSITION_VIBRATION);
+        expectedMapping.put(12, PREDEFINED_VIBRATION);
+        expectedMapping.put(150, PREDEFINED_VIBRATION);
+
+        assertParseCustomizationsSucceeds(xml, expectedMapping);
+    }
+
+    @Test
+    public void testParseCustomizations_noCustomizationFile_returnsNull() throws Exception {
+        setCustomizationFilePath("");
+
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock)).isNull();
+
+        setCustomizationFilePath(null);
+
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock)).isNull();
+
+        setCustomizationFilePath("non_existent_file.xml");
+
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock)).isNull();
+    }
+
+    @Test
+    public void testParseCustomizations_disallowedVibrationForHapticFeedback_throwsException()
+            throws Exception {
+        // The XML content is good, but the serialized vibration is not supported for haptic
+        // feedback usage (i.e. repeating vibration).
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + "<vibration>"
+                + "<waveform-effect>"
+                + "<repeating>"
+                + "<waveform-entry durationMs=\"10\" amplitude=\"100\"/>"
+                + "</repeating>"
+                + "</waveform-effect>"
+                + "</vibration>"
+                + "</constant>"
+                + "</haptic-feedback-constants>");
+    }
+
+    @Test
+    public void testParseCustomizations_emptyXml_throwsException() throws Exception {
+        assertParseCustomizationsFails("");
+    }
+
+    @Test
+    public void testParseCustomizations_noVibrationXml_throwsException() throws Exception {
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + "<constant id=\"1\">"
+                + "</constant>"
+                + "</haptic-feedback-constants>");
+    }
+
+    @Test
+    public void testParseCustomizations_badEffectId_throwsException() throws Exception {
+        // Negative id
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + "<constant id=\"-10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>");
+
+        // Non-numeral id
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + "<constant id=\"xyz\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>");
+    }
+
+    @Test
+    public void testParseCustomizations_malformedXml_throwsException() throws Exception {
+        // No start "<constant>" tag
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>");
+
+        // No end "<constant>" tag
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</haptic-feedback-constants>");
+
+        // No start "<haptic-feedback-constants>" tag
+        assertParseCustomizationsFails(
+                "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>");
+
+        // No end "<haptic-feedback-constants>" tag
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>");
+    }
+
+    @Test
+    public void testParseCustomizations_badVibrationXml_throwsException() throws Exception {
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + "<bad-vibration></bad-vibration>"
+                + "</constant>"
+                + "</haptic-feedback-constants>");
+
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + "<vibration><predefined-effect name=\"bad-effect-name\"/></vibration>"
+                + "</constant>"
+                + "</haptic-feedback-constants>");
+    }
+
+    @Test
+    public void testParseCustomizations_badConstantAttribute_throwsException() throws Exception {
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + "<constant iddddd=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>");
+
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + "<constant id=\"10\" unwanted-attr=\"1\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>");
+    }
+
+    @Test
+    public void testParseCustomizations_duplicateEffects_throwsException() throws Exception {
+        assertParseCustomizationsFails(
+                "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "<constant id=\"10\">"
+                + PREDEFINED_VIBRATION_XML
+                + "</constant>"
+                + "<constant id=\"11\">"
+                + PREDEFINED_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>");
+    }
+
+    private void assertParseCustomizationsSucceeds(
+            String xml, SparseArray<VibrationEffect> expectedCustomizations) throws Exception {
+        setupCustomizationFile(xml);
+        assertThat(expectedCustomizations.contentEquals(
+                HapticFeedbackCustomization.loadVibrations(mResourcesMock))).isTrue();
+    }
+
+    private void assertParseCustomizationsFails(String xml) throws Exception {
+        setupCustomizationFile(xml);
+        assertThrows("Expected haptic feedback customization to fail for " + xml,
+                CustomizationParserException.class,
+                () ->  HapticFeedbackCustomization.loadVibrations(mResourcesMock));
+    }
+
+    private void assertParseCustomizationsFails() throws Exception {
+        assertThrows("Expected haptic feedback customization to fail",
+                CustomizationParserException.class,
+                () ->  HapticFeedbackCustomization.loadVibrations(mResourcesMock));
+    }
+
+    private void setupCustomizationFile(String xml) throws Exception {
+        File file = createFile(xml);
+        setCustomizationFilePath(file.getAbsolutePath());
+    }
+
+    private void setCustomizationFilePath(String path) {
+        when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
+                .thenReturn(path);
+    }
+
+    private static File createFile(String contents) throws Exception {
+        File file = new File(InstrumentationRegistry.getContext().getCacheDir(), "test.xml");
+        file.createNewFile();
+
+        AtomicFile testAtomicXmlFile = new AtomicFile(file);
+        FileOutputStream fos = testAtomicXmlFile.startWrite();
+        fos.write(contents.getBytes());
+        testAtomicXmlFile.finishWrite(fos);
+
+        return file;
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
new file mode 100644
index 0000000..cae811e
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -0,0 +1,261 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vibrator;
+
+import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
+import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK;
+import static android.os.VibrationEffect.EFFECT_TICK;
+import static android.view.HapticFeedbackConstants.CLOCK_TICK;
+import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
+import static android.view.HapticFeedbackConstants.SAFE_MODE_ENABLED;
+import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.VibrationEffect;
+import android.os.test.FakeVibrator;
+import android.util.AtomicFile;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.R;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+public class HapticFeedbackVibrationProviderTest {
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    private static final VibrationEffect PRIMITIVE_TICK_EFFECT =
+            VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
+    private static final VibrationEffect PRIMITIVE_CLICK_EFFECT =
+            VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK, 0.3497f).compose();
+
+    private Context mContext = InstrumentationRegistry.getContext();
+    private FakeVibrator mVibrator = new FakeVibrator(mContext);
+
+    @Mock private Resources mResourcesMock;
+
+    @Test
+    public void testNonExistentCustomization_useDefault() throws Exception {
+        // No customization file is set.
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
+                .isEqualTo(VibrationEffect.get(EFFECT_TICK));
+
+        // The customization file specifies no customization.
+        setupCustomizationFile("<haptic-feedback-constants></haptic-feedback-constants>");
+        hapticProvider = new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
+                .isEqualTo(VibrationEffect.get(EFFECT_TICK));
+    }
+
+    @Test
+    public void testExceptionParsingCustomizations_useDefault() throws Exception {
+        setupCustomizationFile("<bad-xml></bad-xml>");
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
+                .isEqualTo(VibrationEffect.get(EFFECT_TICK));
+    }
+
+    @Test
+    public void testUseValidCustomizedVibration() throws Exception {
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+        SparseArray<VibrationEffect> customizations = new SparseArray<>();
+        customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
+
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+
+        // The override for `CONTEXT_CLICK` is used.
+        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
+                .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+        // `CLOCK_TICK` has no override, so the default vibration is used.
+        assertThat(hapticProvider.getVibrationForHapticFeedback(CLOCK_TICK))
+                .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
+    }
+
+    @Test
+    public void testDoNotUseInvalidCustomizedVibration() throws Exception {
+        mockVibratorPrimitiveSupport(new int[] {});
+        SparseArray<VibrationEffect> customizations = new SparseArray<>();
+        customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
+
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+
+        // The override for `CONTEXT_CLICK` is not used because the vibration is not supported.
+        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
+                .isEqualTo(VibrationEffect.get(EFFECT_TICK));
+        // `CLOCK_TICK` has no override, so the default vibration is used.
+        assertThat(hapticProvider.getVibrationForHapticFeedback(CLOCK_TICK))
+                .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
+    }
+
+    @Test
+    public void testHapticTextDisabled_noVibrationReturnedForTextHandleMove() throws Exception {
+        mockHapticTextSupport(false);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+        SparseArray<VibrationEffect> customizations = new SparseArray<>();
+        customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);
+
+        // Test with a customization available for `TEXT_HANDLE_MOVE`.
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
+
+        // Test with no customization available for `TEXT_HANDLE_MOVE`.
+        hapticProvider =
+                new HapticFeedbackVibrationProvider(
+                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
+    }
+
+    @Test
+    public void testHapticTextEnabled_vibrationReturnedForTextHandleMove() throws Exception {
+        mockHapticTextSupport(true);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+        SparseArray<VibrationEffect> customizations = new SparseArray<>();
+        customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);
+
+        // Test with a customization available for `TEXT_HANDLE_MOVE`.
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
+                .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+
+        // Test with no customization available for `TEXT_HANDLE_MOVE`.
+        hapticProvider =
+                new HapticFeedbackVibrationProvider(
+                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
+                .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
+    }
+
+    @Test
+    public void testValidCustomizationPresentForSafeModeEnabled_usedRegardlessOfVibrationResource()
+                throws Exception {
+        mockSafeModeEnabledVibration(10, 20, 30, 40);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+        SparseArray<VibrationEffect> customizations = new SparseArray<>();
+        customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
+
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
+                .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+
+        mockSafeModeEnabledVibration(null);
+        hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
+                .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+    }
+
+    @Test
+    public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed()
+                throws Exception {
+        mockSafeModeEnabledVibration(10, 20, 30, 40);
+        SparseArray<VibrationEffect> customizations = new SparseArray<>();
+        customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
+
+        // Test with a customization that is not supported by the vibrator.
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
+                .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
+
+        // Test with no customizations.
+        hapticProvider =
+                new HapticFeedbackVibrationProvider(
+                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
+                .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
+    }
+
+    @Test
+    public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed()
+                throws Exception {
+        mockSafeModeEnabledVibration(null);
+        SparseArray<VibrationEffect> customizations = new SparseArray<>();
+        customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
+
+        // Test with a customization that is not supported by the vibrator.
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
+
+        // Test with no customizations.
+        hapticProvider =
+                new HapticFeedbackVibrationProvider(
+                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
+    }
+
+    private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
+        mVibrator = new FakeVibrator(mContext, supportedPrimitives);
+    }
+
+    private void mockHapticTextSupport(boolean supported) {
+        when(mResourcesMock.getBoolean(R.bool.config_enableHapticTextHandle)).thenReturn(supported);
+    }
+
+    private void mockSafeModeEnabledVibration(int... vibrationPattern) {
+        when(mResourcesMock.getIntArray(R.array.config_safeModeEnabledVibePattern))
+                .thenReturn(vibrationPattern);
+    }
+
+    private void setupCustomizationFile(String xml) throws Exception {
+        File file = new File(mContext.getCacheDir(), "test.xml");
+        file.createNewFile();
+
+        AtomicFile atomicXmlFile = new AtomicFile(file);
+        FileOutputStream fos = atomicXmlFile.startWrite();
+        fos.write(xml.getBytes());
+        atomicXmlFile.finishWrite(fos);
+
+        when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
+                .thenReturn(file.getAbsolutePath());
+    }
+}
diff --git a/services/tests/vibrator/utils/android/os/test/FakeVibrator.java b/services/tests/vibrator/utils/android/os/test/FakeVibrator.java
index 56f49d4e..23398e6 100644
--- a/services/tests/vibrator/utils/android/os/test/FakeVibrator.java
+++ b/services/tests/vibrator/utils/android/os/test/FakeVibrator.java
@@ -18,15 +18,23 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.hardware.vibrator.IVibrator;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.VibratorInfo;
 
 /** Fake implementation of {@link Vibrator} for service tests. */
 public final class FakeVibrator extends Vibrator {
+    private final VibratorInfo mVibratorInfo;
 
     public FakeVibrator(Context context) {
+        this(context, /* supportedPrimitives= */ null);
+    }
+
+    public FakeVibrator(Context context, int[] supportedPrimitives) {
         super(context);
+        mVibratorInfo = buildInfoSupportingPrimitives(supportedPrimitives);
     }
 
     @Override
@@ -40,6 +48,11 @@
     }
 
     @Override
+    public VibratorInfo getInfo() {
+        return mVibratorInfo;
+    }
+
+    @Override
     public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason,
             @NonNull VibrationAttributes attributes) {
     }
@@ -51,4 +64,16 @@
     @Override
     public void cancel(int usageFilter) {
     }
+
+    private static VibratorInfo buildInfoSupportingPrimitives(int... primitives) {
+        if (primitives == null || primitives.length == 0) {
+            return VibratorInfo.EMPTY_VIBRATOR_INFO;
+        }
+        VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        for (int primitive : primitives) {
+            builder.setSupportedPrimitive(primitive, 10);
+        }
+        return builder.build();
+    }
 }
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index a0e6cf5..c2812a1 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -52,6 +52,7 @@
         "service-permission.stubs.system_server",
         "androidx.test.runner",
         "androidx.test.rules",
+        "junit-params",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "servicestests-utils",
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 2015ae9..bf88ce4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -63,7 +63,7 @@
     final Context mContext = spy(getInstrumentation().getTargetContext());
 
     /** Modifier key to meta state */
-    private static final Map<Integer, Integer> MODIFIER;
+    protected static final Map<Integer, Integer> MODIFIER;
     static {
         final Map<Integer, Integer> map = new ArrayMap<>();
         map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
new file mode 100644
index 0000000..feca326
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -0,0 +1,245 @@
+/*
+ * 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.
+ */
+
+package com.android.server.policy;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.KeyEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@Presubmit
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class ShortcutLoggingTests extends ShortcutKeyTestBase {
+
+    private static final int VENDOR_ID = 0x123;
+    private static final int PRODUCT_ID = 0x456;
+    private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT;
+    private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT);
+    private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT;
+    private static final int ALT_ON = MODIFIER.get(KeyEvent.KEYCODE_ALT_LEFT);
+    private static final int CTRL_KEY = KeyEvent.KEYCODE_CTRL_LEFT;
+    private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
+    private static final int SHIFT_KEY = KeyEvent.KEYCODE_SHIFT_LEFT;
+    private static final int SHIFT_ON = MODIFIER.get(KeyEvent.KEYCODE_SHIFT_LEFT);
+
+    @Keep
+    private static Object[][] shortcutTestArguments() {
+        // testName, testKeys, expectedLogEvent, expectedKey, expectedModifierState
+        return new Object[][]{
+                {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
+                        KeyboardLogEvent.HOME, KeyEvent.KEYCODE_H, META_ON},
+                {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+                        KeyboardLogEvent.HOME, KeyEvent.KEYCODE_ENTER, META_ON},
+                {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME}, KeyboardLogEvent.HOME,
+                        KeyEvent.KEYCODE_HOME, 0},
+                {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
+                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_RECENT_APPS, 0},
+                {"Meta + Tab -> Open OVerview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
+                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, META_ON},
+                {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
+                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON},
+                {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK,
+                        KeyEvent.KEYCODE_BACK, 0},
+                {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
+                        KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0},
+                {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
+                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_ASSIST, 0},
+                {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
+                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A, META_ON},
+                {"VOICE_ASSIST key -> Launch Voice Assistant",
+                        new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
+                        KeyboardLogEvent.LAUNCH_VOICE_ASSISTANT, KeyEvent.KEYCODE_VOICE_ASSIST, 0},
+                {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
+                        KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS, KeyEvent.KEYCODE_I, META_ON},
+                {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
+                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_N, META_ON},
+                {"NOTIFICATION key -> Toggle Notification Panel",
+                        new int[]{KeyEvent.KEYCODE_NOTIFICATION},
+                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_NOTIFICATION,
+                        0},
+                {"Meta + T -> Toggle Taskbar", new int[]{META_KEY, KeyEvent.KEYCODE_T},
+                        KeyboardLogEvent.TOGGLE_TASKBAR, KeyEvent.KEYCODE_T, META_ON},
+                {"Meta + Ctrl + S -> Take Screenshot",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
+                        KeyboardLogEvent.TAKE_SCREENSHOT, KeyEvent.KEYCODE_S, META_ON | CTRL_ON},
+                {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
+                        KeyboardLogEvent.OPEN_SHORTCUT_HELPER, KeyEvent.KEYCODE_SLASH, META_ON},
+                {"BRIGHTNESS_UP key -> Increase Brightness",
+                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP}, KeyboardLogEvent.BRIGHTNESS_UP,
+                        KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
+                {"BRIGHTNESS_DOWN key -> Decrease Brightness",
+                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
+                        KeyboardLogEvent.BRIGHTNESS_DOWN, KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
+                {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
+                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_UP,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
+                {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
+                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_DOWN,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
+                {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
+                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_TOGGLE,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
+                {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
+                        KeyboardLogEvent.VOLUME_UP, KeyEvent.KEYCODE_VOLUME_UP, 0},
+                {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
+                        KeyboardLogEvent.VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN, 0},
+                {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
+                        KeyboardLogEvent.VOLUME_MUTE, KeyEvent.KEYCODE_VOLUME_MUTE, 0},
+                {"ALL_APPS key -> Open App Drawer", new int[]{KeyEvent.KEYCODE_ALL_APPS},
+                        KeyboardLogEvent.ALL_APPS, KeyEvent.KEYCODE_ALL_APPS, 0},
+                {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
+                        KeyboardLogEvent.LAUNCH_SEARCH, KeyEvent.KEYCODE_SEARCH, 0},
+                {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
+                        new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
+                        KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
+                {"Meta + Space -> Switch Keyboard Language",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_SPACE},
+                        KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_SPACE, META_ON},
+                {"Meta + Shift + Space -> Switch Keyboard Language",
+                        new int[]{META_KEY, SHIFT_KEY, KeyEvent.KEYCODE_SPACE},
+                        KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_SPACE,
+                        META_ON | SHIFT_ON},
+                {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
+                        KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, META_KEY, META_ON},
+                {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
+                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, ALT_KEY, META_ON | ALT_ON},
+                {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
+                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, META_KEY, META_ON | ALT_ON},
+                {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
+                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, KeyEvent.KEYCODE_CAPS_LOCK, 0},
+                {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
+                        KeyboardLogEvent.SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE, 0},
+                {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
+                        KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_UP,
+                        META_ON | CTRL_ON},
+                {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+                        KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_LEFT,
+                        META_ON | CTRL_ON},
+                {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
+                        KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_RIGHT,
+                        META_ON | CTRL_ON},
+                {"Shift + Menu -> Trigger Bug Report", new int[]{SHIFT_KEY, KeyEvent.KEYCODE_MENU},
+                        KeyboardLogEvent.TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_MENU, SHIFT_ON},
+                {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
+                        KeyboardLogEvent.LOCK_SCREEN, KeyEvent.KEYCODE_L, META_ON},
+                {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
+                        KeyboardLogEvent.OPEN_NOTES, KeyEvent.KEYCODE_N, META_ON | CTRL_ON},
+                {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
+                        KeyboardLogEvent.TOGGLE_POWER, KeyEvent.KEYCODE_POWER, 0},
+                {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
+                        KeyboardLogEvent.TOGGLE_POWER, KeyEvent.KEYCODE_TV_POWER, 0},
+                {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
+                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
+                        0},
+                {"SYSTEM_NAVIGATION_UP key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
+                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
+                        0},
+                {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
+                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
+                        0},
+                {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
+                        KeyboardLogEvent.SYSTEM_NAVIGATION,
+                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
+                {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
+                        KeyboardLogEvent.SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
+                {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
+                        KeyboardLogEvent.SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP, 0},
+                {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
+                        KeyboardLogEvent.WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
+                {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
+                        KeyboardLogEvent.MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PLAY, 0},
+                {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
+                        KeyboardLogEvent.MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
+                {"MEDIA_PLAY_PAUSE key -> Media Control",
+                        new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE}, KeyboardLogEvent.MEDIA_KEY,
+                        KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
+                {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_B, META_ON},
+                {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_EXPLORER, 0},
+                {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_C, META_ON},
+                {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_CONTACTS, 0},
+                {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_E, META_ON},
+                {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_ENVELOPE, 0},
+                {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_K, META_ON},
+                {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_CALENDAR, 0},
+                {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_P, META_ON},
+                {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_MUSIC, 0},
+                {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_U, META_ON},
+                {"CALCULATOR key -> Launch Default Calculator",
+                        new int[]{KeyEvent.KEYCODE_CALCULATOR},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_CALCULATOR, 0},
+                {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_MAPS, KeyEvent.KEYCODE_M, META_ON},
+                {"Meta + S -> Launch Default Messaging App",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_S},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_MESSAGING, KeyEvent.KEYCODE_S, META_ON}};
+    }
+
+    @Before
+    @Override
+    public void setUp() {
+        super.setUp();
+        mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID);
+        mPhoneWindowManager.overrideLaunchHome();
+        mPhoneWindowManager.overrideSearchKeyBehavior(
+                PhoneWindowManager.SEARCH_BEHAVIOR_TARGET_ACTIVITY);
+        mPhoneWindowManager.overrideEnableBugReportTrigger(true);
+        mPhoneWindowManager.overrideStatusBarManagerInternal();
+        mPhoneWindowManager.overrideStartActivity();
+        mPhoneWindowManager.overrideUserSetupComplete();
+    }
+
+    @Test
+    @Parameters(method = "shortcutTestArguments")
+    public void testShortcuts(String testName, int[] testKeys, KeyboardLogEvent expectedLogEvent,
+            int expectedKey, int expectedModifierState) {
+        sendKeyCombination(testKeys, 0);
+        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
+                expectedKey, expectedModifierState, "Failed while executing " + testName);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 766a88f..1866767 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -26,6 +26,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.description;
 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.eq;
@@ -35,6 +36,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GO_TO_VOICE_ASSIST;
@@ -50,7 +52,6 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mockingDetails;
 import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.withSettings;
 
 import android.app.ActivityManagerInternal;
@@ -60,8 +61,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputManager;
 import android.media.AudioManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -75,14 +78,17 @@
 import android.telecom.TelecomManager;
 import android.util.FeatureFlagUtils;
 import android.view.Display;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.autofill.AutofillManagerInternal;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
+import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.keyguard.KeyguardServiceDelegate;
@@ -102,6 +108,7 @@
 import org.mockito.MockSettings;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
 
 import java.util.function.Supplier;
 
@@ -117,6 +124,8 @@
     @Mock private ActivityManagerInternal mActivityManagerInternal;
     @Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal;
     @Mock private InputManagerInternal mInputManagerInternal;
+    @Mock private InputManager mInputManager;
+    @Mock private SensorPrivacyManager mSensorPrivacyManager;
     @Mock private DreamManagerInternal mDreamManagerInternal;
     @Mock private PowerManagerInternal mPowerManagerInternal;
     @Mock private DisplayManagerInternal mDisplayManagerInternal;
@@ -186,6 +195,8 @@
         // Return mocked services: LocalServices.getService
         mMockitoSession = mockitoSession()
                 .mockStatic(LocalServices.class, spyStubOnly)
+                .mockStatic(FrameworkStatsLog.class)
+                .strictness(Strictness.LENIENT)
                 .startMocking();
 
         doReturn(mWindowManagerInternal).when(
@@ -213,7 +224,10 @@
 
         doReturn(mAppOpsManager).when(mContext).getSystemService(eq(AppOpsManager.class));
         doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
+        doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class));
         doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(mSensorPrivacyManager).when(mContext).getSystemService(
+                eq(SensorPrivacyManager.class));
         doReturn(false).when(mPackageManager).hasSystemFeature(any());
         try {
             doThrow(new PackageManager.NameNotFoundException("test")).when(mPackageManager)
@@ -409,6 +423,31 @@
         doReturn(isShowing).when(mKeyguardServiceDelegate).isShowing();
     }
 
+    void overrideKeyEventSource(int vendorId, int productId) {
+        InputDevice device = new InputDevice.Builder().setId(1).setVendorId(vendorId).setProductId(
+                productId).setSources(InputDevice.SOURCE_KEYBOARD).setKeyboardType(
+                InputDevice.KEYBOARD_TYPE_ALPHABETIC).build();
+        doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class));
+        doReturn(device).when(mInputManager).getInputDevice(anyInt());
+    }
+
+    void overrideSearchKeyBehavior(int behavior) {
+        mPhoneWindowManager.mSearchKeyBehavior = behavior;
+    }
+
+    void overrideEnableBugReportTrigger(boolean enable) {
+        mPhoneWindowManager.mEnableShiftMenuBugReports = enable;
+    }
+
+    void overrideStartActivity() {
+        doNothing().when(mContext).startActivityAsUser(any(), any());
+        doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+    }
+
+    void overrideUserSetupComplete() {
+        doReturn(true).when(mPhoneWindowManager).isUserSetupComplete();
+    }
+
     /**
      * Below functions will check the policy behavior could be invoked.
      */
@@ -563,4 +602,12 @@
         verify(mContext, after(TEST_SINGLE_KEY_DELAY_MILLIS).never())
                 .startActivityAsUser(any(Intent.class), any(), any(UserHandle.class));
     }
+
+    void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent,
+            int expectedKey, int expectedModifierState, String errorMsg) {
+        waitForIdle();
+        verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
+                        vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
+                        expectedModifierState), description(errorMsg));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index 3090590..cac3262 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityThread;
+import android.app.IApplicationThread;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -91,14 +92,16 @@
         spyOn(mIWindowManager);
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
-            IBinder clientToken = (IBinder) args[0];
-            int displayId = (int) args[2];
+            IApplicationThread appThread = (IApplicationThread) args[0];
+            IBinder clientToken = (IBinder) args[1];
+            int displayId = (int) args[3];
             DisplayContent dc = mWm.mRoot.getDisplayContent(displayId);
-            mWm.mWindowContextListenerController.registerWindowContainerListener(clientToken,
+            final WindowProcessController wpc = mAtm.getProcessController(appThread);
+            mWm.mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
                     dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG,
                     null /* options */);
             return dc.getImeContainer().getConfiguration();
-        }).when(mIWindowManager).attachWindowContextToDisplayArea(any(),
+        }).when(mIWindowManager).attachWindowContextToDisplayArea(any(), any(),
                 eq(TYPE_INPUT_METHOD_DIALOG), anyInt(), any());
         mDisplayManagerGlobal = DisplayManagerGlobal.getInstance();
         spyOn(mDisplayManagerGlobal);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 3934b02..4034dbc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -62,28 +62,31 @@
     @Test
     public void testPostLayout() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        statusBar.setBounds(0, 0, 500, 1000);
         statusBar.getFrame().set(0, 0, 500, 100);
         statusBar.mHasSurface = true;
         mProvider.setWindowContainer(statusBar, null, null);
         mProvider.updateSourceFrame(statusBar.getFrame());
         mProvider.onPostLayout();
         assertEquals(new Rect(0, 0, 500, 100), mProvider.getSource().getFrame());
-        assertEquals(Insets.of(0, 100, 0, 0),
-                mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
-                        false /* ignoreVisibility */));
-        assertEquals(Insets.of(0, 100, 0, 0),
-                mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
+        assertEquals(Insets.of(0, 100, 0, 0), mProvider.getInsetsHint());
+
+        // Change the bounds and call onPostLayout. Make sure the insets hint gets updated.
+        statusBar.setBounds(0, 10, 500, 1000);
+        mProvider.onPostLayout();
+        assertEquals(Insets.of(0, 90, 0, 0), mProvider.getInsetsHint());
     }
 
     @Test
     public void testPostLayout_invisible() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        statusBar.setBounds(0, 0, 500, 1000);
         statusBar.getFrame().set(0, 0, 500, 100);
         mProvider.setWindowContainer(statusBar, null, null);
         mProvider.updateSourceFrame(statusBar.getFrame());
         mProvider.onPostLayout();
-        assertEquals(Insets.NONE, mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
-                false /* ignoreVisibility */));
+        assertTrue(mProvider.getSource().getFrame().isEmpty());
+        assertEquals(Insets.NONE, mProvider.getInsetsHint());
     }
 
     @Test
@@ -160,6 +163,36 @@
     }
 
     @Test
+    public void testUpdateSourceFrame() {
+        final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        mProvider.setWindowContainer(statusBar, null, null);
+        statusBar.setBounds(0, 0, 500, 1000);
+
+        mProvider.setServerVisible(true);
+        statusBar.getFrame().set(0, 0, 500, 100);
+        mProvider.updateSourceFrame(statusBar.getFrame());
+        assertEquals(statusBar.getFrame(), mProvider.getSource().getFrame());
+        assertEquals(Insets.of(0, 100, 0, 0), mProvider.getInsetsHint());
+
+        // Only change the source frame but not the visibility.
+        statusBar.getFrame().set(0, 0, 500, 90);
+        mProvider.updateSourceFrame(statusBar.getFrame());
+        assertEquals(statusBar.getFrame(), mProvider.getSource().getFrame());
+        assertEquals(Insets.of(0, 90, 0, 0), mProvider.getInsetsHint());
+
+        mProvider.setServerVisible(false);
+        statusBar.getFrame().set(0, 0, 500, 80);
+        mProvider.updateSourceFrame(statusBar.getFrame());
+        assertTrue(mProvider.getSource().getFrame().isEmpty());
+        assertEquals(Insets.of(0, 90, 0, 0), mProvider.getInsetsHint());
+
+        // Only change the visibility but not the frame.
+        mProvider.setServerVisible(true);
+        assertEquals(statusBar.getFrame(), mProvider.getSource().getFrame());
+        assertEquals(Insets.of(0, 80, 0, 0), mProvider.getInsetsHint());
+    }
+
+    @Test
     public void testUpdateSourceFrameForIme() {
         final WindowState inputMethod = createWindow(null, TYPE_INPUT_METHOD, "inputMethod");
 
@@ -184,7 +217,7 @@
     }
 
     @Test
-    public void testInsetsModified() {
+    public void testSetRequestedVisibleTypes() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         statusBar.getFrame().set(0, 0, 500, 100);
@@ -196,7 +229,7 @@
     }
 
     @Test
-    public void testInsetsModified_noControl() {
+    public void testSetRequestedVisibleTypes_noControl() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         statusBar.getFrame().set(0, 0, 500, 100);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index b181213..fb95748 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -91,7 +91,7 @@
     }
 
     @Test
-    public void testScreenshotSecureLayers() {
+    public void testScreenshotSecureLayers() throws InterruptedException {
         SurfaceControl secureSC = new SurfaceControl.Builder()
                 .setName("SecureChildSurfaceControl")
                 .setBLASTLayer()
@@ -197,6 +197,8 @@
         private static final long WAIT_TIMEOUT_S = 5;
         private final Handler mHandler = new Handler(Looper.getMainLooper());
 
+        private final CountDownLatch mAttachedLatch = new CountDownLatch(1);
+
         @Override
         protected void onCreate(@Nullable Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
@@ -204,7 +206,16 @@
                     PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL));
         }
 
-        SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) {
+        @Override
+        public void onAttachedToWindow() {
+            super.onAttachedToWindow();
+            mAttachedLatch.countDown();
+        }
+
+        SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl)
+                throws InterruptedException {
+            assertTrue("Failed to wait for onAttachedToWindow",
+                    mAttachedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
             CountDownLatch countDownLatch = new CountDownLatch(1);
             mHandler.post(() -> {
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 e252b9e..be436bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -43,6 +43,7 @@
 import static org.mockito.Mockito.withSettings;
 
 import android.app.ActivityManagerInternal;
+import android.app.ActivityThread;
 import android.app.AppOpsManager;
 import android.app.IApplicationThread;
 import android.app.usage.UsageStatsManagerInternal;
@@ -321,6 +322,10 @@
                 mock(ActivityManagerService.class, withSettings().stubOnly());
         mAtmService = new TestActivityTaskManagerService(mContext, amService);
         LocalServices.addService(ActivityTaskManagerInternal.class, mAtmService.getAtmInternal());
+        // Create a fake WindowProcessController for the system process.
+        final WindowProcessController wpc =
+                addProcess("android", "system", 1485 /* pid */, 1000 /* uid */);
+        wpc.setThread(ActivityThread.currentActivityThread().getApplicationThread());
     }
 
     private void setUpWindowManagerService() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 9d597b1..6a9bb6c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -653,4 +653,23 @@
         assertEquals(mDisplayContent.getImeContainer().getParent().getSurfaceControl(),
                 mDisplayContent.computeImeParent());
     }
+
+    @Test
+    public void testIsolatedNavigation() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .createActivityCount(1)
+                .setOrganizer(mOrganizer)
+                .setFragmentToken(new Binder())
+                .build();
+
+        // Cannot be isolated if not embedded.
+        task.setIsolatedNav(true);
+        assertFalse(task.isIsolatedNav());
+
+        // Ensure the TaskFragment is isolated once set.
+        tf0.setIsolatedNav(true);
+        assertTrue(tf0.isIsolatedNav());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
index f6d0bf1..fa42e26 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
 
 import android.app.IWindowToken;
 import android.content.res.Configuration;
@@ -53,6 +54,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.Mockito;
 
 /**
@@ -68,11 +70,14 @@
     private static final int TEST_UID = 12345;
     private static final int ANOTHER_UID = 1000;
 
+    @Mock
+    private WindowProcessController mWpc;
     private final IBinder mClientToken = new Binder();
     private WindowContainer<?> mContainer;
 
     @Before
     public void setUp() {
+        initMocks(this);
         mController = new WindowContextListenerController();
         mContainer = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent);
         // Make display on to verify configuration propagation.
@@ -82,20 +87,20 @@
 
     @Test
     public void testRegisterWindowContextListener() {
-        mController.registerWindowContainerListener(mClientToken, mContainer, -1,
+        mController.registerWindowContainerListener(mWpc, mClientToken, mContainer, -1,
                 TYPE_APPLICATION_OVERLAY, null /* options */);
 
         assertEquals(1, mController.mListeners.size());
 
         final IBinder clientToken = mock(IBinder.class);
-        mController.registerWindowContainerListener(clientToken, mContainer, -1,
+        mController.registerWindowContainerListener(mWpc, clientToken, mContainer, -1,
                 TYPE_APPLICATION_OVERLAY, null /* options */);
 
         assertEquals(2, mController.mListeners.size());
 
         final WindowContainer<?> container = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
                 mDefaultDisplay);
-        mController.registerWindowContainerListener(mClientToken, container, -1,
+        mController.registerWindowContainerListener(mWpc, mClientToken, container, -1,
                 TYPE_APPLICATION_OVERLAY, null /* options */);
 
         // The number of listeners doesn't increase since the listener just gets updated.
@@ -121,7 +126,7 @@
         config1.densityDpi = 100;
         mContainer.onRequestedOverrideConfigurationChanged(config1);
 
-        mController.registerWindowContainerListener(clientToken, mContainer, -1,
+        mController.registerWindowContainerListener(mWpc, clientToken, mContainer, -1,
                 TYPE_APPLICATION_OVERLAY, null /* options */);
 
         assertEquals(bounds1, clientToken.mConfiguration.windowConfiguration.getBounds());
@@ -137,7 +142,7 @@
         config2.densityDpi = 200;
         container.onRequestedOverrideConfigurationChanged(config2);
 
-        mController.registerWindowContainerListener(clientToken, container, -1,
+        mController.registerWindowContainerListener(mWpc, clientToken, container, -1,
                 TYPE_APPLICATION_OVERLAY, null /* options */);
 
         assertEquals(bounds2, clientToken.mConfiguration.windowConfiguration.getBounds());
@@ -164,7 +169,7 @@
 
     @Test
     public void testAssertCallerCanModifyListener_CanManageAppTokens_ReturnTrue() {
-        mController.registerWindowContainerListener(mClientToken, mContainer, TEST_UID,
+        mController.registerWindowContainerListener(mWpc, mClientToken, mContainer, TEST_UID,
                 TYPE_APPLICATION_OVERLAY, null /* options */);
 
         assertTrue(mController.assertCallerCanModifyListener(mClientToken,
@@ -173,7 +178,7 @@
 
     @Test
     public void testAssertCallerCanModifyListener_SameUid_ReturnTrue() {
-        mController.registerWindowContainerListener(mClientToken, mContainer, TEST_UID,
+        mController.registerWindowContainerListener(mWpc, mClientToken, mContainer, TEST_UID,
                 TYPE_APPLICATION_OVERLAY, null /* options */);
 
         assertTrue(mController.assertCallerCanModifyListener(mClientToken,
@@ -182,7 +187,7 @@
 
     @Test(expected = UnsupportedOperationException.class)
     public void testAssertCallerCanModifyListener_DifferentUid_ThrowException() {
-        mController.registerWindowContainerListener(mClientToken, mContainer, TEST_UID,
+        mController.registerWindowContainerListener(mWpc, mClientToken, mContainer, TEST_UID,
                 TYPE_APPLICATION_OVERLAY, null /* options */);
 
         mController.assertCallerCanModifyListener(mClientToken,
@@ -198,7 +203,7 @@
                 .build();
         final DisplayArea<?> da = windowContextCreatedToken.getDisplayArea();
 
-        mController.registerWindowContainerListener(mClientToken, windowContextCreatedToken,
+        mController.registerWindowContainerListener(mWpc, mClientToken, windowContextCreatedToken,
                 TEST_UID, TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */);
 
         assertThat(mController.getContainer(mClientToken)).isEqualTo(windowContextCreatedToken);
@@ -233,7 +238,7 @@
                 .setDisplayContent(dualDisplayContent)
                 .setFromClientToken(true)
                 .build();
-        mController.registerWindowContainerListener(mClientToken, windowContextCreatedToken,
+        mController.registerWindowContainerListener(mWpc, mClientToken, windowContextCreatedToken,
                 TEST_UID, TYPE_INPUT_METHOD_DIALOG, null /* options */);
 
         assertThat(mController.getContainer(mClientToken)).isEqualTo(windowContextCreatedToken);
@@ -258,7 +263,7 @@
         config1.densityDpi = 100;
         mContainer.onRequestedOverrideConfigurationChanged(config1);
 
-        mController.registerWindowContainerListener(mockToken, mContainer, -1,
+        mController.registerWindowContainerListener(mWpc, mockToken, mContainer, -1,
                 TYPE_APPLICATION_OVERLAY, null /* options */);
 
         verify(mockToken, never()).onConfigurationChanged(any(), anyInt());
@@ -293,7 +298,7 @@
         config1.densityDpi = 100;
         mContainer.onRequestedOverrideConfigurationChanged(config1);
 
-        mController.registerWindowContainerListener(clientToken, mContainer, -1,
+        mController.registerWindowContainerListener(mWpc, clientToken, mContainer, -1,
                 TYPE_APPLICATION_OVERLAY, options);
 
         assertThat(clientToken.mConfiguration).isEqualTo(config1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 45331f7..495a80b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -68,6 +68,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityThread;
+import android.app.IApplicationThread;
 import android.content.pm.ActivityInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -119,6 +121,9 @@
 @RunWith(WindowTestRunner.class)
 public class WindowManagerServiceTests extends WindowTestsBase {
 
+    private final IApplicationThread mAppThread = ActivityThread.currentActivityThread()
+            .getApplicationThread();
+
     @Rule
     public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
             InstrumentationRegistry.getInstrumentation().getUiAutomation(),
@@ -428,7 +433,7 @@
     public void testAttachWindowContextToWindowToken_InvalidToken_EarlyReturn() {
         spyOn(mWm.mWindowContextListenerController);
 
-        mWm.attachWindowContextToWindowToken(new Binder(), new Binder());
+        mWm.attachWindowContextToWindowToken(mAppThread, new Binder(), new Binder());
 
         verify(mWm.mWindowContextListenerController, never()).getWindowType(any());
     }
@@ -441,7 +446,7 @@
         doReturn(INVALID_WINDOW_TYPE).when(mWm.mWindowContextListenerController)
                 .getWindowType(any());
 
-        mWm.attachWindowContextToWindowToken(new Binder(), windowToken.token);
+        mWm.attachWindowContextToWindowToken(mAppThread, new Binder(), windowToken.token);
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -452,7 +457,7 @@
         doReturn(TYPE_APPLICATION).when(mWm.mWindowContextListenerController)
                 .getWindowType(any());
 
-        mWm.attachWindowContextToWindowToken(new Binder(), windowToken.token);
+        mWm.attachWindowContextToWindowToken(mAppThread, new Binder(), windowToken.token);
     }
 
     @Test
@@ -465,10 +470,10 @@
         doReturn(false).when(mWm.mWindowContextListenerController)
                 .assertCallerCanModifyListener(any(), anyBoolean(), anyInt());
 
-        mWm.attachWindowContextToWindowToken(new Binder(), windowToken.token);
+        mWm.attachWindowContextToWindowToken(mAppThread, new Binder(), windowToken.token);
 
         verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(
-                any(), any(), anyInt(), anyInt(), any());
+                any(), any(), any(), anyInt(), anyInt(), any());
     }
 
     @Test
@@ -482,9 +487,9 @@
                 .assertCallerCanModifyListener(any(), anyBoolean(), anyInt());
 
         final IBinder clientToken = new Binder();
-        mWm.attachWindowContextToWindowToken(clientToken, windowToken.token);
-
-        verify(mWm.mWindowContextListenerController).registerWindowContainerListener(
+        mWm.attachWindowContextToWindowToken(mAppThread, clientToken, windowToken.token);
+        final WindowProcessController wpc = mAtm.getProcessController(mAppThread);
+        verify(mWm.mWindowContextListenerController).registerWindowContainerListener(eq(wpc),
                 eq(clientToken), eq(windowToken), anyInt(), eq(TYPE_INPUT_METHOD),
                 eq(windowToken.mOptions));
     }
@@ -514,7 +519,7 @@
                 new InsetsSourceControl.Array(), new Rect(), new float[1]);
 
         verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
-                any(), anyInt(), anyInt(), any());
+                any(), any(), anyInt(), anyInt(), any());
     }
 
     @Test
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
index c47d459..d9cbea9 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -292,6 +292,19 @@
         }
     }
 
+    boolean isPackageExemptedFromBroadcastResponseStats(@NonNull String packageName,
+            @NonNull UserHandle user) {
+        synchronized (mLock) {
+            if (doesPackageHoldExemptedPermission(packageName, user)) {
+                return true;
+            }
+            if (doesPackageHoldExemptedRole(packageName, user)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
     boolean doesPackageHoldExemptedRole(@NonNull String packageName, @NonNull UserHandle user) {
         final List<String> exemptedRoles = mAppStandby.getBroadcastResponseExemptedRoles();
         synchronized (mLock) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 43cebe8..e738d29 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2867,6 +2867,18 @@
         }
 
         @Override
+        public boolean isPackageExemptedFromBroadcastResponseStats(@NonNull String callingPackage,
+                @UserIdInt int userId) {
+            Objects.requireNonNull(callingPackage);
+
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.DUMP,
+                    "isPackageExemptedFromBroadcastResponseStats");
+            return mResponseStatsTracker.isPackageExemptedFromBroadcastResponseStats(
+                    callingPackage, UserHandle.of(userId));
+        }
+
+        @Override
         @Nullable
         public String getAppStandbyConstant(@NonNull String key) {
             Objects.requireNonNull(key);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 248cc26..ccc4ac2 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -27,6 +27,7 @@
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH;
 
+import android.app.AppOpsManager;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.ChangeId;
@@ -548,13 +549,15 @@
     static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
         private final HotwordDetectionConnection mHotwordDetectionConnection;
         private final IHotwordRecognitionStatusCallback mExternalCallback;
-        private final int mVoiceInteractionServiceUid;
+        private final Identity mVoiceInteractorIdentity;
+        private final Context mContext;
 
-        SoundTriggerCallback(IHotwordRecognitionStatusCallback callback,
-                HotwordDetectionConnection connection, int uid) {
+        SoundTriggerCallback(Context context, IHotwordRecognitionStatusCallback callback,
+                HotwordDetectionConnection connection, Identity voiceInteractorIdentity) {
+            mContext = context;
             mHotwordDetectionConnection = connection;
             mExternalCallback = callback;
-            mVoiceInteractionServiceUid = uid;
+            mVoiceInteractorIdentity = voiceInteractorIdentity;
         }
 
         @Override
@@ -568,15 +571,30 @@
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
-                        mVoiceInteractionServiceUid);
+                        mVoiceInteractorIdentity.uid);
                 mHotwordDetectionConnection.detectFromDspSource(
                         recognitionEvent, mExternalCallback);
             } else {
-                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
-                        mVoiceInteractionServiceUid);
-                mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
+                // We have to attribute ops here, since we configure all st clients as trusted to
+                // enable a partial exemption.
+                // TODO (b/292012931) remove once trusted uniformly required.
+                int result = mContext.getSystemService(AppOpsManager.class)
+                        .noteOpNoThrow(AppOpsManager.OP_RECORD_AUDIO_HOTWORD,
+                            mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                            mVoiceInteractorIdentity.attributionTag,
+                            "Non-HDS keyphrase recognition to VoiceInteractionService");
+
+                if (result != AppOpsManager.MODE_ALLOWED) {
+                    Slog.w(TAG, "onKeyphraseDetected suppressed, permission check returned: "
+                            + result);
+                    mExternalCallback.onRecognitionPaused();
+                } else {
+                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
+                            mVoiceInteractorIdentity.uid);
+                    mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
+                }
             }
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 605af03..3502a3f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -104,6 +104,7 @@
 import com.android.server.UiThread;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+import com.android.server.policy.AppOpsPolicy;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -336,6 +337,9 @@
         /** The start value of showSessionId */
         private static final int SHOW_SESSION_START_ID = 0;
 
+        private final boolean IS_HDS_REQUIRED = AppOpsPolicy.isHotwordDetectionServiceRequired(
+                mContext.getPackageManager());
+
         @GuardedBy("this")
         private int mShowSessionId = SHOW_SESSION_START_ID;
 
@@ -393,8 +397,14 @@
             }
             try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
                     originatorIdentity)) {
+                if (!IS_HDS_REQUIRED) {
+                    // For devices which still have hotword exemption, any client (not just HDS
+                    // clients) are trusted.
+                    // TODO (b/292012931) remove once trusted uniformly required.
+                    forHotwordDetectionService = true;
+                }
                 return new SoundTriggerSession(mSoundTriggerInternal.attach(client,
-                            moduleProperties, forHotwordDetectionService));
+                            moduleProperties, forHotwordDetectionService), originatorIdentity);
             }
         }
 
@@ -1674,10 +1684,13 @@
             final SoundTriggerInternal.Session mSession;
             private IHotwordRecognitionStatusCallback mSessionExternalCallback;
             private IRecognitionStatusCallback mSessionInternalCallback;
+            private final Identity mVoiceInteractorIdentity;
 
             SoundTriggerSession(
-                    SoundTriggerInternal.Session session) {
+                    SoundTriggerInternal.Session session,
+                    Identity voiceInteractorIdentity) {
                 mSession = session;
+                mVoiceInteractorIdentity = voiceInteractorIdentity;
             }
 
             @Override
@@ -1731,7 +1744,8 @@
                         if (mSessionExternalCallback == null
                                 || mSessionInternalCallback == null
                                 || callback.asBinder() != mSessionExternalCallback.asBinder()) {
-                            mSessionInternalCallback = createSoundTriggerCallbackLocked(callback);
+                            mSessionInternalCallback = createSoundTriggerCallbackLocked(callback,
+                                    mVoiceInteractorIdentity);
                             mSessionExternalCallback = callback;
                         }
                     }
@@ -1752,7 +1766,8 @@
                     if (mSessionExternalCallback == null
                             || mSessionInternalCallback == null
                             || callback.asBinder() != mSessionExternalCallback.asBinder()) {
-                        soundTriggerCallback = createSoundTriggerCallbackLocked(callback);
+                        soundTriggerCallback = createSoundTriggerCallbackLocked(callback,
+                                mVoiceInteractorIdentity);
                         Slog.w(TAG, "stopRecognition() called with a different callback than"
                                 + "startRecognition()");
                     } else {
@@ -2090,6 +2105,7 @@
                 pw.println("  mTemporarilyDisabled: " + mTemporarilyDisabled);
                 pw.println("  mCurUser: " + mCurUser);
                 pw.println("  mCurUserSupported: " + mCurUserSupported);
+                pw.println("  mIsHdsRequired: " + IS_HDS_REQUIRED);
                 dumpSupportedUsers(pw, "  ");
                 mDbHelper.dump(pw);
                 if (mImpl == null) {
@@ -2165,11 +2181,13 @@
         }
 
         private IRecognitionStatusCallback createSoundTriggerCallbackLocked(
-                IHotwordRecognitionStatusCallback callback) {
+                IHotwordRecognitionStatusCallback callback,
+                Identity voiceInteractorIdentity) {
             if (mImpl == null) {
                 return null;
             }
-            return mImpl.createSoundTriggerCallbackLocked(callback);
+            return mImpl.createSoundTriggerCallbackLocked(mContext, callback,
+                    voiceInteractorIdentity);
         }
 
         class RoleObserver implements OnRoleHoldersChangedListener {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 0ad86c1..5d88a65 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -877,12 +877,13 @@
     }
 
     public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
-            IHotwordRecognitionStatusCallback callback) {
+            Context context, IHotwordRecognitionStatusCallback callback,
+            Identity voiceInteractorIdentity) {
         if (DEBUG) {
             Slog.d(TAG, "createSoundTriggerCallbackLocked");
         }
-        return new HotwordDetectionConnection.SoundTriggerCallback(callback,
-                mHotwordDetectionConnection, mInfo.getServiceInfo().applicationInfo.uid);
+        return new HotwordDetectionConnection.SoundTriggerCallback(context, callback,
+                mHotwordDetectionConnection, voiceInteractorIdentity);
     }
 
     private static ServiceInfo getServiceInfoLocked(@NonNull ComponentName componentName,
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 6722cc8..aaf0158 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,6 +16,7 @@
 
 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.common.flicker.subject.region.RegionSubject
@@ -161,6 +162,7 @@
         }
     }
 
+    @FlakyTest(bugId = 290736037)
     /** Main activity should go from fullscreen to being a split with secondary activity. */
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 24e231c..82de646 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -383,8 +383,11 @@
                     it.wmState.visibleWindows.firstOrNull { window ->
                         this.windowMatchesAnyOf(window)
                     }
-                        ?: return@add false
+                Log.d(TAG, "window " + pipAppWindow)
+                if (pipAppWindow == null) return@add false
                 val pipRegion = pipAppWindow.frameRegion
+                Log.d(TAG, "region " + pipRegion +
+                        " covers " + windowRect.coversMoreThan(pipRegion))
                 return@add windowRect.coversMoreThan(pipRegion)
             }
             .waitForAndVerify()
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 94b090f..063e2c3 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
@@ -71,7 +71,7 @@
      * Checks that the [ComponentNameMatcher.NAV_BAR] layer starts invisible, becomes visible during
      * unlocking animation and remains visible at the end
      */
-    @Presubmit
+    @FlakyTest(bugId = 288341660)
     @Test
     fun navBarLayerVisibilityChanges() {
         Assume.assumeFalse(flicker.scenario.isTablet)
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/Consts.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/notification/Consts.kt
index 49ac64c..00e75c7 100644
--- a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/Consts.kt
@@ -12,15 +12,12 @@
  * WITHOUT WARRANTIES OR CONDITIONS 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.multishade.shared.model
+package com.android.server.wm.flicker.notification
 
-import androidx.annotation.FloatRange
+import android.tools.common.traces.component.ComponentNameMatcher
 
-/** Models the current state of a shade. */
-data class ShadeModel(
-    val id: ShadeId,
-    @FloatRange(from = 0.0, to = 1.0) val expansion: Float = 0f,
-)
+object Consts {
+    val IMAGE_WALLPAPER = ComponentNameMatcher("", "com.android.systemui.wallpapers.ImageWallpaper")
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
index 6f5daeb..6819a38 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
@@ -17,8 +17,10 @@
 package com.android.server.wm.flicker.notification
 
 import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import android.platform.test.rule.SettingOverrideRule
 import android.provider.Settings
+import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -107,6 +109,21 @@
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
+    @Presubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        flicker.assertWm {
+            this.visibleWindowsShownMoreThanOneConsecutiveEntry(
+                listOf(
+                    ComponentNameMatcher.SPLASH_SCREEN,
+                    ComponentNameMatcher.SNAPSHOT,
+                    ComponentNameMatcher.SECONDARY_HOME_HANDLE,
+                    Consts.IMAGE_WALLPAPER
+                )
+            )
+        }
+    }
+
     companion object {
         /**
          * Creates the test configurations.
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 483caa7..bf0f4ab 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
@@ -139,6 +139,21 @@
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
+    @Presubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        flicker.assertWm {
+            this.visibleWindowsShownMoreThanOneConsecutiveEntry(
+                listOf(
+                    ComponentNameMatcher.SPLASH_SCREEN,
+                    ComponentNameMatcher.SNAPSHOT,
+                    ComponentNameMatcher.SECONDARY_HOME_HANDLE,
+                    Consts.IMAGE_WALLPAPER
+                )
+            )
+        }
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 44da69c..5719273 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -100,6 +100,7 @@
 
     private fun clickCloseAppOnAnrDialog() {
         // Find anr dialog and kill app
+        val timestamp = System.currentTimeMillis()
         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
         val closeAppButton: UiObject2? =
                 uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
@@ -107,7 +108,6 @@
             fail("Could not find anr dialog")
             return
         }
-        val initialReasons = getExitReasons()
         closeAppButton.click()
         /**
          * We must wait for the app to be fully closed before exiting this test. This is because
@@ -116,7 +116,7 @@
          * the killing logic will apply to the newly launched 'am start' instance, and the second
          * test will fail because the unresponsive activity will never be launched.
          */
-        waitForNewExitReason(initialReasons[0].timestamp)
+        waitForNewExitReasonAfter(timestamp)
     }
 
     private fun clickWaitOnAnrDialog() {
@@ -140,12 +140,13 @@
         return infos
     }
 
-    private fun waitForNewExitReason(previousExitTimestamp: Long) {
+    private fun waitForNewExitReasonAfter(timestamp: Long) {
         PollingCheck.waitFor {
-            getExitReasons()[0].timestamp > previousExitTimestamp
+            val reasons = getExitReasons()
+            !reasons.isEmpty() && reasons[0].timestamp >= timestamp
         }
         val reasons = getExitReasons()
-        assertTrue(reasons[0].timestamp > previousExitTimestamp)
+        assertTrue(reasons[0].timestamp > timestamp)
         assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
     }
 
diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
index 37b67f4..075cf0c 100644
--- a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
@@ -113,13 +113,11 @@
         val sent = SpyInputEventSender.Timeline(
             inputEventId = 1, gpuCompletedTime = 3, presentTime = 2)
         mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime)
-        val received = mSender.getTimeline()
-        assertEquals(null, received)
+        mSender.assertNoEvents()
         // Sender will no longer receive callbacks for this fd, even if receiver sends a valid
         // timeline later
         mReceiver.reportTimeline(2 /*inputEventId*/, 3 /*gpuCompletedTime*/, 4 /*presentTime*/)
-        val receivedSecondTimeline = mSender.getTimeline()
-        assertEquals(null, receivedSecondTimeline)
+        mSender.assertNoEvents()
     }
 
     /**
diff --git a/tests/Input/src/com/android/test/input/PointerEventDispatcherTest.kt b/tests/Input/src/com/android/test/input/PointerEventDispatcherTest.kt
index 1099878..f311bc2 100644
--- a/tests/Input/src/com/android/test/input/PointerEventDispatcherTest.kt
+++ b/tests/Input/src/com/android/test/input/PointerEventDispatcherTest.kt
@@ -25,7 +25,6 @@
 import com.android.server.UiThread
 import com.android.server.wm.PointerEventDispatcher
 import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNull
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -86,8 +85,7 @@
         // Since the listener raises an exception during the event handling, the event should be
         // marked as 'not handled'.
         assertEquals(SpyInputEventSender.FinishedSignal(seq, handled = false), finishedSignal)
-        // Ensure that there aren't double finish calls. This would crash if there's a call
-        // to finish twice.
-        assertNull(mSender.getFinishedSignal())
+        // Ensure that there aren't double finish calls.
+        mSender.assertNoEvents()
     }
 }
diff --git a/tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt b/tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt
index 2d9af9a..5cbfce5 100644
--- a/tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt
+++ b/tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt
@@ -27,10 +27,17 @@
 import java.util.concurrent.LinkedBlockingQueue
 import java.util.concurrent.TimeUnit
 
+import org.junit.Assert.assertNull
+
 private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T? {
     return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS)
 }
 
+private fun <T> assertNoEvents(queue: LinkedBlockingQueue<T>) {
+    // Poll the queue with a shorter timeout, to make the check faster.
+    assertNull(queue.poll(100L, TimeUnit.MILLISECONDS))
+}
+
 class SpyInputEventReceiver(channel: InputChannel, looper: Looper) :
         InputEventReceiver(channel, looper) {
     private val mInputEvents = LinkedBlockingQueue<InputEvent>()
@@ -72,4 +79,9 @@
     fun getTimeline(): Timeline? {
         return getEvent(mTimelines)
     }
+
+    fun assertNoEvents() {
+        assertNoEvents(mFinishedSignals)
+        assertNoEvents(mTimelines)
+    }
 }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
index 0c267b2..320daee 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
@@ -17,8 +17,9 @@
 package com.android.inputmethod.stresstest;
 
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 
+import static com.android.compatibility.common.util.SystemUtil.eventually;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
@@ -26,11 +27,16 @@
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.content.Intent;
 import android.platform.test.annotations.RootPermissionTest;
 import android.platform.test.rule.UnlockScreenRule;
+import android.support.test.uiautomator.UiDevice;
 import android.widget.EditText;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,6 +45,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Test IME visibility by using system default IME to ensure the behavior is consistent
@@ -59,8 +66,12 @@
     public ScreenCaptureRule mScreenCaptureRule =
             new ScreenCaptureRule("/sdcard/InputMethodStressTest");
 
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(3);
+
     private static final int NUM_TEST_ITERATIONS = 10;
 
+    private final boolean mIsPortrait;
+
     @Parameterized.Parameters(name = "isPortrait={0}")
     public static List<Boolean> isPortraitCases() {
         // Test in both portrait and landscape mode.
@@ -68,6 +79,7 @@
     }
 
     public DefaultImeVisibilityTest(boolean isPortrait) {
+        mIsPortrait = isPortrait;
         mImeStressTestRule.setIsPortrait(isPortrait);
     }
 
@@ -75,14 +87,26 @@
     public void showHideDefaultIme() {
         Intent intent =
                 createIntent(
-                        0x0, /* No window focus flags */
-                        SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE,
+                        0x0 /* No window focus flags */,
+                        SOFT_INPUT_STATE_HIDDEN | SOFT_INPUT_ADJUST_RESIZE,
                         Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
         ImeStressTestUtil.TestActivity activity = ImeStressTestUtil.TestActivity.start(intent);
         EditText editText = activity.getEditText();
+
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        eventually(
+                () ->
+                        assertWithMessage("Display rotation should be updated.")
+                                .that(uiDevice.getDisplayRotation())
+                                .isEqualTo(mIsPortrait ? 0 : 1),
+                TIMEOUT);
+
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
-            callOnMainSync(activity::showImeWithInputMethodManager);
+            // TODO(b/291752364): Remove the explicit focus request once the issue with view focus
+            //  change between fullscreen IME and actual editText is fixed.
+            callOnMainSync(editText::requestFocus);
             verifyWindowAndViewFocus(editText, true, true);
+            callOnMainSync(activity::showImeWithInputMethodManager);
             waitOnMainUntilImeIsShown(editText);
 
             callOnMainSync(activity::hideImeWithInputMethodManager);
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
index 12104b2..c746321 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
@@ -20,9 +20,9 @@
 import android.os.RemoteException;
 import android.support.test.uiautomator.UiDevice;
 
+import androidx.annotation.NonNull;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import org.checkerframework.checker.nullness.qual.NonNull;
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
 
@@ -35,8 +35,6 @@
 public class ImeStressTestRule extends TestWatcher {
     private static final String LOCK_SCREEN_OFF_COMMAND = "locksettings set-disabled true";
     private static final String LOCK_SCREEN_ON_COMMAND = "locksettings set-disabled false";
-    private static final String SET_PORTRAIT_MODE_COMMAND = "settings put system user_rotation 0";
-    private static final String SET_LANDSCAPE_MODE_COMMAND = "settings put system user_rotation 1";
     private static final String SIMPLE_IME_ID =
             "com.android.apps.inputmethod.simpleime/.SimpleInputMethodService";
     private static final String ENABLE_IME_COMMAND = "ime enable " + SIMPLE_IME_ID;
@@ -44,8 +42,10 @@
     private static final String DISABLE_IME_COMMAND = "ime disable " + SIMPLE_IME_ID;
     private static final String RESET_IME_COMMAND = "ime reset";
 
-    @NonNull private final Instrumentation mInstrumentation;
-    @NonNull private final UiDevice mUiDevice;
+    @NonNull
+    private final Instrumentation mInstrumentation;
+    @NonNull
+    private final UiDevice mUiDevice;
     // Whether the screen orientation is set to portrait.
     private boolean mIsPortrait;
     // Whether to use a simple test Ime or system default Ime for test.
@@ -105,12 +105,13 @@
     private void setOrientation() {
         try {
             mUiDevice.freezeRotation();
-            executeShellCommand(
-                    mIsPortrait ? SET_PORTRAIT_MODE_COMMAND : SET_LANDSCAPE_MODE_COMMAND);
-        } catch (IOException e) {
-            throw new RuntimeException("Could not set screen orientation.", e);
+            if (mIsPortrait) {
+                mUiDevice.setOrientationNatural();
+            } else {
+                mUiDevice.setOrientationLeft();
+            }
         } catch (RemoteException e) {
-            throw new RuntimeException("Could not freeze rotation.", e);
+            throw new RuntimeException("Could not freeze rotation or set screen orientation.", e);
         }
     }
 
@@ -147,7 +148,8 @@
         }
     }
 
-    private @NonNull String executeShellCommand(@NonNull String cmd) throws IOException {
+    @NonNull
+    private String executeShellCommand(@NonNull String cmd) throws IOException {
         return mUiDevice.executeShellCommand(cmd);
     }
 }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index f3c8194..c9b5c96 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -392,7 +392,7 @@
         public boolean showImeWithInputMethodManager() {
             boolean showResult =
                     getInputMethodManager()
-                            .showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
+                            .showSoftInput(mEditText, 0 /* flags */);
             if (showResult) {
                 Log.i(TAG, "IMM#showSoftInput successfully");
             } else {
@@ -404,7 +404,8 @@
         /** Hide IME with InputMethodManager. */
         public boolean hideImeWithInputMethodManager() {
             boolean hideResult =
-                    getInputMethodManager().hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+                    getInputMethodManager()
+                            .hideSoftInputFromWindow(mEditText.getWindowToken(), 0 /* flags */);
             if (hideResult) {
                 Log.i(TAG, "IMM#hideSoftInput successfully");
             } else {
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
index 7cf69b7..31ea832 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
@@ -16,6 +16,10 @@
 
 package com.android.test.silkfx.hdr
 
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.animation.ValueAnimator.AnimatorUpdateListener
 import android.content.Context
 import android.graphics.Bitmap
 import android.graphics.Canvas
@@ -65,15 +69,9 @@
         findViewById<RadioGroup>(R.id.output_mode)!!.also {
             it.check(outputMode)
             it.setOnCheckedChangeListener { _, checkedId ->
-                val previousMode = outputMode
                 outputMode = checkedId
-                if (previousMode == R.id.output_sdr && checkedId == R.id.output_hdr) {
-                    animateToHdr()
-                } else if (previousMode == R.id.output_hdr && checkedId == R.id.output_sdr) {
-                    animateToSdr()
-                } else {
-                    updateDisplay()
-                }
+                // Intentionally don't do anything fancy so that mode A/B comparisons are easy
+                updateDisplay()
             }
         }
 
@@ -103,10 +101,41 @@
 
         imageView.apply {
             isClickable = true
+            // Example of animating between SDR and HDR using gainmap params; animates HDR->SDR->HDR
+            // with a brief pause on SDR. The key thing here is that the gainmap's
+            // minDisplayRatioForHdrTransition is animated between its original value (for full HDR)
+            // and displayRatioForFullHdr (for full SDR). The view must also be invalidated during
+            // the animation for the updates to take effect.
             setOnClickListener {
-                animate().alpha(.5f).withEndAction {
-                    animate().alpha(1f).start()
-                }.start()
+                if (gainmap != null && (outputMode == R.id.output_hdr ||
+                                        outputMode == R.id.output_hdr_test)) {
+                    val animationLengthMs: Long = 500
+                    val updateListener = object : AnimatorUpdateListener {
+                        override fun onAnimationUpdate(animation: ValueAnimator) {
+                            imageView.invalidate()
+                        }
+                    }
+                    val hdrToSdr = ObjectAnimator.ofFloat(
+                      gainmap, "minDisplayRatioForHdrTransition",
+                      gainmap!!.minDisplayRatioForHdrTransition,
+                      gainmap!!.displayRatioForFullHdr).apply {
+                        duration = animationLengthMs
+                        addUpdateListener(updateListener)
+                    }
+                    val sdrToHdr = ObjectAnimator.ofFloat(
+                      gainmap, "minDisplayRatioForHdrTransition",
+                      gainmap!!.displayRatioForFullHdr,
+                      gainmap!!.minDisplayRatioForHdrTransition).apply {
+                        duration = animationLengthMs
+                        addUpdateListener(updateListener)
+                    }
+
+                    AnimatorSet().apply {
+                        play(hdrToSdr)
+                        play(sdrToHdr).after(animationLengthMs)
+                        start()
+                    }
+                }
             }
         }
     }
@@ -164,20 +193,6 @@
         updateDisplay()
     }
 
-    private fun animateToHdr() {
-        if (bitmap == null || gainmap == null) return
-
-        // TODO: Trigger an animation
-        updateDisplay()
-    }
-
-    private fun animateToSdr() {
-        if (bitmap == null) return
-
-        // TODO: Trigger an animation
-        updateDisplay()
-    }
-
     private fun updateDisplay() {
         if (bitmap == null) return
 
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index d2ea599..b5c290e 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -186,7 +186,20 @@
     // These are created as weak symbols, and are only generated from default
     // configuration
     // strings and plurals.
-    PseudolocaleGenerator pseudolocale_generator;
+    std::string grammatical_gender_values;
+    std::string grammatical_gender_ratio;
+    if (options.pseudo_localize_gender_values) {
+      grammatical_gender_values = options.pseudo_localize_gender_values.value();
+    } else {
+      grammatical_gender_values = "f,m,n";
+    }
+    if (options.pseudo_localize_gender_ratio) {
+      grammatical_gender_ratio = options.pseudo_localize_gender_ratio.value();
+    } else {
+      grammatical_gender_ratio = "1.0";
+    }
+    PseudolocaleGenerator pseudolocale_generator(grammatical_gender_values,
+                                                 grammatical_gender_ratio);
     if (!pseudolocale_generator.Consume(context, &table)) {
       return false;
     }
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index 14a730a..22890fc 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -35,6 +35,8 @@
   std::optional<std::string> res_dir;
   std::optional<std::string> res_zip;
   std::optional<std::string> generate_text_symbols_path;
+  std::optional<std::string> pseudo_localize_gender_values;
+  std::optional<std::string> pseudo_localize_gender_ratio;
   std::optional<Visibility::Level> visibility;
   bool pseudolocalize = false;
   bool no_png_crunch = false;
@@ -76,6 +78,15 @@
     AddOptionalFlag("--source-path",
                       "Sets the compiled resource file source file path to the given string.",
                       &options_.source_path);
+    AddOptionalFlag("--pseudo-localize-gender-values",
+                    "Sets the gender values to pick up for generating grammatical gender strings, "
+                    "gender values should be f, m, or n, which are shortcuts for feminine, "
+                    "masculine and neuter, and split with comma.",
+                    &options_.pseudo_localize_gender_values);
+    AddOptionalFlag("--pseudo-localize-gender-ratio",
+                    "Sets the ratio of resources to generate grammatical gender strings for. The "
+                    "ratio has to be a float number between 0 and 1.",
+                    &options_.pseudo_localize_gender_ratio);
   }
 
   int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index 3464a76..8880089 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -236,9 +236,24 @@
   // The first string (000) is translatable, the second is not
   // ar-XB uses "\u200F\u202E...\u202C\u200F"
   std::vector<std::string> expected_translatable = {
-      "000", "111", // default locale
-      "[000 one]", // en-XA
-      "\xE2\x80\x8F\xE2\x80\xAE" "000" "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB
+      "(F)[000 one]",  // en-XA-feminine
+      "(F)\xE2\x80\x8F\xE2\x80\xAE"
+      "000"
+      "\xE2\x80\xAC\xE2\x80\x8F",  // ar-XB-feminine
+      "(M)[000 one]",              // en-XA-masculine
+      "(M)\xE2\x80\x8F\xE2\x80\xAE"
+      "000"
+      "\xE2\x80\xAC\xE2\x80\x8F",  // ar-XB-masculine
+      "(N)[000 one]",              // en-XA-neuter
+      "(N)\xE2\x80\x8F\xE2\x80\xAE"
+      "000"
+      "\xE2\x80\xAC\xE2\x80\x8F",  // ar-XB-neuter
+      "000",                       // default locale
+      "111",                       // default locale
+      "[000 one]",                 // en-XA
+      "\xE2\x80\x8F\xE2\x80\xAE"
+      "000"
+      "\xE2\x80\xAC\xE2\x80\x8F",  // ar-XB
   };
   AssertTranslations(this, "foo", expected_translatable);
   AssertTranslations(this, "foo_donottranslate", expected_translatable);
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 09a8560..8143052 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -16,11 +16,15 @@
 
 #include "compile/PseudolocaleGenerator.h"
 
+#include <stdint.h>
+
 #include <algorithm>
+#include <random>
 
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
+#include "androidfw/ResourceTypes.h"
 #include "androidfw/Util.h"
 #include "compile/Pseudolocalizer.h"
 #include "util/Util.h"
@@ -293,8 +297,85 @@
   Pseudolocalizer localizer_;
 };
 
+class GrammaticalGenderVisitor : public ValueVisitor {
+ public:
+  std::unique_ptr<Value> value;
+  std::unique_ptr<Item> item;
+
+  GrammaticalGenderVisitor(android::StringPool* pool, uint8_t grammaticalInflection)
+      : pool_(pool), grammaticalInflection_(grammaticalInflection) {
+  }
+
+  void Visit(Plural* plural) override {
+    CloningValueTransformer cloner(pool_);
+    std::unique_ptr<Plural> grammatical_gendered = util::make_unique<Plural>();
+    for (size_t i = 0; i < plural->values.size(); i++) {
+      if (plural->values[i]) {
+        GrammaticalGenderVisitor sub_visitor(pool_, grammaticalInflection_);
+        plural->values[i]->Accept(&sub_visitor);
+        if (sub_visitor.item) {
+          grammatical_gendered->values[i] = std::move(sub_visitor.item);
+        } else {
+          grammatical_gendered->values[i] = plural->values[i]->Transform(cloner);
+        }
+      }
+    }
+    grammatical_gendered->SetSource(plural->GetSource());
+    grammatical_gendered->SetWeak(true);
+    value = std::move(grammatical_gendered);
+  }
+
+  std::string AddGrammaticalGenderPrefix(const std::string_view& original_string) {
+    std::string result;
+    switch (grammaticalInflection_) {
+      case android::ResTable_config::GRAMMATICAL_GENDER_MASCULINE:
+        result = std::string("(M)") + std::string(original_string);
+        break;
+      case android::ResTable_config::GRAMMATICAL_GENDER_FEMININE:
+        result = std::string("(F)") + std::string(original_string);
+        break;
+      case android::ResTable_config::GRAMMATICAL_GENDER_NEUTER:
+        result = std::string("(N)") + std::string(original_string);
+        break;
+      default:
+        result = std::string(original_string);
+        break;
+    }
+    return result;
+  }
+
+  void Visit(String* string) override {
+    std::string prefixed_string = AddGrammaticalGenderPrefix(std::string(*string->value));
+    std::unique_ptr<String> grammatical_gendered =
+        util::make_unique<String>(pool_->MakeRef(prefixed_string));
+    grammatical_gendered->SetSource(string->GetSource());
+    grammatical_gendered->SetWeak(true);
+    item = std::move(grammatical_gendered);
+  }
+
+  void Visit(StyledString* string) override {
+    std::string prefixed_string = AddGrammaticalGenderPrefix(std::string(string->value->value));
+    android::StyleString new_string;
+    new_string.str = std::move(prefixed_string);
+    for (const android::StringPool::Span& span : string->value->spans) {
+      new_string.spans.emplace_back(android::Span{*span.name, span.first_char, span.last_char});
+    }
+    std::unique_ptr<StyledString> grammatical_gendered =
+        util::make_unique<StyledString>(pool_->MakeRef(new_string));
+    grammatical_gendered->SetSource(string->GetSource());
+    grammatical_gendered->SetWeak(true);
+    item = std::move(grammatical_gendered);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(GrammaticalGenderVisitor);
+  android::StringPool* pool_;
+  uint8_t grammaticalInflection_;
+};
+
 ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base,
-                                              Pseudolocalizer::Method m) {
+                                              Pseudolocalizer::Method m,
+                                              uint8_t grammaticalInflection) {
   ConfigDescription modified = base;
   switch (m) {
     case Pseudolocalizer::Method::kAccent:
@@ -313,12 +394,64 @@
     default:
       break;
   }
+  modified.grammaticalInflection = grammaticalInflection;
   return modified;
 }
 
+void GrammaticalGender(ResourceConfigValue* original_value,
+                       ResourceConfigValue* localized_config_value, android::StringPool* pool,
+                       ResourceEntry* entry, const Pseudolocalizer::Method method,
+                       uint8_t grammaticalInflection) {
+  GrammaticalGenderVisitor visitor(pool, grammaticalInflection);
+  localized_config_value->value->Accept(&visitor);
+
+  std::unique_ptr<Value> grammatical_gendered_value;
+  if (visitor.value) {
+    grammatical_gendered_value = std::move(visitor.value);
+  } else if (visitor.item) {
+    grammatical_gendered_value = std::move(visitor.item);
+  }
+  if (!grammatical_gendered_value) {
+    return;
+  }
+
+  ConfigDescription config =
+      ModifyConfigForPseudoLocale(original_value->config, method, grammaticalInflection);
+
+  ResourceConfigValue* grammatical_gendered_config_value =
+      entry->FindOrCreateValue(config, original_value->product);
+  if (!grammatical_gendered_config_value->value) {
+    // Only use auto-generated pseudo-localization if none is defined.
+    grammatical_gendered_config_value->value = std::move(grammatical_gendered_value);
+  }
+}
+
+const uint32_t MASK_MASCULINE = 1;  // Bit mask for masculine
+const uint32_t MASK_FEMININE = 2;   // Bit mask for feminine
+const uint32_t MASK_NEUTER = 4;     // Bit mask for neuter
+
+void GrammaticalGenderIfNeeded(ResourceConfigValue* original_value, ResourceConfigValue* new_value,
+                               android::StringPool* pool, ResourceEntry* entry,
+                               const Pseudolocalizer::Method method, uint32_t gender_state) {
+  if (gender_state & MASK_FEMININE) {
+    GrammaticalGender(original_value, new_value, pool, entry, method,
+                      android::ResTable_config::GRAMMATICAL_GENDER_FEMININE);
+  }
+
+  if (gender_state & MASK_MASCULINE) {
+    GrammaticalGender(original_value, new_value, pool, entry, method,
+                      android::ResTable_config::GRAMMATICAL_GENDER_MASCULINE);
+  }
+
+  if (gender_state & MASK_NEUTER) {
+    GrammaticalGender(original_value, new_value, pool, entry, method,
+                      android::ResTable_config::GRAMMATICAL_GENDER_NEUTER);
+  }
+}
+
 void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
                             ResourceConfigValue* original_value, android::StringPool* pool,
-                            ResourceEntry* entry) {
+                            ResourceEntry* entry, uint32_t gender_state, bool gender_flag) {
   Visitor visitor(pool, method);
   original_value->value->Accept(&visitor);
 
@@ -333,8 +466,8 @@
     return;
   }
 
-  ConfigDescription config_with_accent =
-      ModifyConfigForPseudoLocale(original_value->config, method);
+  ConfigDescription config_with_accent = ModifyConfigForPseudoLocale(
+      original_value->config, method, android::ResTable_config::GRAMMATICAL_GENDER_ANY);
 
   ResourceConfigValue* new_config_value =
       entry->FindOrCreateValue(config_with_accent, original_value->product);
@@ -342,6 +475,9 @@
     // Only use auto-generated pseudo-localization if none is defined.
     new_config_value->value = std::move(localized_value);
   }
+  if (gender_flag) {
+    GrammaticalGenderIfNeeded(original_value, new_config_value, pool, entry, method, gender_state);
+  }
 }
 
 // A value is pseudolocalizable if it does not define a locale (or is the default locale) and is
@@ -356,16 +492,71 @@
 
 }  // namespace
 
+bool ParseGenderValuesAndSaveState(const std::string& grammatical_gender_values,
+                                   uint32_t* gender_state, android::IDiagnostics* diag) {
+  std::vector<std::string> values = util::SplitAndLowercase(grammatical_gender_values, ',');
+  for (size_t i = 0; i < values.size(); i++) {
+    if (values[i].length() != 0) {
+      if (values[i] == "f") {
+        *gender_state |= MASK_FEMININE;
+      } else if (values[i] == "m") {
+        *gender_state |= MASK_MASCULINE;
+      } else if (values[i] == "n") {
+        *gender_state |= MASK_NEUTER;
+      } else {
+        diag->Error(android::DiagMessage() << "Invalid grammatical gender value: " << values[i]);
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool ParseGenderRatio(const std::string& grammatical_gender_ratio, float* gender_ratio,
+                      android::IDiagnostics* diag) {
+  const char* input = grammatical_gender_ratio.c_str();
+  char* endPtr;
+  errno = 0;
+  *gender_ratio = strtof(input, &endPtr);
+  if (endPtr == input || *endPtr != '\0' || errno == ERANGE || *gender_ratio < 0 ||
+      *gender_ratio > 1) {
+    diag->Error(android::DiagMessage()
+                << "Invalid grammatical gender ratio: " << grammatical_gender_ratio
+                << ", must be a real number between 0 and 1");
+    return false;
+  }
+  return true;
+}
+
 bool PseudolocaleGenerator::Consume(IAaptContext* context, ResourceTable* table) {
+  uint32_t gender_state = 0;
+  if (!ParseGenderValuesAndSaveState(grammatical_gender_values_, &gender_state,
+                                     context->GetDiagnostics())) {
+    return false;
+  }
+
+  float gender_ratio = 0;
+  if (!ParseGenderRatio(grammatical_gender_ratio_, &gender_ratio, context->GetDiagnostics())) {
+    return false;
+  }
+
+  std::random_device rd;
+  std::mt19937 gen(rd());
+  std::uniform_real_distribution<> distrib(0.0, 1.0);
+
   for (auto& package : table->packages) {
     for (auto& type : package->types) {
       for (auto& entry : type->entries) {
+        bool gender_flag = false;
+        if (distrib(gen) < gender_ratio) {
+          gender_flag = true;
+        }
         std::vector<ResourceConfigValue*> values = entry->FindValuesIf(IsPseudolocalizable);
         for (ResourceConfigValue* value : values) {
           PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, &table->string_pool,
-                                 entry.get());
+                                 entry.get(), gender_state, gender_flag);
           PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, &table->string_pool,
-                                 entry.get());
+                                 entry.get(), gender_state, gender_flag);
         }
       }
     }
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h
index 44e6e3e..ce92008 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.h
+++ b/tools/aapt2/compile/PseudolocaleGenerator.h
@@ -27,8 +27,19 @@
                                                          Pseudolocalizer::Method method,
                                                          android::StringPool* pool);
 
-struct PseudolocaleGenerator : public IResourceTableConsumer {
-  bool Consume(IAaptContext* context, ResourceTable* table) override;
+class PseudolocaleGenerator : public IResourceTableConsumer {
+ public:
+  explicit PseudolocaleGenerator(std::string grammatical_gender_values,
+                                 std::string grammatical_gender_ratio)
+      : grammatical_gender_values_(std::move(grammatical_gender_values)),
+        grammatical_gender_ratio_(std::move(grammatical_gender_ratio)) {
+  }
+
+  bool Consume(IAaptContext* context, ResourceTable* table);
+
+ private:
+  std::string grammatical_gender_values_;
+  std::string grammatical_gender_ratio_;
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
index 2f90cbf..1477ebf 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -197,7 +197,7 @@
   val->SetTranslatable(false);
 
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
-  PseudolocaleGenerator generator;
+  PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
   ASSERT_TRUE(generator.Consume(context.get(), table.get()));
 
   // Normal pseudolocalization should take place.
@@ -249,7 +249,7 @@
   expected->values = {util::make_unique<String>(table->string_pool.MakeRef("[žéŕö one]")),
                       util::make_unique<String>(table->string_pool.MakeRef("[öñé one]"))};
 
-  PseudolocaleGenerator generator;
+  PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
   ASSERT_TRUE(generator.Consume(context.get(), table.get()));
 
   const auto* actual = test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo",
@@ -287,7 +287,7 @@
                                    context->GetDiagnostics()));
   }
 
-  PseudolocaleGenerator generator;
+  PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
   ASSERT_TRUE(generator.Consume(context.get(), table.get()));
 
   StyledString* new_styled_string = test::GetValueForConfig<StyledString>(
@@ -305,4 +305,213 @@
   EXPECT_NE(std::string::npos, new_string->value->find("world"));
 }
 
+TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForString) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
+
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
+  ASSERT_TRUE(generator.Consume(context.get(), table.get()));
+
+  String* locale = test::GetValueForConfig<String>(table.get(), "android:string/foo",
+                                                   test::ParseConfigOrDie("en-rXA"));
+  ASSERT_NE(nullptr, locale);
+
+  // Grammatical gendered string
+  auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
+  config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  String* feminine =
+      test::GetValueForConfig<String>(table.get(), "android:string/foo", config_feminine);
+  ASSERT_NE(nullptr, feminine);
+  EXPECT_EQ(std::string("(F)") + *locale->value, *feminine->value);
+
+  auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
+  config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  String* masculine =
+      test::GetValueForConfig<String>(table.get(), "android:string/foo", config_masculine);
+  ASSERT_NE(nullptr, masculine);
+  EXPECT_EQ(std::string("(M)") + *locale->value, *masculine->value);
+
+  auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
+  config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  String* neuter =
+      test::GetValueForConfig<String>(table.get(), "android:string/foo", config_neuter);
+  ASSERT_NE(nullptr, neuter);
+  EXPECT_EQ(std::string("(N)") + *locale->value, *neuter->value);
+}
+
+TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForPlural) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build();
+  std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+  plural->values = {util::make_unique<String>(table->string_pool.MakeRef("zero")),
+                    util::make_unique<String>(table->string_pool.MakeRef("one"))};
+  ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.pkg:plurals/foo"))
+                                     .SetValue(std::move(plural))
+                                     .Build(),
+                                 context->GetDiagnostics()));
+  PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
+  ASSERT_TRUE(generator.Consume(context.get(), table.get()));
+
+  Plural* actual = test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo",
+                                                   test::ParseConfigOrDie("en-rXA"));
+  ASSERT_NE(nullptr, actual);
+
+  // Grammatical gendered Plural
+  auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
+  config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  Plural* actual_feminine =
+      test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_feminine);
+  for (size_t i = 0; i < actual->values.size(); i++) {
+    if (actual->values[i]) {
+      String* locale = ValueCast<String>(actual->values[i].get());
+      String* feminine = ValueCast<String>(actual_feminine->values[i].get());
+      EXPECT_EQ(std::string("(F)") + *locale->value, *feminine->value);
+    }
+  }
+
+  auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
+  config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  Plural* actual_masculine =
+      test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_masculine);
+  ASSERT_NE(nullptr, actual_masculine);
+  for (size_t i = 0; i < actual->values.size(); i++) {
+    if (actual->values[i]) {
+      String* locale = ValueCast<String>(actual->values[i].get());
+      String* masculine = ValueCast<String>(actual_masculine->values[i].get());
+      EXPECT_EQ(std::string("(M)") + *locale->value, *masculine->value);
+    }
+  }
+
+  auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
+  config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  Plural* actual_neuter =
+      test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_neuter);
+  for (size_t i = 0; i < actual->values.size(); i++) {
+    if (actual->values[i]) {
+      String* locale = ValueCast<String>(actual->values[i].get());
+      String* neuter = ValueCast<String>(actual_neuter->values[i].get());
+      EXPECT_EQ(std::string("(N)") + *locale->value, *neuter->value);
+    }
+  }
+}
+
+TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForStyledString) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build();
+  android::StyleString original_style;
+  original_style.str = "Hello world!";
+  original_style.spans = {android::Span{"i", 1, 10}};
+
+  std::unique_ptr<StyledString> original =
+      util::make_unique<StyledString>(table->string_pool.MakeRef(original_style));
+  ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/foo"))
+                                     .SetValue(std::move(original))
+                                     .Build(),
+                                 context->GetDiagnostics()));
+  PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
+  ASSERT_TRUE(generator.Consume(context.get(), table.get()));
+
+  StyledString* locale = test::GetValueForConfig<StyledString>(table.get(), "android:string/foo",
+                                                               test::ParseConfigOrDie("en-rXA"));
+  ASSERT_NE(nullptr, locale);
+  EXPECT_EQ(1, locale->value->spans.size());
+  EXPECT_EQ(std::string("i"), *locale->value->spans[0].name);
+
+  // Grammatical gendered StyledString
+  auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
+  config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  StyledString* feminine =
+      test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_feminine);
+  ASSERT_NE(nullptr, feminine);
+  EXPECT_EQ(1, feminine->value->spans.size());
+  EXPECT_EQ(std::string("i"), *feminine->value->spans[0].name);
+  EXPECT_EQ(std::string("(F)") + locale->value->value, feminine->value->value);
+
+  auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
+  config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  StyledString* masculine =
+      test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_masculine);
+  ASSERT_NE(nullptr, masculine);
+  EXPECT_EQ(1, masculine->value->spans.size());
+  EXPECT_EQ(std::string("i"), *masculine->value->spans[0].name);
+  EXPECT_EQ(std::string("(M)") + locale->value->value, masculine->value->value);
+
+  auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
+  config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  StyledString* neuter =
+      test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_neuter);
+  ASSERT_NE(nullptr, neuter);
+  EXPECT_EQ(1, neuter->value->spans.size());
+  EXPECT_EQ(std::string("i"), *neuter->value->spans[0].name);
+  EXPECT_EQ(std::string("(N)") + locale->value->value, neuter->value->value);
+}
+
+TEST(PseudolocaleGeneratorTest, GrammaticalGenderForCertainValues) {
+  // single gender value
+  std::unique_ptr<ResourceTable> table_0 =
+      test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
+
+  std::unique_ptr<IAaptContext> context_0 = test::ContextBuilder().Build();
+  PseudolocaleGenerator generator_0(std::string("f"), std::string("1.0"));
+  ASSERT_TRUE(generator_0.Consume(context_0.get(), table_0.get()));
+
+  String* locale_0 = test::GetValueForConfig<String>(table_0.get(), "android:string/foo",
+                                                     test::ParseConfigOrDie("en-rXA"));
+  ASSERT_NE(nullptr, locale_0);
+
+  auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
+  config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  String* feminine_0 =
+      test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_feminine);
+  ASSERT_NE(nullptr, feminine_0);
+  EXPECT_EQ(std::string("(F)") + *locale_0->value, *feminine_0->value);
+
+  auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
+  config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  String* masculine_0 =
+      test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_masculine);
+  EXPECT_EQ(nullptr, masculine_0);
+
+  auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
+  config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
+  String* neuter_0 =
+      test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_neuter);
+  EXPECT_EQ(nullptr, neuter_0);
+
+  // multiple gender values
+  std::unique_ptr<ResourceTable> table_1 =
+      test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
+
+  std::unique_ptr<IAaptContext> context_1 = test::ContextBuilder().Build();
+  PseudolocaleGenerator generator_1(std::string("f,n"), std::string("1.0"));
+  ASSERT_TRUE(generator_1.Consume(context_1.get(), table_1.get()));
+
+  String* locale_1 = test::GetValueForConfig<String>(table_1.get(), "android:string/foo",
+                                                     test::ParseConfigOrDie("en-rXA"));
+  ASSERT_NE(nullptr, locale_1);
+
+  String* feminine_1 =
+      test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_feminine);
+  ASSERT_NE(nullptr, feminine_1);
+  EXPECT_EQ(std::string("(F)") + *locale_1->value, *feminine_1->value);
+
+  String* masculine_1 =
+      test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_masculine);
+  EXPECT_EQ(nullptr, masculine_1);
+
+  String* neuter_1 =
+      test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_neuter);
+  ASSERT_NE(nullptr, neuter_1);
+  EXPECT_EQ(std::string("(N)") + *locale_1->value, *neuter_1->value);
+
+  // invalid gender value
+  std::unique_ptr<ResourceTable> table_2 =
+      test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
+
+  std::unique_ptr<IAaptContext> context_2 = test::ContextBuilder().Build();
+  PseudolocaleGenerator generator_2(std::string("invald,"), std::string("1.0"));
+  ASSERT_FALSE(generator_2.Consume(context_2.get(), table_2.get()));
+}
+
 }  // namespace aapt