Merge "[flexiglass] Don't dim screen or go to sleep while typing a password." into main
diff --git a/Android.bp b/Android.bp
index 895ef98..a402c576 100644
--- a/Android.bp
+++ b/Android.bp
@@ -249,6 +249,7 @@
         "android.se.omapi-V1-java",
         "android.system.suspend.control.internal-java",
         "devicepolicyprotosnano",
+        "ImmutabilityAnnotation",
 
         "com.android.sysprop.init",
         "com.android.sysprop.localization",
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index 29a8dfa..a4174ee 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -1,13 +1,3 @@
-// b/305195721
-android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:7428: lint: Unresolved link/see tag "android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Drawables DevicePolicyResources.Drawables" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyResourcesManager.java:179: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyResourcesManager [101]
-
 // b/303477132
 android/app/appsearch/AppSearchSchema.java:402: lint: Unresolved link/see tag "#getIndexableNestedProperties()" in android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder [101]
 android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.AppSearchSession [101]
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index 9d8f1f4..01486c0 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -16,19 +16,10 @@
 
 package com.android.commands.uinput;
 
-import android.util.JsonReader;
-import android.util.JsonToken;
-import android.util.Log;
 import android.util.SparseArray;
 
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
 import java.util.Objects;
-import java.util.function.Function;
-import java.util.stream.IntStream;
 
 import src.com.android.commands.uinput.InputAbsInfo;
 
@@ -40,46 +31,44 @@
     private static final String TAG = "UinputEvent";
 
     enum Command {
-        REGISTER("register"),
-        DELAY("delay"),
-        INJECT("inject"),
-        SYNC("sync");
-
-        final String mCommandName;
-
-        Command(String command) {
-            mCommandName = command;
-        }
+        REGISTER,
+        DELAY,
+        INJECT,
+        SYNC,
     }
 
-    private static final int EV_KEY = 0x01;
-    private static final int EV_REL = 0x02;
-    private static final int EV_ABS = 0x03;
-    private static final int EV_MSC = 0x04;
-    private static final int EV_SW = 0x05;
-    private static final int EV_LED = 0x11;
-    private static final int EV_SND = 0x12;
-    private static final int EV_FF = 0x15;
+    // Constants representing evdev event types, from include/uapi/linux/input-event-codes.h in the
+    // kernel.
+    public static final int EV_KEY = 0x01;
+    public static final int EV_REL = 0x02;
+    public static final int EV_ABS = 0x03;
+    public static final int EV_MSC = 0x04;
+    public static final int EV_SW = 0x05;
+    public static final int EV_LED = 0x11;
+    public static final int EV_SND = 0x12;
+    public static final int EV_FF = 0x15;
 
-    private enum UinputControlCode {
-        UI_SET_EVBIT("UI_SET_EVBIT", 100),
-        UI_SET_KEYBIT("UI_SET_KEYBIT", 101),
-        UI_SET_RELBIT("UI_SET_RELBIT", 102),
-        UI_SET_ABSBIT("UI_SET_ABSBIT", 103),
-        UI_SET_MSCBIT("UI_SET_MSCBIT", 104),
-        UI_SET_LEDBIT("UI_SET_LEDBIT", 105),
-        UI_SET_SNDBIT("UI_SET_SNDBIT", 106),
-        UI_SET_FFBIT("UI_SET_FFBIT", 107),
-        UI_SET_SWBIT("UI_SET_SWBIT", 109),
-        UI_SET_PROPBIT("UI_SET_PROPBIT", 110);
+    public enum UinputControlCode {
+        UI_SET_EVBIT(100),
+        UI_SET_KEYBIT(101),
+        UI_SET_RELBIT(102),
+        UI_SET_ABSBIT(103),
+        UI_SET_MSCBIT(104),
+        UI_SET_LEDBIT(105),
+        UI_SET_SNDBIT(106),
+        UI_SET_FFBIT(107),
+        UI_SET_SWBIT(109),
+        UI_SET_PROPBIT(110);
 
-        final String mName;
-        final int mValue;
+        private final int mValue;
 
-        UinputControlCode(String name, int value) {
-            this.mName = name;
+        UinputControlCode(int value) {
             this.mValue = value;
         }
+
+        public int getValue() {
+            return mValue;
+        }
     }
 
     // These constants come from "include/uapi/linux/input.h" in the kernel
@@ -104,9 +93,9 @@
     private Bus mBus;
     private int[] mInjections;
     private SparseArray<int[]> mConfiguration;
-    private int mDuration;
+    private int mDurationMillis;
     private int mFfEffectsMax = 0;
-    private String mInputport;
+    private String mInputPort;
     private SparseArray<InputAbsInfo> mAbsInfo;
     private String mSyncToken;
 
@@ -138,12 +127,20 @@
         return mInjections;
     }
 
+    /**
+     * Returns a {@link SparseArray} describing the event codes that should be registered for the
+     * device. The keys are uinput ioctl codes (such as those returned from {@link
+     * UinputControlCode#getValue()}, while the values are arrays of event codes to be enabled with
+     * those ioctls. For example, key 101 (corresponding to {@link UinputControlCode#UI_SET_KEYBIT})
+     * could have values 0x110 ({@code BTN_LEFT}, 0x111 ({@code BTN_RIGHT}), and 0x112
+     * ({@code BTN_MIDDLE}).
+     */
     public SparseArray<int[]> getConfiguration() {
         return mConfiguration;
     }
 
-    public int getDuration() {
-        return mDuration;
+    public int getDurationMillis() {
+        return mDurationMillis;
     }
 
     public int getFfEffectsMax() {
@@ -155,7 +152,7 @@
     }
 
     public String getPort() {
-        return mInputport;
+        return mInputPort;
     }
 
     public String getSyncToken() {
@@ -174,13 +171,13 @@
             + ", bus=" + mBus
             + ", events=" + Arrays.toString(mInjections)
             + ", configuration=" + mConfiguration
-            + ", duration=" + mDuration
+            + ", duration=" + mDurationMillis + "ms"
             + ", ff_effects_max=" + mFfEffectsMax
-            + ", port=" + mInputport
+            + ", port=" + mInputPort
             + "}";
     }
 
-    private static class Builder {
+    public static class Builder {
         private Event mEvent;
 
         Builder() {
@@ -191,15 +188,8 @@
             mEvent.mId = id;
         }
 
-        private void setCommand(String command) {
-            Objects.requireNonNull(command, "Command must not be null");
-            for (Command cmd : Command.values()) {
-                if (cmd.mCommandName.equals(command)) {
-                    mEvent.mCommand = cmd;
-                    return;
-                }
-            }
-            throw new IllegalStateException("Unrecognized command: " + command);
+        public void setCommand(String command) {
+            mEvent.mCommand = Command.valueOf(command.toUpperCase());
         }
 
         public void setName(String name) {
@@ -210,6 +200,12 @@
             mEvent.mInjections = events;
         }
 
+        /**
+         * Sets the event codes that should be registered with a {@code register} command.
+         *
+         * @param configuration An array of ioctls and event codes, as described at
+         *                      {@link Event#getConfiguration()}.
+         */
         public void setConfiguration(SparseArray<int[]> configuration) {
             mEvent.mConfiguration = configuration;
         }
@@ -226,8 +222,8 @@
             mEvent.mBus = bus;
         }
 
-        public void setDuration(int duration) {
-            mEvent.mDuration = duration;
+        public void setDurationMillis(int durationMillis) {
+            mEvent.mDurationMillis = durationMillis;
         }
 
         public void setFfEffectsMax(int ffEffectsMax) {
@@ -238,8 +234,8 @@
             mEvent.mAbsInfo = absInfo;
         }
 
-        public void setInputport(String port) {
-            mEvent.mInputport = port;
+        public void setInputPort(String port) {
+            mEvent.mInputPort = port;
         }
 
         public void setSyncToken(String syncToken) {
@@ -260,7 +256,7 @@
                     }
                 }
                 case DELAY -> {
-                    if (mEvent.mDuration <= 0) {
+                    if (mEvent.mDurationMillis <= 0) {
                         throw new IllegalStateException("Delay has missing or invalid duration");
                     }
                 }
@@ -278,343 +274,4 @@
             return mEvent;
         }
     }
-
-    /**
-     *  A class that parses the JSON event format from an input stream to build device events.
-     */
-    public static class Reader {
-        private JsonReader mReader;
-
-        public Reader(InputStreamReader in) {
-            mReader = new JsonReader(in);
-            mReader.setLenient(true);
-        }
-
-        /**
-         * Get next event entry from JSON file reader.
-         */
-        public Event getNextEvent() throws IOException {
-            Event e = null;
-            while (e == null && mReader.peek() != JsonToken.END_DOCUMENT) {
-                Event.Builder eb = new Event.Builder();
-                try {
-                    mReader.beginObject();
-                    while (mReader.hasNext()) {
-                        String name = mReader.nextName();
-                        switch (name) {
-                            case "id":
-                                eb.setId(readInt());
-                                break;
-                            case "command":
-                                eb.setCommand(mReader.nextString());
-                                break;
-                            case "name":
-                                eb.setName(mReader.nextString());
-                                break;
-                            case "vid":
-                                eb.setVid(readInt());
-                                break;
-                            case "pid":
-                                eb.setPid(readInt());
-                                break;
-                            case "bus":
-                                eb.setBus(readBus());
-                                break;
-                            case "events":
-                                int[] injections = readInjectedEvents().stream()
-                                        .mapToInt(Integer::intValue).toArray();
-                                eb.setInjections(injections);
-                                break;
-                            case "configuration":
-                                eb.setConfiguration(readConfiguration());
-                                break;
-                            case "ff_effects_max":
-                                eb.setFfEffectsMax(readInt());
-                                break;
-                            case "abs_info":
-                                eb.setAbsInfo(readAbsInfoArray());
-                                break;
-                            case "duration":
-                                eb.setDuration(readInt());
-                                break;
-                            case "port":
-                                eb.setInputport(mReader.nextString());
-                                break;
-                            case "syncToken":
-                                eb.setSyncToken(mReader.nextString());
-                                break;
-                            default:
-                                mReader.skipValue();
-                        }
-                    }
-                    mReader.endObject();
-                } catch (IllegalStateException ex) {
-                    error("Error reading in object, ignoring.", ex);
-                    consumeRemainingElements();
-                    mReader.endObject();
-                    continue;
-                }
-                e = eb.build();
-            }
-
-            return e;
-        }
-
-        private ArrayList<Integer> readInjectedEvents() throws IOException {
-            ArrayList<Integer> data = new ArrayList<>();
-            try {
-                mReader.beginArray();
-                while (mReader.hasNext()) {
-                    // Read events in groups of three, because we expect an event type, event code,
-                    // and event value.
-                    final int type = readEvdevEventType();
-                    data.add(type);
-                    data.add(readEvdevEventCode(type));
-                    data.add(readInt());
-                }
-                mReader.endArray();
-            } catch (IllegalStateException | NumberFormatException e) {
-                consumeRemainingElements();
-                mReader.endArray();
-                throw new IllegalStateException("Encountered malformed data.", e);
-            }
-            return data;
-        }
-
-        private int readValueAsInt(Function<String, Integer> stringToInt) throws IOException {
-            switch (mReader.peek()) {
-                case NUMBER: {
-                    return mReader.nextInt();
-                }
-                case STRING: {
-                    final var str = mReader.nextString();
-                    try {
-                        // Attempt to first parse the value as an int.
-                        return Integer.decode(str);
-                    } catch (NumberFormatException e) {
-                        // Then fall back to the supplied function.
-                        return stringToInt.apply(str);
-                    }
-                }
-                default: {
-                    throw new IllegalStateException(
-                            "Encountered malformed data. Expected int or string.");
-                }
-            }
-        }
-
-        private int readInt() throws IOException {
-            return readValueAsInt((str) -> {
-                throw new IllegalStateException("Encountered malformed data. Expected int.");
-            });
-        }
-
-        private Bus readBus() throws IOException {
-            String val = mReader.nextString();
-            return Bus.valueOf(val.toUpperCase());
-        }
-
-        private SparseArray<int[]> readConfiguration()
-                throws IllegalStateException, IOException {
-            SparseArray<int[]> configuration = new SparseArray<>();
-            try {
-                mReader.beginArray();
-                while (mReader.hasNext()) {
-                    UinputControlCode controlCode = null;
-                    IntStream data = null;
-                    mReader.beginObject();
-                    while (mReader.hasNext()) {
-                        String name = mReader.nextName();
-                        switch (name) {
-                            case "type":
-                                controlCode = readUinputControlCode();
-                                break;
-                            case "data":
-                                Objects.requireNonNull(controlCode,
-                                        "Configuration 'type' must be specified before 'data'.");
-                                data = readDataForControlCode(controlCode)
-                                        .stream().mapToInt(Integer::intValue);
-                                break;
-                            default:
-                                consumeRemainingElements();
-                                mReader.endObject();
-                                throw new IllegalStateException(
-                                        "Invalid key in device configuration: " + name);
-                        }
-                    }
-                    mReader.endObject();
-                    if (controlCode != null && data != null) {
-                        final int[] existing = configuration.get(controlCode.mValue);
-                        configuration.put(controlCode.mValue, existing == null ? data.toArray()
-                                : IntStream.concat(IntStream.of(existing), data).toArray());
-                    }
-                }
-                mReader.endArray();
-            } catch (IllegalStateException | NumberFormatException e) {
-                consumeRemainingElements();
-                mReader.endArray();
-                throw new IllegalStateException("Encountered malformed data.", e);
-            }
-            return configuration;
-        }
-
-        private UinputControlCode readUinputControlCode() throws IOException {
-            var code = readValueAsInt((controlTypeStr) -> {
-                for (UinputControlCode controlCode : UinputControlCode.values()) {
-                    if (controlCode.mName.equals(controlTypeStr)) {
-                        return controlCode.mValue;
-                    }
-                }
-                return -1;
-            });
-            for (UinputControlCode controlCode : UinputControlCode.values()) {
-                if (controlCode.mValue == code) {
-                    return controlCode;
-                }
-            }
-            return null;
-        }
-
-        private List<Integer> readDataForControlCode(
-                UinputControlCode controlCode) throws IOException {
-            return switch (controlCode) {
-                case UI_SET_EVBIT -> readArrayAsInts(this::readEvdevEventType);
-                case UI_SET_KEYBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_KEY));
-                case UI_SET_RELBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_REL));
-                case UI_SET_ABSBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_ABS));
-                case UI_SET_MSCBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_MSC));
-                case UI_SET_LEDBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_LED));
-                case UI_SET_SNDBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_SND));
-                case UI_SET_FFBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_FF));
-                case UI_SET_SWBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_SW));
-                case UI_SET_PROPBIT -> readArrayAsInts(this::readEvdevInputProp);
-            };
-        }
-
-        interface IntValueReader {
-            int readNextValue() throws IOException;
-        }
-
-        private ArrayList<Integer> readArrayAsInts(
-                IntValueReader nextValueReader) throws IOException {
-            ArrayList<Integer> data = new ArrayList<>();
-            try {
-                mReader.beginArray();
-                while (mReader.hasNext()) {
-                    data.add(nextValueReader.readNextValue());
-                }
-                mReader.endArray();
-            } catch (IllegalStateException | NumberFormatException e) {
-                consumeRemainingElements();
-                mReader.endArray();
-                throw new IllegalStateException("Encountered malformed data.", e);
-            }
-            return data;
-        }
-
-        private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException {
-            InputAbsInfo absInfo = new InputAbsInfo();
-            try {
-                mReader.beginObject();
-                while (mReader.hasNext()) {
-                    String name = mReader.nextName();
-                    switch (name) {
-                        case "value":
-                            absInfo.value = readInt();
-                            break;
-                        case "minimum":
-                            absInfo.minimum = readInt();
-                            break;
-                        case "maximum":
-                            absInfo.maximum = readInt();
-                            break;
-                        case "fuzz":
-                            absInfo.fuzz = readInt();
-                            break;
-                        case "flat":
-                            absInfo.flat = readInt();
-                            break;
-                        case "resolution":
-                            absInfo.resolution = readInt();
-                            break;
-                        default:
-                            consumeRemainingElements();
-                            mReader.endObject();
-                            throw new IllegalStateException("Invalid key in abs info: " + name);
-                    }
-                }
-                mReader.endObject();
-            } catch (IllegalStateException | NumberFormatException e) {
-                consumeRemainingElements();
-                mReader.endObject();
-                throw new IllegalStateException("Encountered malformed data.", e);
-            }
-            return absInfo;
-        }
-
-        private SparseArray<InputAbsInfo> readAbsInfoArray()
-                throws IllegalStateException, IOException {
-            SparseArray<InputAbsInfo> infoArray = new SparseArray<>();
-            try {
-                mReader.beginArray();
-                while (mReader.hasNext()) {
-                    int type = 0;
-                    InputAbsInfo absInfo = null;
-                    mReader.beginObject();
-                    while (mReader.hasNext()) {
-                        String name = mReader.nextName();
-                        switch (name) {
-                            case "code":
-                                type = readEvdevEventCode(EV_ABS);
-                                break;
-                            case "info":
-                                absInfo = readAbsInfo();
-                                break;
-                            default:
-                                consumeRemainingElements();
-                                mReader.endObject();
-                                throw new IllegalStateException("Invalid key in abs info array: "
-                                        + name);
-                        }
-                    }
-                    mReader.endObject();
-                    if (absInfo != null) {
-                        infoArray.put(type, absInfo);
-                    }
-                }
-                mReader.endArray();
-            } catch (IllegalStateException | NumberFormatException e) {
-                consumeRemainingElements();
-                mReader.endArray();
-                throw new IllegalStateException("Encountered malformed data.", e);
-            }
-            return infoArray;
-        }
-
-        private int readEvdevEventType() throws IOException {
-            return readValueAsInt(Device::getEvdevEventTypeByLabel);
-        }
-
-        private int readEvdevEventCode(int type) throws IOException {
-            return readValueAsInt((str) -> Device.getEvdevEventCodeByLabel(type, str));
-        }
-
-        private int readEvdevInputProp() throws IOException {
-            return readValueAsInt(Device::getEvdevInputPropByLabel);
-        }
-
-        private void consumeRemainingElements() throws IOException {
-            while (mReader.hasNext()) {
-                mReader.skipValue();
-            }
-        }
-    }
-
-    private static void error(String msg, Exception e) {
-        System.out.println(msg);
-        Log.e(TAG, msg);
-        if (e != null) {
-            Log.e(TAG, Log.getStackTraceString(e));
-        }
-    }
 }
diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
new file mode 100644
index 0000000..53d0be8
--- /dev/null
+++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
@@ -0,0 +1,333 @@
+/*
+ * 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.commands.uinput;
+
+import android.util.JsonReader;
+import android.util.JsonToken;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+
+import src.com.android.commands.uinput.InputAbsInfo;
+
+/**
+ * A class that parses the JSON-like event format described in the README to build {@link Event}s.
+ */
+public class JsonStyleParser {
+    private static final String TAG = "UinputJsonStyleParser";
+
+    private JsonReader mReader;
+
+    public JsonStyleParser(InputStreamReader in) {
+        mReader = new JsonReader(in);
+        mReader.setLenient(true);
+    }
+
+    /**
+     * Gets the next event entry from the JSON file.
+     */
+    public Event getNextEvent() throws IOException {
+        Event e = null;
+        while (e == null && mReader.peek() != JsonToken.END_DOCUMENT) {
+            Event.Builder eb = new Event.Builder();
+            try {
+                mReader.beginObject();
+                while (mReader.hasNext()) {
+                    String name = mReader.nextName();
+                    switch (name) {
+                        case "id" -> eb.setId(readInt());
+                        case "command" -> eb.setCommand(mReader.nextString());
+                        case "name" -> eb.setName(mReader.nextString());
+                        case "vid" -> eb.setVid(readInt());
+                        case "pid" -> eb.setPid(readInt());
+                        case "bus" -> eb.setBus(readBus());
+                        case "events" -> {
+                            int[] injections = readInjectedEvents().stream()
+                                    .mapToInt(Integer::intValue).toArray();
+                            eb.setInjections(injections);
+                        }
+                        case "configuration" -> eb.setConfiguration(readConfiguration());
+                        case "ff_effects_max" -> eb.setFfEffectsMax(readInt());
+                        case "abs_info" -> eb.setAbsInfo(readAbsInfoArray());
+                        case "duration" -> eb.setDurationMillis(readInt());
+                        case "port" -> eb.setInputPort(mReader.nextString());
+                        case "syncToken" -> eb.setSyncToken(mReader.nextString());
+                        default -> mReader.skipValue();
+                    }
+                }
+                mReader.endObject();
+            } catch (IllegalStateException ex) {
+                error("Error reading in object, ignoring.", ex);
+                consumeRemainingElements();
+                mReader.endObject();
+                continue;
+            }
+            e = eb.build();
+        }
+
+        return e;
+    }
+
+    private ArrayList<Integer> readInjectedEvents() throws IOException {
+        ArrayList<Integer> data = new ArrayList<>();
+        try {
+            mReader.beginArray();
+            while (mReader.hasNext()) {
+                // Read events in groups of three, because we expect an event type, event code,
+                // and event value.
+                final int type = readEvdevEventType();
+                data.add(type);
+                data.add(readEvdevEventCode(type));
+                data.add(readInt());
+            }
+            mReader.endArray();
+        } catch (IllegalStateException | NumberFormatException e) {
+            consumeRemainingElements();
+            mReader.endArray();
+            throw new IllegalStateException("Encountered malformed data.", e);
+        }
+        return data;
+    }
+
+    private int readValueAsInt(Function<String, Integer> stringToInt) throws IOException {
+        switch (mReader.peek()) {
+            case NUMBER: {
+                return mReader.nextInt();
+            }
+            case STRING: {
+                final var str = mReader.nextString();
+                try {
+                    // Attempt to first parse the value as an int.
+                    return Integer.decode(str);
+                } catch (NumberFormatException e) {
+                    // Then fall back to the supplied function.
+                    return stringToInt.apply(str);
+                }
+            }
+            default: {
+                throw new IllegalStateException(
+                        "Encountered malformed data. Expected int or string.");
+            }
+        }
+    }
+
+    private int readInt() throws IOException {
+        return readValueAsInt((str) -> {
+            throw new IllegalStateException("Encountered malformed data. Expected int.");
+        });
+    }
+
+    private Event.Bus readBus() throws IOException {
+        String val = mReader.nextString();
+        return Event.Bus.valueOf(val.toUpperCase());
+    }
+
+    private SparseArray<int[]> readConfiguration()
+            throws IllegalStateException, IOException {
+        SparseArray<int[]> configuration = new SparseArray<>();
+        try {
+            mReader.beginArray();
+            while (mReader.hasNext()) {
+                Event.UinputControlCode controlCode = null;
+                IntStream data = null;
+                mReader.beginObject();
+                while (mReader.hasNext()) {
+                    String name = mReader.nextName();
+                    switch (name) {
+                        case "type":
+                            controlCode = readUinputControlCode();
+                            break;
+                        case "data":
+                            Objects.requireNonNull(controlCode,
+                                    "Configuration 'type' must be specified before 'data'.");
+                            data = readDataForControlCode(controlCode)
+                                    .stream().mapToInt(Integer::intValue);
+                            break;
+                        default:
+                            consumeRemainingElements();
+                            mReader.endObject();
+                            throw new IllegalStateException(
+                                    "Invalid key in device configuration: " + name);
+                    }
+                }
+                mReader.endObject();
+                if (controlCode != null && data != null) {
+                    final int[] existing = configuration.get(controlCode.getValue());
+                    configuration.put(controlCode.getValue(), existing == null ? data.toArray()
+                            : IntStream.concat(IntStream.of(existing), data).toArray());
+                }
+            }
+            mReader.endArray();
+        } catch (IllegalStateException | NumberFormatException e) {
+            consumeRemainingElements();
+            mReader.endArray();
+            throw new IllegalStateException("Encountered malformed data.", e);
+        }
+        return configuration;
+    }
+
+    private Event.UinputControlCode readUinputControlCode() throws IOException {
+        var code = readValueAsInt((controlTypeStr) -> {
+            try {
+                return Event.UinputControlCode.valueOf(controlTypeStr).getValue();
+            } catch (IllegalArgumentException ex) {
+                return -1;
+            }
+        });
+        for (Event.UinputControlCode controlCode : Event.UinputControlCode.values()) {
+            if (controlCode.getValue() == code) {
+                return controlCode;
+            }
+        }
+        return null;
+    }
+
+    private List<Integer> readDataForControlCode(
+            Event.UinputControlCode controlCode) throws IOException {
+        return switch (controlCode) {
+            case UI_SET_EVBIT -> readArrayAsInts(this::readEvdevEventType);
+            case UI_SET_KEYBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_KEY));
+            case UI_SET_RELBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_REL));
+            case UI_SET_ABSBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_ABS));
+            case UI_SET_MSCBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_MSC));
+            case UI_SET_LEDBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_LED));
+            case UI_SET_SNDBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_SND));
+            case UI_SET_FFBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_FF));
+            case UI_SET_SWBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_SW));
+            case UI_SET_PROPBIT -> readArrayAsInts(this::readEvdevInputProp);
+        };
+    }
+
+    interface IntValueReader {
+        int readNextValue() throws IOException;
+    }
+
+    private ArrayList<Integer> readArrayAsInts(
+            IntValueReader nextValueReader) throws IOException {
+        ArrayList<Integer> data = new ArrayList<>();
+        try {
+            mReader.beginArray();
+            while (mReader.hasNext()) {
+                data.add(nextValueReader.readNextValue());
+            }
+            mReader.endArray();
+        } catch (IllegalStateException | NumberFormatException e) {
+            consumeRemainingElements();
+            mReader.endArray();
+            throw new IllegalStateException("Encountered malformed data.", e);
+        }
+        return data;
+    }
+
+    private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException {
+        InputAbsInfo absInfo = new InputAbsInfo();
+        try {
+            mReader.beginObject();
+            while (mReader.hasNext()) {
+                String name = mReader.nextName();
+                switch (name) {
+                    case "value" -> absInfo.value = readInt();
+                    case "minimum" -> absInfo.minimum = readInt();
+                    case "maximum" -> absInfo.maximum = readInt();
+                    case "fuzz" -> absInfo.fuzz = readInt();
+                    case "flat" -> absInfo.flat = readInt();
+                    case "resolution" -> absInfo.resolution = readInt();
+                    default -> {
+                        consumeRemainingElements();
+                        mReader.endObject();
+                        throw new IllegalStateException("Invalid key in abs info: " + name);
+                    }
+                }
+            }
+            mReader.endObject();
+        } catch (IllegalStateException | NumberFormatException e) {
+            consumeRemainingElements();
+            mReader.endObject();
+            throw new IllegalStateException("Encountered malformed data.", e);
+        }
+        return absInfo;
+    }
+
+    private SparseArray<InputAbsInfo> readAbsInfoArray()
+            throws IllegalStateException, IOException {
+        SparseArray<InputAbsInfo> infoArray = new SparseArray<>();
+        try {
+            mReader.beginArray();
+            while (mReader.hasNext()) {
+                int type = 0;
+                InputAbsInfo absInfo = null;
+                mReader.beginObject();
+                while (mReader.hasNext()) {
+                    String name = mReader.nextName();
+                    switch (name) {
+                        case "code" -> type = readEvdevEventCode(Event.EV_ABS);
+                        case "info" -> absInfo = readAbsInfo();
+                        default -> {
+                            consumeRemainingElements();
+                            mReader.endObject();
+                            throw new IllegalStateException("Invalid key in abs info array: "
+                                    + name);
+                        }
+                    }
+                }
+                mReader.endObject();
+                if (absInfo != null) {
+                    infoArray.put(type, absInfo);
+                }
+            }
+            mReader.endArray();
+        } catch (IllegalStateException | NumberFormatException e) {
+            consumeRemainingElements();
+            mReader.endArray();
+            throw new IllegalStateException("Encountered malformed data.", e);
+        }
+        return infoArray;
+    }
+
+    private int readEvdevEventType() throws IOException {
+        return readValueAsInt(Device::getEvdevEventTypeByLabel);
+    }
+
+    private int readEvdevEventCode(int type) throws IOException {
+        return readValueAsInt((str) -> Device.getEvdevEventCodeByLabel(type, str));
+    }
+
+    private int readEvdevInputProp() throws IOException {
+        return readValueAsInt(Device::getEvdevInputPropByLabel);
+    }
+
+    private void consumeRemainingElements() throws IOException {
+        while (mReader.hasNext()) {
+            mReader.skipValue();
+        }
+    }
+
+    private static void error(String msg, Exception e) {
+        System.out.println(msg);
+        Log.e(TAG, msg);
+        if (e != null) {
+            Log.e(TAG, Log.getStackTraceString(e));
+        }
+    }
+}
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
index 47b7a354..fe76abb 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -35,7 +35,7 @@
 public class Uinput {
     private static final String TAG = "UINPUT";
 
-    private final Event.Reader mReader;
+    private final JsonStyleParser mParser;
     private final SparseArray<Device> mDevices;
 
     private static void usage() {
@@ -74,7 +74,7 @@
     private Uinput(InputStream in) {
         mDevices = new SparseArray<Device>();
         try {
-            mReader = new Event.Reader(new InputStreamReader(in, "UTF-8"));
+            mParser = new JsonStyleParser(new InputStreamReader(in, "UTF-8"));
         } catch (UnsupportedEncodingException e) {
             throw new RuntimeException(e);
         }
@@ -83,7 +83,7 @@
     private void run() {
         try {
             Event e = null;
-            while ((e = mReader.getNextEvent()) != null) {
+            while ((e = mParser.getNextEvent()) != null) {
                 process(e);
             }
         } catch (IOException ex) {
@@ -111,7 +111,7 @@
             case REGISTER ->
                     error("Device id=" + e.getId() + " is already registered. Ignoring event.");
             case INJECT -> d.injectEvent(e.getInjections());
-            case DELAY -> d.addDelay(e.getDuration());
+            case DELAY -> d.addDelay(e.getDurationMillis());
             case SYNC -> d.syncEvent(e.getSyncToken());
         }
     }
diff --git a/core/api/current.txt b/core/api/current.txt
index d80b5cb..91e3a3a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5316,21 +5316,23 @@
     method public android.net.Uri getConditionId();
     method @Nullable public android.content.ComponentName getConfigurationActivity();
     method public long getCreationTime();
+    method @FlaggedApi("android.app.modes_api") @Nullable public android.service.notification.ZenDeviceEffects getDeviceEffects();
     method @FlaggedApi("android.app.modes_api") @DrawableRes public int getIconResId();
     method public int getInterruptionFilter();
     method public String getName();
     method public android.content.ComponentName getOwner();
     method @FlaggedApi("android.app.modes_api") @Nullable public String getTriggerDescription();
     method @FlaggedApi("android.app.modes_api") public int getType();
-    method public android.service.notification.ZenPolicy getZenPolicy();
+    method @Nullable public android.service.notification.ZenPolicy getZenPolicy();
     method public boolean isEnabled();
     method @FlaggedApi("android.app.modes_api") public boolean isManualInvocationAllowed();
     method public void setConditionId(android.net.Uri);
     method public void setConfigurationActivity(@Nullable android.content.ComponentName);
+    method @FlaggedApi("android.app.modes_api") public void setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects);
     method public void setEnabled(boolean);
     method public void setInterruptionFilter(int);
     method public void setName(String);
-    method public void setZenPolicy(android.service.notification.ZenPolicy);
+    method public void setZenPolicy(@Nullable android.service.notification.ZenPolicy);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.AutomaticZenRule> CREATOR;
     field @FlaggedApi("android.app.modes_api") public static final int TYPE_BEDTIME = 3; // 0x3
@@ -5350,6 +5352,7 @@
     method @NonNull public android.app.AutomaticZenRule build();
     method @NonNull public android.app.AutomaticZenRule.Builder setConditionId(@NonNull android.net.Uri);
     method @NonNull public android.app.AutomaticZenRule.Builder setConfigurationActivity(@Nullable android.content.ComponentName);
+    method @NonNull public android.app.AutomaticZenRule.Builder setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects);
     method @NonNull public android.app.AutomaticZenRule.Builder setEnabled(boolean);
     method @NonNull public android.app.AutomaticZenRule.Builder setIconResId(@DrawableRes int);
     method @NonNull public android.app.AutomaticZenRule.Builder setInterruptionFilter(int);
@@ -7861,6 +7864,7 @@
     field public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY = "persistentPreferredActivity";
     field public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken";
     field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled";
+    field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
     field public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY = "userControlDisabledPackages";
   }
 
@@ -22804,11 +22808,11 @@
     method public void clearOnSessionLostStateListener();
     method public void close();
     method public void closeSession(@NonNull byte[]);
-    method @android.media.MediaDrm.HdcpLevel public int getConnectedHdcpLevel();
+    method public int getConnectedHdcpLevel();
     method public android.media.MediaDrm.CryptoSession getCryptoSession(@NonNull byte[], @NonNull String, @NonNull String);
     method @NonNull public android.media.MediaDrm.KeyRequest getKeyRequest(@NonNull byte[], @Nullable byte[], @Nullable String, int, @Nullable java.util.HashMap<java.lang.String,java.lang.String>) throws android.media.NotProvisionedException;
     method @NonNull public java.util.List<android.media.MediaDrm.LogMessage> getLogMessages();
-    method @android.media.MediaDrm.HdcpLevel public int getMaxHdcpLevel();
+    method public int getMaxHdcpLevel();
     method public static int getMaxSecurityLevel();
     method public int getMaxSessionCount();
     method public android.os.PersistableBundle getMetrics();
@@ -22822,13 +22826,13 @@
     method @Deprecated @NonNull public byte[] getSecureStop(@NonNull byte[]);
     method @Deprecated @NonNull public java.util.List<byte[]> getSecureStopIds();
     method @Deprecated @NonNull public java.util.List<byte[]> getSecureStops();
-    method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]);
+    method public int getSecurityLevel(@NonNull byte[]);
     method @NonNull public static java.util.List<java.util.UUID> getSupportedCryptoSchemes();
     method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID);
     method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String);
-    method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, @android.media.MediaDrm.SecurityLevel int);
+    method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, int);
     method @NonNull public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException;
-    method @NonNull public byte[] openSession(@android.media.MediaDrm.SecurityLevel int) throws android.media.NotProvisionedException, android.media.ResourceBusyException;
+    method @NonNull public byte[] openSession(int) throws android.media.NotProvisionedException, android.media.ResourceBusyException;
     method @Nullable public byte[] provideKeyResponse(@NonNull byte[], @NonNull byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException;
     method public void provideProvisionResponse(@NonNull byte[]) throws android.media.DeniedByServerException;
     method @NonNull public java.util.HashMap<java.lang.String,java.lang.String> queryKeyStatus(@NonNull byte[]);
@@ -22840,7 +22844,7 @@
     method public void removeOfflineLicense(@NonNull byte[]);
     method @Deprecated public void removeSecureStop(@NonNull byte[]);
     method public boolean requiresSecureDecoder(@NonNull String);
-    method public boolean requiresSecureDecoder(@NonNull String, @android.media.MediaDrm.SecurityLevel int);
+    method public boolean requiresSecureDecoder(@NonNull String, int);
     method public void restoreKeys(@NonNull byte[], @NonNull byte[]);
     method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener);
     method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener, @Nullable android.os.Handler);
@@ -22929,9 +22933,6 @@
     field public static final int ERROR_ZERO_SUBSAMPLES = 33; // 0x21
   }
 
-  @Deprecated @IntDef({android.media.MediaDrm.HDCP_LEVEL_UNKNOWN, android.media.MediaDrm.HDCP_NONE, android.media.MediaDrm.HDCP_V1, android.media.MediaDrm.HDCP_V2, android.media.MediaDrm.HDCP_V2_1, android.media.MediaDrm.HDCP_V2_2, android.media.MediaDrm.HDCP_V2_3, android.media.MediaDrm.HDCP_NO_DIGITAL_OUTPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.HdcpLevel {
-  }
-
   public static final class MediaDrm.KeyRequest {
     method @NonNull public byte[] getData();
     method @NonNull public String getDefaultUrl();
@@ -23033,9 +23034,6 @@
     method @NonNull public String getDefaultUrl();
   }
 
-  @Deprecated @IntDef({android.media.MediaDrm.SECURITY_LEVEL_UNKNOWN, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.SecurityLevel {
-  }
-
   public static final class MediaDrm.SessionException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable {
     ctor public MediaDrm.SessionException(int, @Nullable String);
     method @Deprecated public int getErrorCode();
@@ -33152,6 +33150,7 @@
   public static class PerformanceHintManager.Session implements java.io.Closeable {
     method public void close();
     method public void reportActualWorkDuration(long);
+    method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public void reportActualWorkDuration(@NonNull android.os.WorkDuration);
     method @FlaggedApi("android.os.adpf_prefer_power_efficiency") public void setPreferPowerEfficiency(boolean);
     method public void setThreads(@NonNull int[]);
     method public void updateTargetWorkDuration(long);
@@ -33493,6 +33492,7 @@
     method public static boolean setCurrentTimeMillis(long);
     method public static void sleep(long);
     method public static long uptimeMillis();
+    method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static long uptimeNanos();
   }
 
   public class TestLooperManager {
@@ -33758,6 +33758,22 @@
     method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibration, @Nullable android.os.VibrationAttributes);
   }
 
+  @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public final class WorkDuration implements android.os.Parcelable {
+    ctor public WorkDuration();
+    ctor public WorkDuration(long, long, long, long);
+    method public int describeContents();
+    method public long getActualCpuDurationNanos();
+    method public long getActualGpuDurationNanos();
+    method public long getActualTotalDurationNanos();
+    method public long getWorkPeriodStartTimestampNanos();
+    method public void setActualCpuDurationNanos(long);
+    method public void setActualGpuDurationNanos(long);
+    method public void setActualTotalDurationNanos(long);
+    method public void setWorkPeriodStartTimestampNanos(long);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.WorkDuration> CREATOR;
+  }
+
   public class WorkSource implements android.os.Parcelable {
     ctor public WorkSource();
     ctor public WorkSource(android.os.WorkSource);
@@ -37288,6 +37304,7 @@
   }
 
   public static final class Telephony.Carriers implements android.provider.BaseColumns {
+    field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String ALWAYS_ON = "always_on";
     field public static final String APN = "apn";
     field public static final String AUTH_TYPE = "authtype";
     field @Deprecated public static final String BEARER = "bearer";
@@ -37301,6 +37318,8 @@
     field public static final String MMSPORT = "mmsport";
     field public static final String MMSPROXY = "mmsproxy";
     field @Deprecated public static final String MNC = "mnc";
+    field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String MTU_V4 = "mtu_v4";
+    field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String MTU_V6 = "mtu_v6";
     field @Deprecated public static final String MVNO_MATCH_DATA = "mvno_match_data";
     field @Deprecated public static final String MVNO_TYPE = "mvno_type";
     field public static final String NAME = "name";
@@ -37316,6 +37335,8 @@
     field public static final String SUBSCRIPTION_ID = "sub_id";
     field public static final String TYPE = "type";
     field public static final String USER = "user";
+    field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String USER_EDITABLE = "user_editable";
+    field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String USER_VISIBLE = "user_visible";
   }
 
   public static final class Telephony.Mms implements android.provider.Telephony.BaseMmsColumns {
@@ -40712,6 +40733,26 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.StatusBarNotification> CREATOR;
   }
 
+  @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean shouldDimWallpaper();
+    method public boolean shouldDisplayGrayscale();
+    method public boolean shouldSuppressAmbientDisplay();
+    method public boolean shouldUseNightMode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.ZenDeviceEffects> CREATOR;
+  }
+
+  @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder {
+    ctor public ZenDeviceEffects.Builder();
+    ctor public ZenDeviceEffects.Builder(@NonNull android.service.notification.ZenDeviceEffects);
+    method @NonNull public android.service.notification.ZenDeviceEffects build();
+    method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDimWallpaper(boolean);
+    method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDisplayGrayscale(boolean);
+    method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldSuppressAmbientDisplay(boolean);
+    method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldUseNightMode(boolean);
+  }
+
   public final class ZenPolicy implements android.os.Parcelable {
     method public int describeContents();
     method public int getPriorityCallSenders();
@@ -43666,6 +43707,7 @@
     field public static final String KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT = "iwlan.child_sa_rekey_soft_timer_sec_int";
     field public static final String KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_cbc_key_size_int_array";
     field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_gcm_key_size_int_array";
     field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array";
     field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int";
     field public static final String KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT = "iwlan.epdg_address_ip_type_preference_int";
@@ -43681,12 +43723,15 @@
     field public static final String KEY_IKE_REMOTE_ID_TYPE_INT = "iwlan.ike_remote_id_type_int";
     field public static final String KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_cbc_key_size_int_array";
     field public static final String KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_ctr_key_size_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_gcm_key_size_int_array";
     field public static final String KEY_MAX_RETRIES_INT = "iwlan.max_retries_int";
     field public static final String KEY_MCC_MNCS_STRING_ARRAY = "iwlan.mcc_mncs_string_array";
     field public static final String KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT = "iwlan.natt_keep_alive_timer_sec_int";
     field public static final String KEY_PREFIX = "iwlan.";
     field public static final String KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY = "iwlan.retransmit_timer_sec_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY = "iwlan.supported_child_session_aead_algorithms_int_array";
     field public static final String KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_child_session_encryption_algorithms_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_aead_algorithms_int_array";
     field public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_encryption_algorithms_int_array";
     field public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = "iwlan.supported_integrity_algorithms_int_array";
     field public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = "iwlan.supported_prf_algorithms_int_array";
@@ -45832,6 +45877,7 @@
     method public int getProxyPort();
     method public int getRoamingProtocol();
     method public String getUser();
+    method @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public boolean isAlwaysOn();
     method public boolean isEnabled();
     method public boolean isPersistent();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -45871,6 +45917,7 @@
   public static class ApnSetting.Builder {
     ctor public ApnSetting.Builder();
     method public android.telephony.data.ApnSetting build();
+    method @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") @NonNull public android.telephony.data.ApnSetting.Builder setAlwaysOn(boolean);
     method @NonNull public android.telephony.data.ApnSetting.Builder setApnName(@Nullable String);
     method @NonNull public android.telephony.data.ApnSetting.Builder setApnTypeBitmask(int);
     method @NonNull public android.telephony.data.ApnSetting.Builder setAuthType(int);
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 5a4be65..989bb77 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -224,6 +224,12 @@
     ctor public AudioFormat();
   }
 
+  @Deprecated @IntDef({android.media.MediaDrm.HDCP_LEVEL_UNKNOWN, android.media.MediaDrm.HDCP_NONE, android.media.MediaDrm.HDCP_V1, android.media.MediaDrm.HDCP_V2, android.media.MediaDrm.HDCP_V2_1, android.media.MediaDrm.HDCP_V2_2, android.media.MediaDrm.HDCP_V2_3, android.media.MediaDrm.HDCP_NO_DIGITAL_OUTPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.HdcpLevel {
+  }
+
+  @Deprecated @IntDef({android.media.MediaDrm.SECURITY_LEVEL_UNKNOWN, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.SecurityLevel {
+  }
+
 }
 
 package android.media.tv {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index de07fdde6..fbd2142 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3185,6 +3185,7 @@
     field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2
     field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1
     field public static final int LAUNCH_SUCCESS = 0; // 0x0
+    field @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") public static final String PERSISTENT_DEVICE_ID_DEFAULT = "default:0";
   }
 
   public static interface VirtualDeviceManager.ActivityListener {
@@ -3330,6 +3331,53 @@
 
 }
 
+package android.companion.virtual.camera {
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCamera implements java.io.Closeable {
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig getConfig();
+  }
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_camera") public interface VirtualCameraCallback {
+    method public void onProcessCaptureRequest(int, long, @Nullable android.companion.virtual.camera.VirtualCameraMetadata);
+    method public void onStreamClosed(int);
+    method public void onStreamConfigured(int, @NonNull android.view.Surface, @NonNull android.companion.virtual.camera.VirtualCameraStreamConfig);
+  }
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @StringRes public int getDisplayNameStringRes();
+    method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR;
+  }
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final class VirtualCameraConfig.Builder {
+    ctor public VirtualCameraConfig.Builder();
+    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int);
+    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
+    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setDisplayNameStringRes(@StringRes int);
+    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
+  }
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraMetadata implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraMetadata> CREATOR;
+  }
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraStreamConfig implements android.os.Parcelable {
+    ctor public VirtualCameraStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int);
+    method public int describeContents();
+    method public int getFormat();
+    method @IntRange(from=1) public int getHeight();
+    method @IntRange(from=1) public int getWidth();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraStreamConfig> CREATOR;
+  }
+
+}
+
 package android.companion.virtual.sensor {
 
   public final class VirtualSensor implements android.os.Parcelable {
@@ -4762,7 +4810,7 @@
     method public void onChange(@NonNull String);
   }
 
-  @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult {
+  @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface HdmiControlManager.ControlCallbackResult {
   }
 
   public static interface HdmiControlManager.HotplugEventListener {
@@ -11234,15 +11282,11 @@
     field public static final String MAX_CONNECTIONS = "max_conns";
     field public static final String MODEM_PERSIST = "modem_cognitive";
     field @Deprecated public static final String MTU = "mtu";
-    field public static final String MTU_V4 = "mtu_v4";
-    field public static final String MTU_V6 = "mtu_v6";
     field public static final int NO_APN_SET_ID = 0; // 0x0
     field public static final String TIME_LIMIT_FOR_MAX_CONNECTIONS = "max_conns_time";
     field public static final int UNEDITED = 0; // 0x0
     field public static final int USER_DELETED = 2; // 0x2
-    field public static final String USER_EDITABLE = "user_editable";
     field public static final int USER_EDITED = 1; // 0x1
-    field public static final String USER_VISIBLE = "user_visible";
     field public static final String WAIT_TIME_RETRY = "wait_time";
   }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8b20720..75797ed 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -541,7 +541,6 @@
     field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods";
     field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended";
     field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled";
-    field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
   }
 
   public class DevicePolicyManager {
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 3f9cc65..8ad6ea2 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -632,6 +632,7 @@
             InputDevice.SOURCE_JOYSTICK,
             InputDevice.SOURCE_SENSOR
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface MotionEventSources {}
 
     /**
diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java
index af00f31..6ec956ee 100644
--- a/core/java/android/accessibilityservice/TouchInteractionController.java
+++ b/core/java/android/accessibilityservice/TouchInteractionController.java
@@ -24,6 +24,8 @@
 import android.view.MotionEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.LinkedList;
 import java.util.Queue;
 import java.util.concurrent.Executor;
@@ -92,6 +94,7 @@
         STATE_DRAGGING,
         STATE_DELEGATING
     })
+    @Retention(RetentionPolicy.SOURCE)
     private @interface State {}
 
     // The maximum number of pointers that can be touching the screen at once. (See MAX_POINTER_ID
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index be433d2..ed18d81 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -23,7 +23,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.inMultiWindowMode;
 import static android.os.Process.myUid;
+
 import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
+
 import static java.lang.Character.MIN_VALUE;
 
 import android.annotation.AnimRes;
@@ -1000,6 +1002,7 @@
             FULLSCREEN_MODE_REQUEST_EXIT,
             FULLSCREEN_MODE_REQUEST_ENTER
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FullscreenModeRequest {}
 
     /** Request type of {@link #requestFullscreenMode(int, OutcomeReceiver)}, to request exiting the
@@ -1016,6 +1019,7 @@
             OVERRIDE_TRANSITION_OPEN,
             OVERRIDE_TRANSITION_CLOSE
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface OverrideTransition {}
 
     /**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8b4ebae..854e121 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4998,6 +4998,7 @@
             STOP_USER_ON_SWITCH_TRUE,
             STOP_USER_ON_SWITCH_FALSE
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface StopUserOnSwitch {}
 
     /**
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 919e084..a7b29aa 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -22,12 +22,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.NotificationManager.InterruptionFilter;
-import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.service.notification.Condition;
+import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenPolicy;
 import android.view.WindowInsetsController;
 
@@ -111,6 +111,7 @@
     private ComponentName configurationActivity;
     private long creationTime;
     private ZenPolicy mZenPolicy;
+    private ZenDeviceEffects mDeviceEffects;
     private boolean mModified = false;
     private String mPkg;
     private int mType = TYPE_UNKNOWN;
@@ -190,6 +191,7 @@
     /**
      * @hide
      */
+    // TODO: b/310620812 - Remove when the flag is inlined (all system callers should use Builder).
     public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity,
             Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled,
             long creationTime) {
@@ -209,10 +211,11 @@
         configurationActivity = getTrimmedComponentName(
                 source.readParcelable(null, android.content.ComponentName.class));
         creationTime = source.readLong();
-        mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
+        mZenPolicy = source.readParcelable(null, ZenPolicy.class);
         mModified = source.readInt() == ENABLED;
         mPkg = source.readString();
         if (Flags.modesApi()) {
+            mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
             mAllowManualInvocation = source.readBoolean();
             mIconResId = source.readInt();
             mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
@@ -274,10 +277,18 @@
     /**
      * Gets the zen policy.
      */
+    @Nullable
     public ZenPolicy getZenPolicy() {
         return mZenPolicy == null ? null : this.mZenPolicy.copy();
     }
 
+    /** Gets the {@link ZenDeviceEffects} of this rule. */
+    @Nullable
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public ZenDeviceEffects getDeviceEffects() {
+        return mDeviceEffects;
+    }
+
     /**
      * Returns the time this rule was created, represented as milliseconds since the epoch.
      */
@@ -325,11 +336,21 @@
     /**
      * Sets the zen policy.
      */
-    public void setZenPolicy(ZenPolicy zenPolicy) {
+    public void setZenPolicy(@Nullable ZenPolicy zenPolicy) {
         this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy());
     }
 
     /**
+     * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes to
+     * the device behavior that should apply while the rule is active, but are not directly related
+     * to suppressing notifications (for example: disabling always-on display).
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public void setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
+        mDeviceEffects = deviceEffects;
+    }
+
+    /**
      * Sets the configuration activity - an activity that handles
      * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
      * about this rule and/or allows them to configure it. This is required to be non-null for rules
@@ -451,6 +472,7 @@
         dest.writeInt(mModified ? ENABLED : DISABLED);
         dest.writeString(mPkg);
         if (Flags.modesApi()) {
+            dest.writeParcelable(mDeviceEffects, 0);
             dest.writeBoolean(mAllowManualInvocation);
             dest.writeInt(mIconResId);
             dest.writeString(mTriggerDescription);
@@ -472,7 +494,8 @@
                 .append(",mZenPolicy=").append(mZenPolicy);
 
         if (Flags.modesApi()) {
-            sb.append(",allowManualInvocation=").append(mAllowManualInvocation)
+            sb.append(",deviceEffects=").append(mDeviceEffects)
+                    .append(",allowManualInvocation=").append(mAllowManualInvocation)
                     .append(",iconResId=").append(mIconResId)
                     .append(",triggerDescription=").append(mTriggerDescription)
                     .append(",type=").append(mType);
@@ -498,6 +521,7 @@
                 && other.creationTime == creationTime;
         if (Flags.modesApi()) {
             return finalEquals
+                    && Objects.equals(other.mDeviceEffects, mDeviceEffects)
                     && other.mAllowManualInvocation == mAllowManualInvocation
                     && other.mIconResId == mIconResId
                     && Objects.equals(other.mTriggerDescription, mTriggerDescription)
@@ -510,8 +534,8 @@
     public int hashCode() {
         if (Flags.modesApi()) {
             return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
-                    configurationActivity, mZenPolicy, mModified, creationTime, mPkg,
-                    mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
+                    configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime,
+                    mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
         }
         return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
                 configurationActivity, mZenPolicy, mModified, creationTime, mPkg);
@@ -573,6 +597,7 @@
         private boolean mEnabled;
         private ComponentName mConfigurationActivity = null;
         private ZenPolicy mPolicy = null;
+        private ZenDeviceEffects mDeviceEffects = null;
         private int mType;
         private String mDescription;
         private int mIconResId;
@@ -588,6 +613,7 @@
             mEnabled = rule.isEnabled();
             mConfigurationActivity = rule.getConfigurationActivity();
             mPolicy = rule.getZenPolicy();
+            mDeviceEffects = rule.getDeviceEffects();
             mType = rule.getType();
             mDescription = rule.getTriggerDescription();
             mIconResId = rule.getIconResId();
@@ -639,6 +665,17 @@
         }
 
         /**
+         * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes
+         * to the device behavior that should apply while the rule is active, but are not directly
+         * related to suppressing notifications (for example: disabling always-on display).
+         */
+        @NonNull
+        public Builder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
+            mDeviceEffects = deviceEffects;
+            return this;
+        }
+
+        /**
          * Sets the type of the rule
          */
         public @NonNull Builder setType(@Type int type) {
@@ -687,6 +724,7 @@
         public @NonNull AutomaticZenRule build() {
             AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity,
                     mConditionId, mPolicy, mInterruptionFilter, mEnabled);
+            rule.mDeviceEffects = mDeviceEffects;
             rule.creationTime = mCreationTime;
             rule.mType = mType;
             rule.mTriggerDescription = mDescription;
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index ec5effd..9438571 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -214,6 +214,8 @@
     void setNotificationPolicyAccessGranted(String pkg, boolean granted);
     void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted);
     AutomaticZenRule getAutomaticZenRule(String id);
+    Map<String, AutomaticZenRule> getAutomaticZenRules();
+    // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
     List<ZenModeConfig.ZenRule> getZenRules();
     String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg);
     boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 545ba8e..6aad168 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -64,6 +64,8 @@
 import com.android.internal.widget.PasswordValidationError;
 import com.android.internal.widget.VerifyCredentialResponse;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.List;
@@ -235,6 +237,7 @@
             PIN,
             PATTERN
     })
+    @Retention(RetentionPolicy.SOURCE)
     @interface LockTypes {}
 
     private final IKeyguardLockedStateListener mIKeyguardLockedStateListener =
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2d80b1f..337e3f1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -924,6 +924,7 @@
             VISIBILITY_SECRET,
             NotificationManager.VISIBILITY_NO_OVERRIDE
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface NotificationVisibilityOverride{};
 
     /**
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index ffb79b3..51c937d 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1256,17 +1256,21 @@
     public Map<String, AutomaticZenRule> getAutomaticZenRules() {
         INotificationManager service = getService();
         try {
-            List<ZenModeConfig.ZenRule> rules = service.getZenRules();
-            Map<String, AutomaticZenRule> ruleMap = new HashMap<>();
-            for (ZenModeConfig.ZenRule rule : rules) {
-                AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component,
-                        rule.configurationActivity, rule.conditionId, rule.zenPolicy,
-                        zenModeToInterruptionFilter(rule.zenMode), rule.enabled,
-                        rule.creationTime);
-                azr.setPackageName(rule.pkg);
-                ruleMap.put(rule.id, azr);
+            if (Flags.modesApi()) {
+                return service.getAutomaticZenRules();
+            } else {
+                List<ZenModeConfig.ZenRule> rules = service.getZenRules();
+                Map<String, AutomaticZenRule> ruleMap = new HashMap<>();
+                for (ZenModeConfig.ZenRule rule : rules) {
+                    AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component,
+                            rule.configurationActivity, rule.conditionId, rule.zenPolicy,
+                            zenModeToInterruptionFilter(rule.zenMode), rule.enabled,
+                            rule.creationTime);
+                    azr.setPackageName(rule.pkg);
+                    ruleMap.put(rule.id, azr);
+                }
+                return ruleMap;
             }
-            return ruleMap;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index fa52968..79a5879 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -113,6 +113,7 @@
 import android.hardware.lights.LightsManager;
 import android.hardware.lights.SystemLightsManager;
 import android.hardware.location.ContextHubManager;
+import android.hardware.location.IContextHubService;
 import android.hardware.radio.RadioManager;
 import android.hardware.usb.IUsbManager;
 import android.hardware.usb.UsbManager;
@@ -1118,8 +1119,12 @@
                 new CachedServiceFetcher<ContextHubManager>() {
             @Override
             public ContextHubManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                return new ContextHubManager(ctx.getOuterContext(),
-                  ctx.mMainThread.getHandler().getLooper());
+                IBinder b = ServiceManager.getService(Context.CONTEXTHUB_SERVICE);
+                if (b == null) {
+                    return null;
+                }
+                return new ContextHubManager(IContextHubService.Stub.asInterface(b),
+                        ctx.mMainThread.getHandler().getLooper());
             }});
 
         registerService(Context.INCIDENT_SERVICE, IncidentManager.class,
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 019a1a8..4621634 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -42,6 +42,8 @@
 import android.view.WindowManager;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -120,6 +122,7 @@
             WINDOWING_MODE_PINNED,
             WINDOWING_MODE_FREEFORM,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface WindowingMode {}
 
     /** The current activity type of the configuration. */
@@ -147,6 +150,7 @@
             ACTIVITY_TYPE_ASSISTANT,
             ACTIVITY_TYPE_DREAM,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface ActivityType {}
 
     /** The current always on top status of the configuration. */
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index e4ee959..14462b8 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -47,6 +47,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
 
@@ -175,6 +177,7 @@
     public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1;
 
     @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED})
+    @Retention(RetentionPolicy.SOURCE)
     private @interface HeadlessDeviceOwnerMode {}
 
     /** @hide */
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index 84b1ca5..b0bec78 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -164,11 +164,8 @@
 
     /**
      * String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}.
-     *
-     * @hide
      */
     @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
-    @TestApi
     public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
 
     /**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3df11f6..3ee9d692 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3998,8 +3998,7 @@
 
     /**
      * An integer array extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate which
-     * resource IDs (see {@link DevicePolicyResources.Drawables} and
-     * {@link DevicePolicyResources.Strings}) have been updated.
+     * resource IDs (i.e. strings and drawables) have been updated.
      */
     public static final String EXTRA_RESOURCE_IDS =
             "android.app.extra.RESOURCE_IDS";
@@ -8370,9 +8369,7 @@
      * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
      * successfully set or not. This callback will contain:
      * <ul>
-     * <li> The policy identifier returned from
-     * {@link DevicePolicyIdentifiers#getIdentifierForUserRestriction(String)} with user restriction
-     * {@link UserManager#DISALLOW_CAMERA}
+     * <li> The policy identifier: userRestriction_no_camera
      * <li> The {@link TargetUser} that this policy relates to
      * <li> The {@link PolicyUpdateResult}, which will be
      * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
@@ -13403,7 +13400,7 @@
      * A device owner, by default, may continue granting these permissions. However, for increased
      * user control, the admin may opt out of controlling grants for these permissions by including
      * {@link #EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT} in the provisioning parameters.
-     * In that case the device owner's control will be limited do denying these permissions.
+     * In that case the device owner's control will be limited to denying these permissions.
      * <p>
      * NOTE: On devices running {@link android.os.Build.VERSION_CODES#S} and above, control over
      * the following permissions are restricted for managed profile owners:
@@ -17113,19 +17110,19 @@
      * Returns {@code true} if this device is marked as a financed device.
      *
      * <p>A financed device can be entered into lock task mode (see {@link #setLockTaskPackages})
-     * by the holder of the role {@link android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}.
+     * by the holder of the role {@code android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}.
      * If this occurs, Device Owners and Profile Owners that have set lock task packages or
      * features, or that attempt to set lock task packages or features, will receive a callback
      * indicating that it could not be set. See {@link PolicyUpdateReceiver#onPolicyChanged} and
      * {@link PolicyUpdateReceiver#onPolicySetResult}.
      *
      * <p>To be informed of changes to this status you can subscribe to the broadcast
-     * {@link ACTION_DEVICE_FINANCING_STATE_CHANGED}.
+     * {@link #ACTION_DEVICE_FINANCING_STATE_CHANGED}.
      *
      * @throws SecurityException if the caller is not a device owner, profile owner of an
      * organization-owned managed profile, profile owner on the primary user or holder of one of the
-     * following roles: {@link android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT},
-     * android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION.
+     * following roles: {@code android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT},
+     * {@code android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION}.
      */
     public boolean isDeviceFinanced() {
         throwIfParentInstance("isDeviceFinanced");
diff --git a/core/java/android/app/admin/DevicePolicyResourcesManager.java b/core/java/android/app/admin/DevicePolicyResourcesManager.java
index 2cc189f..7a71231 100644
--- a/core/java/android/app/admin/DevicePolicyResourcesManager.java
+++ b/core/java/android/app/admin/DevicePolicyResourcesManager.java
@@ -452,7 +452,7 @@
 
     /**
      * Returns the appropriate updated string for the {@code stringId} (see
-     * {@link DevicePolicyResources.Strings}) if one was set using
+     * {@code DevicePolicyResources.Strings}) if one was set using
      * {@code setStrings}, otherwise returns the string from {@code defaultStringLoader}.
      *
      * <p>Also returns the string from {@code defaultStringLoader} if {@code stringId} is
diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java
index a6595fe..b5c66ff 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEvent.java
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java
@@ -86,7 +86,9 @@
             EVENT_SNORE,
             EVENT_BACK_DOUBLE_TAP,
             EVENT_VENDOR_WEARABLE_START,
-    }) public @interface EventCode {}
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EventCode {}
 
     /** The integer indicating an unknown level. */
     public static final int LEVEL_UNKNOWN = 0;
@@ -114,7 +116,9 @@
             LEVEL_MEDIUM,
             LEVEL_MEDIUM_HIGH,
             LEVEL_HIGH
-    }) public @interface LevelValue {}
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LevelValue {}
 
     @EventCode private final int mEventType;
     private static int defaultEventType() {
diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java
index bf383f1..159481f 100644
--- a/core/java/android/app/ambientcontext/AmbientContextManager.java
+++ b/core/java/android/app/ambientcontext/AmbientContextManager.java
@@ -32,6 +32,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -106,7 +108,9 @@
             STATUS_SERVICE_UNAVAILABLE,
             STATUS_MICROPHONE_DISABLED,
             STATUS_ACCESS_DENIED
-    }) public @interface StatusCode {}
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StatusCode {}
 
     /**
      * Allows clients to retrieve the list of {@link AmbientContextEvent}s from the intent.
diff --git a/core/java/android/app/cloudsearch/SearchResponse.java b/core/java/android/app/cloudsearch/SearchResponse.java
index c86142e..dab1657 100644
--- a/core/java/android/app/cloudsearch/SearchResponse.java
+++ b/core/java/android/app/cloudsearch/SearchResponse.java
@@ -21,6 +21,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -37,6 +39,7 @@
                     SEARCH_STATUS_OK,
                     SEARCH_STATUS_TIME_OUT,
                     SEARCH_STATUS_NO_INTERNET})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface SearchStatusCode {
     }
 
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index dd332c8..bc8fac5 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -71,17 +71,13 @@
     @NonNull
     public static ActivityConfigurationChangeItem obtain(@NonNull IBinder activityToken,
             @NonNull Configuration config) {
-        if (config == null) {
-            throw new IllegalArgumentException("Config must not be null.");
-        }
-
         ActivityConfigurationChangeItem instance =
                 ObjectPool.obtain(ActivityConfigurationChangeItem.class);
         if (instance == null) {
             instance = new ActivityConfigurationChangeItem();
         }
         instance.setActivityToken(activityToken);
-        instance.mConfiguration = config;
+        instance.mConfiguration = new Configuration(config);
 
         return instance;
     }
@@ -89,7 +85,7 @@
     @Override
     public void recycle() {
         super.recycle();
-        mConfiguration = Configuration.EMPTY;
+        mConfiguration = null;
         ObjectPool.recycle(this);
     }
 
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index a5dd115..3ce094e 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.content.ReferrerIntent;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -51,7 +52,7 @@
 
     /**
      * A record that was properly configured for relaunch. Execution will be cancelled if not
-     * initialized after {@link #preExecute(ClientTransactionHandler, IBinder)}.
+     * initialized after {@link #preExecute(ClientTransactionHandler)}.
      */
     private ActivityClientRecord mActivityClientRecord;
 
@@ -99,10 +100,11 @@
             instance = new ActivityRelaunchItem();
         }
         instance.setActivityToken(activityToken);
-        instance.mPendingResults = pendingResults;
-        instance.mPendingNewIntents = pendingNewIntents;
+        instance.mPendingResults = pendingResults != null ? new ArrayList<>(pendingResults) : null;
+        instance.mPendingNewIntents =
+                pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null;
         instance.mConfigChanges = configChanges;
-        instance.mConfig = config;
+        instance.mConfig = new MergedConfiguration(config);
         instance.mPreserveWindow = preserveWindow;
 
         return instance;
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
index 24fced4..51a09fb 100644
--- a/core/java/android/app/servertransaction/ActivityResultItem.java
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -35,6 +35,7 @@
 import android.os.Parcelable;
 import android.os.Trace;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -82,7 +83,7 @@
             instance = new ActivityResultItem();
         }
         instance.setActivityToken(activityToken);
-        instance.mResultInfoList = resultInfoList;
+        instance.mResultInfoList = new ArrayList<>(resultInfoList);
 
         return instance;
     }
diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java
index 2a65b35..b4ff476f 100644
--- a/core/java/android/app/servertransaction/ActivityTransactionItem.java
+++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java
@@ -20,6 +20,8 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -93,7 +95,7 @@
     }
 
     void setActivityToken(@NonNull IBinder activityToken) {
-        mActivityToken = activityToken;
+        mActivityToken = requireNonNull(activityToken);
     }
 
     // To be overridden
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 9c0cd39..7c34cde 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -54,12 +54,14 @@
 
     /** A list of individual callbacks to a client. */
     @UnsupportedAppUsage
+    @Nullable
     private List<ClientTransactionItem> mActivityCallbacks;
 
     /**
      * Final lifecycle state in which the client activity should be after the transaction is
      * executed.
      */
+    @Nullable
     private ActivityLifecycleItem mLifecycleStateRequest;
 
     /** Target client. */
@@ -123,6 +125,7 @@
     @VisibleForTesting(visibility = PACKAGE)
     @UnsupportedAppUsage
     @Deprecated
+    @Nullable
     public ActivityLifecycleItem getLifecycleStateRequest() {
         return mLifecycleStateRequest;
     }
@@ -207,7 +210,7 @@
             for (int i = 0; i < size; i++) {
                 mActivityCallbacks.get(i).recycle();
             }
-            mActivityCallbacks.clear();
+            mActivityCallbacks = null;
         }
         if (mLifecycleStateRequest != null) {
             mLifecycleStateRequest.recycle();
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
index 96961ace..0e327a7 100644
--- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -64,7 +64,7 @@
         if (instance == null) {
             instance = new ConfigurationChangeItem();
         }
-        instance.mConfiguration = config;
+        instance.mConfiguration = new Configuration(config);
         instance.mDeviceId = deviceId;
 
         return instance;
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index a64c744..d2ef65a 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -45,6 +45,7 @@
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.content.ReferrerIntent;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -135,10 +136,16 @@
         if (instance == null) {
             instance = new LaunchActivityItem();
         }
-        setValues(instance, activityToken, intent, ident, info, curConfig, overrideConfig, deviceId,
-                referrer, voiceInteractor, procState, state, persistentState, pendingResults,
-                pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
-                activityClientController, shareableActivityToken,
+        setValues(instance, activityToken, new Intent(intent), ident,  new ActivityInfo(info),
+                new Configuration(curConfig), new Configuration(overrideConfig), deviceId,
+                referrer, voiceInteractor, procState,
+                state != null ? new Bundle(state) : null,
+                persistentState != null ? new PersistableBundle(persistentState) : null,
+                pendingResults != null ? new ArrayList<>(pendingResults) : null,
+                pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null,
+                activityOptions, isForward,
+                profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
+                assistToken, activityClientController, shareableActivityToken,
                 launchedFromBubble, taskFragmentToken);
 
         return instance;
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index e56d3f8..961da19 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -69,7 +69,7 @@
         }
         instance.setActivityToken(activityToken);
         instance.mTargetDisplayId = targetDisplayId;
-        instance.mConfiguration = configuration;
+        instance.mConfiguration = new Configuration(configuration);
 
         return instance;
     }
@@ -78,7 +78,7 @@
     public void recycle() {
         super.recycle();
         mTargetDisplayId = 0;
-        mConfiguration = Configuration.EMPTY;
+        mConfiguration = null;
         ObjectPool.recycle(this);
     }
 
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
index 8e995aa..acf2ea4 100644
--- a/core/java/android/app/servertransaction/NewIntentItem.java
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.content.ReferrerIntent;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -71,7 +72,7 @@
             instance = new NewIntentItem();
         }
         instance.setActivityToken(activityToken);
-        instance.mIntents = intents;
+        instance.mIntents = new ArrayList<>(intents);
         instance.mResume = resume;
 
         return instance;
diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
index 375d1bf..cbad92f 100644
--- a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
+++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
@@ -65,7 +65,7 @@
             instance = new WindowContextInfoChangeItem();
         }
         instance.mClientToken = requireNonNull(clientToken);
-        instance.mInfo = new WindowContextInfo(config, displayId);
+        instance.mInfo = new WindowContextInfo(new Configuration(config), displayId);
 
         return instance;
     }
diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
index 9828133..7d3eb87 100644
--- a/core/java/android/app/servertransaction/WindowStateResizeItem.java
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -77,10 +77,10 @@
             instance = new WindowStateResizeItem();
         }
         instance.mWindow = requireNonNull(window);
-        instance.mFrames = requireNonNull(frames);
+        instance.mFrames = new ClientWindowFrames(frames);
         instance.mReportDraw = reportDraw;
-        instance.mConfiguration = requireNonNull(configuration);
-        instance.mInsetsState = requireNonNull(insetsState);
+        instance.mConfiguration = new MergedConfiguration(configuration);
+        instance.mInsetsState = new InsetsState(insetsState);
         instance.mForceLayout = forceLayout;
         instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
         instance.mDisplayId = displayId;
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index d0b4fe4..f1ca086 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -35,6 +35,8 @@
 import android.service.wearable.WearableSensingService;
 import android.system.OsConstants;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -105,7 +107,9 @@
             STATUS_SERVICE_UNAVAILABLE,
             STATUS_WEARABLE_UNAVAILABLE,
             STATUS_ACCESS_DENIED
-    }) public @interface StatusCode {}
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StatusCode {}
 
     private final Context mContext;
     private final IWearableSensingManager mService;
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 9ea3dfc..e0ce917 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -33,6 +33,7 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UserHandleAware;
+import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -795,9 +796,19 @@
     @UserHandleAware
     @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
     public @NonNull List<AssociationInfo> getAllAssociations() {
+        return getAllAssociations(mContext.getUserId());
+    }
+
+    /**
+     * Per-user version of {@link #getAllAssociations()}.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
+    public @NonNull List<AssociationInfo> getAllAssociations(@UserIdInt int userId) {
         if (!checkFeaturePresent()) return Collections.emptyList();
         try {
-            return mService.getAllAssociationsForUser(mContext.getUserId());
+            return mService.getAllAssociationsForUser(userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -830,12 +841,25 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
     public void addOnAssociationsChangedListener(
             @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener) {
+        addOnAssociationsChangedListener(executor, listener, mContext.getUserId());
+    }
+
+    /**
+     * Per-user version of
+     * {@link #addOnAssociationsChangedListener(Executor, OnAssociationsChangedListener)}.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
+    public void addOnAssociationsChangedListener(
+            @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener,
+            @UserIdInt int userId) {
         if (!checkFeaturePresent()) return;
         synchronized (mListeners) {
             final OnAssociationsChangedListenerProxy proxy = new OnAssociationsChangedListenerProxy(
                     executor, listener);
             try {
-                mService.addOnAssociationsChangedListener(proxy, mContext.getUserId());
+                mService.addOnAssociationsChangedListener(proxy, userId);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 2f97080..102cbf3 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -23,8 +23,7 @@
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.companion.virtual.sensor.VirtualSensorEvent;
-import android.companion.virtual.camera.IVirtualCamera;
-import android.companion.virtual.camera.VirtualCameraHalConfig;
+import android.companion.virtual.camera.VirtualCameraConfig;
 import android.content.ComponentName;
 import android.content.IntentFilter;
 import android.graphics.Point;
@@ -236,8 +235,15 @@
     void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor);
 
     /**
-     * Creates a new VirtualCamera and registers it with the VirtualCameraProvider.
+     * Creates a new virtual camera and registers it with the virtual camera service.
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
-    void registerVirtualCamera(in IVirtualCamera camera);
+    void registerVirtualCamera(in VirtualCameraConfig camera);
+
+    /**
+     * Destroys the virtual camera with given config and unregisters it from the virtual camera
+     * service.
+     */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    void unregisterVirtualCamera(in VirtualCameraConfig camera);
 }
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 93a3e78..d0c8be6 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -32,9 +32,8 @@
  * Details of a particular virtual device.
  *
  * <p>Read-only device representation exposing the properties of an existing virtual device.
- *
- * @see VirtualDeviceManager#registerVirtualDeviceListener
  */
+// TODO(b/310912420): Link to VirtualDeviceManager#registerVirtualDeviceListener from the docs
 public final class VirtualDevice implements Parcelable {
 
     private final @NonNull IVirtualDevice mVirtualDevice;
@@ -92,8 +91,8 @@
      * per device.
      *
      * @see Context#createDeviceContext
-     * @see #getPersistentDeviceId
      */
+    // TODO(b/310912420): Link to #getPersistentDeviceId from the docs
     public int getDeviceId() {
         return mId;
     }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index b3ea93b..41c90b9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -149,6 +149,19 @@
     @SystemApi
     public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2;
 
+    /**
+     * Persistent device identifier corresponding to the default device.
+     *
+     * @see Context#DEVICE_ID_DEFAULT
+     * @see VirtualDevice#getPersistentDeviceId()
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API)
+    public static final String PERSISTENT_DEVICE_ID_DEFAULT =
+            "default:" + Context.DEVICE_ID_DEFAULT;
+
     private final IVirtualDeviceManager mService;
     private final Context mContext;
 
@@ -199,9 +212,10 @@
      * existing virtual devices.</p>
      *
      * <p>Note that if a virtual device is closed and becomes invalid, the returned objects will
-     * not be updated and may contain stale values. Use a {@link VirtualDeviceListener} for real
-     * time updates of the availability of virtual devices.</p>
+     * not be updated and may contain stale values.</p>
      */
+    // TODO(b/310912420): Add "Use a VirtualDeviceListener for real time updates of the
+    // availability  of virtual devices." in the note paragraph above with a link annotation.
     @NonNull
     public List<android.companion.virtual.VirtualDevice> getVirtualDevices() {
         if (mService == null) {
diff --git a/core/java/android/companion/virtual/camera/IVirtualCamera.aidl b/core/java/android/companion/virtual/camera/IVirtualCamera.aidl
deleted file mode 100644
index 58b850d..0000000
--- a/core/java/android/companion/virtual/camera/IVirtualCamera.aidl
+++ /dev/null
@@ -1,17 +0,0 @@
-package android.companion.virtual.camera;
-
-import android.companion.virtual.camera.IVirtualCameraSession;
-import android.companion.virtual.camera.VirtualCameraHalConfig;
-
-/**
- * Counterpart of ICameraDevice for virtual camera.
- *
- * @hide
- */
-interface IVirtualCamera {
-
-    IVirtualCameraSession open();
-
-    VirtualCameraHalConfig getHalConfig();
-
-}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
new file mode 100644
index 0000000..fac44b5
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
@@ -0,0 +1,70 @@
+/*
+ * 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.companion.virtual.camera;
+
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+import android.companion.virtual.camera.VirtualCameraMetadata;
+import android.view.Surface;
+
+/**
+ * Interface for the virtual camera service and system server to talk back to the virtual camera owner.
+ *
+ * @hide
+ */
+interface IVirtualCameraCallback {
+
+    /**
+     * Called when one of the requested stream has been configured by the virtual camera service and
+     * is ready to receive data onto its {@link Surface}
+     *
+     * @param streamId     The id of the configured stream
+     * @param surface      The surface to write data into for this stream
+     * @param streamConfig The image data configuration for this stream
+     */
+    oneway void onStreamConfigured(
+            int streamId,
+            in Surface surface,
+            in VirtualCameraStreamConfig streamConfig);
+
+    /**
+     * The client application is requesting a camera frame for the given streamId with the provided
+     * metadata.
+     *
+     * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
+     * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
+     * VirtualCameraStreamConfig)} call.
+     *
+     * @param streamId The streamId for which the frame is requested. This corresponds to the
+     *     streamId that was given in {@link #onStreamConfigured(int, Surface,
+     *     VirtualCameraStreamConfig)}
+     * @param frameId The frameId that is being requested. Each request will have a different
+     *     frameId, that will be increasing for each call with a particular streamId.
+     * @param metadata The metadata requested for the frame. The virtual camera should do its best
+     *     to honor the requested metadata.
+     */
+    oneway void onProcessCaptureRequest(
+            int streamId, long frameId, in VirtualCameraMetadata metadata);
+
+    /**
+     * The stream previously configured when {@link #onStreamConfigured(int, Surface,
+     * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
+     * freed. The Surface was disposed on the client side and should not be used anymore by the virtual camera owner
+     *
+     * @param streamId The id of the stream that was closed.
+     */
+    oneway void onStreamClosed(int streamId);
+
+}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/VirtualCamera.java b/core/java/android/companion/virtual/camera/VirtualCamera.java
index 791bf0a..beee86f 100644
--- a/core/java/android/companion/virtual/camera/VirtualCamera.java
+++ b/core/java/android/companion/virtual/camera/VirtualCamera.java
@@ -16,20 +16,44 @@
 
 package android.companion.virtual.camera;
 
+import android.annotation.FlaggedApi;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.flags.Flags;
+import android.hardware.camera2.CameraDevice;
 import android.os.RemoteException;
 
 import androidx.annotation.NonNull;
 
+import java.io.Closeable;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
- * Virtual camera that is used to send image data into system.
+ * A VirtualCamera is the representation of a remote or computer generated camera that will be
+ * exposed to applications using the Android Camera APIs.
  *
+ * <p>A VirtualCamera is created using {@link
+ * VirtualDeviceManager.VirtualDevice#createVirtualCamera(VirtualCameraConfig)}.
+ *
+ * <p>Once a virtual camera is created, it will receive callbacks from the system when an
+ * application attempts to use it via the {@link VirtualCameraCallback} class set using {@link
+ * VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback)}
+ *
+ * @see VirtualDeviceManager.VirtualDevice#createVirtualDevice(int, VirtualDeviceParams)
+ * @see VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback)
+ * @see android.hardware.camera2.CameraManager#openCamera(String, CameraDevice.StateCallback,
+ *     android.os.Handler)
  * @hide
  */
-public final class VirtualCamera extends IVirtualCamera.Stub {
+@SystemApi
+@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+public final class VirtualCamera implements Closeable {
 
+    private final IVirtualDevice mVirtualDevice;
     private final VirtualCameraConfig mConfig;
 
     /**
@@ -37,40 +61,35 @@
      *
      * @param virtualDevice The Binder object representing this camera in the server.
      * @param config Configuration for the new virtual camera
+     * @hide
      */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public VirtualCamera(
             @NonNull IVirtualDevice virtualDevice, @NonNull VirtualCameraConfig config) {
+        mVirtualDevice = virtualDevice;
         mConfig = Objects.requireNonNull(config);
         Objects.requireNonNull(virtualDevice);
+        // TODO(b/310857519): Avoid registration inside constructor.
         try {
-            virtualDevice.registerVirtualCamera(this);
+            mVirtualDevice.registerVirtualCamera(config);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
     }
 
-    /** Get the camera session associated with this device */
-    @Override
-    public IVirtualCameraSession open() {
-        // TODO: b/302255544 - Make this async.
-        VirtualCameraSession session = mConfig.getCallback().onOpenSession();
-        return new VirtualCameraSessionInternal(session);
-    }
-
     /** Returns the configuration of this virtual camera instance. */
     @NonNull
     public VirtualCameraConfig getConfig() {
         return mConfig;
     }
 
-    /**
-     * Returns the configuration to be used by the virtual camera HAL.
-     *
-     * @hide
-     */
     @Override
-    @NonNull
-    public VirtualCameraHalConfig getHalConfig() {
-        return mConfig.getHalConfig();
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void close() {
+        try {
+            mVirtualDevice.unregisterVirtualCamera(mConfig);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 }
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
index a7c3d4f..a18ae03 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
@@ -16,7 +16,12 @@
 
 package android.companion.virtual.camera;
 
-import android.hardware.camera2.params.SessionConfiguration;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.view.Surface;
 
 import java.util.concurrent.Executor;
 
@@ -24,15 +29,53 @@
  * Interface to be provided when creating a new {@link VirtualCamera} in order to receive callbacks
  * from the framework and the camera system.
  *
- * @see VirtualCameraConfig.Builder#setCallback(Executor, VirtualCameraCallback)
+ * @see VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback)
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
 public interface VirtualCameraCallback {
 
     /**
-     * Called when a client opens a new camera session for the associated {@link VirtualCamera}
+     * Called when one of the requested stream has been configured by the virtual camera service and
+     * is ready to receive data onto its {@link Surface}
      *
-     * @see android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration)
+     * @param streamId The id of the configured stream
+     * @param surface The surface to write data into for this stream
+     * @param streamConfig The image data configuration for this stream
      */
-    VirtualCameraSession onOpenSession();
+    void onStreamConfigured(
+            int streamId,
+            @NonNull Surface surface,
+            @NonNull VirtualCameraStreamConfig streamConfig);
+
+    /**
+     * The client application is requesting a camera frame for the given streamId with the provided
+     * metadata.
+     *
+     * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
+     * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
+     * VirtualCameraStreamConfig)} call.
+     *
+     * @param streamId The streamId for which the frame is requested. This corresponds to the
+     *     streamId that was given in {@link #onStreamConfigured(int, Surface,
+     *     VirtualCameraStreamConfig)}
+     * @param frameId The frameId that is being requested. Each request will have a different
+     *     frameId, that will be increasing for each call with a particular streamId.
+     * @param metadata The metadata requested for the frame. The virtual camera should do its best
+     *     to honor the requested metadata but the consumer won't be informed about the metadata set
+     *     for a particular frame. If null, the requested frame can be anything the producer sends.
+     */
+    void onProcessCaptureRequest(
+            int streamId, long frameId, @Nullable VirtualCameraMetadata metadata);
+
+    /**
+     * The stream previously configured when {@link #onStreamConfigured(int, Surface,
+     * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
+     * freed. The Surface corresponding to that streamId was disposed on the client side and should
+     * not be used anymore by the virtual camera owner
+     *
+     * @param streamId The id of the stream that was closed.
+     */
+    void onStreamClosed(int streamId);
 }
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
similarity index 76%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
index 2529807..88c27a5 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
@@ -15,14 +15,5 @@
  */
 package android.companion.virtual.camera;
 
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
-
-    void configureStream(int width, int height, int format);
-
-    void close();
-}
+/** @hide */
+parcelable VirtualCameraConfig;
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index fb464d5..f1eb240 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -18,11 +18,19 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.StringRes;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.content.res.Resources;
 import android.graphics.ImageFormat;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.ArraySet;
+import android.view.Surface;
 
-import java.util.Collections;
 import java.util.Set;
 import java.util.concurrent.Executor;
 
@@ -33,33 +41,115 @@
  *
  * @hide
  */
-public final class VirtualCameraConfig {
+@SystemApi
+@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+public final class VirtualCameraConfig implements Parcelable {
 
-    private final String mDisplayName;
+    private final @StringRes int mNameStringRes;
     private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
-    private final VirtualCameraCallback mCallback;
-    private final Executor mCallbackExecutor;
+    private final IVirtualCameraCallback mCallback;
+
+    private VirtualCameraConfig(
+            int displayNameStringRes,
+            @NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
+            @NonNull Executor executor,
+            @NonNull VirtualCameraCallback callback) {
+        mNameStringRes = displayNameStringRes;
+        mStreamConfigurations =
+                Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations"));
+        if (mStreamConfigurations.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "At least one stream configuration is needed to create a virtual camera.");
+        }
+        mCallback =
+                new VirtualCameraCallbackInternal(
+                        requireNonNull(callback, "Missing callback"),
+                        requireNonNull(executor, "Missing callback executor"));
+    }
+
+    private VirtualCameraConfig(@NonNull Parcel in) {
+        mNameStringRes = in.readInt();
+        mCallback = IVirtualCameraCallback.Stub.asInterface(in.readStrongBinder());
+        mStreamConfigurations =
+                Set.of(
+                        in.readParcelableArray(
+                                VirtualCameraStreamConfig.class.getClassLoader(),
+                                VirtualCameraStreamConfig.class));
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mNameStringRes);
+        dest.writeStrongInterface(mCallback);
+        dest.writeParcelableArray(
+                mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags);
+    }
+
+    /**
+     * @return The display name of this VirtualCamera
+     */
+    @StringRes
+    public int getDisplayNameStringRes() {
+        return mNameStringRes;
+    }
+
+    /**
+     * Returns an unmodifiable set of the stream configurations added to this {@link
+     * VirtualCameraConfig}.
+     *
+     * @see VirtualCameraConfig.Builder#addStreamConfig(int, int, int)
+     */
+    @NonNull
+    public Set<VirtualCameraStreamConfig> getStreamConfigs() {
+        return mStreamConfigurations;
+    }
+
+    /**
+     * Returns the callback used to communicate from the server to the client.
+     *
+     * @hide
+     */
+    @NonNull
+    public IVirtualCameraCallback getCallback() {
+        return mCallback;
+    }
 
     /**
      * Builder for {@link VirtualCameraConfig}.
      *
      * <p>To build an instance of {@link VirtualCameraConfig} the following conditions must be met:
-     * <li>At least one stream must be added wit {@link #addStreamConfiguration(int, int, int)}.
-     * <li>A name must be set with {@link #setDisplayName(String)}
-     * <li>A callback must be set wit {@link #setCallback(Executor, VirtualCameraCallback)}
+     * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}.
+     * <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
+     *     VirtualCameraCallback)}
+     * <li>A user readable name can be set with {@link #setDisplayNameStringRes(int)}
      */
+    @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
     public static final class Builder {
 
-        private String mDisplayName;
-        private final ArraySet<VirtualCameraStreamConfig> mStreamConfiguration = new ArraySet<>();
+        private @StringRes int mDisplayNameStringRes = Resources.ID_NULL;
+        private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
         private Executor mCallbackExecutor;
         private VirtualCameraCallback mCallback;
 
-        /** Set the visible name of this camera for the user. */
-        // TODO: b/290172356 - Take a resource id instead of displayName
+        /**
+         * Set the visible name of this camera for the user.
+         *
+         * <p>Sets the resource to a string representing a user readable name for this virtual
+         * camera.
+         *
+         * @throws IllegalArgumentException if an invalid resource id is passed.
+         */
         @NonNull
-        public Builder setDisplayName(@NonNull String displayName) {
-            mDisplayName = requireNonNull(displayName);
+        public Builder setDisplayNameStringRes(@StringRes int displayNameStringRes) {
+            if (displayNameStringRes <= 0) {
+                throw new IllegalArgumentException("Invalid resource passed for display name");
+            }
+            mDisplayNameStringRes = displayNameStringRes;
             return this;
         }
 
@@ -68,18 +158,21 @@
          *
          * <p>At least one {@link VirtualCameraStreamConfig} must be added.
          *
-         * @param width The width of the stream
-         * @param height The height of the stream
-         * @param format The {@link ImageFormat} of the stream
+         * @param width The width of the stream.
+         * @param height The height of the stream.
+         * @param format The {@link ImageFormat} of the stream.
+         *
+         * @throws IllegalArgumentException if invalid format or dimensions are passed.
          */
         @NonNull
-        public Builder addStreamConfiguration(
-                int width, int height, @ImageFormat.Format int format) {
-            VirtualCameraStreamConfig streamConfig = new VirtualCameraStreamConfig();
-            streamConfig.width = width;
-            streamConfig.height = height;
-            streamConfig.format = format;
-            mStreamConfiguration.add(streamConfig);
+        public Builder addStreamConfig(int width, int height, @ImageFormat.Format int format) {
+            if (width <= 0 || height <= 0) {
+                throw new IllegalArgumentException("Invalid dimensions passed for stream config");
+            }
+            if (!ImageFormat.isPublicFormat(format)) {
+                throw new IllegalArgumentException("Invalid format passed for stream config");
+            }
+            mStreamConfigurations.add(new VirtualCameraStreamConfig(width, height, format));
             return this;
         }
 
@@ -93,7 +186,9 @@
          * @param callback The instance of the callback to be added. Subsequent call to this method
          *     will replace the callback set.
          */
-        public Builder setCallback(
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder") // The configuration is immutable
+        public Builder setVirtualCameraCallback(
                 @NonNull Executor executor, @NonNull VirtualCameraCallback callback) {
             mCallbackExecutor = requireNonNull(executor);
             mCallback = requireNonNull(callback);
@@ -108,67 +203,49 @@
         @NonNull
         public VirtualCameraConfig build() {
             return new VirtualCameraConfig(
-                    mDisplayName, mStreamConfiguration, mCallbackExecutor, mCallback);
+                    mDisplayNameStringRes, mStreamConfigurations, mCallbackExecutor, mCallback);
         }
     }
 
-    private VirtualCameraConfig(
-            @NonNull String displayName,
-            @NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
-            @NonNull Executor executor,
-            @NonNull VirtualCameraCallback callback) {
-        mDisplayName = requireNonNull(displayName, "Missing display name");
-        mStreamConfigurations =
-                Collections.unmodifiableSet(
-                        requireNonNull(streamConfigurations, "Missing stream configuration"));
-        if (mStreamConfigurations.isEmpty()) {
-            throw new IllegalArgumentException(
-                    "At least one StreamConfiguration is needed to create a virtual camera.");
+    private static class VirtualCameraCallbackInternal extends IVirtualCameraCallback.Stub {
+
+        private final VirtualCameraCallback mCallback;
+        private final Executor mExecutor;
+
+        private VirtualCameraCallbackInternal(VirtualCameraCallback callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
         }
-        mCallback = requireNonNull(callback, "Missing callback");
-        mCallbackExecutor = requireNonNull(executor, "Missing callback executor");
+
+        @Override
+        public void onStreamConfigured(
+                int streamId, Surface surface, VirtualCameraStreamConfig streamConfig) {
+            mExecutor.execute(() -> mCallback.onStreamConfigured(streamId, surface, streamConfig));
+        }
+
+        @Override
+        public void onProcessCaptureRequest(
+                int streamId, long frameId, VirtualCameraMetadata metadata) {
+            mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId, metadata));
+        }
+
+        @Override
+        public void onStreamClosed(int streamId) {
+            mExecutor.execute(() -> mCallback.onStreamClosed(streamId));
+        }
     }
 
-    /**
-     * @return The display name of this VirtualCamera
-     */
     @NonNull
-    public String getDisplayName() {
-        return mDisplayName;
-    }
+    public static final Parcelable.Creator<VirtualCameraConfig> CREATOR =
+            new Parcelable.Creator<>() {
+                @Override
+                public VirtualCameraConfig createFromParcel(Parcel in) {
+                    return new VirtualCameraConfig(in);
+                }
 
-    /**
-     * Returns an unmodifiable set of the stream configurations added to this {@link
-     * VirtualCameraConfig}.
-     *
-     * @see VirtualCameraConfig.Builder#addStreamConfiguration(int, int, int)
-     */
-    @NonNull
-    public Set<VirtualCameraStreamConfig> getStreamConfigs() {
-        return mStreamConfigurations;
-    }
-
-    /** Returns the callback used to communicate from the server to the client. */
-    @NonNull
-    public VirtualCameraCallback getCallback() {
-        return mCallback;
-    }
-
-    /** Returns the executor onto which the callback should be run. */
-    @NonNull
-    public Executor getCallbackExecutor() {
-        return mCallbackExecutor;
-    }
-
-    /**
-     * Returns a new instance of {@link VirtualCameraHalConfig} initialized with data from this
-     * {@link VirtualCameraConfig}
-     */
-    @NonNull
-    public VirtualCameraHalConfig getHalConfig() {
-        VirtualCameraHalConfig halConfig = new VirtualCameraHalConfig();
-        halConfig.displayName = mDisplayName;
-        halConfig.streamConfigs = mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]);
-        return halConfig;
-    }
+                @Override
+                public VirtualCameraConfig[] newArray(int size) {
+                    return new VirtualCameraConfig[size];
+                }
+            };
 }
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl
deleted file mode 100644
index 7070a38..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl
+++ /dev/null
@@ -1,12 +0,0 @@
-package android.companion.virtual.camera;
-
-import android.companion.virtual.camera.VirtualCameraStreamConfig;
-
-/**
- * Configuration for VirtualCamera to be passed to the server and HAL service.
- * @hide
- */
-parcelable VirtualCameraHalConfig {
-  String displayName;
-  VirtualCameraStreamConfig[] streamConfigs;
-}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
similarity index 79%
rename from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
rename to core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
index 2529807..6c1f0fc 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
@@ -13,16 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.companion.virtual.camera;
 
 /**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
+ * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
+ * VirtualCamera.
  * @hide
  */
-interface IVirtualCameraSession {
-
-    void configureStream(int width, int height, int format);
-
-    void close();
-}
+parcelable VirtualCameraMetadata;
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java
new file mode 100644
index 0000000..1ba36d0
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java
@@ -0,0 +1,61 @@
+/*
+ * 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.companion.virtual.camera;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data structure used to store camera metadata compatible with VirtualCamera.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+public final class VirtualCameraMetadata implements Parcelable {
+
+    /** @hide */
+    public VirtualCameraMetadata(@NonNull Parcel in) {}
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {}
+
+    @NonNull
+    public static final Creator<VirtualCameraMetadata> CREATOR =
+            new Creator<>() {
+                @Override
+                @NonNull
+                public VirtualCameraMetadata createFromParcel(Parcel in) {
+                    return new VirtualCameraMetadata(in);
+                }
+
+                @Override
+                @NonNull
+                public VirtualCameraMetadata[] newArray(int size) {
+                    return new VirtualCameraMetadata[size];
+                }
+            };
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSession.java b/core/java/android/companion/virtual/camera/VirtualCameraSession.java
deleted file mode 100644
index c25d977..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraSession.java
+++ /dev/null
@@ -1,30 +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 android.companion.virtual.camera;
-
-/***
- * Counterpart of {@link android.hardware.camera2.CameraCaptureSession} for producing
- * images from a {@link VirtualCamera}.
- * @hide
- */
-// TODO: b/289881985 - This is just a POC implementation for now, this will be extended
-// to a full featured Camera Session
-public interface VirtualCameraSession {
-
-    /** Close the session and release its resources. */
-    default void close() {}
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java b/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java
deleted file mode 100644
index da168de..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java
+++ /dev/null
@@ -1,44 +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 android.companion.virtual.camera;
-
-import android.graphics.ImageFormat;
-
-import androidx.annotation.NonNull;
-
-import java.util.Objects;
-
-/**
- * Wraps the client side {@link VirtualCameraSession} into an {@link IVirtualCameraSession}.
- *
- * @hide
- */
-final class VirtualCameraSessionInternal extends IVirtualCameraSession.Stub {
-
-    @SuppressWarnings("FieldCanBeLocal")
-    // TODO: b/289881985: Will be used once connected with the CameraService
-    private final VirtualCameraSession mVirtualCameraSession;
-
-    VirtualCameraSessionInternal(@NonNull VirtualCameraSession virtualCameraSession) {
-        mVirtualCameraSession = Objects.requireNonNull(virtualCameraSession);
-    }
-
-    @Override
-    public void configureStream(int width, int height, @ImageFormat.Format int format) {}
-
-    public void close() {}
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
index 304d455..ce92b6d 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
@@ -16,11 +16,7 @@
 package android.companion.virtual.camera;
 
 /**
- * A stream configuration supported by a virtual camera
+ * The configuration of a single virtual camera stream.
  * @hide
  */
-parcelable VirtualCameraStreamConfig {
-    int width;
-    int height;
-    int format;
-}
+parcelable VirtualCameraStreamConfig;
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
new file mode 100644
index 0000000..e198821
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.companion.virtual.camera;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.graphics.ImageFormat;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The configuration of a single virtual camera stream.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+public final class VirtualCameraStreamConfig implements Parcelable {
+
+    private final int mWidth;
+    private final int mHeight;
+    private final int mFormat;
+
+    /**
+     * Construct a new instance of {@link VirtualCameraStreamConfig} initialized with the provided
+     * width, height and {@link ImageFormat}
+     *
+     * @param width The width of the stream.
+     * @param height The height of the stream.
+     * @param format The {@link ImageFormat} of the stream.
+     */
+    public VirtualCameraStreamConfig(
+            @IntRange(from = 1) int width,
+            @IntRange(from = 1) int height,
+            @ImageFormat.Format int format) {
+        this.mWidth = width;
+        this.mHeight = height;
+        this.mFormat = format;
+    }
+
+    private VirtualCameraStreamConfig(@NonNull Parcel in) {
+        mWidth = in.readInt();
+        mHeight = in.readInt();
+        mFormat = in.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mWidth);
+        dest.writeInt(mHeight);
+        dest.writeInt(mFormat);
+    }
+
+    @NonNull
+    public static final Creator<VirtualCameraStreamConfig> CREATOR =
+            new Creator<>() {
+                @Override
+                public VirtualCameraStreamConfig createFromParcel(Parcel in) {
+                    return new VirtualCameraStreamConfig(in);
+                }
+
+                @Override
+                public VirtualCameraStreamConfig[] newArray(int size) {
+                    return new VirtualCameraStreamConfig[size];
+                }
+            };
+
+    /** Returns the width of this stream. */
+    @IntRange(from = 1)
+    public int getWidth() {
+        return mWidth;
+    }
+
+    /** Returns the height of this stream. */
+    @IntRange(from = 1)
+    public int getHeight() {
+        return mHeight;
+    }
+
+    /** Returns the {@link ImageFormat} of this stream. */
+    @ImageFormat.Format
+    public int getFormat() {
+        return mFormat;
+    }
+}
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index cfab9eb..02066fa 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -58,6 +58,13 @@
 }
 
 flag {
+  name: "persistent_device_id_api"
+  namespace: "virtual_devices"
+  description: "Enable persistent device ID notification API"
+  bug: "295258915"
+}
+
+flag {
   name: "express_metrics"
   namespace: "virtual_devices"
   description: "Enable express metrics in VDM"
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 62630c8..d274792 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -163,6 +163,7 @@
             GRAMMATICAL_GENDER_FEMININE,
             GRAMMATICAL_GENDER_MASCULINE,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface GrammaticalGender {}
 
     /**
@@ -698,6 +699,7 @@
             ORIENTATION_LANDSCAPE,
             ORIENTATION_SQUARE
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface Orientation {
     }
 
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 90bbca8..f82f79e 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -131,6 +131,7 @@
                 BIOMETRIC_CONVENIENCE,
                 DEVICE_CREDENTIAL,
         })
+        @Retention(RetentionPolicy.SOURCE)
         @interface Types {}
 
         /**
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index b0b7a41..440585c 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -159,6 +159,7 @@
         RESULT_INCORRECT_MODE,
         RESULT_COMMUNICATION_FAILED,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface ControlCallbackResult {}
 
     /** Control operation is successfully handled by the framework. */
@@ -1135,6 +1136,7 @@
         CEC_SETTING_NAME_QUERY_SAD_MAX,
         SETTING_NAME_EARC_ENABLED,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface SettingName {}
 
     /**
@@ -1157,6 +1159,7 @@
             CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
             CEC_SETTING_NAME_QUERY_SAD_MAX,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface CecSettingSad {}
 
     // True if we have a logical device of type playback hosted in the system.
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 01ce7b9..481ec72 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -15,6 +15,8 @@
  */
 package android.hardware.location;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -1111,12 +1113,12 @@
         }
     };
 
-    /** @throws ServiceNotFoundException
-     * @hide */
-    public ContextHubManager(Context context, Looper mainLooper) throws ServiceNotFoundException {
+    /** @hide */
+    public ContextHubManager(@NonNull IContextHubService service, @NonNull Looper mainLooper) {
+        requireNonNull(service, "service cannot be null");
+        requireNonNull(mainLooper, "mainLooper cannot be null");
+        mService = service;
         mMainLooper = mainLooper;
-        mService = IContextHubService.Stub.asInterface(
-                ServiceManager.getServiceOrThrow(Context.CONTEXTHUB_SERVICE));
         try {
             mService.registerCallback(mClientCallback);
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 889d3df..81a0234 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -52,6 +52,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -738,6 +740,7 @@
             FUNCTION_NCM,
             FUNCTION_UVC,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface UsbFunctionMode {}
 
     /** @hide */
@@ -748,6 +751,7 @@
             GADGET_HAL_V1_2,
             GADGET_HAL_V2_0,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface UsbGadgetHalVersion {}
 
     /** @hide */
@@ -759,6 +763,7 @@
             USB_HAL_V1_3,
             USB_HAL_V2_0,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface UsbHalVersion {}
 
     /**
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index 6b43e73..fe85da2 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -17,6 +17,8 @@
 
 package android.os;
 
+import android.os.WorkDuration;
+
 /** {@hide} */
 oneway interface IHintSession {
     void updateTargetWorkDuration(long targetDurationNanos);
@@ -24,4 +26,5 @@
     void close();
     void sendHint(int hint);
     void setMode(int mode, boolean enabled);
+    void reportActualWorkDuration2(in WorkDuration[] workDurations);
 }
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index 11084b8..e005910 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -103,7 +103,7 @@
      * Any call in this class will change its internal data, so you must do your own thread
      * safety to protect from racing.
      *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     * All timings should be in {@link SystemClock#uptimeNanos()}.
      */
     public static class Session implements Closeable {
         private long mNativeSessionPtr;
@@ -269,6 +269,40 @@
         public @Nullable int[] getThreadIds() {
             return nativeGetThreadIds(mNativeSessionPtr);
         }
+
+        /**
+         * Reports the work duration for the last cycle of work.
+         *
+         * The system will attempt to adjust the core placement of the threads within the thread
+         * group and/or the frequency of the core on which they are run to bring the actual duration
+         * close to the target duration.
+         *
+         * @param workDuration the work duration of each component.
+         * @throws IllegalArgumentException if work period start timestamp is not positive, or
+         *         actual total duration is not positive, or actual CPU duration is not positive,
+         *         or actual GPU duration is negative.
+         */
+        @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
+        public void reportActualWorkDuration(@NonNull WorkDuration workDuration) {
+            if (workDuration.mWorkPeriodStartTimestampNanos <= 0) {
+                throw new IllegalArgumentException(
+                    "the work period start timestamp should be positive.");
+            }
+            if (workDuration.mActualTotalDurationNanos <= 0) {
+                throw new IllegalArgumentException("the actual total duration should be positive.");
+            }
+            if (workDuration.mActualCpuDurationNanos <= 0) {
+                throw new IllegalArgumentException("the actual CPU duration should be positive.");
+            }
+            if (workDuration.mActualGpuDurationNanos < 0) {
+                throw new IllegalArgumentException(
+                    "the actual GPU duration should be non negative.");
+            }
+            nativeReportActualWorkDuration(mNativeSessionPtr,
+                    workDuration.mWorkPeriodStartTimestampNanos,
+                    workDuration.mActualTotalDurationNanos,
+                    workDuration.mActualCpuDurationNanos, workDuration.mActualGpuDurationNanos);
+        }
     }
 
     private static native long nativeAcquireManager();
@@ -285,4 +319,7 @@
     private static native void nativeSetThreads(long nativeSessionPtr, int[] tids);
     private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr,
             boolean enabled);
+    private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
+            long workPeriodStartTimestampNanos, long actualTotalDurationNanos,
+            long actualCpuDurationNanos, long actualGpuDurationNanos);
 }
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index d4688f8..f71c269 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -57,6 +57,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.RandomAccessFile;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.security.SignatureException;
@@ -166,6 +168,7 @@
             RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED,
             RESUME_ON_REBOOT_REBOOT_ERROR_SLOT_MISMATCH,
             RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface ResumeOnRebootRebootErrorCode {}
 
     /**
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index 49a0bd3..e2a5833 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.app.IAlarmManager;
 import android.app.time.UnixEpochTime;
@@ -202,8 +203,8 @@
      * Returns nanoseconds since boot, not counting time spent in deep sleep.
      *
      * @return nanoseconds of non-sleep uptime since boot.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
     @CriticalNative
     @android.ravenwood.annotation.RavenwoodReplace
     public static native long uptimeNanos();
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index b7e3068..0a8f62f 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -25,6 +25,9 @@
 import android.os.IUpdateEngineCallback;
 import android.os.RemoteException;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * UpdateEngine handles calls to the update engine which takes care of A/B OTA
  * updates. It wraps up the update engine Binder APIs and exposes them as
@@ -178,6 +181,7 @@
             ErrorCodeConstants.NOT_ENOUGH_SPACE,
             ErrorCodeConstants.DEVICE_CORRUPTED,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface ErrorCode {}
 
     /**
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/core/java/android/os/WorkDuration.aidl
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to core/java/android/os/WorkDuration.aidl
index 2529807..0f61204 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/core/java/android/os/WorkDuration.aidl
@@ -13,16 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion.virtual.camera;
 
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+package android.os;
 
-    void configureStream(int width, int height, int format);
-
-    void close();
-}
+parcelable WorkDuration cpp_header "android/WorkDuration.h";
\ No newline at end of file
diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java
new file mode 100644
index 0000000..4fdc34f
--- /dev/null
+++ b/core/java/android/os/WorkDuration.java
@@ -0,0 +1,213 @@
+/*
+ * 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.os;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * WorkDuration contains the measured time in nano seconds of the workload
+ * in each component, see
+ * {@link PerformanceHintManager.Session#reportActualWorkDuration(WorkDuration)}.
+ *
+ * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ */
+@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
+public final class WorkDuration implements Parcelable {
+    long mWorkPeriodStartTimestampNanos = 0;
+    long mActualTotalDurationNanos = 0;
+    long mActualCpuDurationNanos = 0;
+    long mActualGpuDurationNanos = 0;
+    long mTimestampNanos = 0;
+
+    public static final @NonNull Creator<WorkDuration> CREATOR = new Creator<>() {
+        @Override
+        public WorkDuration createFromParcel(Parcel in) {
+            return new WorkDuration(in);
+        }
+
+        @Override
+        public WorkDuration[] newArray(int size) {
+            return new WorkDuration[size];
+        }
+    };
+
+    public WorkDuration() {}
+
+    public WorkDuration(long workPeriodStartTimestampNanos,
+                      long actualTotalDurationNanos,
+                      long actualCpuDurationNanos,
+                      long actualGpuDurationNanos) {
+        mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
+        mActualTotalDurationNanos = actualTotalDurationNanos;
+        mActualCpuDurationNanos = actualCpuDurationNanos;
+        mActualGpuDurationNanos = actualGpuDurationNanos;
+    }
+
+    /**
+     * @hide
+     */
+    public WorkDuration(long workPeriodStartTimestampNanos,
+                      long actualTotalDurationNanos,
+                      long actualCpuDurationNanos,
+                      long actualGpuDurationNanos,
+                      long timestampNanos) {
+        mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
+        mActualTotalDurationNanos = actualTotalDurationNanos;
+        mActualCpuDurationNanos = actualCpuDurationNanos;
+        mActualGpuDurationNanos = actualGpuDurationNanos;
+        mTimestampNanos = timestampNanos;
+    }
+
+    WorkDuration(@NonNull Parcel in) {
+        mWorkPeriodStartTimestampNanos = in.readLong();
+        mActualTotalDurationNanos = in.readLong();
+        mActualCpuDurationNanos = in.readLong();
+        mActualGpuDurationNanos = in.readLong();
+        mTimestampNanos = in.readLong();
+    }
+
+    /**
+     * Sets the work period start timestamp in nanoseconds.
+     *
+     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     */
+    public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) {
+        if (workPeriodStartTimestampNanos <= 0) {
+            throw new IllegalArgumentException(
+                "the work period start timestamp should be positive.");
+        }
+        mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
+    }
+
+    /**
+     * Sets the actual total duration in nanoseconds.
+     *
+     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     */
+    public void setActualTotalDurationNanos(long actualTotalDurationNanos) {
+        if (actualTotalDurationNanos <= 0) {
+            throw new IllegalArgumentException("the actual total duration should be positive.");
+        }
+        mActualTotalDurationNanos = actualTotalDurationNanos;
+    }
+
+    /**
+     * Sets the actual CPU duration in nanoseconds.
+     *
+     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     */
+    public void setActualCpuDurationNanos(long actualCpuDurationNanos) {
+        if (actualCpuDurationNanos <= 0) {
+            throw new IllegalArgumentException("the actual CPU duration should be positive.");
+        }
+        mActualCpuDurationNanos = actualCpuDurationNanos;
+    }
+
+    /**
+     * Sets the actual GPU duration in nanoseconds.
+     *
+     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     */
+    public void setActualGpuDurationNanos(long actualGpuDurationNanos) {
+        if (actualGpuDurationNanos < 0) {
+            throw new IllegalArgumentException("the actual GPU duration should be non negative.");
+        }
+        mActualGpuDurationNanos = actualGpuDurationNanos;
+    }
+
+    /**
+     * Returns the work period start timestamp based in nanoseconds.
+     *
+     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     */
+    public long getWorkPeriodStartTimestampNanos() {
+        return mWorkPeriodStartTimestampNanos;
+    }
+
+    /**
+     * Returns the actual total duration in nanoseconds.
+     *
+     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     */
+    public long getActualTotalDurationNanos() {
+        return mActualTotalDurationNanos;
+    }
+
+    /**
+     * Returns the actual CPU duration in nanoseconds.
+     *
+     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     */
+    public long getActualCpuDurationNanos() {
+        return mActualCpuDurationNanos;
+    }
+
+    /**
+     * Returns the actual GPU duration in nanoseconds.
+     *
+     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     */
+    public long getActualGpuDurationNanos() {
+        return mActualGpuDurationNanos;
+    }
+
+    /**
+     * @hide
+     */
+    public long getTimestampNanos() {
+        return mTimestampNanos;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(mWorkPeriodStartTimestampNanos);
+        dest.writeLong(mActualTotalDurationNanos);
+        dest.writeLong(mActualCpuDurationNanos);
+        dest.writeLong(mActualGpuDurationNanos);
+        dest.writeLong(mTimestampNanos);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof WorkDuration)) {
+            return false;
+        }
+        WorkDuration workDuration = (WorkDuration) obj;
+        return workDuration.mTimestampNanos == this.mTimestampNanos
+            && workDuration.mWorkPeriodStartTimestampNanos == this.mWorkPeriodStartTimestampNanos
+            && workDuration.mActualTotalDurationNanos == this.mActualTotalDurationNanos
+            && workDuration.mActualCpuDurationNanos == this.mActualCpuDurationNanos
+            && workDuration.mActualGpuDurationNanos == this.mActualGpuDurationNanos;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mWorkPeriodStartTimestampNanos, mActualTotalDurationNanos,
+                            mActualCpuDurationNanos, mActualGpuDurationNanos, mTimestampNanos);
+    }
+}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 940ddf2..0809b3b 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -54,4 +54,11 @@
     namespace: "backstage_power"
     description: "Guards a new API in PowerManager to check if battery saver is supported or not."
     bug: "305067031"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "adpf_gpu_report_actual_work_duration"
+    namespace: "game"
+    description: "Guards the ADPF GPU APIs."
+    bug: "284324521"
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2d1802a..6853892 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2133,6 +2133,7 @@
             MOUNT_MODE_EXTERNAL_PASS_THROUGH,
             MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE
     })
+    @Retention(RetentionPolicy.SOURCE)
     /** @hide */
     public @interface MountMode {}
 
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 7369740..fb2ac711 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -44,6 +44,13 @@
 }
 
 flag {
+    name: "enhanced_confirmation_mode_apis"
+    namespace: "permissions"
+    description: "enable enhanced confirmation mode apis"
+    bug: "310220212"
+}
+
+flag {
   name: "op_enable_mobile_data_by_user"
   namespace: "permissions"
   description: "enables logging of the OP_ENABLE_MOBILE_DATA_BY_USER"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c012ff3..33c15d77 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12817,15 +12817,6 @@
                 "render_shadows_in_compositor";
 
         /**
-         * If true, submit buffers using blast in ViewRootImpl.
-         * (0 = false, 1 = true)
-         * @hide
-         */
-        @Readable
-        public static final String DEVELOPMENT_USE_BLAST_ADAPTER_VR =
-                "use_blast_adapter_vr";
-
-        /**
          * Path to the WindowManager display settings file. If unset, the default file path will
          * be used.
          *
@@ -19039,6 +19030,14 @@
             public static final int BATTERY_SAVER_MODE_CUSTOM = 4;
 
             /**
+             Whether 1P apps vote for enabling data during different modes,
+             i.e. BTM, BBSM
+             * @hide
+             */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+            public static final String CONNECTIVITY_KEEP_DATA_ON = "wear_connectivity_keep_data_on";
+
+            /**
              * The maximum ambient mode duration when an activity is allowed to auto resume.
              * @hide
              */
@@ -19464,6 +19463,7 @@
              *
              * @hide
              */
+            @Readable
             public static final String WEAR_MEDIA_CONTROLS_PACKAGE = "wear_media_controls_package";
 
             /**
@@ -19471,6 +19471,7 @@
              *
              * @hide
              */
+            @Readable
             public static final String WEAR_MEDIA_SESSIONS_PACKAGE = "wear_media_sessions_package";
 
             /*
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index bcda25a..72c436e 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -17,6 +17,7 @@
 package android.provider;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -49,6 +50,7 @@
 import android.util.Patterns;
 
 import com.android.internal.telephony.SmsApplication;
+import com.android.internal.telephony.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -3191,8 +3193,8 @@
          * Sets whether the PDU session brought up by this APN should always be on.
          * See 3GPP TS 23.501 section 5.6.13
          * <P>Type: INTEGER</P>
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG)
         public static final String ALWAYS_ON = "always_on";
 
         /**
@@ -3302,18 +3304,16 @@
          * The MTU (maximum transmit unit) size of the mobile interface for IPv4 to which the APN is
          * connected, in bytes.
          * <p>Type: INTEGER </p>
-         * @hide
          */
-        @SystemApi
+        @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG)
         public static final String MTU_V4 = "mtu_v4";
 
         /**
          * The MTU (maximum transmit unit) size of the mobile interface for IPv6 to which the APN is
          * connected, in bytes.
          * <p>Type: INTEGER </p>
-         * @hide
          */
-        @SystemApi
+        @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG)
         public static final String MTU_V6 = "mtu_v6";
 
         /**
@@ -3335,17 +3335,15 @@
         /**
          * {@code true} if this APN visible to the user, {@code false} otherwise.
          * <p>Type: INTEGER (boolean)</p>
-         * @hide
          */
-        @SystemApi
+        @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG)
         public static final String USER_VISIBLE = "user_visible";
 
         /**
          * {@code true} if the user allowed to edit this APN, {@code false} otherwise.
          * <p>Type: INTEGER (boolean)</p>
-         * @hide
          */
-        @SystemApi
+        @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG)
         public static final String USER_EDITABLE = "user_editable";
 
         /**
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 759953e..92c516c 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -309,6 +309,7 @@
             REASON_ASSISTANT_CANCEL,
             REASON_LOCKDOWN,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface NotificationCancelReason{};
 
     /**
@@ -320,6 +321,7 @@
             FLAG_FILTER_TYPE_SILENT,
             FLAG_FILTER_TYPE_ONGOING
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface NotificationFilterTypes {}
     /**
      * A flag value indicating that this notification listener can see conversation type
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
new file mode 100644
index 0000000..5b096c6
--- /dev/null
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -0,0 +1,360 @@
+/*
+ * 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.service.notification;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Represents the set of device effects (affecting display and device behavior in general) that
+ * are applied whenever an {@link android.app.AutomaticZenRule} is active.
+ */
+@FlaggedApi(Flags.FLAG_MODES_API)
+public final class ZenDeviceEffects implements Parcelable {
+
+    private final boolean mGrayscale;
+    private final boolean mSuppressAmbientDisplay;
+    private final boolean mDimWallpaper;
+    private final boolean mNightMode;
+
+    private final boolean mDisableAutoBrightness;
+    private final boolean mDisableTapToWake;
+    private final boolean mDisableTiltToWake;
+    private final boolean mDisableTouch;
+    private final boolean mMinimizeRadioUsage;
+    private final boolean mMaximizeDoze;
+
+    private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay,
+            boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness,
+            boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch,
+            boolean minimizeRadioUsage, boolean maximizeDoze) {
+        mGrayscale = grayscale;
+        mSuppressAmbientDisplay = suppressAmbientDisplay;
+        mDimWallpaper = dimWallpaper;
+        mNightMode = nightMode;
+        mDisableAutoBrightness = disableAutoBrightness;
+        mDisableTapToWake = disableTapToWake;
+        mDisableTiltToWake = disableTiltToWake;
+        mDisableTouch = disableTouch;
+        mMinimizeRadioUsage = minimizeRadioUsage;
+        mMaximizeDoze = maximizeDoze;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof final ZenDeviceEffects that)) return false;
+        if (obj == this) return true;
+
+        return this.mGrayscale == that.mGrayscale
+                && this.mSuppressAmbientDisplay == that.mSuppressAmbientDisplay
+                && this.mDimWallpaper == that.mDimWallpaper
+                && this.mNightMode == that.mNightMode
+                && this.mDisableAutoBrightness == that.mDisableAutoBrightness
+                && this.mDisableTapToWake == that.mDisableTapToWake
+                && this.mDisableTiltToWake == that.mDisableTiltToWake
+                && this.mDisableTouch == that.mDisableTouch
+                && this.mMinimizeRadioUsage == that.mMinimizeRadioUsage
+                && this.mMaximizeDoze == that.mMaximizeDoze;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode,
+                mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch,
+                mMinimizeRadioUsage, mMaximizeDoze);
+    }
+
+    @Override
+    public String toString() {
+        ArrayList<String> effects = new ArrayList<>(10);
+        if (mGrayscale) effects.add("grayscale");
+        if (mSuppressAmbientDisplay) effects.add("suppressAmbientDisplay");
+        if (mDimWallpaper) effects.add("dimWallpaper");
+        if (mNightMode) effects.add("nightMode");
+        if (mDisableAutoBrightness) effects.add("disableAutoBrightness");
+        if (mDisableTapToWake) effects.add("disableTapToWake");
+        if (mDisableTiltToWake) effects.add("disableTiltToWake");
+        if (mDisableTouch) effects.add("disableTouch");
+        if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage");
+        if (mMaximizeDoze) effects.add("maximizeDoze");
+        return "[" + String.join(", ", effects) + "]";
+    }
+
+    /**
+     * Whether the level of color saturation of the display should be set to minimum, effectively
+     * switching it to grayscale, while the rule is active.
+     */
+    public boolean shouldDisplayGrayscale() {
+        return mGrayscale;
+    }
+
+    /**
+     * Whether the ambient (always-on) display feature should be disabled while the rule is active.
+     * This will have no effect if the device doesn't support always-on display or if it's not
+     * generally enabled.
+     */
+    public boolean shouldSuppressAmbientDisplay() {
+        return mSuppressAmbientDisplay;
+    }
+
+    /** Whether the wallpaper should be dimmed while the rule is active. */
+    public boolean shouldDimWallpaper() {
+        return mDimWallpaper;
+    }
+
+    /** Whether night mode (aka dark theme) should be applied while the rule is active. */
+    public boolean shouldUseNightMode() {
+        return mNightMode;
+    }
+
+    /**
+     * Whether the display's automatic brightness adjustment should be disabled while the rule is
+     * active.
+     * @hide
+     */
+    public boolean shouldDisableAutoBrightness() {
+        return mDisableAutoBrightness;
+    }
+
+    /**
+     * Whether "tap to wake" should be disabled while the rule is active.
+     * @hide
+     */
+    public boolean shouldDisableTapToWake() {
+        return mDisableTapToWake;
+    }
+
+    /**
+     * Whether "tilt to wake" should be disabled while the rule is active.
+     * @hide
+     */
+    public boolean shouldDisableTiltToWake() {
+        return mDisableTiltToWake;
+    }
+
+    /**
+     * Whether touch interactions should be disabled while the rule is active.
+     * @hide
+     */
+    public boolean shouldDisableTouch() {
+        return mDisableTouch;
+    }
+
+    /**
+     * Whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption, should be
+     * minimized while the rule is active.
+     * @hide
+     */
+    public boolean shouldMinimizeRadioUsage() {
+        return mMinimizeRadioUsage;
+    }
+
+    /**
+     * Whether Doze should be enhanced (e.g. with more aggresive activation, or less frequent
+     * maintenance windows) while the rule is active.
+     * @hide
+     */
+    public boolean shouldMaximizeDoze() {
+        return mMaximizeDoze;
+    }
+
+    /** {@link Parcelable.Creator} that instantiates {@link ZenDeviceEffects} objects. */
+    @NonNull
+    public static final Creator<ZenDeviceEffects> CREATOR = new Creator<ZenDeviceEffects>() {
+        @Override
+        public ZenDeviceEffects createFromParcel(Parcel in) {
+            return new ZenDeviceEffects(in.readBoolean(), in.readBoolean(), in.readBoolean(),
+                    in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
+                    in.readBoolean(), in.readBoolean(), in.readBoolean());
+        }
+
+        @Override
+        public ZenDeviceEffects[] newArray(int size) {
+            return new ZenDeviceEffects[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBoolean(mGrayscale);
+        dest.writeBoolean(mSuppressAmbientDisplay);
+        dest.writeBoolean(mDimWallpaper);
+        dest.writeBoolean(mNightMode);
+        dest.writeBoolean(mDisableAutoBrightness);
+        dest.writeBoolean(mDisableTapToWake);
+        dest.writeBoolean(mDisableTiltToWake);
+        dest.writeBoolean(mDisableTouch);
+        dest.writeBoolean(mMinimizeRadioUsage);
+        dest.writeBoolean(mMaximizeDoze);
+    }
+
+    /** Builder class for {@link ZenDeviceEffects} objects. */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final class Builder {
+
+        private boolean mGrayscale;
+        private boolean mSuppressAmbientDisplay;
+        private boolean mDimWallpaper;
+        private boolean mNightMode;
+        private boolean mDisableAutoBrightness;
+        private boolean mDisableTapToWake;
+        private boolean mDisableTiltToWake;
+        private boolean mDisableTouch;
+        private boolean mMinimizeRadioUsage;
+        private boolean mMaximizeDoze;
+
+        /**
+         * Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled).
+         */
+        public Builder() {
+        }
+
+        /**
+         * Instantiates a new {@link ZenPolicy.Builder} with all effects set to their corresponding
+         * values in the supplied {@link ZenDeviceEffects}.
+         */
+        public Builder(@NonNull ZenDeviceEffects zenDeviceEffects) {
+            mGrayscale = zenDeviceEffects.shouldDisplayGrayscale();
+            mSuppressAmbientDisplay = zenDeviceEffects.shouldSuppressAmbientDisplay();
+            mDimWallpaper = zenDeviceEffects.shouldDimWallpaper();
+            mNightMode = zenDeviceEffects.shouldUseNightMode();
+            mDisableAutoBrightness = zenDeviceEffects.shouldDisableAutoBrightness();
+            mDisableTapToWake = zenDeviceEffects.shouldDisableTapToWake();
+            mDisableTiltToWake = zenDeviceEffects.shouldDisableTiltToWake();
+            mDisableTouch = zenDeviceEffects.shouldDisableTouch();
+            mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage();
+            mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze();
+        }
+
+        /**
+         * Sets whether the level of color saturation of the display should be set to minimum,
+         * effectively switching it to grayscale, while the rule is active.
+         */
+        @NonNull
+        public Builder setShouldDisplayGrayscale(boolean grayscale) {
+            mGrayscale = grayscale;
+            return this;
+        }
+
+        /**
+         * Sets whether the ambient (always-on) display feature should be disabled while the rule
+         * is active. This will have no effect if the device doesn't support always-on display or if
+         * it's not generally enabled.
+         */
+        @NonNull
+        public Builder setShouldSuppressAmbientDisplay(boolean suppressAmbientDisplay) {
+            mSuppressAmbientDisplay = suppressAmbientDisplay;
+            return this;
+        }
+
+        /** Sets whether the wallpaper should be dimmed while the rule is active. */
+        @NonNull
+        public Builder setShouldDimWallpaper(boolean dimWallpaper) {
+            mDimWallpaper = dimWallpaper;
+            return this;
+        }
+
+        /** Sets whether night mode (aka dark theme) should be applied while the rule is active. */
+        @NonNull
+        public Builder setShouldUseNightMode(boolean nightMode) {
+            mNightMode = nightMode;
+            return this;
+        }
+
+        /**
+         * Sets whether the display's automatic brightness adjustment should be disabled while the
+         * rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldDisableAutoBrightness(boolean disableAutoBrightness) {
+            mDisableAutoBrightness = disableAutoBrightness;
+            return this;
+        }
+
+        /**
+         * Sets whether "tap to wake" should be disabled while the rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldDisableTapToWake(boolean disableTapToWake) {
+            mDisableTapToWake = disableTapToWake;
+            return this;
+        }
+
+        /**
+         * Sets whether "tilt to wake" should be disabled while the rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldDisableTiltToWake(boolean disableTiltToWake) {
+            mDisableTiltToWake = disableTiltToWake;
+            return this;
+        }
+
+        /**
+         * Sets whether touch interactions should be disabled while the rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldDisableTouch(boolean disableTouch) {
+            mDisableTouch = disableTouch;
+            return this;
+        }
+
+        /**
+         * Sets whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption,
+         * should be minimized while the rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldMinimizeRadioUsage(boolean minimizeRadioUsage) {
+            mMinimizeRadioUsage = minimizeRadioUsage;
+            return this;
+        }
+
+        /**
+         * Sets whether Doze should be enhanced (e.g. with more aggresive activation, or less
+         * frequent maintenance windows) while the rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldMaximizeDoze(boolean maximizeDoze) {
+            mMaximizeDoze = maximizeDoze;
+            return this;
+        }
+
+        /** Builds a {@link ZenDeviceEffects} object based on the builder's state. */
+        @NonNull
+        public ZenDeviceEffects build() {
+            return new ZenDeviceEffects(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper,
+                    mNightMode, mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake,
+                    mDisableTouch, mMinimizeRadioUsage, mMaximizeDoze);
+        }
+    }
+}
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index ff6dffd..1e08fd8 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -85,6 +85,7 @@
             CONFIDENCE_LEVEL_HIGH,
             CONFIDENCE_LEVEL_VERY_HIGH
     })
+    @Retention(RetentionPolicy.SOURCE)
     @interface HotwordConfidenceLevelValue {
     }
 
diff --git a/core/java/android/service/voice/HotwordRejectedResult.java b/core/java/android/service/voice/HotwordRejectedResult.java
index 7b3f47d..26c1ca4 100644
--- a/core/java/android/service/voice/HotwordRejectedResult.java
+++ b/core/java/android/service/voice/HotwordRejectedResult.java
@@ -22,6 +22,9 @@
 
 import com.android.internal.util.DataClass;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Represents a result supporting the rejected hotword trigger.
  *
@@ -57,6 +60,7 @@
             CONFIDENCE_LEVEL_MEDIUM,
             CONFIDENCE_LEVEL_HIGH
     })
+    @Retention(RetentionPolicy.SOURCE)
     @interface HotwordConfidenceLevelValue {
     }
 
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 04ae0af..bf09ec1 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -273,8 +273,8 @@
         int mCurHeight;
         float mZoom = 0f;
         int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-        int mWindowPrivateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS
-                | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+        int mWindowPrivateFlags =
+                WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
         int mCurWindowFlags = mWindowFlags;
         int mCurWindowPrivateFlags = mWindowPrivateFlags;
         Rect mPreviewSurfacePosition;
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 35834fd..e6fcc0c 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -38,6 +38,7 @@
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.CloseGuard;
 import android.util.Log;
 import android.util.Slog;
 
@@ -46,6 +47,7 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
 import java.util.List;
 import java.util.Objects;
 import java.util.Queue;
@@ -59,6 +61,9 @@
  * {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)}. This class's methods must be
  * invoked only from the main application thread.
  *
+ * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a
+ * SpeechRecognizer object when it is no longer needed.
+ *
  * <p>The implementation of this API is likely to stream audio to remote servers to perform speech
  * recognition. As such this API is not intended to be used for continuous recognition, which would
  * consume a significant amount of battery and bandwidth.
@@ -301,6 +306,8 @@
     /** The actual RecognitionService endpoint */
     private IRecognitionService mService;
 
+    private final CloseGuard mCloseGuard = new CloseGuard();
+
     /** Context with which the manager was created */
     private final Context mContext;
 
@@ -366,9 +373,7 @@
      * {@link #createSpeechRecognizer} static factory method
      */
     private SpeechRecognizer(final Context context, final ComponentName serviceComponent) {
-        mContext = context;
-        mServiceComponent = serviceComponent;
-        mOnDevice = false;
+        this(context, serviceComponent, false);
     }
 
     /**
@@ -376,9 +381,17 @@
      * {@link #createOnDeviceSpeechRecognizer} static factory method
      */
     private SpeechRecognizer(final Context context, boolean onDevice) {
+        this(context, null, onDevice);
+    }
+
+    private SpeechRecognizer(
+            final Context context,
+            final ComponentName serviceComponent,
+            final boolean onDevice) {
         mContext = context;
-        mServiceComponent = null;
+        mServiceComponent = serviceComponent;
         mOnDevice = onDevice;
+        mCloseGuard.open("SpeechRecognizer#destroy()");
     }
 
     /**
@@ -418,6 +431,9 @@
      * command to the created {@code SpeechRecognizer}, otherwise no notifications will be
      * received.
      *
+     * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a
+     * SpeechRecognizer object when it is no longer needed.
+     *
      * <p>For apps targeting Android 11 (API level 30) interaction with a speech recognition
      * service requires <queries> element to be added to the manifest file:
      * <pre>{@code
@@ -445,6 +461,9 @@
      * Use this version of the method to specify a specific service to direct this
      * {@link SpeechRecognizer} to.
      *
+     * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a
+     * SpeechRecognizer object when it is no longer needed.
+     *
      * <p><strong>Important</strong>: before calling this method, please check via
      * {@link android.content.pm.PackageManager#queryIntentServices(Intent, int)} that {@code
      * serviceComponent} actually exists and provides
@@ -486,6 +505,9 @@
      * before dispatching any command to the created {@code SpeechRecognizer}, otherwise no
      * notifications will be received.
      *
+     * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a
+     * SpeechRecognizer object when it is no longer needed.
+     *
      * @param context in which to create {@code SpeechRecognizer}
      * @return a new on-device {@code SpeechRecognizer}.
      * @throws UnsupportedOperationException iff {@link #isOnDeviceRecognitionAvailable(Context)}
@@ -870,7 +892,7 @@
     }
 
     private boolean checkOpenConnection() {
-        if (mService != null) {
+        if (mService != null && mService.asBinder().isBinderAlive()) {
             return true;
         }
         mListener.onError(ERROR_CLIENT);
@@ -886,17 +908,32 @@
 
     /** Destroys the {@code SpeechRecognizer} object. */
     public void destroy() {
-        if (mService != null) {
-            try {
-                mService.cancel(mListener, /*isShutdown*/ true);
-            } catch (final Exception e) {
-                // Not important
+        try {
+            if (mService != null) {
+                try {
+                    mService.cancel(mListener, /*isShutdown*/ true);
+                } catch (final Exception e) {
+                    // Not important
+                }
             }
-        }
 
-        mService = null;
-        mPendingTasks.clear();
-        mListener.mInternalListener = null;
+            mService = null;
+            mPendingTasks.clear();
+            mListener.mInternalListener = null;
+            mCloseGuard.close();
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            mCloseGuard.warnIfOpen();
+            destroy();
+        } finally {
+            super.finalize();
+        }
     }
 
     /** Establishes a connection to system server proxy and initializes the session. */
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 766e924..b91a878 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -163,12 +163,6 @@
     public static final String SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS =
             "settings_remoteauth_enrollment";
 
-    /** Flag to enable/disable entire page in Accessibility -> Hearing aids
-     *  @hide
-     */
-    public static final String SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE =
-            "settings_accessibility_hearing_aid_page";
-
     /**
      * Flag to enable/disable preferring the AccessibilityMenu service in the system.
      * @hide
@@ -244,7 +238,6 @@
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
         DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
-        DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, "true");
         DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
         DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
         DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 6c3b8ab..17bbee6d0 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -114,8 +114,6 @@
 
     IWindowSession openSession(in IWindowSessionCallback callback);
 
-    boolean useBLAST();
-
     @UnsupportedAppUsage
     void getInitialDisplaySize(int displayId, out Point size);
     @UnsupportedAppUsage
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 45b3fdd..6a588eb8 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -26,6 +26,7 @@
 import android.gui.TouchOcclusionMode;
 import android.os.IBinder;
 import android.os.InputConfig;
+import android.util.Size;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -106,6 +107,9 @@
     // Window frame.
     public final Rect frame = new Rect();
 
+    // The real size of the content, excluding any crop. If no buffer is rendered, this is 0,0
+    public Size contentSize = new Size(0, 0);
+
     public int surfaceInset;
 
     // Global scaling factor applied to touch events when they are dispatched
@@ -199,6 +203,7 @@
             transform.set(other.transform);
         }
         focusTransferTarget = other.focusTransferTarget;
+        contentSize = new Size(other.contentSize.getWidth(), other.contentSize.getHeight());
     }
 
     @Override
@@ -211,6 +216,7 @@
                 .append(", windowToken=").append(windowToken)
                 .append(", displayId=").append(displayId)
                 .append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0)
+                .append(", contentSize=").append(contentSize)
                 .toString();
 
     }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ac2c550..145af2e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -442,9 +442,6 @@
      */
     private boolean mForceNextConfigUpdate;
 
-    private boolean mUseBLASTAdapter;
-    private boolean mForceDisableBLAST;
-
     /** lazily-initialized in getAudioManager() */
     private boolean mFastScrollSoundEffectsEnabled = false;
 
@@ -1290,8 +1287,6 @@
                 if (mWindowAttributes.packageName == null) {
                     mWindowAttributes.packageName = mBasePackageName;
                 }
-                mWindowAttributes.privateFlags |=
-                        WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
 
                 attrs = mWindowAttributes;
                 setTag();
@@ -1502,9 +1497,6 @@
                     Slog.i(mTag, "(" + mBasePackageName + ") Initial DisplayState: "
                             + mAttachInfo.mDisplayState, new Throwable());
                 }
-                if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
-                    mUseBLASTAdapter = true;
-                }
 
                 if (view instanceof RootViewSurfaceTaker) {
                     mInputQueueCallback =
@@ -1901,8 +1893,7 @@
             mWindowAttributes.insetsFlags.appearance = appearance;
             mWindowAttributes.insetsFlags.behavior = behavior;
             mWindowAttributes.privateFlags |= compatibleWindowFlag
-                    | appearanceAndBehaviorPrivateFlags
-                    | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+                    | appearanceAndBehaviorPrivateFlags;
 
             if (mWindowAttributes.preservePreviousSurfaceInsets) {
                 // Restore old surface insets.
@@ -8692,11 +8683,7 @@
         }
 
         if (mSurfaceControl.isValid()) {
-            if (!useBLAST()) {
-                mSurface.copyFrom(mSurfaceControl);
-            } else {
-                updateBlastSurfaceIfNeeded();
-            }
+            updateBlastSurfaceIfNeeded();
             if (mAttachInfo.mThreadedRenderer != null) {
                 mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
             }
@@ -11533,7 +11520,7 @@
         SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
         transaction.setBlurRegions(surfaceControl, regionCopy);
 
-        if (useBLAST() && mBlastBufferQueue != null) {
+        if (mBlastBufferQueue != null) {
             mBlastBufferQueue.mergeWithNextTransaction(transaction, frameNumber);
         }
     }
@@ -11550,18 +11537,6 @@
         mUnbufferedInputSource = mView.mUnbufferedInputSource;
     }
 
-    /**
-     * Force disabling use of the BLAST adapter regardless of the system
-     * flag. Needs to be called before addView.
-     */
-    void forceDisableBLAST() {
-        mForceDisableBLAST = true;
-    }
-
-    boolean useBLAST() {
-        return mUseBLASTAdapter && !mForceDisableBLAST;
-    }
-
     int getSurfaceSequenceId() {
         return mSurfaceSequenceId;
     }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c735142..92ce9f7 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -651,6 +651,7 @@
             REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY,
             REMOVE_CONTENT_MODE_DESTROY,
     })
+    @Retention(RetentionPolicy.SOURCE)
     @interface RemoveContentMode {}
 
     /**
@@ -685,6 +686,7 @@
             DISPLAY_IME_POLICY_FALLBACK_DISPLAY,
             DISPLAY_IME_POLICY_HIDE,
     })
+    @Retention(RetentionPolicy.SOURCE)
     @interface DisplayImePolicy {}
 
     /**
@@ -3249,13 +3251,6 @@
         public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24;
 
         /**
-         * Flag to request creation of a BLAST (Buffer as LayerState) Layer.
-         * If not specified the client will receive a BufferQueue layer.
-         * @hide
-         */
-        public static final int PRIVATE_FLAG_USE_BLAST = 1 << 25;
-
-        /**
          * Flag to indicate that the window is controlling the appearance of system bars. So we
          * don't need to adjust it by reading its system UI flags for compatibility.
          * @hide
@@ -3340,7 +3335,6 @@
                 PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
                 PRIVATE_FLAG_NOT_MAGNIFIABLE,
                 PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
-                PRIVATE_FLAG_USE_BLAST,
                 PRIVATE_FLAG_APPEARANCE_CONTROLLED,
                 PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
                 PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
@@ -3349,6 +3343,7 @@
                 PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP,
                 PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY,
         })
+        @Retention(RetentionPolicy.SOURCE)
         public @interface PrivateFlags {}
 
         /**
@@ -3438,10 +3433,6 @@
                         equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
                         name = "COLOR_SPACE_AGNOSTIC"),
                 @ViewDebug.FlagToString(
-                        mask = PRIVATE_FLAG_USE_BLAST,
-                        equals = PRIVATE_FLAG_USE_BLAST,
-                        name = "USE_BLAST"),
-                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
                         equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
                         name = "APPEARANCE_CONTROLLED"),
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index d20d95d..214f1ec 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -60,8 +60,6 @@
 public final class WindowManagerGlobal {
     private static final String TAG = "WindowManager";
 
-    private static boolean sUseBLASTAdapter = false;
-
     /**
      * This is the first time the window is being drawn,
      * so the client must call drawingFinished() when done
@@ -99,7 +97,6 @@
 
     public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1;
     public static final int ADD_FLAG_APP_VISIBLE = 0x2;
-    public static final int ADD_FLAG_USE_BLAST = 0x8;
 
     /**
      * Like {@link #RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS}, but as a "hint" when adding the
@@ -176,7 +173,6 @@
                     if (sWindowManagerService != null) {
                         ValueAnimator.setDurationScale(
                                 sWindowManagerService.getCurrentAnimatorScale());
-                        sUseBLASTAdapter = sWindowManagerService.useBLAST();
                     }
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
@@ -218,13 +214,6 @@
         }
     }
 
-    /**
-     * Whether or not to use BLAST for ViewRootImpl
-     */
-    public static boolean useBLAST() {
-        return sUseBLASTAdapter;
-    }
-
     @UnsupportedAppUsage
     public String[] getViewRootNames() {
         synchronized (mLock) {
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 652fe24..2746340 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -245,8 +245,7 @@
             }
         }
 
-        final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
-                        WindowManagerGlobal.ADD_FLAG_USE_BLAST;
+        final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
 
         sendLayoutParamsToParent();
         // Include whether the window is in touch mode.
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index c4d43bc..eb2a101 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -86,6 +86,7 @@
      * Granular level on which text should be operated.
      */
     @IntDef({GRANULARITY_CHARACTER, GRANULARITY_WORD})
+    @Retention(RetentionPolicy.SOURCE)
     @interface Granularity {}
 
     /**
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index eeab005..589b7a3 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1176,24 +1176,33 @@
                         mActive = interactive;
                         mFullscreenMode = fullscreen;
                         if (interactive) {
+                            // Find the next view focus to start the input connection when the
+                            // device was interactive.
                             final View rootView =
                                     mCurRootView != null ? mCurRootView.getView() : null;
                             if (rootView == null) {
+                                // No window focused or view was removed, ignore request.
                                 return;
                             }
-                            // Find the next view focus to start the input connection when the
-                            // device was interactive.
                             final ViewRootImpl currentViewRootImpl = mCurRootView;
+                            // Post this on UI thread as required for view focus code.
                             rootView.post(() -> {
                                 synchronized (mH) {
                                     if (mCurRootView != currentViewRootImpl) {
+                                        // Focused window changed since posting, ignore request.
                                         return;
                                     }
                                 }
-                                final View focusedView = currentViewRootImpl.getView().findFocus();
+                                final View curRootView = currentViewRootImpl.getView();
+                                if (curRootView == null) {
+                                    // View was removed, ignore request.
+                                    return;
+                                }
+                                final View focusedView = curRootView.findFocus();
                                 onViewFocusChangedInternal(focusedView, focusedView != null);
                             });
                         } else {
+                            // Finish input connection when device becomes non-interactive.
                             finishInputLocked();
                             if (isImeSessionAvailableLocked()) {
                                 mCurBindState.mImeSession.finishInput();
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index e44f436..4816f35 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -27,6 +27,9 @@
 import android.os.Parcelable;
 import android.os.RemoteCallback;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Information to be sent to SysUI about a back event.
  *
@@ -85,6 +88,7 @@
             TYPE_CROSS_TASK,
             TYPE_CALLBACK
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface BackTargetType {
     }
 
diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java
index 0ce076b6..1bd921b 100644
--- a/core/java/android/window/ClientWindowFrames.java
+++ b/core/java/android/window/ClientWindowFrames.java
@@ -22,6 +22,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * The window frame container class used by client side for layout.
  * @hide
@@ -101,6 +103,29 @@
     }
 
     @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final ClientWindowFrames other = (ClientWindowFrames) o;
+        return frame.equals(other.frame)
+                && displayFrame.equals(other.displayFrame)
+                && parentFrame.equals(other.parentFrame)
+                && Objects.equals(attachedFrame, other.attachedFrame)
+                && isParentFrameClippedByDisplayCutout == other.isParentFrameClippedByDisplayCutout
+                && compatScale == other.compatScale;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(frame, displayFrame, parentFrame, attachedFrame,
+                isParentFrameClippedByDisplayCutout, compatScale);
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 7585826..cc875ad 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -37,7 +37,6 @@
 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
 
 import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
 import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
@@ -426,7 +425,7 @@
         // Setting as trusted overlay to let touches pass through. This is safe because this
         // window is controlled by the system.
         layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
-                | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST;
+                | PRIVATE_FLAG_TRUSTED_OVERLAY;
         layoutParams.token = token;
         layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
         layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
index f1c0d8d..b6c04d9 100644
--- a/core/java/android/window/SplashScreen.java
+++ b/core/java/android/window/SplashScreen.java
@@ -33,6 +33,8 @@
 import android.util.Singleton;
 import android.util.Slog;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
 /**
@@ -65,6 +67,7 @@
             SPLASH_SCREEN_STYLE_SOLID_COLOR,
             SPLASH_SCREEN_STYLE_ICON
     })
+    @Retention(RetentionPolicy.SOURCE)
     @interface SplashScreenStyle {}
 
     /**
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index b63a969..9fe30df 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -6,4 +6,12 @@
   description: "When necessary, configuration decoupled from status bar and display cutout"
   bug: "291870756"
   is_fixed_read_only: true
+}
+
+flag {
+  name: "movable_cutout_configuration"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Make it possible to move cutout across edges through device config"
+  bug: "302387383"
+  is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
similarity index 75%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
index 1826f7a..b0f3578 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,19 +26,6 @@
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedActivity extends ParsedMainComponent {
 
-    /**
-     * Generate activity object that forwards user to App Details page automatically.
-     * This activity should be invisible to user and user should not know or see it.
-     * @hide
-     */
-    @NonNull
-    static ParsedActivity makeAppDetailsActivity(String packageName, String processName,
-            int uiOptions, String taskAffinity, boolean hardwareAccelerated) {
-        // Proxy method since ParsedActivityImpl is supposed to be package visibility
-        return ParsedActivityImpl.makeAppDetailsActivity(packageName, processName, uiOptions,
-                taskAffinity, hardwareAccelerated);
-    }
-
     int getColorMode();
 
     int getConfigChanges();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemService.java
similarity index 95%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedApexSystemService.java
index cf478b1..adb5f4f 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttribution.java
similarity index 96%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedAttribution.java
index 1a5d110..5b623cd 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttribution.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.StringRes;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java b/core/java/com/android/internal/pm/pkg/component/ParsedComponent.java
similarity index 96%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedComponent.java
index 5b6ecba..319ed7f 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedComponent.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentation.java
similarity index 94%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedInstrumentation.java
index c325d8d..76c5008 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentation.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.Nullable;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfo.java
similarity index 95%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedIntentInfo.java
index a7f7b00..fee0017 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfo.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java
similarity index 95%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java
index b926d53..291ed0c 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermission.java
similarity index 95%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedPermission.java
index dc5347a..813d14d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermission.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroup.java
similarity index 94%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroup.java
index 64f4fbd..a5ba513 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroup.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 /** @hide */
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
similarity index 96%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
index 608d08e..e5247f9 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java b/core/java/com/android/internal/pm/pkg/component/ParsedProvider.java
similarity index 96%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedProvider.java
index c66a5c1..ba5470f 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProvider.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java b/core/java/com/android/internal/pm/pkg/component/ParsedService.java
similarity index 94%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedService.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedService.java
index 5fc251c..e361102 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.Nullable;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermission.java
similarity index 96%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedUsesPermission.java
index e17d1c4..984d50d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermission.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index ea3c70f..c3d21a4 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -59,6 +59,7 @@
     jfieldID layoutParamsType;
     jfieldID dispatchingTimeoutMillis;
     jfieldID frame;
+    jfieldID contentSize;
     jfieldID surfaceInset;
     jfieldID scaleFactor;
     jfieldID touchableRegion;
@@ -281,6 +282,9 @@
     ScopedLocalRef<jobject> rectObj(env, JNICommon::objFromRect(env, windowInfo.frame));
     env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.frame, rectObj.get());
 
+    ScopedLocalRef<jobject> sizeObj(env, JNICommon::objFromSize(env, windowInfo.contentSize));
+    env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.contentSize, sizeObj.get());
+
     env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.surfaceInset,
                      windowInfo.surfaceInset);
     env->SetFloatField(inputWindowHandle, gInputWindowHandleClassInfo.scaleFactor,
@@ -393,6 +397,9 @@
 
     GET_FIELD_ID(gInputWindowHandleClassInfo.frame, clazz, "frame", "Landroid/graphics/Rect;");
 
+    GET_FIELD_ID(gInputWindowHandleClassInfo.contentSize, clazz, "contentSize",
+                 "Landroid/util/Size;");
+
     GET_FIELD_ID(gInputWindowHandleClassInfo.surfaceInset, clazz,
             "surfaceInset", "I");
 
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index 95bf49f..aebe7ea 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -16,15 +16,16 @@
 
 #define LOG_TAG "PerfHint-jni"
 
-#include "jni.h"
-
+#include <android/performance_hint.h>
 #include <dlfcn.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <utils/Log.h>
+
 #include <vector>
 
 #include "core_jni_helpers.h"
+#include "jni.h"
 
 namespace android {
 
@@ -44,6 +45,11 @@
 typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t);
 typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const);
 typedef void (*APH_setPreferPowerEfficiency)(APerformanceHintSession*, bool);
+typedef void (*APH_reportActualWorkDuration2)(APerformanceHintSession*, AWorkDuration*);
+
+typedef AWorkDuration* (*AWD_create)();
+typedef void (*AWD_setTimeNanos)(AWorkDuration*, int64_t);
+typedef void (*AWD_release)(AWorkDuration*);
 
 bool gAPerformanceHintBindingInitialized = false;
 APH_getManager gAPH_getManagerFn = nullptr;
@@ -56,6 +62,14 @@
 APH_setThreads gAPH_setThreadsFn = nullptr;
 APH_getThreadIds gAPH_getThreadIdsFn = nullptr;
 APH_setPreferPowerEfficiency gAPH_setPreferPowerEfficiencyFn = nullptr;
+APH_reportActualWorkDuration2 gAPH_reportActualWorkDuration2Fn = nullptr;
+
+AWD_create gAWD_createFn = nullptr;
+AWD_setTimeNanos gAWD_setWorkPeriodStartTimestampNanosFn = nullptr;
+AWD_setTimeNanos gAWD_setActualTotalDurationNanosFn = nullptr;
+AWD_setTimeNanos gAWD_setActualCpuDurationNanosFn = nullptr;
+AWD_setTimeNanos gAWD_setActualGpuDurationNanosFn = nullptr;
+AWD_release gAWD_releaseFn = nullptr;
 
 void ensureAPerformanceHintBindingInitialized() {
     if (gAPerformanceHintBindingInitialized) return;
@@ -112,9 +126,46 @@
             (APH_setPreferPowerEfficiency)dlsym(handle_,
                                                 "APerformanceHint_setPreferPowerEfficiency");
     LOG_ALWAYS_FATAL_IF(gAPH_setPreferPowerEfficiencyFn == nullptr,
-                        "Failed to find required symbol"
+                        "Failed to find required symbol "
                         "APerformanceHint_setPreferPowerEfficiency!");
 
+    gAPH_reportActualWorkDuration2Fn =
+            (APH_reportActualWorkDuration2)dlsym(handle_,
+                                                 "APerformanceHint_reportActualWorkDuration2");
+    LOG_ALWAYS_FATAL_IF(gAPH_reportActualWorkDuration2Fn == nullptr,
+                        "Failed to find required symbol "
+                        "APerformanceHint_reportActualWorkDuration2!");
+
+    gAWD_createFn = (AWD_create)dlsym(handle_, "AWorkDuration_create");
+    LOG_ALWAYS_FATAL_IF(gAWD_createFn == nullptr,
+                        "Failed to find required symbol AWorkDuration_create!");
+
+    gAWD_setWorkPeriodStartTimestampNanosFn =
+            (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setWorkPeriodStartTimestampNanos");
+    LOG_ALWAYS_FATAL_IF(gAWD_setWorkPeriodStartTimestampNanosFn == nullptr,
+                        "Failed to find required symbol "
+                        "AWorkDuration_setWorkPeriodStartTimestampNanos!");
+
+    gAWD_setActualTotalDurationNanosFn =
+            (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualTotalDurationNanos");
+    LOG_ALWAYS_FATAL_IF(gAWD_setActualTotalDurationNanosFn == nullptr,
+                        "Failed to find required symbol "
+                        "AWorkDuration_setActualTotalDurationNanos!");
+
+    gAWD_setActualCpuDurationNanosFn =
+            (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualCpuDurationNanos");
+    LOG_ALWAYS_FATAL_IF(gAWD_setActualCpuDurationNanosFn == nullptr,
+                        "Failed to find required symbol AWorkDuration_setActualCpuDurationNanos!");
+
+    gAWD_setActualGpuDurationNanosFn =
+            (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualGpuDurationNanos");
+    LOG_ALWAYS_FATAL_IF(gAWD_setActualGpuDurationNanosFn == nullptr,
+                        "Failed to find required symbol AWorkDuration_setActualGpuDurationNanos!");
+
+    gAWD_releaseFn = (AWD_release)dlsym(handle_, "AWorkDuration_release");
+    LOG_ALWAYS_FATAL_IF(gAWD_releaseFn == nullptr,
+                        "Failed to find required symbol AWorkDuration_release!");
+
     gAPerformanceHintBindingInitialized = true;
 }
 
@@ -238,6 +289,25 @@
                                     enabled);
 }
 
+static void nativeReportActualWorkDuration2(JNIEnv* env, jclass clazz, jlong nativeSessionPtr,
+                                            jlong workPeriodStartTimestampNanos,
+                                            jlong actualTotalDurationNanos,
+                                            jlong actualCpuDurationNanos,
+                                            jlong actualGpuDurationNanos) {
+    ensureAPerformanceHintBindingInitialized();
+
+    AWorkDuration* workDuration = gAWD_createFn();
+    gAWD_setWorkPeriodStartTimestampNanosFn(workDuration, workPeriodStartTimestampNanos);
+    gAWD_setActualTotalDurationNanosFn(workDuration, actualTotalDurationNanos);
+    gAWD_setActualCpuDurationNanosFn(workDuration, actualCpuDurationNanos);
+    gAWD_setActualGpuDurationNanosFn(workDuration, actualGpuDurationNanos);
+
+    gAPH_reportActualWorkDuration2Fn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
+                                     workDuration);
+
+    gAWD_releaseFn(workDuration);
+}
+
 static const JNINativeMethod gPerformanceHintMethods[] = {
         {"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
         {"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
@@ -249,6 +319,7 @@
         {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
         {"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds},
         {"nativeSetPreferPowerEfficiency", "(JZ)V", (void*)nativeSetPreferPowerEfficiency},
+        {"nativeReportActualWorkDuration", "(JJJJJ)V", (void*)nativeReportActualWorkDuration2},
 };
 
 int register_android_os_PerformanceHintManager(JNIEnv* env) {
diff --git a/core/jni/jni_common.cpp b/core/jni/jni_common.cpp
index b81c9b6..dd69b16 100644
--- a/core/jni/jni_common.cpp
+++ b/core/jni/jni_common.cpp
@@ -34,6 +34,11 @@
     jfieldID top;
 } gRectClassInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gSizeClassInfo;
+
 Rect JNICommon::rectFromObj(JNIEnv* env, jobject rectObj) {
     int left = env->GetIntField(rectObj, gRectClassInfo.left);
     int top = env->GetIntField(rectObj, gRectClassInfo.top);
@@ -47,6 +52,10 @@
                           rect.right, rect.bottom);
 }
 
+jobject JNICommon::objFromSize(JNIEnv* env, Size size) {
+    return env->NewObject(gSizeClassInfo.clazz, gSizeClassInfo.ctor, size.width, size.height);
+}
+
 int register_jni_common(JNIEnv* env) {
     jclass rectClazz = FindClassOrDie(env, "android/graphics/Rect");
     gRectClassInfo.clazz = MakeGlobalRefOrDie(env, rectClazz);
@@ -55,6 +64,11 @@
     gRectClassInfo.left = GetFieldIDOrDie(env, rectClazz, "left", "I");
     gRectClassInfo.right = GetFieldIDOrDie(env, rectClazz, "right", "I");
     gRectClassInfo.top = GetFieldIDOrDie(env, rectClazz, "top", "I");
+
+    jclass sizeClazz = FindClassOrDie(env, "android/util/Size");
+    gSizeClassInfo.clazz = MakeGlobalRefOrDie(env, sizeClazz);
+    gSizeClassInfo.ctor = GetMethodIDOrDie(env, sizeClazz, "<init>", "(II)V");
+
     return 0;
 }
 
diff --git a/core/jni/jni_common.h b/core/jni/jni_common.h
index d670a7d..f1c6014 100644
--- a/core/jni/jni_common.h
+++ b/core/jni/jni_common.h
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 #include <jni.h>
+#include <ui/Size.h>
 
 namespace android {
 
 class Rect;
+using ui::Size;
 
 class JNICommon {
 public:
     static Rect rectFromObj(JNIEnv* env, jobject rectObj);
     static jobject objFromRect(JNIEnv* env, Rect rect);
+    static jobject objFromSize(JNIEnv* env, Size size);
 };
 } // namespace android
\ No newline at end of file
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 7642794..39d958c 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -205,6 +205,13 @@
     <string name="config_satellite_emergency_handover_intent_action" translatable="false"></string>
     <java-symbol type="string" name="config_satellite_emergency_handover_intent_action" />
 
+    <!-- Whether outgoing satellite datagrams should be sent to modem in demo mode. When satellite
+         is enabled for demo mode, if this config is enabled, outgoing datagrams will be sent to
+         modem; otherwise, success results will be returned. If demo mode is disabled, outgoing
+         datagrams are always sent to modem. -->
+    <bool name="config_send_satellite_datagram_to_modem_in_demo_mode">false</bool>
+    <java-symbol type="bool" name="config_send_satellite_datagram_to_modem_in_demo_mode" />
+
     <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
          will not perform handover if the target transport is out of service, or VoPS not
          supported. The network will be torn down on the source transport, and will be
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 709646b..3a2e50a 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -34,7 +34,7 @@
          http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ -->
 
     <!-- Arab Emirates -->
-    <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214|6253" />
+    <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" />
 
     <!-- Albania: 5 digits, known short codes listed -->
     <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
@@ -155,7 +155,7 @@
     <shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" />
 
     <!-- Israel: 4 digits, known premium codes listed -->
-    <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545"  free="37477" />
+    <shortcode country="il" pattern="\\d{4}" premium="4422|4545" />
 
     <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU:
          https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf -->
@@ -198,9 +198,6 @@
     <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
     <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" />
 
-    <!-- Namibia: 5 digits -->
-    <shortcode country="na" pattern="\\d{1,5}" free="40005" />
-
     <!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
     <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" />
 
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 36e1223..4b02257 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -227,8 +227,7 @@
         try {
             // Send process level config change.
             ClientTransaction transaction = newTransaction(activityThread);
-            transaction.addCallback(ConfigurationChangeItem.obtain(
-                    new Configuration(newConfig), DEVICE_ID_INVALID));
+            transaction.addCallback(ConfigurationChangeItem.obtain(newConfig, DEVICE_ID_INVALID));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
@@ -245,7 +244,7 @@
             newConfig.smallestScreenWidthDp++;
             transaction = newTransaction(activityThread);
             transaction.addCallback(ActivityConfigurationChangeItem.obtain(
-                    activity.getActivityToken(), new Configuration(newConfig)));
+                    activity.getActivityToken(), newConfig));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 4bbde0c..723c081 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertSame;
 
+import android.annotation.NonNull;
 import android.app.ActivityOptions;
 import android.app.IApplicationThread;
 import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder;
@@ -80,74 +81,29 @@
 
     @Test
     public void testRecycleActivityConfigurationChangeItem() {
-        ActivityConfigurationChangeItem emptyItem = ActivityConfigurationChangeItem.obtain(
-                null /* activityToken */, Configuration.EMPTY);
-        ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(
-                mActivityToken, config());
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        ActivityConfigurationChangeItem item2 = ActivityConfigurationChangeItem.obtain(
-                mActivityToken, config());
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+        testRecycle(() -> ActivityConfigurationChangeItem.obtain(mActivityToken, config()));
     }
 
     @Test
     public void testRecycleActivityResultItem() {
-        ActivityResultItem emptyItem = ActivityResultItem.obtain(
-                null /* activityToken */, null /* resultInfoList */);
-        ActivityResultItem item = ActivityResultItem.obtain(mActivityToken, resultInfoList());
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        ActivityResultItem item2 = ActivityResultItem.obtain(mActivityToken, resultInfoList());
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+        testRecycle(() -> ActivityResultItem.obtain(mActivityToken, resultInfoList()));
     }
 
     @Test
     public void testRecycleConfigurationChangeItem() {
-        ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null, 0);
-        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1);
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config(), 1);
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+        testRecycle(() -> ConfigurationChangeItem.obtain(config(), 1));
     }
 
     @Test
     public void testRecycleDestroyActivityItem() {
-        DestroyActivityItem emptyItem = DestroyActivityItem.obtain(
-                null /* activityToken */, false, 0);
-        DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true, 117);
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        DestroyActivityItem item2 = DestroyActivityItem.obtain(mActivityToken, true, 14);
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+        testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true, 117));
     }
 
     @Test
     public void testRecycleLaunchActivityItem() {
         final IBinder activityToken = new Binder();
         final Intent intent = new Intent("action");
-        int ident = 57;
+        final int ident = 57;
         final ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.flags = 42;
         activityInfo.setMaxAspectRatio(2.4f);
@@ -158,20 +114,19 @@
         final Configuration overrideConfig = new Configuration();
         overrideConfig.assetsSeq = 5;
         final String referrer = "referrer";
-        int procState = 4;
+        final int procState = 4;
         final Bundle bundle = new Bundle();
         bundle.putString("key", "value");
         final PersistableBundle persistableBundle = new PersistableBundle();
         persistableBundle.putInt("k", 4);
         final IBinder assistToken = new Binder();
         final IBinder shareableActivityToken = new Binder();
-        int deviceId = 3;
+        final int deviceId = 3;
+        final IBinder taskFragmentToken = new Binder();
 
-        final Supplier<LaunchActivityItem> itemSupplier = () -> new LaunchActivityItemBuilder()
-                .setActivityToken(activityToken)
-                .setIntent(intent)
+        testRecycle(() -> new LaunchActivityItemBuilder(
+                activityToken, intent, activityInfo)
                 .setIdent(ident)
-                .setInfo(activityInfo)
                 .setCurConfig(config())
                 .setOverrideConfig(overrideConfig)
                 .setReferrer(referrer)
@@ -183,154 +138,74 @@
                 .setIsForward(true)
                 .setAssistToken(assistToken)
                 .setShareableActivityToken(shareableActivityToken)
-                .setTaskFragmentToken(new Binder())
+                .setTaskFragmentToken(taskFragmentToken)
                 .setDeviceId(deviceId)
-                .build();
-
-        LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build();
-        LaunchActivityItem item = itemSupplier.get();
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        LaunchActivityItem item2 = itemSupplier.get();
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+                .build());
     }
 
     @Test
     public void testRecycleActivityRelaunchItem() {
-        ActivityRelaunchItem emptyItem = ActivityRelaunchItem.obtain(
-                null /* activityToken */, null, null, 0, null, false);
-        Configuration overrideConfig = new Configuration();
-        overrideConfig.assetsSeq = 5;
-        ActivityRelaunchItem item = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(),
-                referrerIntentList(), 42, mergedConfig(), true);
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        ActivityRelaunchItem item2 = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(),
-                referrerIntentList(), 42, mergedConfig(), true);
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+        testRecycle(() -> ActivityRelaunchItem.obtain(mActivityToken,
+                resultInfoList(), referrerIntentList(), 42, mergedConfig(), true));
     }
 
     @Test
     public void testRecycleMoveToDisplayItem() {
-        MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(
-                null /* activityToken */, 0, Configuration.EMPTY);
-        MoveToDisplayItem item = MoveToDisplayItem.obtain(mActivityToken, 4, config());
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        MoveToDisplayItem item2 = MoveToDisplayItem.obtain(mActivityToken, 3, config());
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+        testRecycle(() -> MoveToDisplayItem.obtain(mActivityToken, 4, config()));
     }
 
     @Test
     public void testRecycleNewIntentItem() {
-        NewIntentItem emptyItem = NewIntentItem.obtain(
-                null /* activityToken */, null /* intents */, false /* resume */);
-        NewIntentItem item = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false);
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        NewIntentItem item2 = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false);
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+        testRecycle(() -> NewIntentItem.obtain(mActivityToken, referrerIntentList(), false));
     }
 
     @Test
     public void testRecyclePauseActivityItemItem() {
-        PauseActivityItem emptyItem = PauseActivityItem.obtain(
-                null /* activityToken */, false, false, 0, false, false);
-        PauseActivityItem item = PauseActivityItem.obtain(
-                mActivityToken, true, true, 5, true, true);
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        PauseActivityItem item2 = PauseActivityItem.obtain(
-                mActivityToken, true, false, 5, true, true);
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+        testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, 5, true, true));
     }
 
     @Test
     public void testRecycleResumeActivityItem() {
-        ResumeActivityItem emptyItem = ResumeActivityItem.obtain(
-                null /* activityToken */, false, false);
-        ResumeActivityItem item = ResumeActivityItem.obtain(mActivityToken, 3, true, false);
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        ResumeActivityItem item2 = ResumeActivityItem.obtain(mActivityToken, 2, true, false);
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+        testRecycle(() -> ResumeActivityItem.obtain(mActivityToken, 3, true, false));
     }
 
     @Test
     public void testRecycleStartActivityItem() {
-        StartActivityItem emptyItem = StartActivityItem.obtain(
-                null /* activityToken */, null /* activityOptions */);
-        StartActivityItem item = StartActivityItem.obtain(mActivityToken,
-                ActivityOptions.makeBasic());
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        StartActivityItem item2 = StartActivityItem.obtain(mActivityToken,
-                ActivityOptions.makeBasic().setLaunchDisplayId(10));
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+        testRecycle(() -> StartActivityItem.obtain(mActivityToken, ActivityOptions.makeBasic()));
     }
 
     @Test
     public void testRecycleStopItem() {
-        StopActivityItem emptyItem = StopActivityItem.obtain(null /* activityToken */, 0);
-        StopActivityItem item = StopActivityItem.obtain(mActivityToken, 4);
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        StopActivityItem item2 = StopActivityItem.obtain(mActivityToken, 3);
-        assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+        testRecycle(() -> StopActivityItem.obtain(mActivityToken, 4));
     }
 
     @Test
     public void testRecycleClientTransaction() {
-        ClientTransaction emptyItem = ClientTransaction.obtain(null);
-        ClientTransaction item = ClientTransaction.obtain(mApplicationThread);
-        assertNotSame(item, emptyItem);
-        assertNotEquals(item, emptyItem);
+        testRecycle(() -> ClientTransaction.obtain(mApplicationThread));
+    }
 
+    private void testRecycle(@NonNull Supplier<? extends ObjectPoolItem> obtain) {
+        // Reuse the same object after recycle.
+        final ObjectPoolItem item = obtain.get();
         item.recycle();
-        assertEquals(item, emptyItem);
+        final ObjectPoolItem item2 = obtain.get();
 
-        ClientTransaction item2 = ClientTransaction.obtain(mApplicationThread);
         assertSame(item, item2);
-        assertNotEquals(item2, emptyItem);
+
+        // Create new object when the pool is empty.
+        final ObjectPoolItem item3 = obtain.get();
+
+        assertNotSame(item, item3);
+        assertEquals(item, item3);
+
+        // Reset fields after recycle.
+        item.recycle();
+
+        assertNotEquals(item, item3);
+
+        // Recycled objects are equal.
+        item3.recycle();
+
+        assertEquals(item, item3);
     }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 5a88bad..c0e2a49 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -18,6 +18,8 @@
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
@@ -92,17 +94,18 @@
     }
 
     static class LaunchActivityItemBuilder {
-        @Nullable
-        private IBinder mActivityToken;
-        @Nullable
-        private Intent mIntent;
+        @NonNull
+        private final IBinder mActivityToken;
+        @NonNull
+        private final Intent mIntent;
+        @NonNull
+        private final ActivityInfo mInfo;
+        @NonNull
+        private final Configuration mCurConfig = new Configuration();
+        @NonNull
+        private final Configuration mOverrideConfig = new Configuration();
+
         private int mIdent;
-        @Nullable
-        private ActivityInfo mInfo;
-        @Nullable
-        private Configuration mCurConfig;
-        @Nullable
-        private Configuration mOverrideConfig;
         private int mDeviceId;
         @Nullable
         private String mReferrer;
@@ -130,16 +133,11 @@
         @Nullable
         private IBinder mTaskFragmentToken;
 
-        @NonNull
-        LaunchActivityItemBuilder setActivityToken(@Nullable IBinder activityToken) {
-            mActivityToken = activityToken;
-            return this;
-        }
-
-        @NonNull
-        LaunchActivityItemBuilder setIntent(@Nullable Intent intent) {
-            mIntent = intent;
-            return this;
+        LaunchActivityItemBuilder(@NonNull IBinder activityToken, @NonNull Intent intent,
+                @NonNull ActivityInfo info) {
+            mActivityToken = requireNonNull(activityToken);
+            mIntent = requireNonNull(intent);
+            mInfo = requireNonNull(info);
         }
 
         @NonNull
@@ -149,20 +147,14 @@
         }
 
         @NonNull
-        LaunchActivityItemBuilder setInfo(@Nullable ActivityInfo info) {
-            mInfo = info;
+        LaunchActivityItemBuilder setCurConfig(@NonNull Configuration curConfig) {
+            mCurConfig.setTo(curConfig);
             return this;
         }
 
         @NonNull
-        LaunchActivityItemBuilder setCurConfig(@Nullable Configuration curConfig) {
-            mCurConfig = curConfig;
-            return this;
-        }
-
-        @NonNull
-        LaunchActivityItemBuilder setOverrideConfig(@Nullable Configuration overrideConfig) {
-            mOverrideConfig = overrideConfig;
+        LaunchActivityItemBuilder setOverrideConfig(@NonNull Configuration overrideConfig) {
+            mOverrideConfig.setTo(overrideConfig);
             return this;
         }
 
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index f2b0f2e..443dcb4 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -44,6 +44,8 @@
 import android.app.ClientTransactionHandler;
 import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
 import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -290,7 +292,7 @@
         // A previous queued launch transaction runs on main thread (execute).
         final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
         final LaunchActivityItem launchItem =
-                spy(new LaunchActivityItemBuilder().setActivityToken(token).build());
+                spy(new LaunchActivityItemBuilder(token, new Intent(), new ActivityInfo()).build());
         launchTransaction.addCallback(launchItem);
         mExecutor.execute(launchTransaction);
 
@@ -322,7 +324,7 @@
         // A previous queued launch transaction runs on main thread (execute).
         final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
         final LaunchActivityItem launchItem =
-                spy(new LaunchActivityItemBuilder().setActivityToken(token).build());
+                spy(new LaunchActivityItemBuilder(token, new Intent(), new ActivityInfo()).build());
         launchTransaction.addTransactionItem(launchItem);
         mExecutor.execute(launchTransaction);
 
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 4aa62c5..07921bf 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -173,11 +173,9 @@
         final PersistableBundle persistableBundle = new PersistableBundle();
         persistableBundle.putInt("k", 4);
 
-        final LaunchActivityItem item = new LaunchActivityItemBuilder()
-                .setActivityToken(activityToken)
-                .setIntent(intent)
+        final LaunchActivityItem item = new LaunchActivityItemBuilder(
+                activityToken, intent, activityInfo)
                 .setIdent(ident)
-                .setInfo(activityInfo)
                 .setCurConfig(config())
                 .setOverrideConfig(overrideConfig)
                 .setReferrer(referrer)
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
index c00eb91..4d45daf 100644
--- a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
@@ -52,16 +52,18 @@
     private PendingTransactionActions mPendingActions;
     @Mock
     private IWindow mWindow;
-    @Mock
-    private ClientWindowFrames mFrames;
-    @Mock
-    private MergedConfiguration mConfiguration;
-    @Mock
+
     private InsetsState mInsetsState;
+    private ClientWindowFrames mFrames;
+    private MergedConfiguration mConfiguration;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+
+        mInsetsState = new InsetsState();
+        mFrames = new ClientWindowFrames();
+        mConfiguration = new MergedConfiguration();
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 20ba427..9b4dec4 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -182,4 +182,42 @@
         s.setPreferPowerEfficiency(true);
         s.setPreferPowerEfficiency(true);
     }
+
+    @Test
+    public void testReportActualWorkDurationWithWorkDurationClass() {
+        Session s = createSession();
+        assumeNotNull(s);
+        s.updateTargetWorkDuration(16);
+        s.reportActualWorkDuration(new WorkDuration(1, 12, 8, 6));
+        s.reportActualWorkDuration(new WorkDuration(1, 33, 14, 20));
+        s.reportActualWorkDuration(new WorkDuration(1, 14, 10, 6));
+    }
+
+    @Test
+    public void testReportActualWorkDurationWithWorkDurationClass_IllegalArgument() {
+        Session s = createSession();
+        assumeNotNull(s);
+        s.updateTargetWorkDuration(16);
+        assertThrows(IllegalArgumentException.class, () -> {
+            s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6));
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6));
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6));
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6));
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6));
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6));
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1));
+        });
+    }
 }
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index 66fabec..a4bce9e 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -23,6 +23,8 @@
 
 import libcore.util.NativeAllocationRegistry;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.Buffer;
 import java.nio.ShortBuffer;
 
@@ -43,6 +45,7 @@
      * Determines how the mesh is represented and will be drawn.
      */
     @IntDef({TRIANGLES, TRIANGLE_STRIP})
+    @Retention(RetentionPolicy.SOURCE)
     private @interface Mode {}
 
     /**
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 5509f00..45e29a8 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -62,6 +62,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.Objects;
 
@@ -116,6 +118,7 @@
      */
     @IntDef({TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP,
             TYPE_URI_ADAPTIVE_BITMAP})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface IconType {
     }
 
diff --git a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
index 5c8c84c..ed00a87 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
@@ -21,8 +21,8 @@
     android:layout_width="wrap_content"
     android:paddingTop="48dp"
     android:paddingBottom="48dp"
-    android:paddingEnd="@dimen/bubble_user_education_padding_end"
-    android:layout_marginEnd="@dimen/bubble_user_education_margin_end"
+    android:paddingHorizontal="@dimen/bubble_user_education_padding_horizontal"
+    android:layout_marginEnd="@dimen/bubble_user_education_margin_horizontal"
     android:orientation="vertical"
     android:background="@drawable/bubble_stack_user_education_bg"
     >
diff --git a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
index b28f58f..4f6bdfd 100644
--- a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
@@ -23,8 +23,8 @@
     android:clickable="true"
     android:paddingTop="28dp"
     android:paddingBottom="16dp"
-    android:paddingEnd="@dimen/bubble_user_education_padding_end"
-    android:layout_marginEnd="@dimen/bubble_user_education_margin_end"
+    android:paddingEnd="@dimen/bubble_user_education_padding_horizontal"
+    android:layout_marginEnd="@dimen/bubble_user_education_margin_horizontal"
     android:orientation="vertical"
     android:background="@drawable/bubble_stack_user_education_bg"
     >
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index de9d2a2..f20d44d 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -224,9 +224,9 @@
     <dimen name="bubbles_user_education_width">480dp</dimen>
     <!-- Margin applied to the end of the user education views (really only matters for phone
          since the width is match parent). -->
-    <dimen name="bubble_user_education_margin_end">24dp</dimen>
+    <dimen name="bubble_user_education_margin_horizontal">24dp</dimen>
     <!-- Padding applied to the end of the user education view. -->
-    <dimen name="bubble_user_education_padding_end">58dp</dimen>
+    <dimen name="bubble_user_education_padding_horizontal">58dp</dimen>
     <!-- Padding between the bubble and the user education text. -->
     <dimen name="bubble_user_education_stack_padding">16dp</dimen>
     <!-- Max width for the bubble popup view. -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 144c456..09ae84a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -46,6 +46,12 @@
             ? "BubblePositioner"
             : BubbleDebugConfig.TAG_BUBBLES;
 
+    /** The screen edge the bubble stack is pinned to */
+    public enum StackPinnedEdge {
+        LEFT,
+        RIGHT
+    }
+
     /** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/
     public static final int NUM_VISIBLE_WHEN_RESTING = 2;
     /** Indicates a bubble's height should be the maximum available space. **/
@@ -694,6 +700,15 @@
         final boolean startOnLeft = isAppBubble
                 ? layoutDirection == LAYOUT_DIRECTION_RTL
                 : layoutDirection != LAYOUT_DIRECTION_RTL;
+        return getStartPosition(startOnLeft ? StackPinnedEdge.LEFT : StackPinnedEdge.RIGHT);
+    }
+
+    /**
+     * The stack position to use if user education is being shown.
+     *
+     * @param stackPinnedEdge the screen edge the stack is pinned to.
+     */
+    public PointF getStartPosition(StackPinnedEdge stackPinnedEdge) {
         final RectF allowableStackPositionRegion = getAllowableStackPositionRegion(
                 1 /* default starts with 1 bubble */);
         if (isLargeScreen()) {
@@ -702,7 +717,7 @@
             final float desiredY = mScreenRect.height() / 2f - (mBubbleSize / 2f);
             final float offset = desiredY / mScreenRect.height();
             return new BubbleStackView.RelativeStackPosition(
-                    startOnLeft,
+                    stackPinnedEdge == StackPinnedEdge.LEFT,
                     offset)
                     .getAbsolutePositionInRegion(allowableStackPositionRegion);
         } else {
@@ -710,7 +725,7 @@
                     R.dimen.bubble_stack_starting_offset_y);
             // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
             return new BubbleStackView.RelativeStackPosition(
-                    startOnLeft,
+                    stackPinnedEdge == StackPinnedEdge.LEFT,
                     startingVerticalOffset / mPositionRect.height())
                     .getAbsolutePositionInRegion(allowableStackPositionRegion);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 87461dc..2cee675 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -25,6 +25,8 @@
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.LEFT;
+import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.RIGHT;
 import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
@@ -1296,6 +1298,9 @@
         return shouldShow;
     }
 
+    /**
+     * Show manage education if should show and was not showing before.
+     */
     private void maybeShowManageEdu() {
         if (!shouldShowManageEdu()) {
             return;
@@ -1304,7 +1309,16 @@
             mManageEduView = new ManageEducationView(mContext, mPositioner);
             addView(mManageEduView);
         }
-        mManageEduView.show(mExpandedBubble.getExpandedView());
+        showManageEdu();
+    }
+
+    /**
+     * Show manage education if was not showing before.
+     */
+    private void showManageEdu() {
+        if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) return;
+        mManageEduView.show(mExpandedBubble.getExpandedView(),
+                mStackAnimationController.isStackOnLeftSide());
     }
 
     @VisibleForTesting
@@ -1350,10 +1364,21 @@
             mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
             addView(mStackEduView);
         }
+        return showStackEdu();
+    }
+
+    /**
+     * @return true if education view for the collapsed stack was not showing before.
+     */
+    private boolean showStackEdu() {
+        // Stack appears on top of the education views
         mBubbleContainer.bringToFront();
         // Ensure the stack is in the correct spot
-        mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
-        return mStackEduView.show(mPositioner.getDefaultStartPosition());
+        PointF position = mPositioner.getStartPosition(
+                mStackAnimationController.isStackOnLeftSide() ? LEFT : RIGHT);
+        // Animate stack to the position
+        mStackAnimationController.springStackAfterFling(position.x, position.y);
+        return mStackEduView.show(position);
     }
 
     @VisibleForTesting
@@ -1367,16 +1392,13 @@
             removeView(mStackEduView);
             mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
             addView(mStackEduView);
-            mBubbleContainer.bringToFront(); // Stack appears on top of the stack education
-            // Ensure the stack is in the correct spot
-            mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
-            mStackEduView.show(mPositioner.getDefaultStartPosition());
+            showStackEdu();
         }
         if (isManageEduVisible()) {
             removeView(mManageEduView);
             mManageEduView = new ManageEducationView(mContext, mPositioner);
             addView(mManageEduView);
-            mManageEduView.show(mExpandedBubble.getExpandedView());
+            showManageEdu();
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 1b41f79..61e17c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -19,6 +19,7 @@
 import android.graphics.Color
 import android.graphics.Rect
 import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -32,10 +33,10 @@
  * User education view to highlight the manage button that allows a user to configure the settings
  * for the bubble. Shown only the first time a user expands a bubble.
  */
-class ManageEducationView constructor(context: Context, positioner: BubblePositioner)
-    : LinearLayout(context) {
+class ManageEducationView(context: Context, positioner: BubblePositioner) : LinearLayout(context) {
 
-    private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
+    private val TAG =
+        if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
         else BubbleDebugConfig.TAG_BUBBLES
 
     private val ANIMATE_DURATION: Long = 200
@@ -62,7 +63,7 @@
 
     override fun setLayoutDirection(layoutDirection: Int) {
         super.setLayoutDirection(layoutDirection)
-        setDrawableDirection()
+        setDrawableDirection(layoutDirection == LAYOUT_DIRECTION_LTR)
     }
 
     override fun onFinishInflate() {
@@ -71,8 +72,10 @@
     }
 
     private fun setButtonColor() {
-        val typedArray = mContext.obtainStyledAttributes(intArrayOf(
-                com.android.internal.R.attr.colorAccentPrimary))
+        val typedArray =
+            mContext.obtainStyledAttributes(
+                intArrayOf(com.android.internal.R.attr.colorAccentPrimary)
+            )
         val buttonColor = typedArray.getColor(0 /* index */, Color.TRANSPARENT)
         typedArray.recycle()
 
@@ -81,11 +84,11 @@
         gotItButton.setBackgroundDrawable(ColorDrawable(buttonColor))
     }
 
-    private fun setDrawableDirection() {
+    private fun setDrawableDirection(isOnLeft: Boolean) {
         manageView.setBackgroundResource(
-            if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL)
-                R.drawable.bubble_stack_user_education_bg_rtl
-            else R.drawable.bubble_stack_user_education_bg)
+            if (isOnLeft) R.drawable.bubble_stack_user_education_bg
+            else R.drawable.bubble_stack_user_education_bg_rtl
+        )
     }
 
     /**
@@ -93,48 +96,31 @@
      * bubble stack is expanded for the first time.
      *
      * @param expandedView the expandedView the user education is shown on top of.
+     * @param isStackOnLeft the bubble stack position on the screen
      */
-    fun show(expandedView: BubbleExpandedView) {
+    fun show(expandedView: BubbleExpandedView, isStackOnLeft: Boolean) {
         setButtonColor()
         if (visibility == VISIBLE) return
 
         bubbleExpandedView = expandedView
         expandedView.taskView?.setObscuredTouchRect(Rect(positioner.screenRect))
 
-        layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape)
-            context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
-        else ViewGroup.LayoutParams.MATCH_PARENT
-
         alpha = 0f
         visibility = View.VISIBLE
         expandedView.getManageButtonBoundsOnScreen(realManageButtonRect)
-        val isRTL = mContext.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
-        if (isRTL) {
-            val rightPadding = positioner.screenRect.right - realManageButtonRect.right -
-                    expandedView.manageButtonMargin
-            manageView.setPadding(manageView.paddingLeft, manageView.paddingTop,
-                    rightPadding, manageView.paddingBottom)
-        } else {
-            manageView.setPadding(realManageButtonRect.left - expandedView.manageButtonMargin,
-            manageView.paddingTop, manageView.paddingRight, manageView.paddingBottom)
-        }
+        layoutManageView(realManageButtonRect, expandedView.manageButtonMargin, isStackOnLeft)
+
         post {
-            manageButton
-                .setOnClickListener {
-                    hide()
-                    expandedView.requireViewById<View>(R.id.manage_button).performClick()
-                }
+            manageButton.setOnClickListener {
+                hide()
+                expandedView.requireViewById<View>(R.id.manage_button).performClick()
+            }
             gotItButton.setOnClickListener { hide() }
             setOnClickListener { hide() }
 
             val offsetViewBounds = Rect()
             manageButton.getDrawingRect(offsetViewBounds)
             manageView.offsetDescendantRectToMyCoords(manageButton, offsetViewBounds)
-            if (isRTL && (positioner.isLargeScreen || positioner.isLandscape)) {
-                translationX = (positioner.screenRect.right - width).toFloat()
-            } else {
-                translationX = 0f
-            }
             translationY = (realManageButtonRect.top - offsetViewBounds.top).toFloat()
             bringToFront()
             animate()
@@ -145,6 +131,79 @@
         setShouldShow(false)
     }
 
+    /**
+     * On tablet the user education is aligned to the left or to right side depending on where the
+     * stack is positioned when collapsed. On phone the user education follows the layout direction.
+     *
+     * @param manageButtonRect the manage button rect on the screen
+     * @param manageButtonMargin the manage button margin
+     * @param isStackOnLeft the bubble stack position on the screen
+     */
+    private fun layoutManageView(
+        manageButtonRect: Rect,
+        manageButtonMargin: Int,
+        isStackOnLeft: Boolean
+    ) {
+        val isLTR = resources.configuration.layoutDirection == LAYOUT_DIRECTION_LTR
+        val isPinnedLeft = if (positioner.isLargeScreen) isStackOnLeft else isLTR
+        val paddingHorizontal =
+            resources.getDimensionPixelSize(R.dimen.bubble_user_education_padding_horizontal)
+
+        // The user education view background image direction
+        setDrawableDirection(isPinnedLeft)
+
+        // The user education view layout gravity
+        gravity = if (isPinnedLeft) Gravity.LEFT else Gravity.RIGHT
+
+        // The user education view width
+        manageView.layoutParams.width =
+            when {
+                // Left-to-Right direction and the education is on the right side
+                isLTR && !isPinnedLeft ->
+                    positioner.screenRect.right -
+                        (manageButtonRect.left - manageButtonMargin - paddingHorizontal)
+                // Right-to-Left direction and the education is on the left side
+                !isLTR && isPinnedLeft ->
+                    manageButtonRect.right + manageButtonMargin + paddingHorizontal
+                // Large screen and the education position matches the layout direction
+                positioner.isLargeScreen -> ViewGroup.LayoutParams.WRAP_CONTENT
+                // Small screen, landscape orientation
+                positioner.isLandscape ->
+                    resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
+                // Otherwise
+                else -> ViewGroup.LayoutParams.MATCH_PARENT
+            }
+
+        // The user education view margin on the opposite side of where it's pinned
+        (manageView.layoutParams as MarginLayoutParams).apply {
+            val edgeMargin =
+                resources.getDimensionPixelSize(R.dimen.bubble_user_education_margin_horizontal)
+            leftMargin = if (isPinnedLeft) 0 else edgeMargin
+            rightMargin = if (isPinnedLeft) edgeMargin else 0
+        }
+
+        // The user education view padding
+        manageView.apply {
+            val paddingLeft =
+                if (isLTR && isPinnedLeft) {
+                    // Offset on the left to align with the manage button
+                    manageButtonRect.left - manageButtonMargin
+                } else {
+                    // Use default padding
+                    paddingHorizontal
+                }
+            val paddingRight =
+                if (!isLTR && !isPinnedLeft) {
+                    // Offset on the right to align with the manage button
+                    positioner.screenRect.right - manageButtonRect.right - manageButtonMargin
+                } else {
+                    // Use default padding
+                    paddingHorizontal
+                }
+            setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
+        }
+    }
+
     fun hide() {
         bubbleExpandedView?.taskView?.setObscuredTouchRect(null)
         if (visibility != VISIBLE || isHiding) return
@@ -160,9 +219,12 @@
     }
 
     private fun setShouldShow(shouldShow: Boolean) {
-        context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
-                .edit().putBoolean(PREF_MANAGED_EDUCATION, !shouldShow).apply()
+        context
+            .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+            .edit()
+            .putBoolean(PREF_MANAGED_EDUCATION, !shouldShow)
+            .apply()
     }
 }
 
-const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
\ No newline at end of file
+const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 5e3a077..2cabb65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -21,7 +21,6 @@
 import android.view.KeyEvent
 import android.view.LayoutInflater
 import android.view.View
-import android.view.View.OnKeyListener
 import android.view.ViewGroup
 import android.widget.LinearLayout
 import android.widget.TextView
@@ -30,16 +29,17 @@
 import com.android.wm.shell.animation.Interpolators
 
 /**
- * User education view to highlight the collapsed stack of bubbles.
- * Shown only the first time a user taps the stack.
+ * User education view to highlight the collapsed stack of bubbles. Shown only the first time a user
+ * taps the stack.
  */
-class StackEducationView constructor(
+class StackEducationView(
     context: Context,
     positioner: BubblePositioner,
     controller: BubbleController
 ) : LinearLayout(context) {
 
-    private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
+    private val TAG =
+        if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
         else BubbleDebugConfig.TAG_BUBBLES
 
     private val ANIMATE_DURATION: Long = 200
@@ -69,7 +69,7 @@
 
     override fun setLayoutDirection(layoutDirection: Int) {
         super.setLayoutDirection(layoutDirection)
-        setDrawableDirection()
+        setDrawableDirection(layoutDirection == LAYOUT_DIRECTION_LTR)
     }
 
     override fun onFinishInflate() {
@@ -111,16 +111,16 @@
         descTextView.setTextColor(textColor)
     }
 
-    private fun setDrawableDirection() {
+    private fun setDrawableDirection(isOnLeft: Boolean) {
         view.setBackgroundResource(
-            if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR)
-                R.drawable.bubble_stack_user_education_bg
-            else R.drawable.bubble_stack_user_education_bg_rtl)
+            if (isOnLeft) R.drawable.bubble_stack_user_education_bg
+            else R.drawable.bubble_stack_user_education_bg_rtl
+        )
     }
 
     /**
-     * If necessary, shows the user education view for the bubble stack. This appears the first
-     * time a user taps on a bubble.
+     * If necessary, shows the user education view for the bubble stack. This appears the first time
+     * a user taps on a bubble.
      *
      * @return true if user education was shown and wasn't showing before, false otherwise.
      */
@@ -129,29 +129,44 @@
         if (visibility == VISIBLE) return false
 
         controller.updateWindowFlagsForBackpress(true /* interceptBack */)
-        layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape)
-            context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
-        else ViewGroup.LayoutParams.MATCH_PARENT
+        layoutParams.width =
+                if (positioner.isLargeScreen || positioner.isLandscape)
+                    context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
+                else ViewGroup.LayoutParams.MATCH_PARENT
 
-        val stackPadding = context.resources.getDimensionPixelSize(
-                R.dimen.bubble_user_education_stack_padding)
+        val isStackOnLeft = positioner.isStackOnLeft(stackPosition)
+        (view.layoutParams as MarginLayoutParams).apply {
+            // Update the horizontal margins depending on the stack position
+            val edgeMargin =
+                resources.getDimensionPixelSize(R.dimen.bubble_user_education_margin_horizontal)
+            leftMargin = if (isStackOnLeft) 0 else edgeMargin
+            rightMargin = if (isStackOnLeft) edgeMargin else 0
+        }
+
+        val stackPadding =
+            context.resources.getDimensionPixelSize(R.dimen.bubble_user_education_stack_padding)
         setAlpha(0f)
         setVisibility(View.VISIBLE)
+        setDrawableDirection(isOnLeft = isStackOnLeft)
         post {
             requestFocus()
             with(view) {
-                if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
-                    setPadding(positioner.bubbleSize + stackPadding, paddingTop, paddingRight,
-                            paddingBottom)
+                if (isStackOnLeft) {
+                    setPadding(
+                        positioner.bubbleSize + stackPadding,
+                        paddingTop,
+                        paddingRight,
+                        paddingBottom
+                    )
+                    translationX = 0f
                 } else {
-                    setPadding(paddingLeft, paddingTop, positioner.bubbleSize + stackPadding,
-                            paddingBottom)
-                    if (positioner.isLargeScreen || positioner.isLandscape) {
-                        translationX = (positioner.screenRect.right - width - stackPadding)
-                                .toFloat()
-                    } else {
-                        translationX = 0f
-                    }
+                    setPadding(
+                        paddingLeft,
+                        paddingTop,
+                        positioner.bubbleSize + stackPadding,
+                        paddingBottom
+                    )
+                    translationX = (positioner.screenRect.right - width - stackPadding).toFloat()
                 }
                 translationY = stackPosition.y + positioner.bubbleSize / 2 - getHeight() / 2
             }
@@ -168,7 +183,7 @@
      * If necessary, hides the stack education view.
      *
      * @param isExpanding if true this indicates the hide is happening due to the bubble being
-     *                      expanded, false if due to a touch outside of the bubble stack.
+     *   expanded, false if due to a touch outside of the bubble stack.
      */
     fun hide(isExpanding: Boolean) {
         if (visibility != VISIBLE || isHiding) return
@@ -182,9 +197,12 @@
     }
 
     private fun setShouldShow(shouldShow: Boolean) {
-        context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
-                .edit().putBoolean(PREF_STACK_EDUCATION, !shouldShow).apply()
+        context
+            .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+            .edit()
+            .putBoolean(PREF_STACK_EDUCATION, !shouldShow)
+            .apply()
     }
 }
 
-const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
\ No newline at end of file
+const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index b26d061..07c5429 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.unfold;
 
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
@@ -188,23 +189,27 @@
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
             @NonNull TransitionFinishCallback finishCallback) {
-        if (info.getType() == TRANSIT_CHANGE) {
-            // TODO (b/286928742) unfold transition handler should be part of mixed handler to
-            //  handle merges better.
-            for (int i = 0; i < info.getChanges().size(); ++i) {
-                final TransitionInfo.Change change = info.getChanges().get(i);
-                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                if (taskInfo != null
-                        && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
-                    // Tasks that are always on top (e.g. bubbles), will handle their own transition
-                    // as they are on top of everything else. So skip merging transitions here.
-                    return;
-                }
-            }
-            // Apply changes happening during the unfold animation immediately
-            t.apply();
-            finishCallback.onTransitionFinished(null);
+        if (info.getType() != TRANSIT_CHANGE) {
+            return;
         }
+        if ((info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
+            return;
+        }
+        // TODO (b/286928742) unfold transition handler should be part of mixed handler to
+        //  handle merges better.
+        for (int i = 0; i < info.getChanges().size(); ++i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+            if (taskInfo != null
+                    && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
+                // Tasks that are always on top (e.g. bubbles), will handle their own transition
+                // as they are on top of everything else. So skip merging transitions here.
+                return;
+            }
+        }
+        // Apply changes happening during the unfold animation immediately
+        t.apply();
+        finishCallback.onTransitionFinished(null);
     }
 
     /** Whether `request` contains an unfold action. */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index 7917ba6..6d73c12 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.unfold;
 
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_NONE;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -46,6 +47,7 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
 import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
 import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
 import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
@@ -265,6 +267,42 @@
         verify(finishCallback).onTransitionFinished(any());
     }
 
+    @Test
+    public void mergeAnimation_eatsDisplayOnlyTransitions() {
+        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+        TransitionFinishCallback mergeCallback = mock(TransitionFinishCallback.class);
+
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback);
+
+        // Offer a keyguard unlock transition - this should NOT merge
+        mUnfoldTransitionHandler.mergeAnimation(
+                new Binder(),
+                new TransitionInfoBuilder(TRANSIT_CHANGE, TRANSIT_FLAG_KEYGUARD_GOING_AWAY).build(),
+                mock(SurfaceControl.Transaction.class),
+                mTransition,
+                mergeCallback);
+        verify(finishCallback, never()).onTransitionFinished(any());
+
+        // Offer a CHANGE-only transition - this SHOULD merge (b/278064943)
+        mUnfoldTransitionHandler.mergeAnimation(
+                new Binder(),
+                new TransitionInfoBuilder(TRANSIT_CHANGE).build(),
+                mock(SurfaceControl.Transaction.class),
+                mTransition,
+                mergeCallback);
+        verify(mergeCallback).onTransitionFinished(any());
+
+        // We should never have finished the original transition.
+        verify(finishCallback, never()).onTransitionFinished(any());
+    }
+
     private TransitionRequestInfo createUnfoldTransitionRequestInfo() {
         ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
         TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
@@ -372,4 +410,4 @@
     private TransitionInfo createNonUnfoldTransitionInfo() {
         return new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
     }
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java
index 985a758..0f48abeb 100644
--- a/media/java/android/media/AudioHalVersionInfo.java
+++ b/media/java/android/media/AudioHalVersionInfo.java
@@ -22,6 +22,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -54,6 +56,7 @@
             flag = false,
             prefix = "AUDIO_HAL_TYPE_",
             value = {AUDIO_HAL_TYPE_HIDL, AUDIO_HAL_TYPE_AIDL})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface AudioHalType {}
 
     /** AudioHalVersionInfo object of all valid Audio HAL versions. */
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index faa7f7f..5331046 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -2018,6 +2018,8 @@
      * {@link #HDCP_V2_1},
      * {@link #HDCP_V2_2},
      * {@link #HDCP_V2_3}
+     *
+     * @removed mistakenly exposed previously
      */
     @Deprecated
     @Retention(RetentionPolicy.SOURCE)
@@ -2121,6 +2123,8 @@
      * {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO},
      * {@link #SECURITY_LEVEL_HW_SECURE_DECODE},
      * {@link #SECURITY_LEVEL_HW_SECURE_ALL}
+     *
+     * @removed mistakenly exposed previously
      */
     @Deprecated
     @Retention(RetentionPolicy.SOURCE)
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index 74ca4b8..99fcaf2 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -267,20 +267,26 @@
             HEAD_TRACKING_MODE_DISABLED,
             HEAD_TRACKING_MODE_RELATIVE_WORLD,
             HEAD_TRACKING_MODE_RELATIVE_DEVICE,
-    }) public @interface HeadTrackingMode {};
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HeadTrackingMode {};
 
     /** @hide */
     @IntDef(flag = false, value = {
             HEAD_TRACKING_MODE_DISABLED,
             HEAD_TRACKING_MODE_RELATIVE_WORLD,
             HEAD_TRACKING_MODE_RELATIVE_DEVICE,
-    }) public @interface HeadTrackingModeSet {};
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HeadTrackingModeSet {};
 
     /** @hide */
     @IntDef(flag = false, value = {
             HEAD_TRACKING_MODE_RELATIVE_WORLD,
             HEAD_TRACKING_MODE_RELATIVE_DEVICE,
-    }) public @interface HeadTrackingModeSupported {};
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HeadTrackingModeSupported {};
 
     /**
      * @hide
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b0af09c..f4be33c7 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -335,6 +335,13 @@
     APerformanceHint_closeSession; # introduced=Tiramisu
     APerformanceHint_setThreads; # introduced=UpsideDownCake
     APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream
+    APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream
+    AWorkDuration_create; # introduced=VanillaIceCream
+    AWorkDuration_release; # introduced=VanillaIceCream
+    AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream
+    AWorkDuration_setActualTotalDurationNanos; # introduced=VanillaIceCream
+    AWorkDuration_setActualCpuDurationNanos; # introduced=VanillaIceCream
+    AWorkDuration_setActualGpuDurationNanos; # introduced=VanillaIceCream
   local:
     *;
 };
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index c25df6e..c4c8128 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -18,12 +18,14 @@
 
 #include <aidl/android/hardware/power/SessionHint.h>
 #include <aidl/android/hardware/power/SessionMode.h>
+#include <android/WorkDuration.h>
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
 #include <android/performance_hint.h>
 #include <binder/Binder.h>
 #include <binder/IBinder.h>
 #include <binder/IServiceManager.h>
+#include <inttypes.h>
 #include <performance_hint_private.h>
 #include <utils/SystemClock.h>
 
@@ -75,10 +77,13 @@
     int setThreads(const int32_t* threadIds, size_t size);
     int getThreadIds(int32_t* const threadIds, size_t* size);
     int setPreferPowerEfficiency(bool enabled);
+    int reportActualWorkDuration(AWorkDuration* workDuration);
 
 private:
     friend struct APerformanceHintManager;
 
+    int reportActualWorkDurationInternal(WorkDuration* workDuration);
+
     sp<IHintManager> mHintManager;
     sp<IHintSession> mHintSession;
     // HAL preferred update rate
@@ -92,8 +97,7 @@
     // Last hint reported from sendHint indexed by hint value
     std::vector<int64_t> mLastHintSentTimestamp;
     // Cached samples
-    std::vector<int64_t> mActualDurationsNanos;
-    std::vector<int64_t> mTimestampsNanos;
+    std::vector<WorkDuration> mActualWorkDurations;
 };
 
 static IHintManager* gIHintManagerForTesting = nullptr;
@@ -195,8 +199,7 @@
      * Most of the workload is target_duration dependent, so now clear the cached samples
      * as they are most likely obsolete.
      */
-    mActualDurationsNanos.clear();
-    mTimestampsNanos.clear();
+    mActualWorkDurations.clear();
     mFirstTargetMetTimestamp = 0;
     mLastTargetMetTimestamp = 0;
     return 0;
@@ -207,43 +210,10 @@
         ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
         return EINVAL;
     }
-    int64_t now = elapsedRealtimeNano();
-    mActualDurationsNanos.push_back(actualDurationNanos);
-    mTimestampsNanos.push_back(now);
 
-    if (actualDurationNanos >= mTargetDurationNanos) {
-        // Reset timestamps if we are equal or over the target.
-        mFirstTargetMetTimestamp = 0;
-    } else {
-        // Set mFirstTargetMetTimestamp for first time meeting target.
-        if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp ||
-            (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) {
-            mFirstTargetMetTimestamp = now;
-        }
-        /**
-         * Rate limit the change if the update is over mPreferredRateNanos since first
-         * meeting target and less than mPreferredRateNanos since last meeting target.
-         */
-        if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
-            now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
-            return 0;
-        }
-        mLastTargetMetTimestamp = now;
-    }
+    WorkDuration workDuration(0, actualDurationNanos, actualDurationNanos, 0);
 
-    binder::Status ret =
-            mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos);
-    if (!ret.isOk()) {
-        ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
-              ret.exceptionMessage().c_str());
-        mFirstTargetMetTimestamp = 0;
-        mLastTargetMetTimestamp = 0;
-        return EPIPE;
-    }
-    mActualDurationsNanos.clear();
-    mTimestampsNanos.clear();
-
-    return 0;
+    return reportActualWorkDurationInternal(&workDuration);
 }
 
 int APerformanceHintSession::sendHint(SessionHint hint) {
@@ -322,6 +292,67 @@
     return OK;
 }
 
+int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* aWorkDuration) {
+    WorkDuration* workDuration = static_cast<WorkDuration*>(aWorkDuration);
+    if (workDuration->workPeriodStartTimestampNanos <= 0) {
+        ALOGE("%s: workPeriodStartTimestampNanos must be positive", __FUNCTION__);
+        return EINVAL;
+    }
+    if (workDuration->actualTotalDurationNanos <= 0) {
+        ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
+        return EINVAL;
+    }
+    if (workDuration->actualCpuDurationNanos <= 0) {
+        ALOGE("%s: cpuDurationNanos must be positive", __FUNCTION__);
+        return EINVAL;
+    }
+    if (workDuration->actualGpuDurationNanos < 0) {
+        ALOGE("%s: gpuDurationNanos must be non negative", __FUNCTION__);
+        return EINVAL;
+    }
+
+    return reportActualWorkDurationInternal(workDuration);
+}
+
+int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* workDuration) {
+    int64_t actualTotalDurationNanos = workDuration->actualTotalDurationNanos;
+    int64_t now = uptimeNanos();
+    workDuration->timestampNanos = now;
+    mActualWorkDurations.push_back(std::move(*workDuration));
+
+    if (actualTotalDurationNanos >= mTargetDurationNanos) {
+        // Reset timestamps if we are equal or over the target.
+        mFirstTargetMetTimestamp = 0;
+    } else {
+        // Set mFirstTargetMetTimestamp for first time meeting target.
+        if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp ||
+            (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) {
+            mFirstTargetMetTimestamp = now;
+        }
+        /**
+         * Rate limit the change if the update is over mPreferredRateNanos since first
+         * meeting target and less than mPreferredRateNanos since last meeting target.
+         */
+        if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
+            now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
+            return 0;
+        }
+        mLastTargetMetTimestamp = now;
+    }
+
+    binder::Status ret = mHintSession->reportActualWorkDuration2(mActualWorkDurations);
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
+              ret.exceptionMessage().c_str());
+        mFirstTargetMetTimestamp = 0;
+        mLastTargetMetTimestamp = 0;
+        return ret.exceptionCode() == binder::Status::EX_ILLEGAL_ARGUMENT ? EINVAL : EPIPE;
+    }
+    mActualWorkDurations.clear();
+
+    return 0;
+}
+
 // ===================================== C API
 APerformanceHintManager* APerformanceHint_getManager() {
     return APerformanceHintManager::getInstance();
@@ -376,6 +407,64 @@
     return session->setPreferPowerEfficiency(enabled);
 }
 
+int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session,
+                                               AWorkDuration* workDuration) {
+    if (session == nullptr || workDuration == nullptr) {
+        ALOGE("Invalid value: (session %p, workDuration %p)", session, workDuration);
+        return EINVAL;
+    }
+    return session->reportActualWorkDuration(workDuration);
+}
+
+AWorkDuration* AWorkDuration_create() {
+    WorkDuration* workDuration = new WorkDuration();
+    return static_cast<AWorkDuration*>(workDuration);
+}
+
+void AWorkDuration_release(AWorkDuration* aWorkDuration) {
+    if (aWorkDuration == nullptr) {
+        ALOGE("%s: aWorkDuration is nullptr", __FUNCTION__);
+    }
+    delete aWorkDuration;
+}
+
+void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* aWorkDuration,
+                                                    int64_t workPeriodStartTimestampNanos) {
+    if (aWorkDuration == nullptr || workPeriodStartTimestampNanos <= 0) {
+        ALOGE("%s: Invalid value. (AWorkDuration: %p, workPeriodStartTimestampNanos: %" PRIi64 ")",
+              __FUNCTION__, aWorkDuration, workPeriodStartTimestampNanos);
+    }
+    static_cast<WorkDuration*>(aWorkDuration)->workPeriodStartTimestampNanos =
+            workPeriodStartTimestampNanos;
+}
+
+void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration,
+                                               int64_t actualTotalDurationNanos) {
+    if (aWorkDuration == nullptr || actualTotalDurationNanos <= 0) {
+        ALOGE("%s: Invalid value. (AWorkDuration: %p, actualTotalDurationNanos: %" PRIi64 ")",
+              __FUNCTION__, aWorkDuration, actualTotalDurationNanos);
+    }
+    static_cast<WorkDuration*>(aWorkDuration)->actualTotalDurationNanos = actualTotalDurationNanos;
+}
+
+void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration,
+                                             int64_t actualCpuDurationNanos) {
+    if (aWorkDuration == nullptr || actualCpuDurationNanos <= 0) {
+        ALOGE("%s: Invalid value. (AWorkDuration: %p, actualCpuDurationNanos: %" PRIi64 ")",
+              __FUNCTION__, aWorkDuration, actualCpuDurationNanos);
+    }
+    static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos;
+}
+
+void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* aWorkDuration,
+                                             int64_t actualGpuDurationNanos) {
+    if (aWorkDuration == nullptr || actualGpuDurationNanos < 0) {
+        ALOGE("%s: Invalid value. (AWorkDuration: %p, actualGpuDurationNanos: %" PRIi64 ")",
+              __FUNCTION__, aWorkDuration, actualGpuDurationNanos);
+    }
+    static_cast<WorkDuration*>(aWorkDuration)->actualGpuDurationNanos = actualGpuDurationNanos;
+}
+
 void APerformanceHint_setIHintManagerForTesting(void* iManager) {
     delete gHintManagerForTesting;
     gHintManagerForTesting = nullptr;
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 22d33b1..4553b49 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "PerformanceHintNativeTest"
 
+#include <android/WorkDuration.h>
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
 #include <android/performance_hint.h>
@@ -60,6 +61,8 @@
     MOCK_METHOD(Status, setMode, (int32_t mode, bool enabled), (override));
     MOCK_METHOD(Status, close, (), (override));
     MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+    MOCK_METHOD(Status, reportActualWorkDuration2,
+                (const ::std::vector<android::os::WorkDuration>& workDurations), (override));
 };
 
 class PerformanceHintTest : public Test {
@@ -120,6 +123,7 @@
     std::vector<int64_t> actualDurations;
     actualDurations.push_back(20);
     EXPECT_CALL(*iSession, reportActualWorkDuration(Eq(actualDurations), _)).Times(Exactly(1));
+    EXPECT_CALL(*iSession, reportActualWorkDuration2(_)).Times(Exactly(1));
     result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos);
     EXPECT_EQ(0, result);
 
@@ -238,4 +242,125 @@
     APerformanceHintSession* session =
             APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
     ASSERT_TRUE(session);
-}
\ No newline at end of file
+}
+
+MATCHER_P(WorkDurationEq, expected, "") {
+    if (arg.size() != expected.size()) {
+        *result_listener << "WorkDuration vectors are different sizes. Expected: "
+                         << expected.size() << ", Actual: " << arg.size();
+        return false;
+    }
+    for (int i = 0; i < expected.size(); ++i) {
+        android::os::WorkDuration expectedWorkDuration = expected[i];
+        android::os::WorkDuration actualWorkDuration = arg[i];
+        if (!expectedWorkDuration.equalsWithoutTimestamp(actualWorkDuration)) {
+            *result_listener << "WorkDuration at [" << i << "] is different: "
+                             << "Expected: " << expectedWorkDuration
+                             << ", Actual: " << actualWorkDuration;
+            return false;
+        }
+    }
+    return true;
+}
+
+TEST_F(PerformanceHintTest, TestAPerformanceHint_reportActualWorkDuration2) {
+    APerformanceHintManager* manager = createManager();
+
+    std::vector<int32_t> tids;
+    tids.push_back(1);
+    tids.push_back(2);
+    int64_t targetDuration = 56789L;
+
+    StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
+    sp<IHintSession> session_sp(iSession);
+
+    EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
+
+    APerformanceHintSession* session =
+            APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
+    ASSERT_TRUE(session);
+
+    int64_t targetDurationNanos = 10;
+    EXPECT_CALL(*iSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1));
+    int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos);
+    EXPECT_EQ(0, result);
+
+    usleep(2); // Sleep for longer than preferredUpdateRateNanos.
+    {
+        std::vector<android::os::WorkDuration> actualWorkDurations;
+        android::os::WorkDuration workDuration(1, 20, 13, 8);
+        actualWorkDurations.push_back(workDuration);
+
+        EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
+                .Times(Exactly(1));
+        result = APerformanceHint_reportActualWorkDuration2(session,
+                                                            static_cast<AWorkDuration*>(
+                                                                    &workDuration));
+        EXPECT_EQ(0, result);
+    }
+
+    {
+        std::vector<android::os::WorkDuration> actualWorkDurations;
+        android::os::WorkDuration workDuration(-1, 20, 13, 8);
+        actualWorkDurations.push_back(workDuration);
+
+        EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
+                .Times(Exactly(1));
+        result = APerformanceHint_reportActualWorkDuration2(session,
+                                                            static_cast<AWorkDuration*>(
+                                                                    &workDuration));
+        EXPECT_EQ(22, result);
+    }
+    {
+        std::vector<android::os::WorkDuration> actualWorkDurations;
+        android::os::WorkDuration workDuration(1, -20, 13, 8);
+        actualWorkDurations.push_back(workDuration);
+
+        EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
+                .Times(Exactly(1));
+        result = APerformanceHint_reportActualWorkDuration2(session,
+                                                            static_cast<AWorkDuration*>(
+                                                                    &workDuration));
+        EXPECT_EQ(22, result);
+    }
+    {
+        std::vector<android::os::WorkDuration> actualWorkDurations;
+        android::os::WorkDuration workDuration(1, 20, -13, 8);
+        actualWorkDurations.push_back(workDuration);
+
+        EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
+                .Times(Exactly(1));
+        result = APerformanceHint_reportActualWorkDuration2(session,
+                                                            static_cast<AWorkDuration*>(
+                                                                    &workDuration));
+        EXPECT_EQ(EINVAL, result);
+    }
+    {
+        std::vector<android::os::WorkDuration> actualWorkDurations;
+        android::os::WorkDuration workDuration(1, 20, 13, -8);
+        actualWorkDurations.push_back(workDuration);
+
+        EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
+                .Times(Exactly(1));
+        result = APerformanceHint_reportActualWorkDuration2(session,
+                                                            static_cast<AWorkDuration*>(
+                                                                    &workDuration));
+        EXPECT_EQ(EINVAL, result);
+    }
+
+    EXPECT_CALL(*iSession, close()).Times(Exactly(1));
+    APerformanceHint_closeSession(session);
+}
+
+TEST_F(PerformanceHintTest, TestAWorkDuration) {
+    AWorkDuration* aWorkDuration = AWorkDuration_create();
+    ASSERT_NE(aWorkDuration, nullptr);
+
+    AWorkDuration_setWorkPeriodStartTimestampNanos(aWorkDuration, 1);
+    AWorkDuration_setActualTotalDurationNanos(aWorkDuration, 20);
+    AWorkDuration_setActualCpuDurationNanos(aWorkDuration, 13);
+    AWorkDuration_setActualGpuDurationNanos(aWorkDuration, 8);
+    AWorkDuration_release(aWorkDuration);
+}
diff --git a/packages/SettingsLib/Spa/screenshot/Android.bp b/packages/SettingsLib/Spa/screenshot/Android.bp
index bd508cb..dbf4ce0 100644
--- a/packages/SettingsLib/Spa/screenshot/Android.bp
+++ b/packages/SettingsLib/Spa/screenshot/Android.bp
@@ -18,6 +18,14 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+filegroup {
+    name: "SpaScreenshotTestRNGFiles",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+}
+
 android_test {
     name: "SpaScreenshotTests",
     use_resource_processor: true,
@@ -36,6 +44,7 @@
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "mockito-target-minus-junit4",
+        "platform-parametric-runner-lib",
         "platform-screenshot-diff-core",
     ],
     kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/Android.bp b/packages/SettingsLib/Spa/screenshot/robotests/Android.bp
new file mode 100644
index 0000000..6b8197c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/Android.bp
@@ -0,0 +1,74 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+    name: "SpaRoboRNGTestsAssetsLib",
+    asset_dirs: ["assets"],
+    sdk_version: "current",
+    platform_apis: true,
+    manifest: "AndroidManifest.xml",
+    optimize: {
+        enabled: false,
+    },
+    use_resource_processor: true,
+    resource_dirs: ["res"],
+}
+
+android_app {
+    name: "SpaRoboApp",
+    srcs: [],
+    static_libs: [
+        "androidx.test.espresso.core",
+        "androidx.appcompat_appcompat",
+        "flag-junit",
+        "guava",
+        "SpaLib",
+        "SpaLibTestUtils",
+        "SpaRoboRNGTestsAssetsLib",
+        "platform-screenshot-diff-core",
+        "PlatformComposeSceneTransitionLayoutTestsUtils",
+    ],
+    manifest: "robo-manifest.xml",
+    aaptflags: [
+        "--extra-packages",
+        "com.android.settingslib.spa.screenshot",
+    ],
+    dont_merge_manifests: true,
+    platform_apis: true,
+    system_ext_specific: true,
+    certificate: "platform",
+    privileged: true,
+    resource_dirs: [],
+    kotlincflags: ["-Xjvm-default=all"],
+
+    plugins: ["dagger2-compiler"],
+    use_resource_processor: true,
+}
+
+android_robolectric_test {
+    name: "SpaRoboRNGTests",
+    srcs: [
+        ":SpaScreenshotTestRNGFiles",
+        ":flag-junit",
+        ":platform-test-screenshot-rules",
+    ],
+    // Do not add any new libraries here, they should be added to SpaRoboApp above.
+    static_libs: [
+        "androidx.compose.runtime_runtime",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.ext.junit",
+        "inline-mockito-robolectric-prebuilt",
+        "platform-parametric-runner-lib",
+        "uiautomator-helpers",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "truth",
+    ],
+    upstream: true,
+    java_resource_dirs: ["config"],
+    instrumentation_for: "SpaRoboApp",
+}
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/AndroidManifest.xml b/packages/SettingsLib/Spa/screenshot/robotests/AndroidManifest.xml
new file mode 100644
index 0000000..1918e1c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.settingslib.spa.screenshot">
+
+    <uses-sdk android:minSdkVersion="21"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+</manifest>
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png
new file mode 100644
index 0000000..b2f3cf1
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_barChart.png
new file mode 100644
index 0000000..dc7e756
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_footer.png
new file mode 100644
index 0000000..95021fa
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_imageIllustration.png
new file mode 100644
index 0000000..281f572
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_lineChart.png
new file mode 100644
index 0000000..0dc707f
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
new file mode 100644
index 0000000..7c135a0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_pieChart.png
new file mode 100644
index 0000000..7c3e993
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png
new file mode 100644
index 0000000..7b438c4
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
new file mode 100644
index 0000000..ac64619
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png
new file mode 100644
index 0000000..5506c8c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
new file mode 100644
index 0000000..bb518c0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png
new file mode 100644
index 0000000..f4b9063
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png
new file mode 100644
index 0000000..fc60c0f
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png
new file mode 100644
index 0000000..fa06927
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_barChart.png
new file mode 100644
index 0000000..698cb4e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_footer.png
new file mode 100644
index 0000000..95021fa
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png
new file mode 100644
index 0000000..a5f3fa5
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_lineChart.png
new file mode 100644
index 0000000..c1938b4
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
new file mode 100644
index 0000000..81a181f
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_pieChart.png
new file mode 100644
index 0000000..ffc6729
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png
new file mode 100644
index 0000000..a620040
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
new file mode 100644
index 0000000..0b40aa8
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
new file mode 100644
index 0000000..cfe8587
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
new file mode 100644
index 0000000..bb518c0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png
new file mode 100644
index 0000000..3632755
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
new file mode 100644
index 0000000..7e5b602
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
new file mode 100644
index 0000000..a3692cd
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
new file mode 100644
index 0000000..233d088
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
new file mode 100644
index 0000000..10869f2
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
new file mode 100644
index 0000000..3eaecc1
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
new file mode 100644
index 0000000..ca61911
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
new file mode 100644
index 0000000..8a0da31
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
new file mode 100644
index 0000000..19d0afd
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
new file mode 100644
index 0000000..236a1a0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
new file mode 100644
index 0000000..72b7954
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
new file mode 100644
index 0000000..36486c4
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
new file mode 100644
index 0000000..5bb318f
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
new file mode 100644
index 0000000..fedce44
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
new file mode 100644
index 0000000..3b389d7
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties b/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties
new file mode 100644
index 0000000..83d7549
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties
@@ -0,0 +1,15 @@
+# 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.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/res/drawable/accessibility_captioning_banner.xml b/packages/SettingsLib/Spa/screenshot/robotests/res/drawable/accessibility_captioning_banner.xml
new file mode 100644
index 0000000..6597ffb
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/res/drawable/accessibility_captioning_banner.xml
@@ -0,0 +1,52 @@
+<?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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <path
+      android:pathData="M383.9,300H28.1C12.6,300 0,287.4 0,271.9V28.1C0,12.6 12.6,0 28.1,0h355.8C399.4,0 412,12.6 412,28.1v243.8C412,287.4 399.4,300 383.9,300z"
+      android:fillColor="#FFFFFF"/>
+  <path
+      android:pathData="M79.2,179.6h53.6v8.5h-53.6z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M142.5,179.6h30.4v8.5h-30.4z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M79.2,195.5h79.2v8.5h-79.2z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M168.1,195.5h34.1v8.5h-34.1z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M211.9,195.5h34.1v8.5h-34.1z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M182.7,179.6h73.1v8.5h-73.1z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M265.5,179.6h26.8v8.5h-26.8z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M302.1,179.6h26.8v8.5h-26.8z"
+      android:fillColor="#1A73E8"/>
+  <path
+      android:pathData="M142.7,67.9h-11.5c-1.6,0 -2.9,1.3 -2.9,2.9H67.8c-7.9,0 -14.4,6.5 -14.4,14.4v132.4c0,7.9 6.5,14.4 14.4,14.4h276.4c7.9,0 14.4,-6.5 14.4,-14.4V85.2c0,-7.9 -6.5,-14.4 -14.4,-14.4H203.1c0,-1.6 -1.3,-2.9 -2.9,-2.9h-28.8c-1.6,0 -2.9,1.3 -2.9,2.9h-23C145.5,69.2 144.3,67.9 142.7,67.9zM344.2,73.7c6.4,0 11.5,5.2 11.5,11.5v132.4c0,6.3 -5.2,11.5 -11.5,11.5H67.8c-6.4,0 -11.5,-5.2 -11.5,-11.5V85.2c0,-6.3 5.2,-11.5 11.5,-11.5H344.2z"
+      android:fillColor="#DADCE0"/>
+</vector>
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/robo-manifest.xml b/packages/SettingsLib/Spa/screenshot/robotests/robo-manifest.xml
new file mode 100644
index 0000000..af1a11e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/robo-manifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--- Include all the namespaces we will ever need anywhere, because this is the source the manifest merger uses for namespaces -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.settingslib.spa.screenshot"
+          coreApp="true">
+  <application>
+    <activity
+        android:name="androidx.activity.ComponentActivity"
+        android:exported="true">
+    </activity>
+  </application>
+</manifest>
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
index 3dcefe9..1cbdc33 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.spa.screenshot.util
 
+import android.os.Build
 import androidx.activity.ComponentActivity
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
@@ -49,15 +50,19 @@
             )
         )
     private val composeRule = createAndroidComposeRule<ComponentActivity>()
-    private val delegateRule =
-        RuleChain.outerRule(colorsRule)
-            .around(deviceEmulationRule)
+    private val roboRule =
+        RuleChain.outerRule(deviceEmulationRule)
             .around(screenshotRule)
             .around(composeRule)
+    private val delegateRule =
+        RuleChain.outerRule(colorsRule)
+            .around(roboRule)
     private val matcher = UnitTestBitmapMatcher
+    private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
 
     override fun apply(base: Statement, description: Description): Statement {
-        return delegateRule.apply(base, description)
+        val ruleToApply = if (isRobolectric) roboRule else delegateRule
+        return ruleToApply.apply(base, description)
     }
 
     /**
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
index b74a243..2cb6044 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
@@ -26,15 +26,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class ActionButtonsScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
index 2529807..8e55695 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
@@ -13,16 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion.virtual.camera;
 
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.settingslib.spa.screenshot.widget.button;
 
-    void configureStream(int width, int height, int format);
-
-    void close();
-}
+import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
index 051ef77..7ef9f10 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
@@ -25,15 +25,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class BarChartScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
index 3822571..3790164 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
@@ -25,15 +25,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class LineChartScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
index 6dd62ec..3c3cc85 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
@@ -23,15 +23,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class PieChartScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java
index 2529807..afe3f07 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java
@@ -13,16 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion.virtual.camera;
 
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.settingslib.spa.screenshot.widget.chart;
 
-    void configureStream(int width, int height, int format);
-
-    void close();
-}
+import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
index 0ccfc0b..616b225 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
@@ -24,15 +24,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class ImageIllustrationScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java
index 2529807..0089c2e 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java
@@ -13,16 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion.virtual.camera;
 
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.settingslib.spa.screenshot.widget.illustration;
 
-    void configureStream(int width, int height, int format);
-
-    void close();
-}
+import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
index e547d26..8dd4ce7 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
@@ -23,15 +23,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class MainSwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
index dd6b553..1e1a785 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
@@ -28,15 +28,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class PreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
         private const val TITLE = "Title"
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
index 357d815..d1878a74 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
@@ -29,15 +29,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class ProgressBarPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
index fdee7ee..c9f098b 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
@@ -25,15 +25,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class SliderPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
index 6966a74..eca40fb 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
@@ -27,15 +27,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class SwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
index 72c5cb8..f81a59f 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
@@ -24,15 +24,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class TwoTargetSwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java
index 2529807..fd6a5dd 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java
@@ -13,16 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion.virtual.camera;
 
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.settingslib.spa.screenshot.widget.preference;
 
-    void configureStream(int width, int height, int format);
-
-    void close();
-}
+import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
index fb01f77..98a4288 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
@@ -21,15 +21,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class FooterScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
index 2867741..5417095 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
@@ -22,15 +22,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.PhoneAndTabletMinimal
 
 /** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class SpinnerScreenshotTest(emulationSpec: DeviceEmulationSpec) {
     companion object {
-        @Parameterized.Parameters(name = "{0}")
+        @Parameters(name = "{0}")
         @JvmStatic
         fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
     }
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java
index 2529807..45210ab 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java
@@ -13,16 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion.virtual.camera;
 
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.settingslib.spa.screenshot.widget.ui;
 
-    void configureStream(int width, int height, int format);
-
-    void close();
-}
+import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
new file mode 100644
index 0000000..d0d2dc0
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.framework.common
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+
+/**
+ * A [BroadcastReceiver] flow for the given [intentFilter].
+ */
+fun Context.broadcastReceiverFlow(intentFilter: IntentFilter): Flow<Intent> = callbackFlow {
+    val broadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            trySend(intent)
+        }
+    }
+    registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
+
+    awaitClose { unregisterReceiver(broadcastReceiver) }
+}.conflate().flowOn(Dispatchers.Default)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
new file mode 100644
index 0000000..dfaf3c6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.framework.common
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+class BroadcastReceiverFlowTest {
+
+    private var registeredBroadcastReceiver: BroadcastReceiver? = null
+
+    private val context = mock<Context> {
+        on {
+            registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_NOT_EXPORTED))
+        } doAnswer {
+            registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
+            null
+        }
+    }
+
+    @Test
+    fun broadcastReceiverFlow_registered() = runBlocking {
+        val flow = context.broadcastReceiverFlow(INTENT_FILTER)
+
+        flow.firstWithTimeoutOrNull()
+
+        assertThat(registeredBroadcastReceiver).isNotNull()
+    }
+
+    @Test
+    fun broadcastReceiverFlow_isCalledOnReceive() = runBlocking {
+        var onReceiveIsCalled = false
+        launch {
+            context.broadcastReceiverFlow(INTENT_FILTER).first {
+                onReceiveIsCalled = true
+                true
+            }
+        }
+
+        delay(100)
+        registeredBroadcastReceiver!!.onReceive(context, Intent())
+        delay(100)
+
+        assertThat(onReceiveIsCalled).isTrue()
+    }
+
+    private companion object {
+        val INTENT_FILTER = IntentFilter()
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index e9f4b5c..245fe6e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1310,7 +1310,7 @@
             // Set default string with battery level in device connected situation.
             if (isTwsBatteryAvailable(leftBattery, rightBattery)) {
                 stringRes = R.string.bluetooth_battery_level_untethered;
-            } else if (batteryLevelPercentageString != null) {
+            } else if (batteryLevelPercentageString != null && !shortSummary) {
                 stringRes = R.string.bluetooth_battery_level;
             }
 
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 7e8fe7e..d9fe733 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -445,5 +445,6 @@
         VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_SUPPORTED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 2e174e2..f1b53ed 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -233,7 +233,6 @@
                     Settings.Global.DEVELOPMENT_FORCE_RTL,
                     Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
                     Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR,
-                    Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_VR,
                     Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH,
                     Settings.Global.DEVICE_DEMO_MODE,
                     Settings.Global.DISABLE_WINDOW_BLURS,
@@ -686,7 +685,8 @@
                     Settings.Global.Wearable.PHONE_SWITCHING_SUPPORTED,
                     Settings.Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE,
                     Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE,
-                    Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED);
+                    Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED,
+                    Settings.Global.Wearable.CONNECTIVITY_KEEP_DATA_ON);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
              newHashSet(
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index aebdaab..e340209 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -107,8 +107,16 @@
 }
 
 flag {
-   name: "qs_new_pipeline"
-   namespace: "systemui"
-   description: "Use the new pipeline for Quick Settings. Should have no behavior changes."
-   bug: "241772429"
+    name: "qs_new_pipeline"
+    namespace: "systemui"
+    description: "Use the new pipeline for Quick Settings. Should have no behavior changes."
+    bug: "241772429"
 }
+
+flag {
+    name: "coroutine_tracing"
+    namespace: "systemui"
+    description: "Adds thread-local data to System UI's global coroutine scopes to "
+        "allow for tracing of coroutine continuations using System UI's tracinglib"
+    bug: "289353932"
+}
\ No newline at end of file
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 fb50f69..243751fa 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
@@ -76,6 +76,8 @@
     val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState()
     val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState()
     val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+    val isDigitButtonAnimationEnabled: Boolean by
+        viewModel.isDigitButtonAnimationEnabled.collectAsState()
 
     val buttonScaleAnimatables = remember { List(12) { Animatable(1f) } }
     LaunchedEffect(animateFailure) {
@@ -94,10 +96,11 @@
     ) {
         repeat(9) { index ->
             DigitButton(
-                index + 1,
-                isInputEnabled,
-                viewModel::onPinButtonClicked,
-                buttonScaleAnimatables[index]::value,
+                digit = index + 1,
+                isInputEnabled = isInputEnabled,
+                onClicked = viewModel::onPinButtonClicked,
+                scaling = buttonScaleAnimatables[index]::value,
+                isAnimationEnabled = isDigitButtonAnimationEnabled,
             )
         }
 
@@ -116,10 +119,11 @@
         )
 
         DigitButton(
-            0,
-            isInputEnabled,
-            viewModel::onPinButtonClicked,
-            buttonScaleAnimatables[10]::value,
+            digit = 0,
+            isInputEnabled = isInputEnabled,
+            onClicked = viewModel::onPinButtonClicked,
+            scaling = buttonScaleAnimatables[10]::value,
+            isAnimationEnabled = isDigitButtonAnimationEnabled,
         )
 
         ActionButton(
@@ -143,15 +147,17 @@
     isInputEnabled: Boolean,
     onClicked: (Int) -> Unit,
     scaling: () -> Float,
+    isAnimationEnabled: Boolean,
 ) {
     PinPadButton(
         onClicked = { onClicked(digit) },
         isEnabled = isInputEnabled,
         backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
         foregroundColor = MaterialTheme.colorScheme.onSurfaceVariant,
+        isAnimationEnabled = isAnimationEnabled,
         modifier =
             Modifier.graphicsLayer {
-                val scale = scaling()
+                val scale = if (isAnimationEnabled) scaling() else 1f
                 scaleX = scale
                 scaleY = scale
             }
@@ -195,6 +201,7 @@
         isEnabled = isInputEnabled && !isHidden,
         backgroundColor = backgroundColor,
         foregroundColor = foregroundColor,
+        isAnimationEnabled = true,
         modifier =
             Modifier.graphicsLayer {
                 alpha = hiddenAlpha
@@ -216,6 +223,7 @@
     isEnabled: Boolean,
     backgroundColor: Color,
     foregroundColor: Color,
+    isAnimationEnabled: Boolean,
     modifier: Modifier = Modifier,
     onLongPressed: (() -> Unit)? = null,
     content: @Composable (contentColor: () -> Color) -> Unit,
@@ -243,7 +251,7 @@
 
     val cornerRadius: Dp by
         animateDpAsState(
-            if (isPressed) 24.dp else pinButtonSize / 2,
+            if (isAnimationEnabled && isPressed) 24.dp else pinButtonSize / 2,
             label = "PinButton round corners",
             animationSpec = tween(animDurationMillis, easing = animEasing)
         )
@@ -251,7 +259,7 @@
     val containerColor: Color by
         animateColorAsState(
             when {
-                isPressed -> MaterialTheme.colorScheme.primary
+                isAnimationEnabled && isPressed -> MaterialTheme.colorScheme.primary
                 else -> backgroundColor
             },
             label = "Pin button container color",
@@ -260,7 +268,7 @@
     val contentColor =
         animateColorAsState(
             when {
-                isPressed -> MaterialTheme.colorScheme.onPrimary
+                isAnimationEnabled && isPressed -> MaterialTheme.colorScheme.onPrimary
                 else -> foregroundColor
             },
             label = "Pin button container color",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 3780468..09706be 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -12,7 +12,6 @@
 import androidx.compose.material.icons.filled.Close
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
@@ -25,10 +24,12 @@
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.FixedSizeEdgeDetector
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.transitions
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -76,17 +77,24 @@
         currentScene = currentScene,
         onChangeScene = { sceneKey -> viewModel.onSceneChanged(sceneKey.toCommunalSceneKey()) },
         transitions = sceneTransitions,
+        edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize)
     ) {
         scene(
             TransitionSceneKey.Blank,
-            userActions = mapOf(Swipe.Left to TransitionSceneKey.Communal)
+            userActions =
+                mapOf(
+                    Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to TransitionSceneKey.Communal
+                )
         ) {
             BlankScene { showSceneTransitionLayout = false }
         }
 
         scene(
             TransitionSceneKey.Communal,
-            userActions = mapOf(Swipe.Right to TransitionSceneKey.Blank),
+            userActions =
+                mapOf(
+                    Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TransitionSceneKey.Blank
+                ),
         ) {
             CommunalScene(viewModel, modifier = modifier)
         }
@@ -105,14 +113,12 @@
     Box(modifier.fillMaxSize()) {
         Column(
             Modifier.fillMaxHeight()
-                .width(100.dp)
+                .width(ContainerDimensions.EdgeSwipeSize)
                 .align(Alignment.CenterEnd)
                 .background(Color(0x55e9f2eb)),
             verticalArrangement = Arrangement.Center,
             horizontalAlignment = Alignment.CenterHorizontally
         ) {
-            Text("Default scene")
-
             IconButton(onClick = hideSceneTransitionLayout) {
                 Icon(Icons.Filled.Close, contentDescription = "Close button")
             }
@@ -142,3 +148,7 @@
 fun SceneKey.toCommunalSceneKey(): CommunalSceneKey {
     return this.identity as CommunalSceneKey
 }
+
+object ContainerDimensions {
+    val EdgeSwipeSize = 40.dp
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 0569431..6e18cb9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -22,6 +22,8 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
@@ -41,6 +43,7 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
@@ -64,6 +67,7 @@
         LazyHorizontalGrid(
             modifier = modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart),
             rows = GridCells.Fixed(CommunalContentSize.FULL.span),
+            contentPadding = PaddingValues(horizontal = Dimensions.Spacing),
             horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
             verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
         ) {
@@ -91,6 +95,16 @@
                 LocalContext.current.getString(R.string.button_to_open_widget_picker)
             )
         }
+
+        // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
+        // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
+        // swipe back to the blank scene.
+        Spacer(
+            Modifier.height(Dimensions.GridHeight)
+                .align(Alignment.CenterStart)
+                .width(Dimensions.Spacing)
+                .pointerInput(Unit) {}
+        )
     }
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
deleted file mode 100644
index c84a5e9..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.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.qs.footer.ui.compose
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.unit.dp
-import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.SceneScope
-
-object QuickSettings {
-    object Elements {
-        // TODO RENAME
-        val Content = ElementKey("QuickSettingsContent")
-        val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
-        val FooterActions = ElementKey("QuickSettingsFooterActions")
-    }
-}
-
-@Composable
-fun SceneScope.QuickSettings(
-    modifier: Modifier = Modifier,
-) {
-    // TODO(b/272780058): implement.
-    Column(
-        modifier =
-            modifier
-                .element(QuickSettings.Elements.Content)
-                .fillMaxWidth()
-                .defaultMinSize(minHeight = 300.dp)
-                .clip(RoundedCornerShape(32.dp))
-                .background(MaterialTheme.colorScheme.primary)
-                .padding(16.dp),
-    ) {
-        Text(
-            text = "Quick settings grid",
-            modifier =
-                Modifier.element(QuickSettings.Elements.CollapsedGrid)
-                    .align(Alignment.CenterHorizontally),
-            style = MaterialTheme.typography.titleLarge,
-            color = MaterialTheme.colorScheme.onPrimary,
-        )
-        Spacer(modifier = Modifier.weight(1f))
-        Text(
-            text = "QS footer actions",
-            modifier =
-                Modifier.element(QuickSettings.Elements.FooterActions)
-                    .align(Alignment.CenterHorizontally),
-            style = MaterialTheme.typography.titleSmall,
-            color = MaterialTheme.colorScheme.onPrimary,
-        )
-    }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
new file mode 100644
index 0000000..28a4801
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.ui.composable
+
+import android.view.ContextThemeWrapper
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.theme.colorAttr
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.res.R
+
+object QuickSettings {
+    object Elements {
+        // TODO RENAME
+        val Content = ElementKey("QuickSettingsContent")
+        val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
+        val FooterActions = ElementKey("QuickSettingsFooterActions")
+    }
+}
+
+@Composable
+private fun QuickSettingsTheme(content: @Composable () -> Unit) {
+    val context = LocalContext.current
+    val themedContext =
+        remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) }
+    CompositionLocalProvider(LocalContext provides themedContext) { content() }
+}
+
+@Composable
+fun SceneScope.QuickSettings(
+    modifier: Modifier = Modifier,
+    qsSceneAdapter: QSSceneAdapter,
+    state: QSSceneAdapter.State
+) {
+    // TODO(b/272780058): implement.
+    Column(
+        modifier =
+            modifier
+                .element(QuickSettings.Elements.Content)
+                .fillMaxWidth()
+                .defaultMinSize(minHeight = 300.dp)
+                .clip(RoundedCornerShape(32.dp))
+                .background(MaterialTheme.colorScheme.primary)
+                .padding(1.dp),
+    ) {
+        QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, state)
+    }
+}
+
+@Composable
+private fun QuickSettingsContent(
+    qsSceneAdapter: QSSceneAdapter,
+    state: QSSceneAdapter.State,
+    modifier: Modifier = Modifier,
+) {
+    val qsView by qsSceneAdapter.qsView.collectAsState(null)
+    QuickSettingsTheme {
+        val context = LocalContext.current
+
+        val frame by remember(context) { mutableStateOf(FrameLayout(context)) }
+
+        LaunchedEffect(key1 = context) {
+            if (qsView == null) {
+                qsSceneAdapter.inflate(context, frame)
+            }
+        }
+        qsView?.let {
+            it.attachToParent(frame)
+            AndroidView(
+                modifier = modifier.fillMaxSize().background(colorAttr(R.attr.underSurface)),
+                factory = { _ ->
+                    qsSceneAdapter.setState(state)
+                    frame
+                },
+                onRelease = { frame.removeAllViews() },
+                update = { qsSceneAdapter.setState(state) }
+            )
+        }
+    }
+}
+
+private fun View.attachToParent(parent: ViewGroup) {
+    if (this.parent != null && this.parent != parent) {
+        (this.parent as ViewGroup).removeView(this)
+    }
+    if (this.parent != parent) {
+        parent.addView(
+            this,
+            ViewGroup.LayoutParams.MATCH_PARENT,
+            ViewGroup.LayoutParams.MATCH_PARENT,
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index a33eac5..b9451d1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -17,43 +17,53 @@
 package com.android.systemui.qs.ui.composable
 
 import android.view.ViewGroup
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.shrinkVertically
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
-import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.scene.ui.composable.ComposableScene
 import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
+import com.android.systemui.shade.ui.composable.ShadeHeader
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
 
 /** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
 @SysUISingleton
 class QuickSettingsScene
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     private val viewModel: QuickSettingsSceneViewModel,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -61,14 +71,12 @@
 ) : ComposableScene {
     override val key = SceneKey.QuickSettings
 
-    private val _destinationScenes =
-        MutableStateFlow<Map<UserAction, SceneModel>>(
-                mapOf(
-                    UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
-                )
-            )
-            .asStateFlow()
-    override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> = _destinationScenes
+    override val destinationScenes =
+        viewModel.destinationScenes.stateIn(
+            scope = applicationScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = emptyMap(),
+        )
 
     @Composable
     override fun SceneScope.Content(
@@ -93,6 +101,9 @@
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/280887232): implement the real UI.
+    val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+    val collapsedHeaderHeight =
+        with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
         modifier =
@@ -103,12 +114,27 @@
     ) {
         when (LocalWindowSizeClass.current.widthSizeClass) {
             WindowWidthSizeClass.Compact ->
-                ExpandedShadeHeader(
-                    viewModel = viewModel.shadeHeaderViewModel,
-                    createTintedIconManager = createTintedIconManager,
-                    createBatteryMeterViewController = createBatteryMeterViewController,
-                    statusBarIconController = statusBarIconController,
-                )
+                AnimatedVisibility(
+                    visible = !isCustomizing,
+                    enter =
+                        expandVertically(
+                            animationSpec = tween(1000),
+                            initialHeight = { collapsedHeaderHeight },
+                        ) + fadeIn(tween(1000)),
+                    exit =
+                        shrinkVertically(
+                            animationSpec = tween(1000),
+                            targetHeight = { collapsedHeaderHeight },
+                            shrinkTowards = Alignment.Top,
+                        ) + fadeOut(tween(1000)),
+                ) {
+                    ExpandedShadeHeader(
+                        viewModel = viewModel.shadeHeaderViewModel,
+                        createTintedIconManager = createTintedIconManager,
+                        createBatteryMeterViewController = createBatteryMeterViewController,
+                        statusBarIconController = statusBarIconController,
+                    )
+                }
             else ->
                 CollapsedShadeHeader(
                     viewModel = viewModel.shadeHeaderViewModel,
@@ -118,6 +144,10 @@
                 )
         }
         Spacer(modifier = Modifier.height(16.dp))
-        QuickSettings()
+        QuickSettings(
+            modifier = Modifier.fillMaxHeight(),
+            viewModel.qsSceneAdapter,
+            QSSceneAdapter.State.QS
+        )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
index 7ecfb62..fadbdce 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -4,7 +4,7 @@
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.shade.ui.composable.Shade
 
 fun TransitionBuilder.lockscreenToShadeTransition() {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index be85bee..5616175 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -4,7 +4,7 @@
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.shade.ui.composable.ShadeHeader
 
 fun TransitionBuilder.shadeToQuickSettingsTransition() {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 13ebdf9..a02f046 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
@@ -37,7 +38,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -152,7 +154,11 @@
                 statusBarIconController = statusBarIconController,
             )
             Spacer(modifier = Modifier.height(16.dp))
-            QuickSettings(modifier = Modifier.height(160.dp))
+            QuickSettings(
+                modifier = Modifier.wrapContentHeight(),
+                viewModel.qsSceneAdapter,
+                QSSceneAdapter.State.QQS
+            )
             Spacer(modifier = Modifier.height(16.dp))
             Notifications(modifier = Modifier.weight(1f))
         }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index b77a60b..6153e19 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -18,7 +18,6 @@
 
 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.movableContentOf
@@ -36,7 +35,6 @@
 import androidx.compose.ui.geometry.isUnspecified
 import androidx.compose.ui.geometry.lerp
 import androidx.compose.ui.graphics.drawscope.scale
-import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.IntermediateMeasureScope
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.Placeable
@@ -47,7 +45,6 @@
 import androidx.compose.ui.unit.round
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
-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. */
@@ -146,8 +143,6 @@
 
             element
         }
-    val lastSharedValues = element.lastSharedValues
-    val lastSceneValues = sceneValues.lastValues
 
     DisposableEffect(scene, sceneValues, element) {
         onDispose {
@@ -160,18 +155,6 @@
         }
     }
 
-    val alpha =
-        remember(layoutImpl, element, scene, sceneValues) {
-            derivedStateOf { elementAlpha(layoutImpl, element, scene, sceneValues) }
-        }
-    val isOpaque by remember(alpha) { derivedStateOf { alpha.value == 1f } }
-    SideEffect {
-        if (isOpaque) {
-            lastSharedValues.alpha = 1f
-            lastSceneValues.alpha = 1f
-        }
-    }
-
     val drawScale by
         remember(layoutImpl, element, scene, sceneValues) {
             derivedStateOf { getDrawScale(layoutImpl, element, scene, sceneValues) }
@@ -200,14 +183,6 @@
                 place(layoutImpl, scene, element, sceneValues, placeable, placementScope = this)
             }
         }
-        .thenIf(!isOpaque) {
-            Modifier.graphicsLayer {
-                val alpha = alpha.value
-                this.alpha = alpha
-                lastSharedValues.alpha = alpha
-                lastSceneValues.alpha = alpha
-            }
-        }
         .testTag(key.testTag)
 }
 
@@ -325,6 +300,61 @@
     }
 }
 
+/**
+ * Whether the element is opaque or not.
+ *
+ * Important: The logic here should closely match the logic in [elementAlpha]. Note that we don't
+ * reuse [elementAlpha] and simply check if alpha == 1f because [isElementOpaque] is checked during
+ * placement and we don't want to read the transition progress in that phase.
+ */
+private fun isElementOpaque(
+    layoutImpl: SceneTransitionLayoutImpl,
+    element: Element,
+    scene: Scene,
+    sceneValues: Element.TargetValues,
+): Boolean {
+    val state = layoutImpl.state.transitionState
+
+    if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+        return true
+    }
+
+    if (!layoutImpl.isTransitionReady(state)) {
+        val lastValue =
+            sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified }
+                ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f
+
+        return lastValue == 1f
+    }
+
+    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")
+    }
+
+    val isSharedElement = fromValues != null && toValues != null
+    if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) {
+        return true
+    }
+
+    return layoutImpl.transitions
+        .transitionSpec(fromScene, toScene)
+        .transformations(element.key, scene.key)
+        .alpha == null
+}
+
+/**
+ * Whether the element is opaque or not.
+ *
+ * Important: The logic here should closely match the logic in [isElementOpaque]. Note that we don't
+ * reuse [elementAlpha] in [isElementOpaque] and simply check if alpha == 1f because
+ * [isElementOpaque] is checked during placement and we don't want to read the transition progress
+ * in that phase.
+ */
 private fun elementAlpha(
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
@@ -446,6 +476,8 @@
         }
 
         val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero)
+        val lastSharedValues = element.lastSharedValues
+        val lastValues = sceneValues.lastValues
         val targetOffset =
             computeValue(
                 layoutImpl,
@@ -456,16 +488,31 @@
                 idleValue = targetOffsetInScene,
                 currentValue = { currentOffset },
                 lastValue = {
-                    sceneValues.lastValues.offset.takeIf { it.isSpecified }
-                        ?: element.lastSharedValues.offset.takeIf { it.isSpecified }
-                            ?: currentOffset
+                    lastValues.offset.takeIf { it.isSpecified }
+                        ?: lastSharedValues.offset.takeIf { it.isSpecified } ?: currentOffset
                 },
                 ::lerp,
             )
 
-        element.lastSharedValues.offset = targetOffset
-        sceneValues.lastValues.offset = targetOffset
-        placeable.place((targetOffset - currentOffset).round())
+        lastSharedValues.offset = targetOffset
+        lastValues.offset = targetOffset
+
+        val offset = (targetOffset - currentOffset).round()
+        if (isElementOpaque(layoutImpl, element, scene, sceneValues)) {
+            // TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is not
+            // animated once b/305195729 is fixed. Test that drawing is not invalidated in that
+            // case.
+            placeable.place(offset)
+            lastSharedValues.alpha = 1f
+            lastValues.alpha = 1f
+        } else {
+            placeable.placeWithLayer(offset) {
+                val alpha = elementAlpha(layoutImpl, element, scene, sceneValues)
+                this.alpha = alpha
+                lastSharedValues.alpha = alpha
+                lastValues.alpha = alpha
+            }
+        }
     }
 }
 
@@ -527,21 +574,17 @@
         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.
     val isSharedElement = fromValues != null && toValues != null
     if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) {
-        return lerp(
-            sceneValue(fromValues!!),
-            sceneValue(toValues!!),
-            transitionProgress,
-        )
+        val start = sceneValue(fromValues!!)
+        val end = sceneValue(toValues!!)
+
+        // Make sure we don't read progress if values are the same and we don't need to interpolate,
+        // so we don't invalidate the phase where this is read.
+        return if (start == end) start else lerp(start, end, state.progress)
     }
 
     val transformation =
@@ -576,8 +619,15 @@
             idleValue,
         )
 
+    // Make sure we don't read progress if values are the same and we don't need to interpolate, so
+    // we don't invalidate the phase where this is read.
+    if (targetValue == idleValue) {
+        return targetValue
+    }
+
+    val progress = state.progress
     // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range.
-    val rangeProgress = transformation.range?.progress(transitionProgress) ?: transitionProgress
+    val rangeProgress = transformation.range?.progress(progress) ?: progress
 
     // Interpolate between the value at rest and the value before entering/after leaving.
     val isEntering = scene.key == toScene
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 9d71801..838cb3b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -39,13 +39,13 @@
 
 @VisibleForTesting
 class SceneGestureHandler(
-    private val layoutImpl: SceneTransitionLayoutImpl,
+    internal val layoutImpl: SceneTransitionLayoutImpl,
     internal val orientation: Orientation,
     private val coroutineScope: CoroutineScope,
 ) {
     val draggable: DraggableHandler = SceneDraggableHandler(this)
 
-    private var transitionState
+    internal var transitionState
         get() = layoutImpl.state.transitionState
         set(value) {
             layoutImpl.state.transitionState = value
@@ -58,7 +58,7 @@
      * Note: the initialScene here does not matter, it's only used for initializing the transition
      * and will be replaced when a drag event starts.
      */
-    private val swipeTransition = SwipeTransition(initialScene = currentScene)
+    internal val swipeTransition = SwipeTransition(initialScene = currentScene)
 
     internal val currentScene: Scene
         get() = layoutImpl.scene(transitionState.currentScene)
@@ -415,7 +415,7 @@
         }
     }
 
-    private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
+    internal class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
         var _currentScene by mutableStateOf(initialScene)
         override val currentScene: SceneKey
             get() = _currentScene.key
@@ -598,9 +598,29 @@
         return PriorityNestedScrollConnection(
             canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
                 canChangeScene = offsetBeforeStart == Offset.Zero
-                gestureHandler.isDrivingTransition &&
+
+                val canInterceptSwipeTransition =
                     canChangeScene &&
-                    offsetAvailable.toAmount() != 0f
+                        gestureHandler.isDrivingTransition &&
+                        offsetAvailable.toAmount() != 0f
+                if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
+
+                val progress = gestureHandler.swipeTransition.progress
+                val threshold = gestureHandler.layoutImpl.transitionInterceptionThreshold
+                fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
+
+                // The transition is always between 0 and 1. If it is close to either of these
+                // intervals, we want to go directly to the TransitionState.Idle.
+                // The progress value can go beyond this range in the case of overscroll.
+                val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
+                if (shouldSnapToIdle) {
+                    gestureHandler.swipeTransition.stopOffsetAnimation()
+                    gestureHandler.transitionState =
+                        TransitionState.Idle(gestureHandler.swipeTransition.currentScene)
+                }
+
+                // Start only if we cannot consume this event
+                !shouldSnapToIdle
             },
             canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
                 val amount = offsetAvailable.toAmount()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index efdfe7a..9c31445 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -16,6 +16,7 @@
 
 package com.android.compose.animation.scene
 
+import androidx.annotation.FloatRange
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
@@ -41,6 +42,8 @@
  * @param transitions the definition of the transitions used to animate a change of scene.
  * @param state the observable state of this layout.
  * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
+ * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
+ *   intercepted, the progress value must be above the threshold, and below (1 - threshold).
  * @param scenes the configuration of the different scenes of this layout.
  */
 @Composable
@@ -51,6 +54,7 @@
     modifier: Modifier = Modifier,
     state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
     edgeDetector: EdgeDetector = DefaultEdgeDetector,
+    @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
     scenes: SceneTransitionLayoutScope.() -> Unit,
 ) {
     val density = LocalDensity.current
@@ -63,6 +67,7 @@
             state = state,
             density = density,
             edgeDetector = edgeDetector,
+            transitionInterceptionThreshold = transitionInterceptionThreshold,
             coroutineScope = coroutineScope,
         )
     }
@@ -71,6 +76,7 @@
     layoutImpl.transitions = transitions
     layoutImpl.density = density
     layoutImpl.edgeDetector = edgeDetector
+    layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
 
     layoutImpl.setScenes(scenes)
     layoutImpl.setCurrentScene(currentScene)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 0b06953..94f2737 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -50,6 +50,7 @@
     internal val state: SceneTransitionLayoutState,
     density: Density,
     edgeDetector: EdgeDetector,
+    transitionInterceptionThreshold: Float,
     coroutineScope: CoroutineScope,
 ) {
     internal val scenes = SnapshotStateMap<SceneKey, Scene>()
@@ -62,6 +63,7 @@
     internal var transitions by mutableStateOf(transitions)
     internal var density: Density by mutableStateOf(density)
     internal var edgeDetector by mutableStateOf(edgeDetector)
+    internal var transitionInterceptionThreshold by mutableStateOf(transitionInterceptionThreshold)
 
     private val horizontalGestureHandler: SceneGestureHandler
     private val verticalGestureHandler: SceneGestureHandler
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
new file mode 100644
index 0000000..6401bb3
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -0,0 +1,212 @@
+/*
+ * 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.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+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 ElementTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Composable
+    @OptIn(ExperimentalComposeUiApi::class)
+    private fun SceneScope.Element(
+        key: ElementKey,
+        size: Dp,
+        offset: Dp,
+        modifier: Modifier = Modifier,
+        onLayout: () -> Unit = {},
+        onPlacement: () -> Unit = {},
+    ) {
+        Box(
+            modifier
+                .offset(offset)
+                .element(key)
+                .intermediateLayout { measurable, constraints ->
+                    onLayout()
+                    val placement = measurable.measure(constraints)
+                    layout(placement.width, placement.height) {
+                        onPlacement()
+                        placement.place(0, 0)
+                    }
+                }
+                .size(size)
+        )
+    }
+
+    @Test
+    fun staticElements_noLayout_noPlacement() {
+        val nFrames = 20
+        val layoutSize = 100.dp
+        val elementSize = 50.dp
+        val elementOffset = 20.dp
+
+        var fooLayouts = 0
+        var fooPlacements = 0
+        var barLayouts = 0
+        var barPlacements = 0
+
+        rule.testTransition(
+            fromSceneContent = {
+                Box(Modifier.size(layoutSize)) {
+                    // Shared element.
+                    Element(
+                        TestElements.Foo,
+                        elementSize,
+                        elementOffset,
+                        onLayout = { fooLayouts++ },
+                        onPlacement = { fooPlacements++ },
+                    )
+
+                    // Transformed element
+                    Element(
+                        TestElements.Bar,
+                        elementSize,
+                        elementOffset,
+                        onLayout = { barLayouts++ },
+                        onPlacement = { barPlacements++ },
+                    )
+                }
+            },
+            toSceneContent = {
+                Box(Modifier.size(layoutSize)) {
+                    // Shared element.
+                    Element(TestElements.Foo, elementSize, elementOffset)
+                }
+            },
+            transition = {
+                spec = tween(nFrames * 16)
+
+                // no-op transformations.
+                translate(TestElements.Bar, x = 0.dp, y = 0.dp)
+                scaleSize(TestElements.Bar, width = 1f, height = 1f)
+            },
+        ) {
+            var numberOfLayoutsAfterOneAnimationFrame = 0
+            var numberOfPlacementsAfterOneAnimationFrame = 0
+
+            fun assertNumberOfLayoutsAndPlacements() {
+                assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
+                assertThat(fooPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame)
+                assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
+                assertThat(barPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame)
+            }
+
+            at(16) {
+                // Capture the number of layouts and placements that happened after 1 animation
+                // frame.
+                numberOfLayoutsAfterOneAnimationFrame = fooLayouts
+                numberOfPlacementsAfterOneAnimationFrame = fooPlacements
+            }
+            repeat(nFrames - 2) { i ->
+                // Ensure that all animation frames (except the final one) don't relayout or replace
+                // static (shared or transformed) elements.
+                at(32L + i * 16) { assertNumberOfLayoutsAndPlacements() }
+            }
+        }
+    }
+
+    @Test
+    fun onlyMovingElements_noLayout_onlyPlacement() {
+        val nFrames = 20
+        val layoutSize = 100.dp
+        val elementSize = 50.dp
+
+        var fooLayouts = 0
+        var fooPlacements = 0
+        var barLayouts = 0
+        var barPlacements = 0
+
+        rule.testTransition(
+            fromSceneContent = {
+                Box(Modifier.size(layoutSize)) {
+                    // Shared element.
+                    Element(
+                        TestElements.Foo,
+                        elementSize,
+                        offset = 0.dp,
+                        onLayout = { fooLayouts++ },
+                        onPlacement = { fooPlacements++ },
+                    )
+
+                    // Transformed element
+                    Element(
+                        TestElements.Bar,
+                        elementSize,
+                        offset = 0.dp,
+                        onLayout = { barLayouts++ },
+                        onPlacement = { barPlacements++ },
+                    )
+                }
+            },
+            toSceneContent = {
+                Box(Modifier.size(layoutSize)) {
+                    // Shared element.
+                    Element(TestElements.Foo, elementSize, offset = 20.dp)
+                }
+            },
+            transition = {
+                spec = tween(nFrames * 16)
+
+                // Only translate Bar.
+                translate(TestElements.Bar, x = 20.dp, y = 20.dp)
+                scaleSize(TestElements.Bar, width = 1f, height = 1f)
+            },
+        ) {
+            var numberOfLayoutsAfterOneAnimationFrame = 0
+            var lastNumberOfPlacements = 0
+
+            fun assertNumberOfLayoutsAndPlacements() {
+                // The number of layouts have not changed.
+                assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
+                assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
+
+                // The number of placements have increased.
+                assertThat(fooPlacements).isGreaterThan(lastNumberOfPlacements)
+                assertThat(barPlacements).isGreaterThan(lastNumberOfPlacements)
+                lastNumberOfPlacements = fooPlacements
+            }
+
+            at(16) {
+                // Capture the number of layouts and placements that happened after 1 animation
+                // frame.
+                numberOfLayoutsAfterOneAnimationFrame = fooLayouts
+                lastNumberOfPlacements = fooPlacements
+            }
+            repeat(nFrames - 2) { i ->
+                // Ensure that all animation frames (except the final one) only replaced the
+                // elements.
+                at(32L + i * 16) { assertNumberOfLayoutsAndPlacements() }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index 7ab2096..49ef31b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -69,6 +69,8 @@
             scene(SceneC) { Text("SceneC") }
         }
 
+        val transitionInterceptionThreshold = 0.05f
+
         val sceneGestureHandler =
             SceneGestureHandler(
                 layoutImpl =
@@ -79,6 +81,7 @@
                             state = layoutState,
                             density = Density(1f),
                             edgeDetector = DefaultEdgeDetector,
+                            transitionInterceptionThreshold = transitionInterceptionThreshold,
                             coroutineScope = coroutineScope,
                         )
                         .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) },
@@ -107,6 +110,9 @@
         val transitionState: TransitionState
             get() = layoutState.transitionState
 
+        val progress: Float
+            get() = (transitionState as Transition).progress
+
         fun advanceUntilIdle() {
             coroutineScope.testScheduler.advanceUntilIdle()
         }
@@ -145,13 +151,12 @@
     fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
         draggable.onDragStarted()
         assertScene(currentScene = SceneA, isIdle = false)
-        val transition = transitionState as Transition
 
         draggable.onDelta(pixels = deltaInPixels10)
-        assertThat(transition.progress).isEqualTo(0.1f)
+        assertThat(progress).isEqualTo(0.1f)
 
         draggable.onDelta(pixels = deltaInPixels10)
-        assertThat(transition.progress).isEqualTo(0.2f)
+        assertThat(progress).isEqualTo(0.2f)
     }
 
     @Test
@@ -257,8 +262,7 @@
             )
 
         assertScene(currentScene = SceneA, isIdle = false)
-        val transition = transitionState as Transition
-        assertThat(transition.progress).isEqualTo(0.1f)
+        assertThat(progress).isEqualTo(0.1f)
         assertThat(consumed).isEqualTo(offsetY10)
     }
 
@@ -282,13 +286,12 @@
         nestedScroll.scroll(available = offsetY10)
         assertScene(currentScene = SceneA, isIdle = false)
 
-        val transition = transitionState as Transition
-        assertThat(transition.progress).isEqualTo(0.1f)
+        assertThat(progress).isEqualTo(0.1f)
 
         // start intercept preScroll
         val consumed =
             nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
-        assertThat(transition.progress).isEqualTo(0.2f)
+        assertThat(progress).isEqualTo(0.2f)
 
         // do nothing on postScroll
         nestedScroll.onPostScroll(
@@ -296,13 +299,71 @@
             available = Offset.Zero,
             source = NestedScrollSource.Drag
         )
-        assertThat(transition.progress).isEqualTo(0.2f)
+        assertThat(progress).isEqualTo(0.2f)
 
         nestedScroll.scroll(available = offsetY10)
-        assertThat(transition.progress).isEqualTo(0.3f)
+        assertThat(progress).isEqualTo(0.3f)
         assertScene(currentScene = SceneA, isIdle = false)
     }
 
+    private suspend fun TestGestureScope.preScrollAfterSceneTransition(
+        firstScroll: Float,
+        secondScroll: Float
+    ) {
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        // start scene transition
+        nestedScroll.scroll(available = Offset(0f, SCREEN_SIZE * firstScroll))
+
+        // stop scene transition (start the "stop animation")
+        nestedScroll.onPreFling(available = Velocity.Zero)
+
+        // a pre scroll event, that could be intercepted by SceneGestureHandler
+        nestedScroll.onPreScroll(Offset(0f, SCREEN_SIZE * secondScroll), NestedScrollSource.Drag)
+    }
+
+    // Float tolerance for comparisons
+    private val tolerance = 0.00001f
+
+    @Test
+    fun scrollAndFling_scrollLessThanInterceptable_goToIdleOnCurrentScene() = runGestureTest {
+        val first = transitionInterceptionThreshold - tolerance
+        val second = 0.01f
+
+        preScrollAfterSceneTransition(firstScroll = first, secondScroll = second)
+
+        assertScene(SceneA, isIdle = true)
+    }
+
+    @Test
+    fun scrollAndFling_scrollMinInterceptable_interceptPreScrollEvents() = runGestureTest {
+        val first = transitionInterceptionThreshold + tolerance
+        val second = 0.01f
+
+        preScrollAfterSceneTransition(firstScroll = first, secondScroll = second)
+
+        assertThat(progress).isWithin(tolerance).of(first + second)
+    }
+
+    @Test
+    fun scrollAndFling_scrollMaxInterceptable_interceptPreScrollEvents() = runGestureTest {
+        val first = 1f - transitionInterceptionThreshold - tolerance
+        val second = 0.01f
+
+        preScrollAfterSceneTransition(firstScroll = first, secondScroll = second)
+
+        assertThat(progress).isWithin(tolerance).of(first + second)
+    }
+
+    @Test
+    fun scrollAndFling_scrollMoreThanInterceptable_goToIdleOnNextScene() = runGestureTest {
+        val first = 1f - transitionInterceptionThreshold + tolerance
+        val second = 0.01f
+
+        preScrollAfterSceneTransition(firstScroll = first, secondScroll = second)
+
+        assertScene(SceneC, isIdle = true)
+    }
+
     @Test
     fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
@@ -444,24 +505,23 @@
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = Always)
         draggable.onDragStarted()
         assertScene(currentScene = SceneA, isIdle = false)
-        val transition = transitionState as Transition
 
         draggable.onDelta(deltaInPixels10)
-        assertThat(transition.progress).isEqualTo(0.1f)
+        assertThat(progress).isEqualTo(0.1f)
 
         // now we can intercept the scroll events
         nestedScroll.scroll(available = offsetY10)
-        assertThat(transition.progress).isEqualTo(0.2f)
+        assertThat(progress).isEqualTo(0.2f)
 
         // this should be ignored, we are scrolling now!
         draggable.onDragStopped(velocityThreshold)
         assertScene(currentScene = SceneA, isIdle = false)
 
         nestedScroll.scroll(available = offsetY10)
-        assertThat(transition.progress).isEqualTo(0.3f)
+        assertThat(progress).isEqualTo(0.3f)
 
         nestedScroll.scroll(available = offsetY10)
-        assertThat(transition.progress).isEqualTo(0.4f)
+        assertThat(progress).isEqualTo(0.4f)
 
         nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
         assertScene(currentScene = SceneC, isIdle = false)
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index 2459eea..cb638ee 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -28,26 +28,28 @@
         style="@android:style/Widget.DeviceDefault.Button.Colored"
         android:layout_width="wrap_content"
         android:layout_height="48dp"
-        android:text="@string/app_clips_save_add_to_note"
         android:layout_marginStart="8dp"
         android:background="@drawable/overlay_button_background"
+        android:paddingHorizontal="24dp"
+        android:text="@string/app_clips_save_add_to_note"
         android:textColor="?android:textColorSecondary"
+        app:layout_constraintBottom_toTopOf="@id/preview"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/preview" />
+        app:layout_constraintTop_toTopOf="parent" />
 
     <Button
         android:id="@+id/cancel"
         style="@android:style/Widget.DeviceDefault.Button.Colored"
         android:layout_width="wrap_content"
         android:layout_height="48dp"
-        android:text="@android:string/cancel"
-        android:layout_marginStart="6dp"
+        android:layout_marginStart="8dp"
         android:background="@drawable/overlay_button_background"
+        android:paddingHorizontal="24dp"
+        android:text="@android:string/cancel"
         android:textColor="?android:textColorSecondary"
+        app:layout_constraintBottom_toTopOf="@id/preview"
         app:layout_constraintStart_toEndOf="@id/save"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/preview" />
+        app:layout_constraintTop_toTopOf="parent" />
 
     <ImageView
         android:id="@+id/preview"
diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml
index 08eccbb..4336ccc 100644
--- a/packages/SystemUI/res/layout/bluetooth_device_item.xml
+++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml
@@ -21,7 +21,7 @@
     style="@style/BluetoothTileDialog.Device"
     android:layout_width="match_parent"
     android:layout_height="@dimen/bluetooth_dialog_device_height"
-    android:paddingEnd="24dp"
+    android:paddingEnd="0dp"
     android:paddingStart="20dp"
     android:layout_marginBottom="4dp">
 
@@ -86,6 +86,7 @@
         android:id="@+id/gear_icon_image"
         android:layout_width="0dp"
         android:layout_height="24dp"
+        android:paddingEnd="24dp"
         android:src="@drawable/ic_settings_24dp"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index c11a18b..af29cad 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -120,6 +120,16 @@
                 android:visibility="gone"
                 app:constraint_referenced_ids="ic_arrow,see_all_text" />
 
+            <View
+                android:id="@+id/see_all_clickable_row"
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:contentDescription="@string/accessibility_bluetooth_device_settings_see_all"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/device_list"
+                app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" />
+
             <ImageView
                 android:id="@+id/ic_arrow"
                 android:layout_marginStart="36dp"
@@ -141,6 +151,8 @@
                 android:maxLines="1"
                 android:ellipsize="end"
                 android:gravity="center_vertical"
+                android:importantForAccessibility="no"
+                android:clickable="false"
                 android:layout_marginStart="0dp"
                 android:paddingStart="20dp"
                 android:text="@string/see_all_bluetooth_devices"
@@ -158,6 +170,16 @@
                 android:visibility="gone"
                 app:constraint_referenced_ids="ic_add,pair_new_device_text" />
 
+            <View
+                android:id="@+id/pair_new_device_clickable_row"
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:contentDescription="@string/accessibility_bluetooth_device_settings_pair_new_device"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/see_all_text"
+                app:layout_constraintBottom_toTopOf="@+id/done_button" />
+
             <ImageView
                 android:id="@+id/ic_add"
                 android:layout_width="24dp"
@@ -180,6 +202,8 @@
                 android:maxLines="1"
                 android:ellipsize="end"
                 android:gravity="center_vertical"
+                android:importantForAccessibility="no"
+                android:clickable="false"
                 android:layout_marginStart="0dp"
                 android:paddingStart="20dp"
                 android:text="@string/pair_new_bluetooth_devices"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9a3c6d5..3163533 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -467,6 +467,10 @@
 
     <!-- Content description of the bluetooth device settings gear icon. [CHAR LIMIT=NONE] -->
     <string name="accessibility_bluetooth_device_settings_gear">Click to configure device detail</string>
+    <!-- Content description of the bluetooth device settings see all. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_bluetooth_device_settings_see_all">Click to see all devices</string>
+    <!-- Content description of the bluetooth device settings pair new device. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_bluetooth_device_settings_pair_new_device">Click to pair new device</string>
 
     <!-- Content description of the battery when battery state is unknown for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_battery_unknown">Battery percentage unknown.</string>
@@ -640,6 +644,10 @@
     <string name="quick_settings_bluetooth_device_connected">Connected</string>
     <!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]-->
     <string name="quick_settings_bluetooth_device_saved">Saved</string>
+    <!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]-->
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect">disconnect</string>
+    <!-- QuickSettings: Accessibility label to activate a device [CHAR LIMIT=NONE]-->
+    <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate">activate</string>
 
     <!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
     <string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 6b390b1..c02ffa7 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -39,10 +39,10 @@
 import com.android.systemui.dagger.qualifiers.DisplaySpecific
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW
 import com.android.systemui.flags.Flags.REGION_SAMPLING
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.LogBuffer
@@ -298,7 +298,7 @@
         object : KeyguardUpdateMonitorCallback() {
             override fun onKeyguardVisibilityChanged(visible: Boolean) {
                 isKeyguardVisible = visible
-                if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                if (!KeyguardShadeMigrationNssl.isEnabled) {
                     if (!isKeyguardVisible) {
                         clock?.run {
                             smallClock.animations.doze(if (isDozing) 1f else 0f)
@@ -345,7 +345,7 @@
             parent.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
                     listenForDozing(this)
-                    if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                    if (KeyguardShadeMigrationNssl.isEnabled) {
                         listenForDozeAmountTransition(this)
                         listenForAnyStateToAodTransition(this)
                     } else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 32f9c30..36e18b3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -46,6 +46,7 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder;
 import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
@@ -62,6 +63,7 @@
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -105,6 +107,7 @@
     private final DozeParameters mDozeParameters;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore;
+    private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker;
     private FrameLayout mSmallClockFrame; // top aligned clock
     private FrameLayout mLargeClockFrame; // centered clock
 
@@ -179,6 +182,7 @@
             LockscreenSmartspaceController smartspaceController,
             ConfigurationController configurationController,
             ScreenOffAnimationController screenOffAnimationController,
+            StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             SecureSettings secureSettings,
             @Main DelayableExecutor uiExecutor,
@@ -202,6 +206,7 @@
         mSmartspaceController = smartspaceController;
         mConfigurationController = configurationController;
         mScreenOffAnimationController = screenOffAnimationController;
+        mIconViewBindingFailureTracker = iconViewBindingFailureTracker;
         mSecureSettings = secureSettings;
         mUiExecutor = uiExecutor;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
@@ -362,7 +367,7 @@
     }
 
     int getNotificationIconAreaHeight() {
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (KeyguardShadeMigrationNssl.isEnabled()) {
             return 0;
         } else if (NotificationIconContainerRefactor.isEnabled()) {
             return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0;
@@ -590,7 +595,7 @@
     }
 
     private void updateAodIcons() {
-        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled()) {
             NotificationIconContainer nic = (NotificationIconContainer)
                     mView.findViewById(
                             com.android.systemui.res.R.id.left_aligned_notification_icon_container);
@@ -605,6 +610,7 @@
                             mAodIconsViewModel,
                             mConfigurationState,
                             mConfigurationController,
+                            mIconViewBindingFailureTracker,
                             mAodIconViewStore);
                     final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility(
                             nic,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 758d1fe..87d937b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -51,10 +51,9 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ViewHierarchyAnimator;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.plugins.ClockController;
@@ -100,7 +99,6 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final ConfigurationController mConfigurationController;
     private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
-    private final FeatureFlags mFeatureFlags;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final Rect mClipBounds = new Rect();
     private final KeyguardInteractor mKeyguardInteractor;
@@ -136,7 +134,6 @@
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController,
             KeyguardLogger logger,
-            FeatureFlags featureFlags,
             InteractionJankMonitor interactionJankMonitor,
             KeyguardInteractor keyguardInteractor,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
@@ -149,9 +146,8 @@
         mConfigurationController = configurationController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
                 dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
-                featureFlags, logger.getBuffer());
+                logger.getBuffer());
         mInteractionJankMonitor = interactionJankMonitor;
-        mFeatureFlags = featureFlags;
         mDumpManager = dumpManager;
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
@@ -188,7 +184,7 @@
         }
 
         mDumpManager.registerDumpable(getInstanceName(), this);
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (KeyguardShadeMigrationNssl.isEnabled()) {
             startCoroutines(EmptyCoroutineContext.INSTANCE);
         }
     }
@@ -454,7 +450,7 @@
         ConstraintSet constraintSet = new ConstraintSet();
         constraintSet.clone(layout);
         int guideline;
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (KeyguardShadeMigrationNssl.isEnabled()) {
             guideline = R.id.split_shade_guideline;
         } else {
             guideline = R.id.qs_edge_guideline;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index d524e4a..ef65144 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -23,8 +23,7 @@
 import android.view.View;
 
 import com.android.app.animation.Interpolators;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.statusbar.StatusBarState;
@@ -55,7 +54,6 @@
     private boolean mKeyguardViewVisibilityAnimating;
     private boolean mLastOccludedState = false;
     private final AnimationProperties mAnimationProperties = new AnimationProperties();
-    private final FeatureFlags mFeatureFlags;
     private final LogBuffer mLogBuffer;
 
     public KeyguardVisibilityHelper(View view,
@@ -63,14 +61,12 @@
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController,
             boolean animateYPos,
-            FeatureFlags featureFlags,
             LogBuffer logBuffer) {
         mView = view;
         mKeyguardStateController = keyguardStateController;
         mDozeParameters = dozeParameters;
         mScreenOffAnimationController = screenOffAnimationController;
         mAnimateYPos = animateYPos;
-        mFeatureFlags = featureFlags;
         mLogBuffer = logBuffer;
     }
 
@@ -167,7 +163,7 @@
                         animProps,
                         true /* animate */);
             } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
-                if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                if (KeyguardShadeMigrationNssl.isEnabled()) {
                     log("Using GoneToAodTransition");
                     mKeyguardViewVisibilityAnimating = false;
                 } else {
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 ee3a55f..7769dd9 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
@@ -108,6 +108,9 @@
     /** The minimal length of a pattern. */
     val minPatternLength: Int
 
+    /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */
+    val isPinEnhancedPrivacyEnabled: StateFlow<Boolean>
+
     /**
      * Returns the currently-configured authentication method. This determines how the
      * authentication challenge needs to be completed in order to unlock an otherwise locked device.
@@ -212,6 +215,12 @@
 
     override val minPatternLength: Int = LockPatternUtils.MIN_LOCK_PATTERN_SIZE
 
+    override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> =
+        refreshingFlow(
+            initialValue = true,
+            getFreshValue = { userId -> lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) },
+        )
+
     override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
         return withContext(backgroundDispatcher) {
             blockingAuthenticationMethodInternal(userRepository.selectedUserId)
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 1ede530..5eefbf5 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
@@ -145,6 +145,9 @@
     val authenticationChallengeResult: SharedFlow<Boolean> =
         repository.authenticationChallengeResult
 
+    /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */
+    val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled
+
     private var throttlingCountdownJob: Job? = null
 
     init {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 31ad14d..138a76c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -94,6 +94,10 @@
     /** Whether the pattern should be visible for the currently-selected user. */
     val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
 
+    /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */
+    val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> =
+        authenticationInteractor.isPinEnhancedPrivacyEnabled
+
     /** Whether the user switcher should be displayed within the bouncer UI on large screens. */
     val isUserSwitcherVisible: Boolean
         get() = repository.isUserSwitcherVisible
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 2ed0d5d..b2b8049 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
@@ -84,6 +84,19 @@
 
     override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
 
+    /**
+     * Whether the digit buttons should be animated when touched. Note that this doesn't affect the
+     * delete or enter buttons; those should always animate.
+     */
+    val isDigitButtonAnimationEnabled: StateFlow<Boolean> =
+        interactor.isPinEnhancedPrivacyEnabled
+            .map { !it }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = !interactor.isPinEnhancedPrivacyEnabled.value,
+            )
+
     /** Notifies that the user clicked on a PIN button with the given digit value. */
     fun onPinButtonClicked(input: Int) {
         val pinInput = mutablePinInput.value
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 9fc86ad..1dcc540 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -59,6 +59,7 @@
 import com.android.systemui.keyboard.KeyboardModule;
 import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
 import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule;
+import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.log.dagger.MonitorLog;
 import com.android.systemui.log.table.TableLogBuffer;
@@ -120,6 +121,7 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
 import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
+import com.android.systemui.statusbar.ui.binder.StatusBarViewBinderModule;
 import com.android.systemui.statusbar.window.StatusBarWindowModule;
 import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule;
 import com.android.systemui.temporarydisplay.dagger.TemporaryDisplayModule;
@@ -189,6 +191,7 @@
         KeyEventRepositoryModule.class,
         KeyboardModule.class,
         KeyguardBlueprintModule.class,
+        KeyguardSectionsModule.class,
         LetterboxModule.class,
         LogModule.class,
         MediaProjectionModule.class,
@@ -215,6 +218,7 @@
         StatusBarModule.class,
         StatusBarPipelineModule.class,
         StatusBarPolicyModule.class,
+        StatusBarViewBinderModule.class,
         StatusBarWindowModule.class,
         SystemPropertiesFlagsModule.class,
         SysUIConcurrencyModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index da3fd14..83c16ae 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.flags
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.Flags as Classic
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import javax.inject.Inject
@@ -28,9 +27,5 @@
     FlagDependenciesBase(featureFlags, handler) {
     override fun defineDependencies() {
         FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
-
-        // These two flags are effectively linked. We should migrate them to a single aconfig flag.
-        Classic.MIGRATE_NSSL dependsOn Classic.MIGRATE_KEYGUARD_STATUS_VIEW
-        Classic.MIGRATE_KEYGUARD_STATUS_VIEW dependsOn Classic.MIGRATE_NSSL
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index cd3ecb3..ad3d6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -254,14 +254,6 @@
     // TODO(b/287268101): Tracking bug.
     @JvmField val TRANSIT_CLOCK = releasedFlag("lockscreen_custom_transit_clock")
 
-    /** 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("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("migrate_keyguard_status_view")
-
     /** Migrate the status bar view on keyguard from notification panel to keyguard root view. */
     // TODO(b/299115332): Tracking Bug.
     @JvmField val MIGRATE_KEYGUARD_STATUS_BAR_VIEW =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 8d5d73f..5c76be8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -49,8 +49,10 @@
  * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
  * this repository.
  *
- * To print all transitions to logcat to help with debugging, run this command: adb shell settings
- * put global systemui/buffer/KeyguardLog VERBOSE
+ * To print all transitions to logcat to help with debugging, run this command:
+ * ```
+ * adb shell cmd statusbar echo -b KeyguardLog:VERBOSE
+ * ```
  *
  * This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag.
  */
@@ -175,9 +177,11 @@
                     override fun onAnimationStart(animation: Animator) {
                         emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED))
                     }
+
                     override fun onAnimationCancel(animation: Animator) {
                         endAnimation(lastStep.value, TransitionState.CANCELED)
                     }
+
                     override fun onAnimationEnd(animation: Animator) {
                         endAnimation(1f, TransitionState.FINISHED)
                     }
@@ -222,7 +226,7 @@
     }
 
     private fun emitTransition(nextStep: TransitionStep, isManual: Boolean = false) {
-        trace(nextStep, isManual)
+        logAndTrace(nextStep, isManual)
         val emitted = _transitions.tryEmit(nextStep)
         if (!emitted) {
             Log.w(TAG, "Failed to emit next value without suspending")
@@ -230,26 +234,22 @@
         lastStep = nextStep
     }
 
-    private fun trace(step: TransitionStep, isManual: Boolean) {
+    private fun logAndTrace(step: TransitionStep, isManual: Boolean) {
         if (step.transitionState == TransitionState.RUNNING) {
             return
         }
-        val traceName =
-            "Transition: ${step.from} -> ${step.to} " +
-                if (isManual) {
-                    "(manual)"
-                } else {
-                    ""
-                }
+        val manualStr = if (isManual) " (manual)" else ""
+        val traceName = "Transition: ${step.from} -> ${step.to}$manualStr"
+
         val traceCookie = traceName.hashCode()
-        if (step.transitionState == TransitionState.STARTED) {
-            Trace.beginAsyncSection(traceName, traceCookie)
-        } else if (
-            step.transitionState == TransitionState.FINISHED ||
-                step.transitionState == TransitionState.CANCELED
-        ) {
-            Trace.endAsyncSection(traceName, traceCookie)
+        when (step.transitionState) {
+            TransitionState.STARTED -> Trace.beginAsyncSection(traceName, traceCookie)
+            TransitionState.FINISHED -> Trace.endAsyncSection(traceName, traceCookie)
+            TransitionState.CANCELED -> Trace.endAsyncSection(traceName, traceCookie)
+            else -> {}
         }
+
+        Log.i(TAG, "${step.transitionState.name} transition: $step$manualStr")
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 419524fe..a03fa38 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -75,24 +75,6 @@
         }
 
         scope.launch {
-            interactor.finishedKeyguardTransitionStep.collect {
-                logger.log(TAG, VERBOSE, "Finished transition", it)
-            }
-        }
-
-        scope.launch {
-            interactor.canceledKeyguardTransitionStep.collect {
-                logger.log(TAG, VERBOSE, "Canceled transition", it)
-            }
-        }
-
-        scope.launch {
-            interactor.startedKeyguardTransitionStep.collect {
-                logger.log(TAG, VERBOSE, "Started transition", it)
-            }
-        }
-
-        scope.launch {
             keyguardInteractor.dozeTransitionModel.collect {
                 logger.log(TAG, VERBOSE, "Doze transition", it)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt
new file mode 100644
index 0000000..23642a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.keyguard.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the keyguard shade migration nssl flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object KeyguardShadeMigrationNssl {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.keyguardShadeMigrationNssl()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
deleted file mode 100644
index 5900a24..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
+++ /dev/null
@@ -1,128 +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.keyguard.ui.binder
-
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewPropertyAnimator
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-
-object KeyguardAmbientIndicationAreaViewBinder {
-    /**
-     * Defines interface for an object that acts as the binding between the view and its view-model.
-     *
-     * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
-     * it is bound.
-     */
-    interface Binding {
-        /**
-         * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
-         * indication areas.
-         */
-        fun getIndicationAreaAnimators(): List<ViewPropertyAnimator>
-
-        /** Notifies that device configuration has changed. */
-        fun onConfigurationChanged()
-
-        /** Destroys this binding, releases resources, and cancels any coroutines. */
-        fun destroy()
-    }
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    fun bind(
-        view: ViewGroup,
-        viewModel: KeyguardAmbientIndicationViewModel,
-        keyguardRootViewModel: KeyguardRootViewModel,
-    ): Binding {
-        val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
-        val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
-
-        val disposableHandle =
-            view.repeatWhenAttached {
-                repeatOnLifecycle(Lifecycle.State.STARTED) {
-                    launch {
-                        keyguardRootViewModel.alpha.collect { alpha ->
-                            ambientIndicationArea?.apply {
-                                this.importantForAccessibility =
-                                    if (alpha == 0f) {
-                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                    } else {
-                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                                    }
-                                this.alpha = alpha
-                            }
-                        }
-                    }
-
-                    launch {
-                        viewModel.indicationAreaTranslationX.collect { translationX ->
-                            ambientIndicationArea?.translationX = translationX
-                        }
-                    }
-
-                    launch {
-                        configurationBasedDimensions
-                            .map { it.defaultBurnInPreventionYOffsetPx }
-                            .flatMapLatest { defaultBurnInOffsetY ->
-                                viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
-                            }
-                            .collect { translationY ->
-                                ambientIndicationArea?.translationY = translationY
-                            }
-                    }
-
-                }
-            }
-
-
-        return object : Binding {
-            override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
-                return listOf(ambientIndicationArea).mapNotNull { it?.animate() }
-            }
-
-            override fun onConfigurationChanged() {
-                configurationBasedDimensions.value = loadFromResources(view)
-            }
-
-            override fun destroy() {
-                disposableHandle.dispose()
-            }
-        }
-    }
-
-    private fun loadFromResources(view: View): ConfigurationBasedDimensions {
-        return ConfigurationBasedDimensions(
-            defaultBurnInPreventionYOffsetPx =
-                view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
-        )
-    }
-
-    private data class ConfigurationBasedDimensions(
-        val defaultBurnInPreventionYOffsetPx: Int,
-    )
-}
\ No newline at end of file
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
index 1f74bb6..4de9fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -41,7 +41,7 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.RefactorFlag
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
@@ -116,7 +116,7 @@
                         launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
                     }
 
-                    if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                    if (KeyguardShadeMigrationNssl.isEnabled) {
                         launch {
                             viewModel.burnInLayerVisibility.collect { visibility ->
                                 childViews[burnInLayerId]?.visibility = visibility
@@ -342,7 +342,7 @@
         featureFlags: FeatureFlagsClassic,
         screenOffAnimationController: ScreenOffAnimationController,
     ): DisposableHandle? {
-        RefactorFlag(featureFlags, Flags.MIGRATE_KEYGUARD_STATUS_VIEW).assertInLegacyMode()
+        KeyguardShadeMigrationNssl.assertInLegacyMode()
         if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return null
         return view.repeatWhenAttached {
             lifecycleScope.launch {
@@ -368,7 +368,7 @@
         iconsAppearTranslationPx: Int,
         screenOffAnimationController: ScreenOffAnimationController,
     ) {
-        val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
+        val statusViewMigrated = KeyguardShadeMigrationNssl.isEnabled
         animate().cancel()
         val animatorListener =
             object : AnimatorListenerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index bdd9a6bf..a2e930c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -49,7 +49,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -445,7 +445,7 @@
 
     private fun setUpClock(previewContext: Context, parentView: ViewGroup) {
         largeClockHostView =
-            if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            if (KeyguardShadeMigrationNssl.isEnabled) {
                 parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view_large)
             } else {
                 val hostView = FrameLayout(previewContext)
@@ -460,7 +460,7 @@
         largeClockHostView.isInvisible = true
 
         smallClockHostView =
-            if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            if (KeyguardShadeMigrationNssl.isEnabled) {
                 parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view)
             } else {
                 val resources = parentView.resources
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 2e64c41..0cf891c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -20,10 +20,10 @@
 import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.layout.items.ClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
@@ -31,8 +31,13 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule.Companion.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION
 import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import java.util.Optional
 import javax.inject.Inject
+import javax.inject.Named
+import kotlin.jvm.optionals.getOrNull
 
 /**
  * Positions elements of the lockscreen to the default position.
@@ -47,7 +52,8 @@
     defaultIndicationAreaSection: DefaultIndicationAreaSection,
     defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
     defaultShortcutsSection: DefaultShortcutsSection,
-    defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
+    @Named(KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
+    defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
     defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
     defaultStatusViewSection: DefaultStatusViewSection,
     defaultStatusBarSection: DefaultStatusBarSection,
@@ -61,11 +67,11 @@
     override val id: String = DEFAULT
 
     override val sections =
-        listOf(
+        listOfNotNull(
             defaultIndicationAreaSection,
             defaultDeviceEntryIconSection,
             defaultShortcutsSection,
-            defaultAmbientIndicationAreaSection,
+            defaultAmbientIndicationAreaSection.getOrNull(),
             defaultSettingsPopupMenuSection,
             defaultStatusViewSection,
             defaultStatusBarSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index d8b368b..14e8f89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -19,18 +19,22 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import com.android.systemui.util.kotlin.getOrNull
+import java.util.Optional
 import javax.inject.Inject
+import javax.inject.Named
 
 /** Vertically aligns the shortcuts with the udfps. */
 @SysUISingleton
@@ -39,7 +43,8 @@
 constructor(
     defaultIndicationAreaSection: DefaultIndicationAreaSection,
     defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
-    defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
+    @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
+    defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
     defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
     alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
     defaultStatusViewSection: DefaultStatusViewSection,
@@ -52,10 +57,10 @@
     override val id: String = SHORTCUTS_BESIDE_UDFPS
 
     override val sections =
-        listOf(
+        listOfNotNull(
             defaultIndicationAreaSection,
             defaultDeviceEntryIconSection,
-            defaultAmbientIndicationAreaSection,
+            defaultAmbientIndicationAreaSection.getOrNull(),
             defaultSettingsPopupMenuSection,
             alignShortcutsToUdfpsSection,
             defaultStatusViewSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 35679b8..0d397bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -20,18 +20,22 @@
 import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
+import com.android.systemui.util.kotlin.getOrNull
+import java.util.Optional
 import javax.inject.Inject
+import javax.inject.Named
 
 /**
  * Split-shade layout, mostly used for larger devices like foldables and tablets when in landscape
@@ -45,7 +49,8 @@
     defaultIndicationAreaSection: DefaultIndicationAreaSection,
     defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
     defaultShortcutsSection: DefaultShortcutsSection,
-    defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
+    @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
+    defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
     defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
     defaultStatusViewSection: DefaultStatusViewSection,
     defaultStatusBarSection: DefaultStatusBarSection,
@@ -58,11 +63,11 @@
     override val id: String = ID
 
     override val sections =
-        listOf(
+        listOfNotNull(
             defaultIndicationAreaSection,
             defaultDeviceEntryIconSection,
             defaultShortcutsSection,
-            defaultAmbientIndicationAreaSection,
+            defaultAmbientIndicationAreaSection.getOrNull(),
             defaultSettingsPopupMenuSection,
             defaultStatusViewSection,
             defaultStatusBarSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index eb01d4f6..b7a165c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -28,6 +28,8 @@
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.res.R
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -46,6 +48,7 @@
     private val falsingManager: FalsingManager,
     private val indicationController: KeyguardIndicationController,
     private val vibratorHelper: VibratorHelper,
+    private val featureFlags: FeatureFlagsClassic,
 ) : BaseShortcutSection() {
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (keyguardBottomAreaRefactor()) {
@@ -83,20 +86,26 @@
         val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
         val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
 
+        val lockIconViewId = if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+            R.id.device_entry_icon_view
+        } else {
+            R.id.lock_icon_view
+        }
+
         constraintSet.apply {
             constrainWidth(R.id.start_button, width)
             constrainHeight(R.id.start_button, height)
             connect(R.id.start_button, LEFT, PARENT_ID, LEFT)
-            connect(R.id.start_button, RIGHT, R.id.lock_icon_view, LEFT)
-            connect(R.id.start_button, TOP, R.id.lock_icon_view, TOP)
-            connect(R.id.start_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
+            connect(R.id.start_button, RIGHT, lockIconViewId, LEFT)
+            connect(R.id.start_button, TOP, lockIconViewId, TOP)
+            connect(R.id.start_button, BOTTOM, lockIconViewId, BOTTOM)
 
             constrainWidth(R.id.end_button, width)
             constrainHeight(R.id.end_button, height)
             connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT)
-            connect(R.id.end_button, LEFT, R.id.lock_icon_view, RIGHT)
-            connect(R.id.end_button, TOP, R.id.lock_icon_view, TOP)
-            connect(R.id.end_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
+            connect(R.id.end_button, LEFT, lockIconViewId, RIGHT)
+            connect(R.id.end_button, TOP, lockIconViewId, TOP)
+            connect(R.id.end_button, BOTTOM, lockIconViewId, BOTTOM)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 09caf45..484d351 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -22,8 +22,7 @@
 import androidx.constraintlayout.helper.widget.Layer
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.res.R
 import javax.inject.Inject
@@ -33,11 +32,10 @@
 @Inject
 constructor(
     private val context: Context,
-    private val featureFlags: FeatureFlags,
 ) : KeyguardSection() {
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
 
@@ -53,13 +51,13 @@
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 975d62a..c438e49 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -29,14 +29,15 @@
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
-import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -49,8 +50,8 @@
     private val context: Context,
     private val configurationState: ConfigurationState,
     private val configurationController: ConfigurationController,
-    private val dozeParameters: DozeParameters,
     private val featureFlags: FeatureFlagsClassic,
+    private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
     private val notificationIconAreaController: NotificationIconAreaController,
@@ -62,7 +63,7 @@
     private lateinit var nic: NotificationIconContainer
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
         nic =
@@ -81,7 +82,7 @@
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
 
@@ -94,6 +95,7 @@
                     nicAodViewModel,
                     configurationState,
                     configurationController,
+                    iconBindingFailureTracker,
                     nicAodIconViewStore,
                 )
         } else {
@@ -102,7 +104,7 @@
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
         val bottomMargin =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
deleted file mode 100644
index 20cb9b0..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
+++ /dev/null
@@ -1,101 +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.keyguard.ui.view.layout.sections
-
-import android.view.LayoutInflater
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.res.R
-import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import javax.inject.Inject
-
-class DefaultAmbientIndicationAreaSection
-@Inject
-constructor(
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel,
-    private val keyguardRootViewModel: KeyguardRootViewModel,
-) : KeyguardSection() {
-    private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null
-
-    override fun addViews(constraintLayout: ConstraintLayout) {
-        if (keyguardBottomAreaRefactor()) {
-            val view =
-                LayoutInflater.from(constraintLayout.context)
-                    .inflate(R.layout.ambient_indication, constraintLayout, false)
-
-            constraintLayout.addView(view)
-        }
-    }
-
-    override fun bindData(constraintLayout: ConstraintLayout) {
-        if (keyguardBottomAreaRefactor()) {
-            ambientIndicationAreaHandle =
-                KeyguardAmbientIndicationAreaViewBinder.bind(
-                    constraintLayout,
-                    keyguardAmbientIndicationViewModel,
-                    keyguardRootViewModel,
-                )
-        }
-    }
-
-    override fun applyConstraints(constraintSet: ConstraintSet) {
-        constraintSet.apply {
-            constrainWidth(R.id.ambient_indication_container, MATCH_PARENT)
-
-            if (keyguardUpdateMonitor.isUdfpsSupported) {
-                // constrain below udfps and above indication area
-                constrainHeight(R.id.ambient_indication_container, MATCH_CONSTRAINT)
-                connect(R.id.ambient_indication_container, TOP, R.id.lock_icon_view, BOTTOM)
-                connect(
-                    R.id.ambient_indication_container,
-                    BOTTOM,
-                    R.id.keyguard_indication_area,
-                    TOP
-                )
-                connect(R.id.ambient_indication_container, START, PARENT_ID, START)
-                connect(R.id.ambient_indication_container, END, PARENT_ID, END)
-            } else {
-                // constrain above lock icon
-                constrainHeight(R.id.ambient_indication_container, WRAP_CONTENT)
-                connect(R.id.ambient_indication_container, BOTTOM, R.id.lock_icon_view, TOP)
-                connect(R.id.ambient_indication_container, START, PARENT_ID, START)
-                connect(R.id.ambient_indication_container, END, PARENT_ID, END)
-            }
-        }
-    }
-
-    override fun removeViews(constraintLayout: ConstraintLayout) {
-        ambientIndicationAreaHandle?.destroy()
-
-        constraintLayout.removeView(R.id.ambient_indication_container)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index c2aedca..165ee36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
@@ -40,7 +41,7 @@
 @Inject
 constructor(
     context: Context,
-    featureFlags: FeatureFlags,
+    private val featureFlags: FeatureFlags,
     notificationPanelView: NotificationPanelView,
     sharedNotificationContainer: SharedNotificationContainer,
     sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
@@ -50,7 +51,6 @@
 ) :
     NotificationStackScrollLayoutSection(
         context,
-        featureFlags,
         notificationPanelView,
         sharedNotificationContainer,
         sharedNotificationContainerViewModel,
@@ -58,7 +58,7 @@
         notificationStackSizeCalculator,
     ) {
     override fun applyConstraints(constraintSet: ConstraintSet) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
         constraintSet.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
index 0b0f21d..4abcca9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -31,9 +31,8 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.keyguard.KeyguardStatusView
 import com.android.keyguard.dagger.KeyguardStatusViewComponent
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardViewConfigurator
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.media.controls.ui.KeyguardMediaController
 import com.android.systemui.res.R
@@ -49,7 +48,6 @@
 @Inject
 constructor(
     private val context: Context,
-    private val featureFlags: FeatureFlags,
     private val notificationPanelView: NotificationPanelView,
     private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
     private val keyguardViewConfigurator: Lazy<KeyguardViewConfigurator>,
@@ -60,7 +58,7 @@
     private val statusViewId = R.id.keyguard_status_view
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
         // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available.
@@ -82,7 +80,7 @@
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (KeyguardShadeMigrationNssl.isEnabled) {
             constraintLayout.findViewById<KeyguardStatusView?>(R.id.keyguard_status_view)?.let {
                 val statusViewComponent =
                     keyguardStatusViewComponentFactory.build(it, context.display)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt
new file mode 100644
index 0000000..37c00b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import dagger.BindsOptionalOf
+import dagger.Module
+import javax.inject.Named
+
+@Module
+abstract class KeyguardSectionsModule {
+
+    companion object {
+        const val KEYGUARD_AMBIENT_INDICATION_AREA_SECTION =
+                "keyguard_ambient_indication_area_section"
+    }
+
+    @BindsOptionalOf
+    @Named(KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
+    abstract fun defaultAmbientIndicationAreaSection(): KeyguardSection
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index ea2bdf7..441f59d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -21,8 +21,7 @@
 import android.view.View
 import android.view.ViewGroup
 import androidx.constraintlayout.widget.ConstraintLayout
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
@@ -36,7 +35,6 @@
 abstract class NotificationStackScrollLayoutSection
 constructor(
     protected val context: Context,
-    protected val featureFlags: FeatureFlags,
     private val notificationPanelView: NotificationPanelView,
     private val sharedNotificationContainer: SharedNotificationContainer,
     private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
@@ -47,7 +45,7 @@
     private var disposableHandle: DisposableHandle? = null
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
         // This moves the existing NSSL view to a different parent, as the controller is a
@@ -62,7 +60,7 @@
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
         disposableHandle?.dispose()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index dc2ad8d..2c45da6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
@@ -40,7 +41,7 @@
 @Inject
 constructor(
     context: Context,
-    featureFlags: FeatureFlags,
+    private val featureFlags: FeatureFlags,
     notificationPanelView: NotificationPanelView,
     sharedNotificationContainer: SharedNotificationContainer,
     sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
@@ -50,7 +51,6 @@
 ) :
     NotificationStackScrollLayoutSection(
         context,
-        featureFlags,
         notificationPanelView,
         sharedNotificationContainer,
         sharedNotificationContainerViewModel,
@@ -58,7 +58,7 @@
         notificationStackSizeCalculator,
     ) {
     override fun applyConstraints(constraintSet: ConstraintSet) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
         constraintSet.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt
deleted file mode 100644
index dd3967a..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt
+++ /dev/null
@@ -1,51 +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.keyguard.ui.viewmodel
-
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import javax.inject.Inject
-
-class KeyguardAmbientIndicationViewModel
-@Inject
-constructor(
-    private val keyguardInteractor: KeyguardInteractor,
-    private val burnInHelperWrapper: BurnInHelperWrapper,
-) {
-
-    /** An observable for the x-offset by which the indication area should be translated. */
-    val indicationAreaTranslationX: Flow<Float> =
-        keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
-
-    /** Returns an observable for the y-offset by which the indication area should be translated. */
-    fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
-        return keyguardInteractor.dozeAmount
-            .map { dozeAmount ->
-                dozeAmount *
-                    (burnInHelperWrapper.burnInOffset(
-                        /* amplitude = */ defaultBurnInOffset * 2,
-                        /* xAxis= */ false,
-                    ) - defaultBurnInOffset)
-            }
-            .distinctUntilChanged()
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 8453af1..0f54e93 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -80,12 +80,13 @@
                     R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
                 }
 
+            val singleAppOptionDisabled =
+                appName != null &&
+                    mediaProjectionConfig?.regionToCapture ==
+                        MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
+
             val singleAppDisabledText =
-                if (
-                    appName != null &&
-                        mediaProjectionConfig?.regionToCapture ==
-                            MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
-                ) {
+                if (singleAppOptionDisabled) {
                     context.getString(
                         R.string.media_projection_entry_app_permission_dialog_single_app_disabled,
                         appName
@@ -93,19 +94,26 @@
                 } else {
                     null
                 }
-            return listOf(
-                ScreenShareOption(
-                    mode = SINGLE_APP,
-                    spinnerText = R.string.screen_share_permission_dialog_option_single_app,
-                    warningText = singleAppWarningText,
-                    spinnerDisabledText = singleAppDisabledText,
-                ),
-                ScreenShareOption(
-                    mode = ENTIRE_SCREEN,
-                    spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
-                    warningText = entireScreenWarningText
+            val options =
+                listOf(
+                    ScreenShareOption(
+                        mode = SINGLE_APP,
+                        spinnerText = R.string.screen_share_permission_dialog_option_single_app,
+                        warningText = singleAppWarningText,
+                        spinnerDisabledText = singleAppDisabledText,
+                    ),
+                    ScreenShareOption(
+                        mode = ENTIRE_SCREEN,
+                        spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
+                        warningText = entireScreenWarningText
+                    )
                 )
-            )
+            return if (singleAppOptionDisabled) {
+                // Make sure "Entire screen" is the first option when "Single App" is disabled.
+                options.reversed()
+            } else {
+                options
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 79aedff..9f5e1b7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -35,6 +35,7 @@
 import android.provider.Settings;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.IWindowManager;
 import android.view.View;
@@ -94,6 +95,9 @@
     @VisibleForTesting
     SparseArray<NavigationBar> mNavigationBars = new SparseArray<>();
 
+    /** Local cache for {@link IWindowManager#hasNavigationBar(int)}. */
+    private SparseBooleanArray mHasNavBar = new SparseBooleanArray();
+
     // Tracks config changes that will actually recreate the nav bar
     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
             ActivityInfo.CONFIG_FONT_SCALE
@@ -221,10 +225,16 @@
     }
 
     private boolean shouldCreateNavBarAndTaskBar(int displayId) {
+        if (mHasNavBar.indexOfKey(displayId) > -1) {
+            return mHasNavBar.get(displayId);
+        }
+
         final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
 
         try {
-            return wms.hasNavigationBar(displayId);
+            boolean hasNavigationBar = wms.hasNavigationBar(displayId);
+            mHasNavBar.put(displayId, hasNavigationBar);
+            return hasNavigationBar;
         } catch (RemoteException e) {
             // Cannot get wms, just return false with warning message.
             Log.w(TAG, "Cannot get WindowManager.");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index b0f54ee..0644237 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -28,8 +28,8 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.util.LargeScreenUtils;
 
@@ -59,6 +59,8 @@
     private boolean mClippingEnabled;
     private boolean mIsFullWidth;
 
+    private boolean mSceneContainerEnabled;
+
     public QSContainerImpl(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -72,6 +74,10 @@
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
     }
 
+    void setSceneContainerEnabled(boolean enabled) {
+        mSceneContainerEnabled = enabled;
+    }
+
     @Override
     public boolean hasOverlappingRendering() {
         return false;
@@ -161,7 +167,7 @@
         }
         mQSPanelContainer.setPaddingRelative(
                 mQSPanelContainer.getPaddingStart(),
-                topPadding,
+                mSceneContainerEnabled ? 0 : topPadding,
                 mQSPanelContainer.getPaddingEnd(),
                 mQSPanelContainer.getPaddingBottom());
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 73a5faa..7b001c7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -24,6 +24,7 @@
 
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.ViewController;
 
@@ -44,6 +45,7 @@
             mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
         }
     };
+    private final boolean mSceneContainerEnabled;
 
     private final View.OnTouchListener mContainerTouchHandler = new View.OnTouchListener() {
         @Override
@@ -64,18 +66,21 @@
             QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController,
             ConfigurationController configurationController,
-            FalsingManager falsingManager) {
+            FalsingManager falsingManager,
+            SceneContainerFlags sceneContainerFlags) {
         super(view);
         mQsPanelController = qsPanelController;
         mQuickStatusBarHeaderController = quickStatusBarHeaderController;
         mConfigurationController = configurationController;
         mFalsingManager = falsingManager;
         mQSPanelContainer = mView.getQSPanelContainer();
+        mSceneContainerEnabled = sceneContainerFlags.isEnabled();
     }
 
     @Override
     public void onInit() {
         mQuickStatusBarHeaderController.init();
+        mView.setSceneContainerEnabled(mSceneContainerEnabled);
     }
 
     public void setListening(boolean listening) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index fab7e95..7f91fd2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -97,6 +97,7 @@
     private boolean mStackScrollerOverscrolling;
 
     private QSAnimator mQSAnimator;
+    @Nullable
     private HeightListener mPanelView;
     private QSSquishinessController mQSSquishinessController;
     protected QuickStatusBarHeader mHeader;
@@ -340,6 +341,7 @@
         }
         if (mQSCustomizerController != null) {
             mQSCustomizerController.setQs(null);
+            mQSCustomizerController.setContainerController(null);
         }
         mScrollListener = null;
         if (mContainer != null) {
@@ -347,6 +349,10 @@
         }
         mDumpManager.unregisterDumpable(getClass().getSimpleName());
         mListeningAndVisibilityLifecycleOwner.destroy();
+        ViewGroup parent = ((ViewGroup) getView().getParent());
+        if (parent != null) {
+            parent.removeView(getView());
+        }
     }
 
     public void onSaveInstanceState(Bundle outState) {
@@ -853,6 +859,10 @@
         mQSCustomizerController.hide();
     }
 
+    public void closeCustomizerImmediately() {
+        mQSCustomizerController.hide(false);
+    }
+
     public void notifyCustomizeChanged() {
         // The customize state changed, so our height changed.
         mContainer.updateExpansion();
@@ -863,7 +873,9 @@
         mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
         // Let the panel know the position changed and it needs to update where notifications
         // and whatnot are.
-        mPanelView.onQsHeightChanged();
+        if (mPanelView != null) {
+            mPanelView.onQsHeightChanged();
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 3227f75..ddd7d67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -39,9 +39,9 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.widget.RemeasuringLinearLayout;
-import com.android.systemui.res.R;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -110,6 +110,8 @@
      */
     private boolean mCanCollapse = true;
 
+    private boolean mSceneContainerEnabled;
+
     public QSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
         mUsingMediaPlayer = useQsMediaPlayer(context);
@@ -153,6 +155,13 @@
         }
     }
 
+    void setSceneContainerEnabled(boolean enabled) {
+        mSceneContainerEnabled = enabled;
+        if (mSceneContainerEnabled) {
+            updatePadding();
+        }
+    }
+
     protected void setHorizontalContentContainerClipping() {
         mHorizontalContentContainer.setClipChildren(true);
         mHorizontalContentContainer.setClipToPadding(false);
@@ -375,7 +384,7 @@
         int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
         int paddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
         setPaddingRelative(getPaddingStart(),
-                paddingTop,
+                mSceneContainerEnabled ? 0 : paddingTop,
                 getPaddingEnd(),
                 paddingBottom);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 6bbdc54..5eb9620 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -34,6 +34,7 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -61,6 +62,8 @@
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private boolean mListening;
 
+    private final boolean mSceneContainerEnabled;
+
     private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
         @Override
         public boolean onTouch(View v, MotionEvent event) {
@@ -82,7 +85,8 @@
             BrightnessSliderController.Factory brightnessSliderFactory,
             FalsingManager falsingManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            SplitShadeStateController splitShadeStateController) {
+            SplitShadeStateController splitShadeStateController,
+            SceneContainerFlags sceneContainerFlags) {
         super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
         mTunerService = tunerService;
@@ -96,6 +100,7 @@
         mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
         mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mSceneContainerEnabled = sceneContainerFlags.isEnabled();
     }
 
     @Override
@@ -116,6 +121,7 @@
 
         mTunerService.addTunable(mView, QS_SHOW_BRIGHTNESS);
         mView.updateResources();
+        mView.setSceneContainerEnabled(mSceneContainerEnabled);
         if (mView.isListening()) {
             refreshAllTiles();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 67c4208..c657b55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -40,6 +40,8 @@
 
     protected QuickQSPanel mHeaderQsPanel;
 
+    private boolean mSceneContainerEnabled;
+
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -52,6 +54,13 @@
         updateResources();
     }
 
+    void setSceneContainerEnabled(boolean enabled) {
+        mSceneContainerEnabled = enabled;
+        if (mSceneContainerEnabled) {
+            updateResources();
+        }
+    }
+
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -82,7 +91,9 @@
         setLayoutParams(lp);
 
         MarginLayoutParams qqsLP = (MarginLayoutParams) mHeaderQsPanel.getLayoutParams();
-        if (largeScreenHeaderActive) {
+        if (mSceneContainerEnabled) {
+            qqsLP.topMargin = 0;
+        } else if (largeScreenHeaderActive) {
             qqsLP.topMargin = mContext.getResources()
                     .getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 64960e6..1d92d78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.qs;
 
 import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
@@ -29,17 +30,20 @@
 
     private final QuickQSPanelController mQuickQSPanelController;
     private boolean mListening;
+    private final boolean mSceneContainerEnabled;
 
     @Inject
     QuickStatusBarHeaderController(QuickStatusBarHeader view,
-            QuickQSPanelController quickQSPanelController
+            QuickQSPanelController quickQSPanelController,
+            SceneContainerFlags sceneContainerFlags
     ) {
         super(view);
         mQuickQSPanelController = quickQSPanelController;
+        mSceneContainerEnabled = sceneContainerFlags.isEnabled();
     }
-
     @Override
     protected void onViewAttached() {
+        mView.setSceneContainerEnabled(mSceneContainerEnabled);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index 024e760..c28371c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -244,7 +244,12 @@
 
     /** Hice the customizer. */
     public void hide() {
-        final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
+        hide(true);
+    }
+
+    public void hide(boolean animated) {
+        final boolean animate = animated
+                && mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
         if (mView.isShown()) {
             mUiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED);
             mToolbar.dismissPopupMenus();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index bd4c6e1..1aef920 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -32,6 +32,8 @@
 import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.di.QSTilesModule;
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter;
+import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.policy.CastController;
@@ -42,18 +44,19 @@
 import com.android.systemui.statusbar.policy.WalletController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
-
 import java.util.Map;
 
 import javax.inject.Named;
 
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.Multibinds;
+
 /**
  * Module for QS dependencies
  */
-@Module(subcomponents = {QSFragmentComponent.class, QSFlexiglassComponent.class},
+@Module(subcomponents = {QSFragmentComponent.class, QSSceneComponent.class},
         includes = {
                 MediaModule.class,
                 QSExternalModule.class,
@@ -110,4 +113,7 @@
         manager.init();
         return manager;
     }
+
+    @Binds
+    QSSceneAdapter bindsQsSceneInteractor(QSSceneAdapterImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneComponent.kt
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt
rename to packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneComponent.kt
index ba1aa62..b942368 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneComponent.kt
@@ -21,12 +21,12 @@
 import dagger.BindsInstance
 import dagger.Subcomponent
 
-@Subcomponent(modules = [QSFlexiglassModule::class])
+@Subcomponent(modules = [QSSceneModule::class])
 @QSScope
-interface QSFlexiglassComponent : QSComponent {
+interface QSSceneComponent : QSComponent {
 
     @Subcomponent.Factory
     interface Factory {
-        fun create(@RootView @BindsInstance rootView: View): QSFlexiglassComponent
+        fun create(@RootView @BindsInstance rootView: View): QSSceneComponent
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneModule.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt
rename to packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneModule.kt
index 36fac44..446cb62 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneModule.kt
@@ -24,7 +24,7 @@
 import javax.inject.Named
 
 @Module(includes = [QSScopeModule::class])
-interface QSFlexiglassModule {
+interface QSSceneModule {
 
     @Module
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
index 7b4b557..5bdb592 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
@@ -20,9 +20,12 @@
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
+import android.view.View.AccessibilityDelegate
 import android.view.View.GONE
 import android.view.View.VISIBLE
 import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
 import android.widget.ImageView
 import android.widget.Switch
 import android.widget.TextView
@@ -32,13 +35,18 @@
 import androidx.recyclerview.widget.RecyclerView
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.withContext
 
 /** Dialog for showing active, connected and saved bluetooth devices. */
 @SysUISingleton
@@ -47,6 +55,7 @@
     private val bluetoothToggleInitialValue: Boolean,
     private val subtitleResIdInitialValue: Int,
     private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
+    @Main private val mainDispatcher: CoroutineDispatcher,
     private val systemClock: SystemClock,
     private val uiEventLogger: UiEventLogger,
     private val logger: BluetoothTileDialogLogger,
@@ -65,13 +74,17 @@
 
     private val deviceItemAdapter: Adapter = Adapter(bluetoothTileDialogCallback)
 
+    private var lastUiUpdateMs: Long = -1
+
+    private var lastItemRow: Int = -1
+
     private lateinit var toggleView: Switch
     private lateinit var subtitleTextView: TextView
     private lateinit var doneButton: View
     private lateinit var seeAllViewGroup: View
     private lateinit var pairNewDeviceViewGroup: View
-    private lateinit var seeAllText: View
-    private lateinit var pairNewDeviceText: View
+    private lateinit var seeAllRow: View
+    private lateinit var pairNewDeviceRow: View
     private lateinit var deviceListView: RecyclerView
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -88,8 +101,8 @@
         doneButton = requireViewById(R.id.done_button)
         seeAllViewGroup = requireViewById(R.id.see_all_layout_group)
         pairNewDeviceViewGroup = requireViewById(R.id.pair_new_device_layout_group)
-        seeAllText = requireViewById(R.id.see_all_text)
-        pairNewDeviceText = requireViewById(R.id.pair_new_device_text)
+        seeAllRow = requireViewById(R.id.see_all_clickable_row)
+        pairNewDeviceRow = requireViewById(R.id.pair_new_device_clickable_row)
         deviceListView = requireViewById<RecyclerView>(R.id.device_list)
 
         setupToggle()
@@ -97,22 +110,37 @@
 
         subtitleTextView.text = context.getString(subtitleResIdInitialValue)
         doneButton.setOnClickListener { dismiss() }
-        seeAllText.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
-        pairNewDeviceText.setOnClickListener {
+        seeAllRow.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
+        pairNewDeviceRow.setOnClickListener {
             bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
         }
     }
 
-    internal fun onDeviceItemUpdated(
+    override fun start() {
+        lastUiUpdateMs = systemClock.elapsedRealtime()
+    }
+
+    internal suspend fun onDeviceItemUpdated(
         deviceItem: List<DeviceItem>,
         showSeeAll: Boolean,
         showPairNewDevice: Boolean
     ) {
-        val start = systemClock.elapsedRealtime()
-        deviceItemAdapter.refreshDeviceItemList(deviceItem) {
-            seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE
-            pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE
-            logger.logDeviceUiUpdate(systemClock.elapsedRealtime() - start)
+        withContext(mainDispatcher) {
+            val start = systemClock.elapsedRealtime()
+            val itemRow = deviceItem.size + showSeeAll.toInt() + showPairNewDevice.toInt()
+            // Add a slight delay for smoother dialog height change
+            if (itemRow != lastItemRow) {
+                delay(MIN_HEIGHT_CHANGE_INTERVAL_MS - (start - lastUiUpdateMs))
+            }
+            if (isActive) {
+                deviceItemAdapter.refreshDeviceItemList(deviceItem) {
+                    seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE
+                    pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE
+                    lastUiUpdateMs = systemClock.elapsedRealtime()
+                    lastItemRow = itemRow
+                    logger.logDeviceUiUpdate(lastUiUpdateMs - start)
+                }
+            }
         }
     }
 
@@ -169,7 +197,8 @@
                         deviceItem1.iconWithDescription?.second ==
                             deviceItem2.iconWithDescription?.second &&
                         deviceItem1.background == deviceItem2.background &&
-                        deviceItem1.isEnabled == deviceItem2.isEnabled
+                        deviceItem1.isEnabled == deviceItem2.isEnabled &&
+                        deviceItem1.actionAccessibilityLabel == deviceItem2.actionAccessibilityLabel
                 }
             }
 
@@ -213,6 +242,21 @@
                         mutableDeviceItemClick.tryEmit(item)
                         uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED)
                     }
+                    accessibilityDelegate =
+                        object : AccessibilityDelegate() {
+                            override fun onInitializeAccessibilityNodeInfo(
+                                host: View,
+                                info: AccessibilityNodeInfo
+                            ) {
+                                super.onInitializeAccessibilityNodeInfo(host, info)
+                                info.addAction(
+                                    AccessibilityAction(
+                                        AccessibilityAction.ACTION_CLICK.id,
+                                        item.actionAccessibilityLabel
+                                    )
+                                )
+                            }
+                        }
                 }
                 nameView.text = item.deviceName
                 summaryView.text = item.connectionSummary
@@ -230,6 +274,7 @@
     }
 
     internal companion object {
+        const val MIN_HEIGHT_CHANGE_INTERVAL_MS = 800L
         const val MAX_DEVICE_ITEM_ENTRY = 3
         const val ACTION_BLUETOOTH_DEVICE_DETAILS =
             "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"
@@ -238,5 +283,9 @@
         const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
         const val DISABLED_ALPHA = 0.3f
         const val ENABLED_ALPHA = 1f
+
+        private fun Boolean.toInt(): Int {
+            return if (this) 1 else 0
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
index f7e0de3..34c2aba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
@@ -40,7 +40,6 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -76,6 +75,7 @@
         dismissDialog()
 
         var updateDeviceItemJob: Job? = null
+        var updateDialogUiJob: Job? = null
 
         job =
             coroutineScope.launch(mainDispatcher) {
@@ -93,10 +93,9 @@
                     )
                 }
                     ?: dialog!!.show()
+
                 updateDeviceItemJob?.cancel()
                 updateDeviceItemJob = launch {
-                    // Add a slight delay for smoother dialog bounds change
-                    delay(FIRST_LOAD_DELAY_MS)
                     deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD)
                 }
 
@@ -128,11 +127,14 @@
 
                 deviceItemInteractor.deviceItemUpdate
                     .onEach {
-                        dialog!!.onDeviceItemUpdated(
-                            it.take(MAX_DEVICE_ITEM_ENTRY),
-                            showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
-                            showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
-                        )
+                        updateDialogUiJob?.cancel()
+                        updateDialogUiJob = launch {
+                            dialog?.onDeviceItemUpdated(
+                                it.take(MAX_DEVICE_ITEM_ENTRY),
+                                showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
+                                showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
+                            )
+                        }
                     }
                     .launchIn(this)
 
@@ -153,6 +155,7 @@
                 bluetoothStateInteractor.isBluetoothEnabled,
                 getSubtitleResId(bluetoothStateInteractor.isBluetoothEnabled),
                 this@BluetoothTileDialogViewModel,
+                mainDispatcher,
                 systemClock,
                 uiEventLogger,
                 logger,
@@ -205,7 +208,6 @@
 
     companion object {
         private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog"
-        private const val FIRST_LOAD_DELAY_MS = 500L
         private fun getSubtitleResId(isBluetoothEnabled: Boolean) =
             if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle
             else R.string.bt_is_off
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
index 2c8d2a0..1c621b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
@@ -49,5 +49,6 @@
     val connectionSummary: String = "",
     val iconWithDescription: Pair<Drawable, String>? = null,
     val background: Int? = null,
-    var isEnabled: Boolean = true
+    var isEnabled: Boolean = true,
+    var actionAccessibilityLabel: String = "",
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
index 7bb1619..1c9be0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
@@ -28,6 +28,10 @@
 private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy
 private val connected = R.string.quick_settings_bluetooth_device_connected
 private val saved = R.string.quick_settings_bluetooth_device_saved
+private val actionAccessibilityLabelActivate =
+    R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate
+private val actionAccessibilityLabelDisconnect =
+    R.string.accessibility_quick_settings_bluetooth_device_tap_to_disconnect
 
 /** Factories to create different types of Bluetooth device items from CachedBluetoothDevice. */
 internal abstract class DeviceItemFactory {
@@ -60,6 +64,7 @@
                 },
             background = backgroundOn,
             isEnabled = !cachedDevice.isBusy,
+            actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
         )
     }
 }
@@ -87,6 +92,7 @@
                 },
             background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
             isEnabled = !cachedDevice.isBusy,
+            actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
         )
     }
 }
@@ -112,6 +118,7 @@
                 },
             background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
             isEnabled = !cachedDevice.isBusy,
+            actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
         )
     }
 }
@@ -137,6 +144,7 @@
                 },
             background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
             isEnabled = !cachedDevice.isBusy,
+            actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
new file mode 100644
index 0000000..b4340f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.ui.adapter
+
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.VisibleForTesting
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.QSImpl
+import com.android.systemui.qs.dagger.QSSceneComponent
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.sample
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+// TODO(307945185) Split View concerns into a ViewBinder
+/** Adapter to use between Scene system and [QSImpl] */
+interface QSSceneAdapter {
+    /** Whether [QSImpl] is currently customizing */
+    val isCustomizing: StateFlow<Boolean>
+
+    /**
+     * A view with the QS content ([QSContainerImpl]), managed by an instance of [QSImpl] tracked by
+     * the interactor.
+     */
+    val qsView: Flow<View>
+
+    /**
+     * Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in
+     * [qsView]
+     */
+    suspend fun inflate(context: Context, parent: ViewGroup? = null)
+
+    /** Set the current state for QS. [state] must not be [State.INITIAL]. */
+    fun setState(state: State)
+
+    sealed class State(
+        val isVisible: Boolean,
+        val expansion: Float,
+    ) {
+        data object CLOSED : State(false, 0f)
+        data object QQS : State(true, 0f)
+        data object QS : State(true, 1f)
+    }
+}
+
+@SysUISingleton
+class QSSceneAdapterImpl
+@VisibleForTesting
+constructor(
+    private val qsSceneComponentFactory: QSSceneComponent.Factory,
+    private val qsImplProvider: Provider<QSImpl>,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Application applicationScope: CoroutineScope,
+    private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater,
+) : QSContainerController, QSSceneAdapter {
+
+    @Inject
+    constructor(
+        qsSceneComponentFactory: QSSceneComponent.Factory,
+        qsImplProvider: Provider<QSImpl>,
+        @Main dispatcher: CoroutineDispatcher,
+        @Application scope: CoroutineScope,
+    ) : this(qsSceneComponentFactory, qsImplProvider, dispatcher, scope, ::AsyncLayoutInflater)
+
+    private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED)
+    private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val isCustomizing = _isCustomizing.asStateFlow()
+
+    private val _qsImpl: MutableStateFlow<QSImpl?> = MutableStateFlow(null)
+    val qsImpl = _qsImpl.asStateFlow()
+    override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull()
+
+    init {
+        applicationScope.launch {
+            state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
+                _qsImpl.value?.apply {
+                    if (state != QSSceneAdapter.State.QS && customizing) {
+                        this@apply.closeCustomizerImmediately()
+                    }
+                    applyState(state)
+                }
+            }
+        }
+    }
+
+    override fun setCustomizerAnimating(animating: Boolean) {}
+
+    override fun setCustomizerShowing(showing: Boolean) {
+        _isCustomizing.value = showing
+    }
+
+    override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) {
+        setCustomizerShowing(showing)
+    }
+
+    override fun setDetailShowing(showing: Boolean) {}
+
+    override suspend fun inflate(context: Context, parent: ViewGroup?) {
+        withContext(mainDispatcher) {
+            val inflater = asyncLayoutInflaterFactory(context)
+            val view = suspendCoroutine { continuation ->
+                inflater.inflate(R.layout.qs_panel, parent) { view, _, _ ->
+                    continuation.resume(view)
+                }
+            }
+            val bundle = Bundle()
+            _qsImpl.value?.onSaveInstanceState(bundle)
+            _qsImpl.value?.onDestroy()
+            val component = qsSceneComponentFactory.create(view)
+            val qs = qsImplProvider.get()
+            qs.onCreate(null)
+            qs.onComponentCreated(component, bundle)
+            _qsImpl.value = qs
+            qs.view.setPadding(0, 0, 0, 0)
+            qs.setContainerController(this@QSSceneAdapterImpl)
+            qs.applyState(state.value)
+        }
+    }
+    override fun setState(state: QSSceneAdapter.State) {
+        this.state.value = state
+    }
+
+    private fun QSImpl.applyState(state: QSSceneAdapter.State) {
+        setQsVisible(state.isVisible)
+        setExpanded(state.isVisible)
+        setListening(state.isVisible)
+        setQsExpansion(state.expansion, 1f, 0f, 1f)
+        setTransitionToFullShadeProgress(false, 1f, 1f)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 5993cf1..3941fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -18,8 +18,14 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import javax.inject.Inject
+import kotlinx.coroutines.flow.map
 
 /** Models UI state and handles user input for the quick settings scene. */
 @SysUISingleton
@@ -28,7 +34,20 @@
 constructor(
     private val deviceEntryInteractor: DeviceEntryInteractor,
     val shadeHeaderViewModel: ShadeHeaderViewModel,
+    val qsSceneAdapter: QSSceneAdapter,
 ) {
     /** Notifies that some content in quick settings was clicked. */
     fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry()
+
+    val destinationScenes =
+        qsSceneAdapter.isCustomizing.map { customizing ->
+            if (customizing) {
+                mapOf<UserAction, SceneModel>(UserAction.Back to SceneModel(SceneKey.QuickSettings))
+            } else {
+                mapOf(
+                    UserAction.Back to SceneModel(SceneKey.Shade),
+                    UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
+                )
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index e40d2b7..d14ef35 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.scene.shared.flag
 
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.Flags.sceneContainer
 import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
@@ -28,6 +28,7 @@
 import com.android.systemui.flags.ReleasedFlag
 import com.android.systemui.flags.ResourceBooleanFlag
 import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import dagger.Module
 import dagger.Provides
 import dagger.assisted.Assisted
@@ -58,8 +59,6 @@
         @VisibleForTesting
         val classicFlagTokens: List<Flag<Boolean>> =
             listOf(
-                Flags.MIGRATE_NSSL,
-                Flags.MIGRATE_KEYGUARD_STATUS_VIEW,
                 Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW,
             )
     }
@@ -75,6 +74,10 @@
                 flagName = AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
                 flagValue = keyguardBottomAreaRefactor(),
             ),
+            AconfigFlagMustBeEnabled(
+                flagName = KeyguardShadeMigrationNssl.FLAG_NAME,
+                flagValue = KeyguardShadeMigrationNssl.isEnabled,
+            ),
         ) +
             classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
             listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled())
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index ead10d6..9f4ea27 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -181,12 +181,16 @@
             if (mCancelTimeoutRunnable != null) {
                 mCancelTimeoutRunnable.run();
             }
-            finish();
+            requestFinish();
         }
 
         return super.onKeyDown(keyCode, event);
     }
 
+    protected void requestFinish() {
+        finish();
+    }
+
     private boolean triggeredByBrightnessKey() {
         return getIntent().getBooleanExtra(EXTRA_FROM_BRIGHTNESS_KEY, false);
     }
@@ -197,6 +201,6 @@
         }
         final int timeout = mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS,
                 AccessibilityManager.FLAG_CONTENT_CONTROLS);
-        mCancelTimeoutRunnable = mMainExecutor.executeDelayed(this::finish, timeout);
+        mCancelTimeoutRunnable = mMainExecutor.executeDelayed(this::requestFinish, timeout);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e9c930a..7201b35 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -29,7 +29,6 @@
 import static com.android.systemui.classifier.Classifier.GENERIC;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.classifier.Classifier.UNLOCK;
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
 import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
@@ -41,7 +40,6 @@
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
-import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
 import static com.android.systemui.util.DumpUtilsKt.asIndenting;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -64,7 +62,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.PowerManager;
-import android.os.Process;
 import android.os.Trace;
 import android.os.UserManager;
 import android.os.VibrationEffect;
@@ -137,6 +134,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
@@ -1265,7 +1263,7 @@
             mKeyguardStatusViewController.onDestroy();
         }
 
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (KeyguardShadeMigrationNssl.isEnabled()) {
             // Need a shared controller until mKeyguardStatusViewController can be removed from
             // here, due to important state being set in that controller. Rebind in order to pick
             // up config changes
@@ -1318,7 +1316,7 @@
         // Reset any left over overscroll state. It is a rare corner case but can happen.
         mQsController.setOverScrollAmount(0);
         mScrimController.setNotificationsOverScrollAmount(0);
-        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled()) {
             mNotificationStackScrollLayoutController.setOverExpansion(0);
             mNotificationStackScrollLayoutController.setOverScrollAmount(0);
         }
@@ -1339,7 +1337,7 @@
         }
         updateClockAppearance();
         mQsController.updateQsState();
-        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled()) {
             mNotificationStackScrollLayoutController.updateFooter();
         }
     }
@@ -1371,7 +1369,7 @@
     void reInflateViews() {
         debugLog("reInflateViews");
         // Re-inflate the status view group.
-        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled()) {
             KeyguardStatusView keyguardStatusView =
                     mNotificationContainerParent.findViewById(R.id.keyguard_status_view);
             int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView);
@@ -1483,7 +1481,7 @@
     }
 
     private void updateMaxDisplayedNotifications(boolean recompute) {
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (KeyguardShadeMigrationNssl.isEnabled()) {
             return;
         }
 
@@ -1640,7 +1638,7 @@
                 mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
                 mKeyguardStatusViewController.isClockTopAligned());
         mClockPositionAlgorithm.run(mClockPositionResult);
-        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled()) {
             mKeyguardStatusViewController.setLockscreenClockY(
                     mClockPositionAlgorithm.getExpandedPreferredClockY());
         }
@@ -1654,7 +1652,7 @@
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
 
-        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled()) {
             mKeyguardStatusViewController.updatePosition(
                     mClockPositionResult.clockX, mClockPositionResult.clockY,
                     mClockPositionResult.clockScale, animateClock);
@@ -1727,7 +1725,7 @@
     private void updateKeyguardStatusViewAlignment(boolean animate) {
         boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
         ConstraintLayout layout;
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (KeyguardShadeMigrationNssl.isEnabled()) {
             layout = mKeyguardViewConfigurator.getKeyguardRootView();
         } else {
             layout = mNotificationContainerParent;
@@ -1902,7 +1900,7 @@
         }
         float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
         mKeyguardStatusViewController.setAlpha(alpha);
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (KeyguardShadeMigrationNssl.isEnabled()) {
             // TODO (b/296373478) This is for split shade media movement.
         } else {
             mKeyguardStatusViewController
@@ -2482,7 +2480,7 @@
     void requestScrollerTopPaddingUpdate(boolean animate) {
         float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
                 getKeyguardNotificationStaticPadding(), mExpandedFraction);
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (KeyguardShadeMigrationNssl.isEnabled()) {
             mSharedNotificationContainerInteractor.setTopPosition(padding);
         } else {
             mNotificationStackScrollLayoutController.updateTopPadding(padding, animate);
@@ -2840,16 +2838,7 @@
         }
 
         if (!mStatusBarStateController.isDozing()) {
-            if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-                mVibratorHelper.performHapticFeedback(mView, HapticFeedbackConstants.REJECT);
-            } else {
-                mVibratorHelper.vibrate(
-                        Process.myUid(),
-                        mView.getContext().getPackageName(),
-                        ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT,
-                        "falsing-additional-tap-required",
-                        TOUCH_VIBRATION_ATTRIBUTES);
-            }
+            mVibratorHelper.performHapticFeedback(mView, HapticFeedbackConstants.REJECT);
         }
     }
 
@@ -2949,7 +2938,7 @@
 
     @Override
     public void onScreenTurningOn() {
-        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled()) {
             mKeyguardStatusViewController.dozeTimeTick();
         }
     }
@@ -3201,7 +3190,7 @@
 
     public void dozeTimeTick() {
         mLockIconViewController.dozeTimeTick();
-        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+        if (!KeyguardShadeMigrationNssl.isEnabled()) {
             mKeyguardStatusViewController.dozeTimeTick();
         }
         if (mInterpolatedDarkAmount > 0) {
@@ -3677,14 +3666,10 @@
     private void maybeVibrateOnOpening(boolean openingWithTouch) {
         if (mVibrateOnOpening && mBarState != KEYGUARD && mBarState != SHADE_LOCKED) {
             if (!openingWithTouch || !mHasVibratedOnOpen) {
-                if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-                    mVibratorHelper.performHapticFeedback(
-                            mView,
-                            HapticFeedbackConstants.GESTURE_START
-                    );
-                } else {
-                    mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
-                }
+                mVibratorHelper.performHapticFeedback(
+                        mView,
+                        HapticFeedbackConstants.GESTURE_START
+                );
                 mHasVibratedOnOpen = true;
                 mShadeLog.v("Vibrating on opening, mHasVibratedOnOpen=true");
             }
@@ -4427,7 +4412,7 @@
                     && statusBarState == KEYGUARD) {
                 // This means we're doing the screen off animation - position the keyguard status
                 // view where it'll be on AOD, so we can animate it in.
-                if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                if (!KeyguardShadeMigrationNssl.isEnabled()) {
                     mKeyguardStatusViewController.updatePosition(
                             mClockPositionResult.clockX,
                             mClockPositionResult.clockYFullyDozing,
@@ -4547,7 +4532,7 @@
         setDozing(true /* dozing */, false /* animate */);
         mStatusBarStateController.setUpcomingState(KEYGUARD);
 
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (KeyguardShadeMigrationNssl.isEnabled()) {
             mStatusBarStateController.setState(KEYGUARD);
         } else {
             mStatusBarStateListener.onStateChanged(KEYGUARD);
@@ -4608,7 +4593,7 @@
             setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
 
             // Update Clock Pivot (used by anti-burnin transformations)
-            if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            if (!KeyguardShadeMigrationNssl.isEnabled()) {
                 mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
             }
 
@@ -4718,7 +4703,7 @@
     private Consumer<Float> setTransitionY(
                 NotificationStackScrollLayoutController stackScroller) {
         return (Float translationY) -> {
-            if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            if (!KeyguardShadeMigrationNssl.isEnabled()) {
                 mKeyguardStatusViewController.setTranslationY(translationY,
                         /* excludeMedia= */false);
                 stackScroller.setTranslationY(translationY);
@@ -4760,7 +4745,7 @@
          */
         @Override
         public boolean onInterceptTouchEvent(MotionEvent event) {
-            if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL) && !mUseExternalTouch) {
+            if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) {
                 return false;
             }
 
@@ -4926,7 +4911,7 @@
          */
         @Override
         public boolean onTouchEvent(MotionEvent event) {
-            if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL) && !mUseExternalTouch) {
+            if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) {
                 return false;
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index a2ca49d..d0f2784 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -53,6 +53,7 @@
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
@@ -457,7 +458,7 @@
                         && !bouncerShowing
                         && !mStatusBarStateController.isDozing()) {
                     if (mDragDownHelper.isDragDownEnabled()) {
-                        if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
+                        if (KeyguardShadeMigrationNssl.isEnabled()) {
                             // When on lockscreen, if the touch originates at the top of the screen
                             // go directly to QS and not the shade
                             if (mQuickSettingsController.shouldQuickSettingsIntercept(
@@ -469,7 +470,7 @@
 
                         // This handles drag down over lockscreen
                         boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
-                        if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
+                        if (KeyguardShadeMigrationNssl.isEnabled()) {
                             if (result) {
                                 mLastInterceptWasDragDownHelper = true;
                                 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -501,7 +502,7 @@
                 MotionEvent cancellation = MotionEvent.obtain(ev);
                 cancellation.setAction(MotionEvent.ACTION_CANCEL);
                 mStackScrollLayout.onInterceptTouchEvent(cancellation);
-                if (!mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
+                if (!KeyguardShadeMigrationNssl.isEnabled()) {
                     mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
                 }
                 cancellation.recycle();
@@ -516,7 +517,7 @@
                 if (mStatusBarKeyguardViewManager.onTouch(ev)) {
                     return true;
                 }
-                if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
+                if (KeyguardShadeMigrationNssl.isEnabled()) {
                     if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
                         // we still want to finish our drag down gesture when locking the screen
                         handled |= mDragDownHelper.onTouchEvent(ev) || handled;
@@ -602,7 +603,7 @@
     }
 
     private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
-        if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (KeyguardShadeMigrationNssl.isEnabled()) {
             // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
             // to also ask NotificationPanelViewController directly, in order to process swipe up
             // events originating from notifications
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 866cfb4..9c8a286 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.fragments.FragmentService
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.plugins.qs.QS
@@ -129,7 +130,6 @@
         isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
 
         mView.setStackScroller(notificationStackScrollLayoutController.getView())
-        mView.setMigratingNSSL(featureFlags.isEnabled(Flags.MIGRATE_NSSL))
         if (featureFlags.isEnabled(Flags.QS_CONTAINER_GRAPH_OPTIMIZER)){
             mView.enableGraphOptimization()
         }
@@ -283,7 +283,7 @@
     }
 
     private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
-        if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
         val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index af44c4e..de3d16a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -32,6 +32,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
 
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.res.R;
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.plugins.qs.QS;
@@ -59,7 +60,6 @@
     private QS mQs;
     private View mQSContainer;
     private int mLastQSPaddingBottom;
-    private boolean mIsMigratingNSSL;
 
     /**
      *  These are used to compute the bounding box containing the shade and the notification scrim,
@@ -180,10 +180,6 @@
         super.dispatchDraw(canvas);
     }
 
-    void setMigratingNSSL(boolean isMigrating) {
-        mIsMigratingNSSL = isMigrating;
-    }
-
     void enableGraphOptimization() {
         setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
     }
@@ -196,7 +192,7 @@
 
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
-        if (mIsMigratingNSSL) {
+        if (KeyguardShadeMigrationNssl.isEnabled()) {
             return super.drawChild(canvas, child, drawingTime);
         }
         int layoutIndex = mLayoutDrawingOrder.indexOf(child);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index c8b6a2e..bbcf10b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -6,8 +6,12 @@
 
 per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com
 per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com
-per-file *ShadeHeader* = kozynski@google.com, asc@google.com
-per-file *Shade* = justinweir@google.com
+per-file *ShadeHeader* = syeonlee@google.com, kozynski@google.com, asc@google.com
+
+per-file *Interactor* = set noparent
+per-file *Interactor* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com
+per-file *Repository* = set noparent
+per-file *Repository* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com
 
 per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
 per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index d73fa14..4ca763f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -68,10 +68,9 @@
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -107,12 +106,12 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /** Handles QuickSettings touch handling, expansion and animation state
  * TODO (b/264460656) make this dumpable
  */
@@ -155,7 +154,6 @@
     private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     private final CastController mCastController;
     private final SplitShadeStateController mSplitShadeStateController;
-    private final FeatureFlags mFeatureFlags;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final ShadeRepository mShadeRepository;
     private final ShadeInteractor mShadeInteractor;
@@ -333,7 +331,6 @@
             AccessibilityManager accessibilityManager,
             LockscreenGestureLogger lockscreenGestureLogger,
             MetricsLogger metricsLogger,
-            FeatureFlags featureFlags,
             InteractionJankMonitor interactionJankMonitor,
             ShadeLogger shadeLog,
             DumpManager dumpManager,
@@ -384,7 +381,6 @@
         mShadeLog = shadeLog;
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
         mCastController = castController;
-        mFeatureFlags = featureFlags;
         mInteractionJankMonitor = interactionJankMonitor;
         mShadeRepository = shadeRepository;
         mShadeInteractor = shadeInteractor;
@@ -724,7 +720,9 @@
 
     /** Closes the Qs customizer. */
     public void closeQsCustomizer() {
-        mQs.closeCustomizer();
+        if (mQs != null) {
+            mQs.closeCustomizer();
+        }
     }
 
     /** Returns whether touches from the notification panel should be disallowed */
@@ -1776,7 +1774,7 @@
                     // Dragging down on the lockscreen statusbar should prohibit other interactions
                     // immediately, otherwise we'll wait on the touchslop. This is to allow
                     // dragging down to expanded quick settings directly on the lockscreen.
-                    if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                    if (!KeyguardShadeMigrationNssl.isEnabled()) {
                         mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
                     }
                 }
@@ -1821,7 +1819,7 @@
                         && Math.abs(h) > Math.abs(x - mInitialTouchX)
                         && shouldQuickSettingsIntercept(
                         mInitialTouchX, mInitialTouchY, h)) {
-                    if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                    if (!KeyguardShadeMigrationNssl.isEnabled()) {
                         mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
                     }
                     mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 20b9ede..af88081 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -19,13 +19,14 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.shared.model.SceneKey
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
 
 /** Models UI state and handles user input for the shade scene. */
 @SysUISingleton
@@ -34,6 +35,7 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     private val deviceEntryInteractor: DeviceEntryInteractor,
+    val qsSceneAdapter: QSSceneAdapter,
     val shadeHeaderViewModel: ShadeHeaderViewModel,
 ) {
     /** The key of the scene we should switch to when swiping up. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 2ea7f61..f8bc0ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -23,27 +23,37 @@
 import androidx.collection.ArrayMap
 import androidx.lifecycle.lifecycleScope
 import com.android.internal.policy.SystemBarUtils
+import com.android.internal.statusbar.StatusBarIcon
 import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.CoreStartable
 import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.IconPack
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.onConfigChanged
-import com.android.systemui.util.children
+import com.android.systemui.util.asIndenting
 import com.android.systemui.util.kotlin.mapValuesNotNullTo
-import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.kotlin.stateFlow
+import com.android.systemui.util.printCollection
 import com.android.systemui.util.ui.isAnimating
 import com.android.systemui.util.ui.stopAnimating
 import com.android.systemui.util.ui.value
+import dagger.Binds
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.Job
@@ -62,11 +72,18 @@
         viewModel: NotificationIconContainerShelfViewModel,
         configuration: ConfigurationState,
         configurationController: ConfigurationController,
+        failureTracker: StatusBarIconViewBindingFailureTracker,
         viewStore: ShelfNotificationIconViewStore,
     ): DisposableHandle {
         return view.repeatWhenAttached {
             lifecycleScope.launch {
-                viewModel.icons.bindIcons(view, configuration, configurationController, viewStore)
+                viewModel.icons.bindIcons(
+                    view,
+                    configuration,
+                    configurationController,
+                    notifyBindingFailures = { failureTracker.shelfFailures = it },
+                    viewStore,
+                )
             }
         }
     }
@@ -77,18 +94,20 @@
         viewModel: NotificationIconContainerStatusBarViewModel,
         configuration: ConfigurationState,
         configurationController: ConfigurationController,
+        failureTracker: StatusBarIconViewBindingFailureTracker,
         viewStore: StatusBarNotificationIconViewStore,
     ): DisposableHandle {
         val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
         return view.repeatWhenAttached {
             lifecycleScope.run {
                 launch {
-                    val iconColors =
+                    val iconColors: Flow<NotificationIconColors> =
                         viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) }
                     viewModel.icons.bindIcons(
                         view,
                         configuration,
                         configurationController,
+                        notifyBindingFailures = { failureTracker.statusBarFailures = it },
                         viewStore,
                     ) { _, sbiv ->
                         StatusBarIconViewBinder.bindIconColors(
@@ -110,6 +129,7 @@
         viewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
         configuration: ConfigurationState,
         configurationController: ConfigurationController,
+        failureTracker: StatusBarIconViewBindingFailureTracker,
         viewStore: IconViewStore,
     ): DisposableHandle {
         return view.repeatWhenAttached {
@@ -119,6 +139,7 @@
                         view,
                         configuration,
                         configurationController,
+                        notifyBindingFailures = { failureTracker.aodFailures = it },
                         viewStore,
                     ) { _, sbiv ->
                         viewModel.bindAodStatusBarIconView(sbiv, configuration)
@@ -176,7 +197,7 @@
     }
 
     /**
-     * Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s [children].
+     * Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s children.
      *
      * [bindIcon] will be invoked to bind a child [StatusBarIconView] to an icon associated with the
      * given `iconKey`. The parent [Job] of this coroutine will be cancelled automatically when the
@@ -186,6 +207,7 @@
         view: NotificationIconContainer,
         configuration: ConfigurationState,
         configurationController: ConfigurationController,
+        notifyBindingFailures: (Collection<String>) -> Unit,
         viewStore: IconViewStore,
         bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit = { _, _ -> },
     ): Unit = coroutineScope {
@@ -208,57 +230,59 @@
                 FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight)
             }
 
-        launch {
-            layoutParams.collect { params: FrameLayout.LayoutParams ->
-                for (child in view.children) {
-                    child.layoutParams = params
-                }
-            }
-        }
-
-        val iconBindings = mutableMapOf<String, Job>()
+        val failedBindings = mutableSetOf<String>()
+        val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>()
         var prevIcons = NotificationIconsViewData()
-        sample(layoutParams, ::Pair).collect {
-            (iconsData: NotificationIconsViewData, layoutParams: FrameLayout.LayoutParams),
-            ->
+        collect { iconsData: NotificationIconsViewData ->
             val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
             prevIcons = iconsData
 
-            val replacingIcons =
-                iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, v) ->
-                    viewStore.iconView(v.notifKey).statusBarIcon
+            val replacingIcons: ArrayMap<String, StatusBarIcon> =
+                iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, info) ->
+                    boundViewsByNotifKey[info.notifKey]?.first?.statusBarIcon
                 }
             view.setReplacingIcons(replacingIcons)
 
-            val childrenByNotifKey: Map<String, StatusBarIconView> =
-                view.children.filterIsInstance<StatusBarIconView>().associateByTo(ArrayMap()) {
-                    it.notification.key
-                }
-
-            iconsDiff.removed
-                .mapNotNull { key -> childrenByNotifKey[key]?.let { key to it } }
-                .forEach { (key, child) ->
-                    view.removeView(child)
-                    iconBindings.remove(key)?.cancel()
-                }
-
-            val toAdd = iconsDiff.added.map { it.notifKey to viewStore.iconView(it.notifKey) }
-            for ((i, keyAndView) in toAdd.withIndex()) {
-                val (key, sbiv) = keyAndView
-                // The view might still be transiently added if it was just removed
-                // and added again
-                view.removeTransientView(sbiv)
-                view.addView(sbiv, i, layoutParams)
-                iconBindings.remove(key)?.cancel()
-                iconBindings[key] = launch { bindIcon(key, sbiv) }
+            for (notifKey in iconsDiff.removed) {
+                failedBindings.remove(notifKey)
+                val (child, job) = boundViewsByNotifKey.remove(notifKey) ?: continue
+                view.removeView(child)
+                job.cancel()
             }
 
+            val toAdd: Sequence<String> =
+                iconsDiff.added.asSequence().map { it.notifKey } + failedBindings
+            for ((idx, notifKey) in toAdd.withIndex()) {
+                val sbiv = viewStore.iconView(notifKey)
+                if (sbiv == null) {
+                    failedBindings.add(notifKey)
+                    continue
+                }
+                // The view might still be transiently added if it was just removed and added again
+                view.removeTransientView(sbiv)
+                view.addView(sbiv, idx)
+                boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
+                boundViewsByNotifKey[notifKey] =
+                    Pair(
+                        sbiv,
+                        launch {
+                            launch { layoutParams.collect { sbiv.layoutParams = it } }
+                            bindIcon(notifKey, sbiv)
+                        },
+                    )
+            }
+
+            notifyBindingFailures(failedBindings)
+
             view.setChangingViewPositions(true)
+
             // Re-sort notification icons
+            val expectedChildren =
+                iconsData.visibleKeys.mapNotNull { boundViewsByNotifKey[it.notifKey]?.first }
             val childCount = view.childCount
             for (i in 0 until childCount) {
                 val actual = view.getChildAt(i)
-                val expected = viewStore.iconView(iconsData.visibleKeys[i].notifKey)
+                val expected = expectedChildren[i]
                 if (actual === expected) {
                     continue
                 }
@@ -273,47 +297,30 @@
 
     /** External storage for [StatusBarIconView] instances. */
     fun interface IconViewStore {
-        fun iconView(key: String): StatusBarIconView
+        fun iconView(key: String): StatusBarIconView?
     }
 
     @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE
 }
 
 /** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
-class ShelfNotificationIconViewStore
-@Inject
-constructor(
-    private val notifCollection: NotifCollection,
-) : IconViewStore {
-    override fun iconView(key: String): StatusBarIconView {
-        val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key")
-        return entry.icons.shelfIcon ?: error("No shelf IconView found for key: $key")
-    }
-}
+class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
+    IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon })
 
 /** [IconViewStore] for the always-on display. */
 class AlwaysOnDisplayNotificationIconViewStore
 @Inject
-constructor(
-    private val notifCollection: NotifCollection,
-) : IconViewStore {
-    override fun iconView(key: String): StatusBarIconView {
-        val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key")
-        return entry.icons.aodIcon ?: error("No AOD IconView found for key: $key")
-    }
-}
+constructor(notifCollection: NotifCollection) :
+    IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon })
 
 /** [IconViewStore] for the status bar. */
-class StatusBarNotificationIconViewStore
-@Inject
-constructor(
-    private val notifCollection: NotifCollection,
-) : IconViewStore {
-    override fun iconView(key: String): StatusBarIconView {
-        val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key")
-        return entry.icons.statusBarIcon ?: error("No status bar IconView found for key: $key")
+class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
+    IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon })
+
+private fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) =
+    IconViewStore { key ->
+        getEntry(key)?.icons?.let(block)
     }
-}
 
 private val View.viewBounds: Rect
     get() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt
new file mode 100644
index 0000000..0c114a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.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.systemui.statusbar.notification.icon.ui.viewbinder
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.printCollection
+import dagger.Binds
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import java.io.PrintWriter
+import javax.inject.Inject
+
+@SysUISingleton
+class StatusBarIconViewBindingFailureTracker @Inject constructor() : CoreStartable {
+
+    var aodFailures: Collection<String> = emptyList()
+    var statusBarFailures: Collection<String> = emptyList()
+    var shelfFailures: Collection<String> = emptyList()
+
+    // TODO(b/310681665): Ideally we wouldn't need to implement CoreStartable at all, and could just
+    //  @Binds @IntoSet the Dumpable.
+    override fun start() {
+        // no-op, we're just using this as a dumpable
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        if (!NotificationIconContainerRefactor.isEnabled) return
+        pw.asIndenting().run {
+            printCollection("AOD Icon binding failures:", aodFailures)
+            printCollection("Status Bar Icon binding failures:", statusBarFailures)
+            printCollection("Shelf Icon binding failures:", shelfFailures)
+        }
+    }
+
+    @dagger.Module
+    interface Module {
+        @Binds
+        @IntoMap
+        @ClassKey(StatusBarIconViewBindingFailureTracker::class)
+        fun bindStartable(impl: StatusBarIconViewBindingFailureTracker): CoreStartable
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index 82626acc..c03a4a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -31,6 +31,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 
@@ -83,19 +84,18 @@
     /** An Icon to show "isolated" in the IconContainer. */
     val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> =
         headsUpIconInteractor.isolatedNotification
+            .combine(icons) { isolatedNotif, iconsViewData ->
+                isolatedNotif?.let {
+                    iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif }
+                }
+            }
             .pairwise(initialValue = null)
-            .sample(combine(icons, shadeInteractor.shadeExpansion, ::Pair)) {
-                (prev, isolatedNotif),
-                (iconsViewData, shadeExpansion),
-                ->
-                val iconInfo =
-                    isolatedNotif?.let {
-                        iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif }
-                    }
+            .distinctUntilChanged()
+            .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion ->
                 val animate =
                     when {
-                        isolatedNotif == prev -> false
-                        isolatedNotif == null || prev == null -> shadeExpansion == 0f
+                        iconInfo?.notifKey == prev?.notifKey -> false
+                        iconInfo == null || prev == null -> shadeExpansion == 0f
                         else -> false
                     }
                 AnimatableEvent(iconInfo, animate)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 538be14..9ff416a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -43,9 +43,9 @@
 class PeekDisabledSuppressor(
     private val globalSettings: GlobalSettings,
     private val headsUpManager: HeadsUpManager,
-    private val logger: NotificationInterruptLogger,
+    private val logger: VisualInterruptionDecisionLogger,
     @Main private val mainHandler: Handler,
-) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek setting disabled") {
+) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek disabled by global setting") {
     private var isEnabled = false
 
     override fun shouldSuppress(): Boolean = !isEnabled
@@ -87,16 +87,13 @@
 class PulseDisabledSuppressor(
     private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
     private val userTracker: UserTracker,
-) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse setting disabled") {
+) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by user setting") {
     override fun shouldSuppress(): Boolean =
         !ambientDisplayConfiguration.pulseOnNotificationEnabled(userTracker.userId)
 }
 
 class PulseBatterySaverSuppressor(private val batteryController: BatteryController) :
-    VisualInterruptionCondition(
-        types = setOf(PULSE),
-        reason = "pulsing disabled by battery saver"
-    ) {
+    VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by battery saver") {
     override fun shouldSuppress() = batteryController.isAodPowerSave()
 }
 
@@ -128,14 +125,14 @@
 }
 
 class PeekNotImportantSuppressor() :
-    VisualInterruptionFilter(types = setOf(PEEK), reason = "not important") {
+    VisualInterruptionFilter(types = setOf(PEEK), reason = "importance < HIGH") {
     override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_HIGH
 }
 
 class PeekDeviceNotInUseSuppressor(
     private val powerManager: PowerManager,
     private val statusBarStateController: StatusBarStateController
-) : VisualInterruptionCondition(types = setOf(PEEK), reason = "not in use") {
+) : VisualInterruptionCondition(types = setOf(PEEK), reason = "device not in use") {
     override fun shouldSuppress() =
         when {
             !powerManager.isScreenOn || statusBarStateController.isDreaming -> true
@@ -144,7 +141,7 @@
 }
 
 class PeekOldWhenSuppressor(private val systemClock: SystemClock) :
-    VisualInterruptionFilter(types = setOf(PEEK), reason = "old when") {
+    VisualInterruptionFilter(types = setOf(PEEK), reason = "has old `when`") {
     private fun whenAge(entry: NotificationEntry) =
         systemClock.currentTimeMillis() - entry.sbn.notification.`when`
 
@@ -165,21 +162,21 @@
 }
 
 class PulseEffectSuppressor() :
-    VisualInterruptionFilter(types = setOf(PULSE), reason = "ambient effect suppressed") {
+    VisualInterruptionFilter(types = setOf(PULSE), reason = "suppressed by DND") {
     override fun shouldSuppress(entry: NotificationEntry) = entry.shouldSuppressAmbient()
 }
 
 class PulseLockscreenVisibilityPrivateSuppressor() :
     VisualInterruptionFilter(
         types = setOf(PULSE),
-        reason = "notification hidden on lock screen by override"
+        reason = "hidden by lockscreen visibility override"
     ) {
     override fun shouldSuppress(entry: NotificationEntry) =
         entry.ranking.lockscreenVisibilityOverride == VISIBILITY_PRIVATE
 }
 
 class PulseLowImportanceSuppressor() :
-    VisualInterruptionFilter(types = setOf(PULSE), reason = "importance less than DEFAULT") {
+    VisualInterruptionFilter(types = setOf(PULSE), reason = "importance < DEFAULT") {
     override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_DEFAULT
 }
 
@@ -198,12 +195,12 @@
 }
 
 class BubbleNotAllowedSuppressor() :
-    VisualInterruptionFilter(types = setOf(BUBBLE), reason = "not allowed") {
+    VisualInterruptionFilter(types = setOf(BUBBLE), reason = "cannot bubble") {
     override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble()
 }
 
 class BubbleNoMetadataSuppressor() :
-    VisualInterruptionFilter(types = setOf(BUBBLE), reason = "no bubble metadata") {
+    VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") {
 
     private fun isValidMetadata(metadata: BubbleMetadata?) =
         metadata != null && (metadata.intent != null || metadata.shortcutId != null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
index 6af2543..b44a367 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
@@ -50,19 +50,32 @@
         val shouldFsi: Boolean
         val wouldFsiWithoutDnd: Boolean
         val logReason: String
+        val shouldLog: Boolean
+        val isWarning: Boolean
     }
 
     private enum class DecisionImpl(
         override val shouldFsi: Boolean,
         override val logReason: String,
         override val wouldFsiWithoutDnd: Boolean = shouldFsi,
-        val supersedesDnd: Boolean = false
+        val supersedesDnd: Boolean = false,
+        override val shouldLog: Boolean = true,
+        override val isWarning: Boolean = false
     ) : Decision {
-        NO_FSI_NO_FULL_SCREEN_INTENT(false, "no full-screen intent", supersedesDnd = true),
+        NO_FSI_NO_FULL_SCREEN_INTENT(
+            false,
+            "no full-screen intent",
+            supersedesDnd = true,
+            shouldLog = false
+        ),
         NO_FSI_SHOW_STICKY_HUN(false, "full-screen intents are disabled", supersedesDnd = true),
         NO_FSI_NOT_IMPORTANT_ENOUGH(false, "not important enough"),
-        NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(false, "suppressive group alert behavior"),
-        NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false, "suppressive bubble metadata"),
+        NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(
+            false,
+            "suppressive group alert behavior",
+            isWarning = true
+        ),
+        NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false, "suppressive bubble metadata", isWarning = true),
         NO_FSI_PACKAGE_SUSPENDED(false, "package suspended"),
         FSI_DEVICE_NOT_INTERACTIVE(true, "device is not interactive"),
         FSI_DEVICE_DREAMING(true, "device is dreaming"),
@@ -71,7 +84,7 @@
         FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"),
         FSI_LOCKED_SHADE(true, "locked shade"),
         FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"),
-        NO_FSI_NO_HUN_OR_KEYGUARD(false, "no HUN or keyguard"),
+        NO_FSI_NO_HUN_OR_KEYGUARD(false, "no HUN or keyguard", isWarning = true),
         NO_FSI_SUPPRESSED_BY_DND(false, "suppressed by DND", wouldFsiWithoutDnd = false),
         NO_FSI_SUPPRESSED_ONLY_BY_DND(false, "suppressed only by DND", wouldFsiWithoutDnd = true)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
new file mode 100644
index 0000000..1470b03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.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.statusbar.notification.interruption
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
+import com.android.systemui.log.dagger.NotificationInterruptLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class VisualInterruptionDecisionLogger
+@Inject
+constructor(@NotificationInterruptLog val buffer: LogBuffer) {
+    fun logHeadsUpFeatureChanged(isEnabled: Boolean) {
+        buffer.log(
+            TAG,
+            INFO,
+            { bool1 = isEnabled },
+            { "HUN feature is now ${if (bool1) "enabled" else "disabled"}" }
+        )
+    }
+
+    fun logWillDismissAll() {
+        buffer.log(TAG, INFO, {}, { "dismissing all HUNs since feature was disabled" })
+    }
+
+    fun logDecision(
+        type: String,
+        entry: NotificationEntry,
+        decision: VisualInterruptionDecisionProvider.Decision
+    ) {
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = type
+                bool1 = decision.shouldInterrupt
+                str2 = decision.logReason
+                str3 = entry.logKey
+            },
+            {
+                val outcome = if (bool1) "allowed" else "suppressed"
+                "$str1 $outcome: $str2 (key=$str3)"
+            }
+        )
+    }
+
+    fun logFullScreenIntentDecision(
+        entry: NotificationEntry,
+        decision: FullScreenIntentDecision,
+        warning: Boolean
+    ) {
+        buffer.log(
+            TAG,
+            if (warning) WARNING else DEBUG,
+            {
+                bool1 = decision.shouldInterrupt
+                bool2 = decision.wouldInterruptWithoutDnd
+                str1 = decision.logReason
+                str2 = entry.logKey
+            },
+            {
+                val outcome =
+                    when {
+                        bool1 -> "allowed"
+                        bool2 -> "suppressed only by DND"
+                        else -> "suppressed"
+                    }
+                "FSI $outcome: $str1 (key=$str2)"
+            }
+        )
+    }
+}
+
+private const val TAG = "VisualInterruptionDecisionProvider"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 9640682..c0a1a32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -18,6 +18,7 @@
 import android.hardware.display.AmbientDisplayConfiguration
 import android.os.Handler
 import android.os.PowerManager
+import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -46,7 +47,7 @@
     private val headsUpManager: HeadsUpManager,
     private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
     keyguardStateController: KeyguardStateController,
-    private val logger: NotificationInterruptLogger,
+    private val logger: VisualInterruptionDecisionLogger,
     @Main private val mainHandler: Handler,
     private val powerManager: PowerManager,
     private val statusBarStateController: StatusBarStateController,
@@ -58,9 +59,32 @@
         override val logReason: String
     ) : Decision
 
+    private data class LoggableDecision private constructor(val decision: DecisionImpl) {
+        companion object {
+            val unsuppressed =
+                LoggableDecision(DecisionImpl(shouldInterrupt = true, logReason = "not suppressed"))
+
+            fun suppressed(legacySuppressor: NotificationInterruptSuppressor, methodName: String) =
+                LoggableDecision(
+                    DecisionImpl(
+                        shouldInterrupt = false,
+                        logReason = "${legacySuppressor.name}.$methodName"
+                    )
+                )
+
+            fun suppressed(suppressor: VisualInterruptionSuppressor) =
+                LoggableDecision(
+                    DecisionImpl(shouldInterrupt = false, logReason = suppressor.reason)
+                )
+        }
+    }
+
     private class FullScreenIntentDecisionImpl(
+        val entry: NotificationEntry,
         private val fsiDecision: FullScreenIntentDecisionProvider.Decision
     ) : FullScreenIntentDecision {
+        var hasBeenLogged = false
+
         override val shouldInterrupt
             get() = fsiDecision.shouldFsi
 
@@ -69,6 +93,12 @@
 
         override val logReason
             get() = fsiDecision.logReason
+
+        val shouldLog
+            get() = fsiDecision.shouldLog
+
+        val isWarning
+            get() = fsiDecision.isWarning
     }
 
     private val fullScreenIntentDecisionProvider =
@@ -139,137 +169,113 @@
 
     override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision {
         check(started)
-        return makeHeadsUpDecision(entry)
+
+        return if (statusBarStateController.isDozing) {
+                makeLoggablePulseDecision(entry)
+            } else {
+                makeLoggablePeekDecision(entry)
+            }
+            .decision
     }
 
     override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision {
         check(started)
-        return makeHeadsUpDecision(entry).also { logHeadsUpDecision(entry, it) }
+
+        return if (statusBarStateController.isDozing) {
+                makeLoggablePulseDecision(entry).also { logDecision(PULSE, entry, it) }
+            } else {
+                makeLoggablePeekDecision(entry).also { logDecision(PEEK, entry, it) }
+            }
+            .decision
     }
 
+    private fun makeLoggablePeekDecision(entry: NotificationEntry): LoggableDecision =
+        checkConditions(PEEK)
+            ?: checkFilters(PEEK, entry) ?: checkSuppressInterruptions(entry)
+                ?: checkSuppressAwakeInterruptions(entry) ?: checkSuppressAwakeHeadsUp(entry)
+                ?: LoggableDecision.unsuppressed
+
+    private fun makeLoggablePulseDecision(entry: NotificationEntry): LoggableDecision =
+        checkConditions(PULSE)
+            ?: checkFilters(PULSE, entry) ?: checkSuppressInterruptions(entry)
+                ?: LoggableDecision.unsuppressed
+
     override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision {
         check(started)
-        return makeBubbleDecision(entry).also { logBubbleDecision(entry, it) }
+
+        return makeLoggableBubbleDecision(entry).also { logDecision(BUBBLE, entry, it) }.decision
+    }
+
+    private fun makeLoggableBubbleDecision(entry: NotificationEntry): LoggableDecision =
+        checkConditions(BUBBLE)
+            ?: checkFilters(BUBBLE, entry) ?: checkSuppressInterruptions(entry)
+                ?: checkSuppressAwakeInterruptions(entry) ?: LoggableDecision.unsuppressed
+
+    private fun logDecision(
+        type: VisualInterruptionType,
+        entry: NotificationEntry,
+        loggable: LoggableDecision
+    ) {
+        logger.logDecision(type.name, entry, loggable.decision)
     }
 
     override fun makeUnloggedFullScreenIntentDecision(
         entry: NotificationEntry
     ): FullScreenIntentDecision {
         check(started)
-        return makeFullScreenIntentDecision(entry)
+
+        val couldHeadsUp = makeUnloggedHeadsUpDecision(entry).shouldInterrupt
+        val fsiDecision =
+            fullScreenIntentDecisionProvider.makeFullScreenIntentDecision(entry, couldHeadsUp)
+        return FullScreenIntentDecisionImpl(entry, fsiDecision)
     }
 
     override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) {
         check(started)
-        // Not yet implemented.
+
+        if (decision !is FullScreenIntentDecisionImpl) {
+            Log.wtf(TAG, "FSI decision $decision was not created by this class")
+            return
+        }
+
+        if (decision.hasBeenLogged) {
+            Log.wtf(TAG, "FSI decision $decision has already been logged")
+            return
+        }
+
+        decision.hasBeenLogged = true
+
+        if (!decision.shouldLog) {
+            return
+        }
+
+        logger.logFullScreenIntentDecision(decision.entry, decision, decision.isWarning)
     }
 
-    private fun makeHeadsUpDecision(entry: NotificationEntry): DecisionImpl {
-        if (statusBarStateController.isDozing) {
-            return makePulseDecision(entry)
-        } else {
-            return makePeekDecision(entry)
-        }
-    }
+    private fun checkSuppressInterruptions(entry: NotificationEntry) =
+        legacySuppressors
+            .firstOrNull { it.suppressInterruptions(entry) }
+            ?.let { LoggableDecision.suppressed(it, "suppressInterruptions") }
 
-    private fun makePeekDecision(entry: NotificationEntry): DecisionImpl {
-        checkConditions(PEEK)?.let {
-            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
-        }
-        checkFilters(PEEK, entry)?.let {
-            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
-        }
-        checkSuppressors(entry)?.let {
-            return DecisionImpl(
-                shouldInterrupt = false,
-                logReason = "${it.name}.suppressInterruptions"
-            )
-        }
-        checkAwakeSuppressors(entry)?.let {
-            return DecisionImpl(
-                shouldInterrupt = false,
-                logReason = "${it.name}.suppressAwakeInterruptions"
-            )
-        }
-        checkAwakeHeadsUpSuppressors(entry)?.let {
-            return DecisionImpl(
-                shouldInterrupt = false,
-                logReason = "${it.name}.suppressAwakeHeadsUpInterruptions"
-            )
-        }
-        return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
-    }
+    private fun checkSuppressAwakeInterruptions(entry: NotificationEntry) =
+        legacySuppressors
+            .firstOrNull { it.suppressAwakeInterruptions(entry) }
+            ?.let { LoggableDecision.suppressed(it, "suppressAwakeInterruptions") }
 
-    private fun makePulseDecision(entry: NotificationEntry): DecisionImpl {
-        checkConditions(PULSE)?.let {
-            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
-        }
-        checkFilters(PULSE, entry)?.let {
-            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
-        }
-        checkSuppressors(entry)?.let {
-            return DecisionImpl(
-                shouldInterrupt = false,
-                logReason = "${it.name}.suppressInterruptions"
-            )
-        }
-        return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
-    }
+    private fun checkSuppressAwakeHeadsUp(entry: NotificationEntry) =
+        legacySuppressors
+            .firstOrNull { it.suppressAwakeHeadsUp(entry) }
+            ?.let { LoggableDecision.suppressed(it, "suppressAwakeHeadsUp") }
 
-    private fun makeBubbleDecision(entry: NotificationEntry): DecisionImpl {
-        checkConditions(BUBBLE)?.let {
-            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
-        }
-        checkFilters(BUBBLE, entry)?.let {
-            return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
-        }
-        checkSuppressors(entry)?.let {
-            return DecisionImpl(
-                shouldInterrupt = false,
-                logReason = "${it.name}.suppressInterruptions"
-            )
-        }
-        checkAwakeSuppressors(entry)?.let {
-            return DecisionImpl(
-                shouldInterrupt = false,
-                logReason = "${it.name}.suppressAwakeInterruptions"
-            )
-        }
-        return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
-    }
+    private fun checkConditions(type: VisualInterruptionType) =
+        conditions
+            .firstOrNull { it.types.contains(type) && it.shouldSuppress() }
+            ?.let { LoggableDecision.suppressed(it) }
 
-    private fun logHeadsUpDecision(entry: NotificationEntry, decision: DecisionImpl) {
-        // Not yet implemented.
-    }
-
-    private fun logBubbleDecision(entry: NotificationEntry, decision: DecisionImpl) {
-        // Not yet implemented.
-    }
-
-    private fun makeFullScreenIntentDecision(entry: NotificationEntry): FullScreenIntentDecision {
-        val wouldHeadsUp = makeUnloggedHeadsUpDecision(entry).shouldInterrupt
-        val fsiDecision =
-            fullScreenIntentDecisionProvider.makeFullScreenIntentDecision(entry, wouldHeadsUp)
-        return FullScreenIntentDecisionImpl(fsiDecision)
-    }
-
-    private fun checkSuppressors(entry: NotificationEntry) =
-        legacySuppressors.firstOrNull { it.suppressInterruptions(entry) }
-
-    private fun checkAwakeSuppressors(entry: NotificationEntry) =
-        legacySuppressors.firstOrNull { it.suppressAwakeInterruptions(entry) }
-
-    private fun checkAwakeHeadsUpSuppressors(entry: NotificationEntry) =
-        legacySuppressors.firstOrNull { it.suppressAwakeHeadsUp(entry) }
-
-    private fun checkConditions(type: VisualInterruptionType): VisualInterruptionCondition? =
-        conditions.firstOrNull { it.types.contains(type) && it.shouldSuppress() }
-
-    private fun checkFilters(
-        type: VisualInterruptionType,
-        entry: NotificationEntry
-    ): VisualInterruptionFilter? =
-        filters.firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) }
+    private fun checkFilters(type: VisualInterruptionType, entry: NotificationEntry) =
+        filters
+            .firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) }
+            ?.let { LoggableDecision.suppressed(it) }
 }
 
 private const val TAG = "VisualInterruptionDecisionProviderImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 7667e17..5cdead4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
 import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
@@ -40,6 +41,7 @@
         configuration: ConfigurationState,
         configurationController: ConfigurationController,
         falsingManager: FalsingManager,
+        iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
         notificationIconAreaController: NotificationIconAreaController,
         shelfIconViewStore: ShelfNotificationIconViewStore,
     ) {
@@ -51,6 +53,7 @@
                     viewModel.icons,
                     configuration,
                     configurationController,
+                    iconViewBindingFailureTracker,
                     shelfIconViewStore,
                 )
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 14ec08f35..46488c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4668,22 +4668,6 @@
         return mClearAllInProgress;
     }
 
-    public boolean isFooterViewNotGone() {
-        return mFooterView != null
-                && mFooterView.getVisibility() != View.GONE
-                && !mFooterView.willBeGone();
-    }
-
-    public boolean isFooterViewContentVisible() {
-        return mFooterView != null && mFooterView.isContentVisible();
-    }
-
-    public int getFooterViewHeightWithPadding() {
-        return mFooterView == null ? 0 : mFooterView.getHeight()
-                + mPaddingBetweenElements
-                + mGapHeight;
-    }
-
     /**
      * @return the padding after the media header on the lockscreen
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 2cf0c26..3e140a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -858,10 +858,6 @@
         return row.getVisibility() == View.VISIBLE;
     }
 
-    public boolean isViewAffectedBySwipe(ExpandableView expandableView) {
-        return mNotificationRoundnessManager.isViewAffectedBySwipe(expandableView);
-    }
-
     public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
         mView.addOnExpandedHeightChangedListener(listener);
     }
@@ -1261,18 +1257,6 @@
         mView.setPanelFlinging(flinging);
     }
 
-    public boolean isFooterViewNotGone() {
-        return mView.isFooterViewNotGone();
-    }
-
-    public boolean isFooterViewContentVisible() {
-        return mView.isFooterViewContentVisible();
-    }
-
-    public int getFooterViewHeightWithPadding() {
-        return mView.getFooterViewHeightWithPadding();
-    }
-
     /**
      * Sets whether the bouncer is currently showing. Should only be called from
      * {@link CentralSurfaces}.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 6cf5610..a5b87f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
 import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -45,6 +46,7 @@
     private val configurationController: ConfigurationController,
     private val falsingManager: FalsingManager,
     private val iconAreaController: NotificationIconAreaController,
+    private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val shelfIconViewStore: ShelfNotificationIconViewStore,
 ) {
 
@@ -68,6 +70,7 @@
             configuration,
             configurationController,
             falsingManager,
+            iconViewBindingFailureTracker,
             iconAreaController,
             shelfIconViewStore,
         )
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt
similarity index 65%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt
index 2529807..bcf7322 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt
@@ -13,16 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion.virtual.camera;
 
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+package com.android.systemui.statusbar.notification.ui.viewbinder
 
-    void configureStream(int width, int height, int format);
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
+import dagger.Module
 
-    void close();
-}
+@Module(includes = [StatusBarIconViewBindingFailureTracker.Module::class])
+object StatusBarNotificationViewBinderModule
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 2e5717d..cd7a9ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -139,6 +139,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
 import com.android.systemui.navigationbar.NavigationBarController;
@@ -159,6 +160,7 @@
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -583,6 +585,8 @@
 
     private final InteractionJankMonitor mJankMonitor;
 
+    private final SceneContainerFlags mSceneContainerFlags;
+
     /**
      * Public constructor for CentralSurfaces.
      *
@@ -696,7 +700,8 @@
             AlternateBouncerInteractor alternateBouncerInteractor,
             UserTracker userTracker,
             Provider<FingerprintManager> fingerprintManager,
-            ActivityStarter activityStarter
+            ActivityStarter activityStarter,
+            SceneContainerFlags sceneContainerFlags
     ) {
         mContext = context;
         mNotificationsController = notificationsController;
@@ -793,6 +798,7 @@
         mUserTracker = userTracker;
         mFingerprintManager = fingerprintManager;
         mActivityStarter = activityStarter;
+        mSceneContainerFlags = sceneContainerFlags;
 
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         mStartingSurfaceOptional = startingSurfaceOptional;
@@ -1247,7 +1253,7 @@
 
         // Set up the quick settings tile panel
         final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
-        if (container != null) {
+        if (container != null && !mSceneContainerFlags.isEnabled()) {
             FragmentHostManager fragmentHostManager =
                     mFragmentService.getFragmentHostManager(container);
             ExtensionFragmentListener.attachExtensonToFragment(
@@ -1449,7 +1455,7 @@
         return (v, event) -> {
             mAutoHideController.checkUserAutoHide(event);
             mRemoteInputManager.checkRemoteInputOutside(event);
-            if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            if (!KeyguardShadeMigrationNssl.isEnabled()) {
                 mShadeController.onStatusBarTouch(event);
             }
             return getNotificationShadeWindowView().onTouchEvent(event);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index beeee1b..495b4e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -252,7 +252,7 @@
             }
             if (NotificationIconContainerRefactor.isEnabled()) {
                 mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
-                        newEntry == null ? null : newEntry.getKey());
+                        newEntry == null ? null : newEntry.getRepresentativeEntry().getKey());
             } else {
                 updateIsolatedIconLocation(false /* requireUpdate */);
                 mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 3b3d8b6..1f9952a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -40,7 +40,7 @@
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -115,7 +115,6 @@
     private int mAodIconTint;
     private boolean mAodIconsVisible;
     private boolean mShowLowPriority = true;
-    private boolean mIsStatusViewMigrated = false;
 
     @VisibleForTesting
     final NotificationListener.NotificationSettingsListener mSettingsListener =
@@ -159,7 +158,6 @@
         mStatusBarWindowController = statusBarWindowController;
         mScreenOffAnimationController = screenOffAnimationController;
         notificationListener.addNotificationSettingsListener(mSettingsListener);
-        mIsStatusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW);
         initializeNotificationAreaViews(context);
         reloadAodColor();
         darkIconDispatcher.addDarkReceiver(this);
@@ -551,7 +549,7 @@
             return;
         }
         if (mScreenOffAnimationController.shouldAnimateAodIcons()) {
-            if (!mIsStatusViewMigrated) {
+            if (!KeyguardShadeMigrationNssl.isEnabled()) {
                 mAodIcons.setTranslationY(-mAodIconAppearTranslation);
             }
             mAodIcons.setAlpha(0);
@@ -563,14 +561,14 @@
                     .start();
         } else {
             mAodIcons.setAlpha(1.0f);
-            if (!mIsStatusViewMigrated) {
+            if (!KeyguardShadeMigrationNssl.isEnabled()) {
                 mAodIcons.setTranslationY(0);
             }
         }
     }
 
     private void animateInAodIconTranslation() {
-        if (!mIsStatusViewMigrated) {
+        if (!KeyguardShadeMigrationNssl.isEnabled()) {
             mAodIcons.animate()
                     .setInterpolator(Interpolators.DECELERATE_QUINT)
                     .translationY(0)
@@ -673,7 +671,7 @@
                 }
             } else {
                 mAodIcons.setAlpha(1.0f);
-                if (!mIsStatusViewMigrated) {
+                if (!KeyguardShadeMigrationNssl.isEnabled()) {
                     mAodIcons.setTranslationY(0);
                 }
                 mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index c207324..2235035 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -24,6 +24,8 @@
 import android.util.MathUtils;
 import android.util.TimeUtils;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.app.animation.Interpolators;
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.Dumpable;
@@ -96,13 +98,14 @@
     private final KeyguardStateController mKeyguardStateController;
     private final StatusBarStateController mStatusBarStateController;
     private final CommandQueue mCommandQueue;
-    private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
+    private GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
 
     private boolean mTransitionDeferring;
     private long mTransitionDeferringStartTime;
     private long mTransitionDeferringDuration;
     private boolean mTransitionPending;
     private boolean mTintChangePending;
+    private boolean mNavigationButtonsForcedVisible;
     private float mPendingDarkIntensity;
     private ValueAnimator mTintAnimator;
     private float mDarkIntensity;
@@ -137,13 +140,16 @@
         mContext = context;
         mDisplayId = mContext.getDisplayId();
         mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
-                mHandler, mContext, null);
+                mHandler, mContext, this::onNavigationSettingsChanged);
+        mGestureNavigationSettingsObserver.register();
+        onNavigationSettingsChanged();
     }
 
     /** Call to cleanup the LightBarTransitionsController when done with it. */
     public void destroy() {
         mCommandQueue.removeCallback(mCallback);
         mStatusBarStateController.removeCallback(mCallback);
+        mGestureNavigationSettingsObserver.unregister();
     }
 
     public void saveState(Bundle outState) {
@@ -199,6 +205,12 @@
         mTransitionPending = false;
     }
 
+    @VisibleForTesting
+    void setNavigationSettingsObserver(GestureNavigationSettingsObserver observer) {
+        mGestureNavigationSettingsObserver = observer;
+        onNavigationSettingsChanged();
+    }
+
     public void setIconsDark(boolean dark, boolean animate) {
         if (!animate) {
             setIconTintInternal(dark ? 1.0f : 0.0f);
@@ -253,6 +265,28 @@
         mApplier.applyDarkIntensity(MathUtils.lerp(mDarkIntensity, 0f, mDozeAmount));
     }
 
+    public void onDozeAmountChanged(float linear, float eased) {
+        mDozeAmount = eased;
+        dispatchDark();
+    }
+
+    /**
+     * Called when the navigation settings change.
+     */
+    private void onNavigationSettingsChanged() {
+        mNavigationButtonsForcedVisible =
+                mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
+    }
+
+    /**
+     * Return whether to use the tint calculated in this class for nav icons.
+     */
+    public boolean supportsIconTintForNavMode(int navigationMode) {
+        // In gesture mode, we already do region sampling to update tint based on content beneath.
+        return !QuickStepContract.isGesturalMode(navigationMode)
+                || mNavigationButtonsForcedVisible;
+    }
+
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.print("  mTransitionDeferring="); pw.print(mTransitionDeferring);
@@ -271,20 +305,7 @@
         pw.print("  mPendingDarkIntensity="); pw.print(mPendingDarkIntensity);
         pw.print(" mDarkIntensity="); pw.print(mDarkIntensity);
         pw.print(" mNextDarkIntensity="); pw.println(mNextDarkIntensity);
-    }
-
-    public void onDozeAmountChanged(float linear, float eased) {
-        mDozeAmount = eased;
-        dispatchDark();
-    }
-
-    /**
-     * Return whether to use the tint calculated in this class for nav icons.
-     */
-    public boolean supportsIconTintForNavMode(int navigationMode) {
-        // In gesture mode, we already do region sampling to update tint based on content beneath.
-        return !QuickStepContract.isGesturalMode(navigationMode)
-                || mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
+        pw.print(" mAreNavigationButtonForcedVisible="); pw.println(mNavigationButtonsForcedVisible);
     }
 
     /**
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 fb586ea..1a17e7c 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.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealScrim
@@ -34,8 +35,6 @@
 import com.android.app.tracing.TraceUtils
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 
 /**
  * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely
@@ -68,7 +67,6 @@
     private val interactionJankMonitor: InteractionJankMonitor,
     private val powerManager: PowerManager,
     private val handler: Handler = Handler(),
-    private val featureFlags: FeatureFlags,
 ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
     private lateinit var centralSurfaces: CentralSurfaces
     private lateinit var shadeViewController: ShadeViewController
@@ -288,7 +286,7 @@
                 // up, with unpredictable consequences.
                 if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&
                         shouldAnimateInKeyguard) {
-                    if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                    if (!KeyguardShadeMigrationNssl.isEnabled) {
                         // Tracking this state should no longer be relevant, as the isInteractive
                         // check covers it
                         aodUiAnimationPlaying = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 3921e69..7adc08c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -56,6 +56,7 @@
 import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
@@ -217,6 +218,7 @@
         mWaitingForWindowStateChangeAfterCameraLaunch = false;
         mTransitionFromLockscreenToDreamStarted = false;
     };
+    private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker;
 
     @Inject
     public CollapsedStatusBarFragment(
@@ -235,6 +237,7 @@
             KeyguardStateController keyguardStateController,
             ShadeViewController shadeViewController,
             StatusBarStateController statusBarStateController,
+            StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker,
             CommandQueue commandQueue,
             CarrierConfigTracker carrierConfigTracker,
             CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
@@ -264,6 +267,7 @@
         mKeyguardStateController = keyguardStateController;
         mShadeViewController = shadeViewController;
         mStatusBarStateController = statusBarStateController;
+        mIconViewBindingFailureTracker = iconViewBindingFailureTracker;
         mCommandQueue = commandQueue;
         mCarrierConfigTracker = carrierConfigTracker;
         mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
@@ -471,6 +475,7 @@
                     mStatusBarIconsViewModel,
                     mConfigurationState,
                     mConfigurationController,
+                    mIconViewBindingFailureTracker,
                     mStatusBarIconViewStore);
         } else {
             mNotificationIconAreaInner =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index b614b6d..78954de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -35,13 +35,12 @@
 import com.android.keyguard.KeyguardVisibilityHelper;
 import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
 import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.Expandable;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.user.UserSwitchDialogController;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -149,7 +148,6 @@
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController,
             UserSwitchDialogController userSwitchDialogController,
-            FeatureFlags featureFlags,
             UiEventLogger uiEventLogger) {
         super(view);
         if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
@@ -163,7 +161,7 @@
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
                 keyguardStateController, dozeParameters,
                 screenOffAnimationController,  /* animateYPos= */ false,
-                featureFlags, /* logBuffer= */ null);
+                /* logBuffer= */ null);
         mUserSwitchDialogController = userSwitchDialogController;
         mUiEventLogger = uiEventLogger;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index dfe2686..770f441 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -39,7 +39,6 @@
 import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
 import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
@@ -161,7 +160,6 @@
             KeyguardStateController keyguardStateController,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            FeatureFlags featureFlags,
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController) {
         super(keyguardUserSwitcherView);
@@ -177,7 +175,7 @@
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
                 keyguardStateController, dozeParameters,
                 screenOffAnimationController, /* animateYPos= */ false,
-                featureFlags, /* logBuffer= */ null);
+                /* logBuffer= */ null);
         mBackground = new KeyguardUserSwitcherScrim(context);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 818ba95..3304b98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -71,6 +71,7 @@
 import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
 import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl;
 import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepositoryModule;
+import com.android.systemui.statusbar.policy.data.repository.ZenModeRepositoryModule;
 
 import dagger.Binds;
 import dagger.Module;
@@ -81,7 +82,7 @@
 import javax.inject.Named;
 
 /** Dagger Module for code in the statusbar.policy package. */
-@Module(includes = { DeviceProvisioningRepositoryModule.class })
+@Module(includes = { DeviceProvisioningRepositoryModule.class, ZenModeRepositoryModule.class })
 public interface StatusBarPolicyModule {
 
     String DEVICE_STATE_ROTATION_LOCK_DEFAULTS = "DEVICE_STATE_ROTATION_LOCK_DEFAULTS";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt
new file mode 100644
index 0000000..94ab58a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.policy.data.repository
+
+import android.app.NotificationManager
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.statusbar.policy.ZenModeController
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * A repository that holds information about the status and configuration of Zen Mode (or Do Not
+ * Disturb/DND Mode).
+ */
+interface ZenModeRepository {
+    val zenMode: Flow<Int>
+    val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?>
+}
+
+class ZenModeRepositoryImpl
+@Inject
+constructor(
+    private val zenModeController: ZenModeController,
+) : ZenModeRepository {
+    // TODO(b/308591859): ZenModeController should use flows instead of callbacks. The
+    // conflatedCallbackFlows here should be replaced eventually, see:
+    // https://docs.google.com/document/d/1gAiuYupwUAFdbxkDXa29A4aFNu7XoCd7sCIk31WTnHU/edit?resourcekey=0-J4ZBiUhLhhQnNobAcI2vIw
+
+    override val zenMode: Flow<Int> = conflatedCallbackFlow {
+        val callback =
+            object : ZenModeController.Callback {
+                override fun onZenChanged(zen: Int) {
+                    trySend(zen)
+                }
+            }
+        zenModeController.addCallback(callback)
+        trySend(zenModeController.zen)
+
+        awaitClose { zenModeController.removeCallback(callback) }
+    }
+
+    override val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?> =
+        conflatedCallbackFlow {
+            val callback =
+                object : ZenModeController.Callback {
+                    override fun onConsolidatedPolicyChanged(policy: NotificationManager.Policy?) {
+                        trySend(policy)
+                    }
+                }
+            zenModeController.addCallback(callback)
+            trySend(zenModeController.consolidatedPolicy)
+
+            awaitClose { zenModeController.removeCallback(callback) }
+        }
+}
+
+@Module
+interface ZenModeRepositoryModule {
+    @Binds fun bindImpl(impl: ZenModeRepositoryImpl): ZenModeRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
new file mode 100644
index 0000000..ae31851
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.domain.interactor
+
+import android.provider.Settings
+import com.android.systemui.statusbar.policy.data.repository.ZenModeRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/**
+ * An interactor that performs business logic related to the status and configuration of Zen Mode
+ * (or Do Not Disturb/DND Mode).
+ */
+class ZenModeInteractor @Inject constructor(repository: ZenModeRepository) {
+    val isZenModeEnabled: Flow<Boolean> =
+        repository.zenMode
+            .map {
+                when (it) {
+                    Settings.Global.ZEN_MODE_ALARMS -> true
+                    Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS -> true
+                    Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> true
+                    Settings.Global.ZEN_MODE_OFF -> false
+                    else -> false
+                }
+            }
+            .distinctUntilChanged()
+
+    val areNotificationsHiddenInShade: Flow<Boolean> =
+        combine(isZenModeEnabled, repository.consolidatedNotificationPolicy) { dndEnabled, policy ->
+                if (!dndEnabled) {
+                    false
+                } else {
+                    val showInNotificationList = policy?.showInNotificationList() ?: true
+                    !showInNotificationList
+                }
+            }
+            .distinctUntilChanged()
+}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt
similarity index 68%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt
index 2529807..4cbdd6f 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt
@@ -13,16 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.companion.virtual.camera;
 
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+package com.android.systemui.statusbar.ui.binder
 
-    void configureStream(int width, int height, int format);
+import com.android.systemui.statusbar.notification.ui.viewbinder.StatusBarNotificationViewBinderModule
+import dagger.Module
 
-    void close();
-}
+@Module(includes = [StatusBarNotificationViewBinderModule::class]) object StatusBarViewBinderModule
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 fc414b6..2f2c4b0 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -39,7 +39,6 @@
 import com.android.app.animation.Interpolators
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.Gefingerpoken
-import com.android.systemui.res.R
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text.Companion.loadText
@@ -48,9 +47,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
@@ -96,7 +94,6 @@
     wakeLockBuilder: WakeLock.Builder,
     systemClock: SystemClock,
     tempViewUiEventLogger: TemporaryViewUiEventLogger,
-    private val featureFlags: FeatureFlags,
 ) :
     TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
         context,
@@ -234,18 +231,14 @@
         maybeGetAccessibilityFocus(newInfo, currentView)
 
         // ---- Haptics ----
-        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-            vibratorHelper.performHapticFeedback(parent, newInfo.vibrationConstant)
-        } else {
-            newInfo.vibrationEffect?.let {
-                vibratorHelper.vibrate(
-                    Process.myUid(),
-                    context.getApplicationContext().getPackageName(),
-                    it,
-                    newInfo.windowTitle,
-                    VIBRATION_ATTRIBUTES,
-                )
-            }
+        newInfo.vibrationEffect?.let {
+            vibratorHelper.vibrate(
+                Process.myUid(),
+                context.getApplicationContext().getPackageName(),
+                it,
+                newInfo.windowTitle,
+                VIBRATION_ATTRIBUTES,
+            )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 4449f8d0..7475388 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -17,13 +17,12 @@
 package com.android.systemui.temporarydisplay.chipbar
 
 import android.os.VibrationEffect
-import android.view.HapticFeedbackConstants
 import android.view.View
 import androidx.annotation.AttrRes
 import com.android.internal.logging.InstanceId
-import com.android.systemui.res.R
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.res.R
 import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.android.systemui.temporarydisplay.ViewPriority
 
@@ -43,7 +42,6 @@
     val text: Text,
     val endItem: ChipbarEndItem?,
     val vibrationEffect: VibrationEffect? = null,
-    val vibrationConstant: Int = HapticFeedbackConstants.NO_HAPTICS,
     val allowSwipeToDismiss: Boolean = false,
     override val windowTitle: String,
     override val wakeReason: String,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 4d8768f..2e9b7e8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -127,7 +127,6 @@
 
         withDeps.featureFlags.apply {
             set(Flags.REGION_SAMPLING, false)
-            set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
             set(Flags.FACE_AUTH_REFACTOR, false)
         }
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index a38ba00..6a08eea 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -21,7 +21,6 @@
 import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
 import static com.android.systemui.flags.Flags.MIGRATE_CLOCKS_TO_BLUEPRINT;
-import static com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeast;
@@ -60,6 +59,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
@@ -181,7 +181,6 @@
         mFakeFeatureFlags = new FakeFeatureFlags();
         mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false);
         mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
-        mFakeFeatureFlags.set(MIGRATE_KEYGUARD_STATUS_VIEW, false);
         mFakeFeatureFlags.set(MIGRATE_CLOCKS_TO_BLUEPRINT, false);
         mController = new KeyguardClockSwitchController(
                 mView,
@@ -192,6 +191,7 @@
                 mSmartspaceController,
                 mock(ConfigurationController.class),
                 mock(ScreenOffAnimationController.class),
+                mock(StatusBarIconViewBindingFailureTracker.class),
                 mKeyguardUnlockAnimationController,
                 mSecureSettings,
                 mExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 22c75d8..146715d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -30,7 +30,6 @@
 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.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -60,7 +59,6 @@
     @Mock protected ScreenOffAnimationController mScreenOffAnimationController;
     @Mock protected KeyguardLogger mKeyguardLogger;
     @Mock protected KeyguardStatusViewController mControllerMock;
-    @Mock protected FeatureFlags mFeatureFlags;
     @Mock protected InteractionJankMonitor mInteractionJankMonitor;
     @Mock protected ViewTreeObserver mViewTreeObserver;
     @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -91,7 +89,6 @@
                 mDozeParameters,
                 mScreenOffAnimationController,
                 mKeyguardLogger,
-                mFeatureFlags,
                 mInteractionJankMonitor,
                 deps.getKeyguardInteractor(),
                 mKeyguardTransitionInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 776799e..2b41e080 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -3436,7 +3436,8 @@
         when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
         when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
         when(mUsbPortStatus.isConnected()).thenReturn(true);
-        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1});
+        when(mUsbPortStatus.getComplianceWarnings())
+                .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY});
     }
 
     private Context getSpyContext() {
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
index ae2ec2c..87ab5b0 100644
--- 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
@@ -143,6 +143,23 @@
             assertThat(authenticationChallengeResults).isEqualTo(listOf(true, false, true))
         }
 
+    @Test
+    fun isPinEnhancedPrivacyEnabled() =
+        testScope.runTest {
+            whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[0].id))
+                .thenReturn(false)
+            whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[1].id))
+                .thenReturn(true)
+
+            val values by collectValues(underTest.isPinEnhancedPrivacyEnabled)
+            assertThat(values.first()).isTrue()
+            assertThat(values.last()).isFalse()
+
+            userRepository.setSelectedUserInfo(USER_INFOS[1])
+            assertThat(values.last()).isTrue()
+
+    }
+
     private fun setSecurityModeAndDispatchBroadcast(
         securityMode: KeyguardSecurityModel.SecurityMode,
     ) {
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 6da6951..7a9cb6c 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
@@ -354,6 +354,18 @@
             assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
         }
 
+    @Test
+    fun isDigitButtonAnimationEnabled() =
+        testScope.runTest {
+            val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled)
+
+            utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true)
+            assertThat(isAnimationEnabled).isFalse()
+
+            utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false)
+            assertThat(isAnimationEnabled).isTrue()
+        }
+
     private fun TestScope.lockDeviceAndOpenPinBouncer() {
         utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
         utils.deviceEntryRepository.setUnlocked(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 43d70ad..76c2589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.keyguard.ui.view.layout.items.ClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
@@ -49,6 +48,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import java.util.Optional
 
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
@@ -60,7 +60,7 @@
     @Mock private lateinit var mDefaultDeviceEntryIconSection: DefaultDeviceEntryIconSection
     @Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection
     @Mock
-    private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection
+    private lateinit var defaultAmbientIndicationAreaSection: Optional<KeyguardSection>
     @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection
     @Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection
     @Mock private lateinit var defaultStatusBarViewSection: DefaultStatusBarSection
@@ -98,7 +98,7 @@
     fun replaceViews() {
         val constraintLayout = ConstraintLayout(context, null)
         underTest.replaceViews(null, constraintLayout)
-        underTest.sections.forEach { verify(it).addViews(constraintLayout) }
+        underTest.sections.forEach { verify(it)?.addViews(constraintLayout) }
     }
 
     @Test
@@ -110,7 +110,7 @@
         val constraintLayout = ConstraintLayout(context, null)
         underTest.replaceViews(prevBlueprint, constraintLayout)
         underTest.sections.minus(mDefaultDeviceEntryIconSection).forEach {
-            verify(it, never()).addViews(constraintLayout)
+            verify(it, never())?.addViews(constraintLayout)
         }
 
         verify(mDefaultDeviceEntryIconSection).addViews(constraintLayout)
@@ -121,6 +121,6 @@
     fun applyConstraints() {
         val cs = ConstraintSet()
         underTest.applyConstraints(cs)
-        underTest.sections.forEach { verify(it).applyConstraints(cs) }
+        underTest.sections.forEach { verify(it)?.applyConstraints(cs) }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 83145bd..3be50b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -35,15 +35,13 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.statusbar.IUndoMediaTransferCallback
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.Text.Companion.loadText
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -113,7 +111,6 @@
     private lateinit var uiEventLogger: MediaTttSenderUiEventLogger
     private lateinit var tempViewUiEventLogger: TemporaryViewUiEventLogger
     private val defaultTimeout = context.resources.getInteger(R.integer.heads_up_notification_decay)
-    private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setUp() {
@@ -163,9 +160,7 @@
                 fakeWakeLockBuilder,
                 fakeClock,
                 tempViewUiEventLogger,
-                featureFlags
             )
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
         chipbarCoordinator.start()
 
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 5e4c954..1f7a029 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -16,6 +16,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.customize.QSCustomizerController
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.settings.brightness.BrightnessController
 import com.android.systemui.settings.brightness.BrightnessSliderController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -61,6 +62,8 @@
     @Mock private lateinit var configuration: Configuration
     @Mock private lateinit var pagedTileLayout: PagedTileLayout
 
+    private val sceneContainerFlags = FakeSceneContainerFlags()
+
     private lateinit var controller: QSPanelController
     private val testableResources: TestableResources = mContext.orCreateTestableResources
 
@@ -96,7 +99,8 @@
             brightnessSliderFactory,
             falsingManager,
             statusBarKeyguardViewManager,
-                ResourcesSplitShadeStateController()
+            ResourcesSplitShadeStateController(),
+            sceneContainerFlags,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 555484c..8acde36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -20,6 +20,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -42,6 +43,8 @@
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private lateinit var context: Context
 
+    private val sceneContainerFlags = FakeSceneContainerFlags()
+
     private lateinit var controller: QuickStatusBarHeaderController
 
     @Before
@@ -51,7 +54,11 @@
         `when`(view.isAttachedToWindow).thenReturn(true)
         `when`(view.context).thenReturn(context)
 
-        controller = QuickStatusBarHeaderController(view, quickQSPanelController)
+        controller = QuickStatusBarHeaderController(
+                view,
+                quickQSPanelController,
+                sceneContainerFlags,
+        )
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
index 3b6bfee..3808c7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
@@ -32,6 +32,11 @@
 import com.android.systemui.res.R
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -67,17 +72,24 @@
 
     private val fakeSystemClock = FakeSystemClock()
 
+    private lateinit var scheduler: TestCoroutineScheduler
+    private lateinit var dispatcher: CoroutineDispatcher
+    private lateinit var testScope: TestScope
     private lateinit var icon: Pair<Drawable, String>
     private lateinit var bluetoothTileDialog: BluetoothTileDialog
     private lateinit var deviceItem: DeviceItem
 
     @Before
     fun setUp() {
+        scheduler = TestCoroutineScheduler()
+        dispatcher = UnconfinedTestDispatcher(scheduler)
+        testScope = TestScope(dispatcher)
         bluetoothTileDialog =
             BluetoothTileDialog(
                 ENABLED,
                 subtitleResId,
                 bluetoothTileDialogCallback,
+                dispatcher,
                 fakeSystemClock,
                 uiEventLogger,
                 logger,
@@ -111,29 +123,33 @@
 
     @Test
     fun testShowDialog_displayBluetoothDevice() {
-        bluetoothTileDialog =
-            BluetoothTileDialog(
-                ENABLED,
-                subtitleResId,
-                bluetoothTileDialogCallback,
-                fakeSystemClock,
-                uiEventLogger,
-                logger,
-                mContext
+        testScope.runTest {
+            bluetoothTileDialog =
+                BluetoothTileDialog(
+                    ENABLED,
+                    subtitleResId,
+                    bluetoothTileDialogCallback,
+                    dispatcher,
+                    fakeSystemClock,
+                    uiEventLogger,
+                    logger,
+                    mContext
+                )
+            bluetoothTileDialog.show()
+            fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
+            bluetoothTileDialog.onDeviceItemUpdated(
+                listOf(deviceItem),
+                showSeeAll = false,
+                showPairNewDevice = false
             )
-        bluetoothTileDialog.show()
-        bluetoothTileDialog.onDeviceItemUpdated(
-            listOf(deviceItem),
-            showSeeAll = false,
-            showPairNewDevice = false
-        )
 
-        val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
-        val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter
-        assertThat(adapter.itemCount).isEqualTo(1)
-        assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
-        assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY)
-        assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon)
+            val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
+            val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter
+            assertThat(adapter.itemCount).isEqualTo(1)
+            assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
+            assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY)
+            assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon)
+        }
     }
 
     @Test
@@ -147,6 +163,7 @@
                     ENABLED,
                     subtitleResId,
                     bluetoothTileDialogCallback,
+                    dispatcher,
                     fakeSystemClock,
                     uiEventLogger,
                     logger,
@@ -173,6 +190,7 @@
                     ENABLED,
                     subtitleResId,
                     bluetoothTileDialogCallback,
+                    dispatcher,
                     fakeSystemClock,
                     uiEventLogger,
                     logger,
@@ -190,33 +208,37 @@
 
     @Test
     fun testOnDeviceUpdated_hideSeeAll_showPairNew() {
-        bluetoothTileDialog =
-            BluetoothTileDialog(
-                ENABLED,
-                subtitleResId,
-                bluetoothTileDialogCallback,
-                fakeSystemClock,
-                uiEventLogger,
-                logger,
-                mContext
+        testScope.runTest {
+            bluetoothTileDialog =
+                BluetoothTileDialog(
+                    ENABLED,
+                    subtitleResId,
+                    bluetoothTileDialogCallback,
+                    dispatcher,
+                    fakeSystemClock,
+                    uiEventLogger,
+                    logger,
+                    mContext
+                )
+            bluetoothTileDialog.show()
+            fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
+            bluetoothTileDialog.onDeviceItemUpdated(
+                listOf(deviceItem),
+                showSeeAll = false,
+                showPairNewDevice = true
             )
-        bluetoothTileDialog.show()
-        bluetoothTileDialog.onDeviceItemUpdated(
-            listOf(deviceItem),
-            showSeeAll = false,
-            showPairNewDevice = true
-        )
 
-        val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group)
-        val pairNewLayout =
-            bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group)
-        val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
-        val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter
+            val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group)
+            val pairNewLayout =
+                bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group)
+            val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
+            val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter
 
-        assertThat(seeAllLayout).isNotNull()
-        assertThat(seeAllLayout.visibility).isEqualTo(GONE)
-        assertThat(pairNewLayout).isNotNull()
-        assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE)
-        assertThat(adapter.itemCount).isEqualTo(1)
+            assertThat(seeAllLayout).isNotNull()
+            assertThat(seeAllLayout.visibility).isEqualTo(GONE)
+            assertThat(pairNewLayout).isNotNull()
+            assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE)
+            assertThat(adapter.itemCount).isEqualTo(1)
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
new file mode 100644
index 0000000..c1c0126
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -0,0 +1,294 @@
+/*
+ * 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.ui.adapter
+
+import android.os.Bundle
+import android.view.View
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.QSImpl
+import com.android.systemui.qs.dagger.QSComponent
+import com.android.systemui.qs.dagger.QSSceneComponent
+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.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class QSSceneAdapterImplTest : SysuiTestCase() {
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val qsImplProvider =
+        object : Provider<QSImpl> {
+            val impls = mutableListOf<QSImpl>()
+
+            override fun get(): QSImpl {
+                return mock<QSImpl> {
+                        lateinit var _view: View
+                        whenever(onComponentCreated(any(), any())).then {
+                            _view = it.getArgument<QSComponent>(0).getRootView()
+                            Unit
+                        }
+                        whenever(view).thenAnswer { _view }
+                    }
+                    .also { impls.add(it) }
+            }
+        }
+
+    private val qsSceneComponentFactory =
+        object : QSSceneComponent.Factory {
+            val components = mutableListOf<QSSceneComponent>()
+
+            override fun create(rootView: View): QSSceneComponent {
+                return mock<QSSceneComponent> { whenever(this.getRootView()).thenReturn(rootView) }
+                    .also { components.add(it) }
+            }
+        }
+
+    private val mockAsyncLayoutInflater =
+        mock<AsyncLayoutInflater>() {
+            whenever(inflate(anyInt(), nullable(), any())).then { invocation ->
+                val mockView = mock<View>()
+                invocation
+                    .getArgument<AsyncLayoutInflater.OnInflateFinishedListener>(2)
+                    .onInflateFinished(
+                        mockView,
+                        invocation.getArgument(0),
+                        invocation.getArgument(1),
+                    )
+            }
+        }
+
+    private val underTest =
+        QSSceneAdapterImpl(
+            qsSceneComponentFactory,
+            qsImplProvider,
+            testDispatcher,
+            testScope.backgroundScope,
+            { mockAsyncLayoutInflater },
+        )
+
+    @Test
+    fun inflate() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            assertThat(qsImpl).isNull()
+
+            underTest.inflate(context)
+            runCurrent()
+
+            assertThat(qsImpl).isNotNull()
+            assertThat(qsImpl).isSameInstanceAs(qsImplProvider.impls[0])
+            val inOrder = inOrder(qsImpl!!)
+            inOrder.verify(qsImpl!!).onCreate(nullable())
+            inOrder
+                .verify(qsImpl!!)
+                .onComponentCreated(
+                    eq(qsSceneComponentFactory.components[0]),
+                    any(),
+                )
+        }
+
+    @Test
+    fun initialState_closed() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+
+            with(qsImpl!!) {
+                verify(this).setQsVisible(false)
+                verify(this)
+                    .setQsExpansion(
+                        /* expansion= */ 0f,
+                        /* panelExpansionFraction= */ 1f,
+                        /* proposedTranslation= */ 0f,
+                        /* squishinessFraction= */ 1f,
+                    )
+                verify(this).setListening(false)
+                verify(this).setExpanded(false)
+                verify(this)
+                    .setTransitionToFullShadeProgress(
+                        /* isTransitioningToFullShade= */ false,
+                        /* qsTransitionFraction= */ 1f,
+                        /* qsSquishinessFraction = */ 1f,
+                    )
+            }
+        }
+
+    @Test
+    fun state_qqs() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+            clearInvocations(qsImpl!!)
+
+            underTest.setState(QSSceneAdapter.State.QQS)
+            with(qsImpl!!) {
+                verify(this).setQsVisible(true)
+                verify(this)
+                    .setQsExpansion(
+                        /* expansion= */ 0f,
+                        /* panelExpansionFraction= */ 1f,
+                        /* proposedTranslation= */ 0f,
+                        /* squishinessFraction= */ 1f,
+                    )
+                verify(this).setListening(true)
+                verify(this).setExpanded(true)
+                verify(this)
+                    .setTransitionToFullShadeProgress(
+                        /* isTransitioningToFullShade= */ false,
+                        /* qsTransitionFraction= */ 1f,
+                        /* qsSquishinessFraction = */ 1f,
+                    )
+            }
+        }
+
+    @Test
+    fun state_qs() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+            clearInvocations(qsImpl!!)
+
+            underTest.setState(QSSceneAdapter.State.QS)
+            with(qsImpl!!) {
+                verify(this).setQsVisible(true)
+                verify(this)
+                    .setQsExpansion(
+                        /* expansion= */ 1f,
+                        /* panelExpansionFraction= */ 1f,
+                        /* proposedTranslation= */ 0f,
+                        /* squishinessFraction= */ 1f,
+                    )
+                verify(this).setListening(true)
+                verify(this).setExpanded(true)
+                verify(this)
+                    .setTransitionToFullShadeProgress(
+                        /* isTransitioningToFullShade= */ false,
+                        /* qsTransitionFraction= */ 1f,
+                        /* qsSquishinessFraction = */ 1f,
+                    )
+            }
+        }
+
+    @Test
+    fun customizing_QS() =
+        testScope.runTest {
+            val customizing by collectLastValue(underTest.isCustomizing)
+
+            underTest.inflate(context)
+            runCurrent()
+            underTest.setState(QSSceneAdapter.State.QS)
+
+            assertThat(customizing).isFalse()
+
+            underTest.setCustomizerShowing(true)
+            assertThat(customizing).isTrue()
+
+            underTest.setCustomizerShowing(false)
+            assertThat(customizing).isFalse()
+        }
+
+    @Test
+    fun customizing_moveToQQS_stopCustomizing() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+            underTest.setState(QSSceneAdapter.State.QS)
+            underTest.setCustomizerShowing(true)
+
+            underTest.setState(QSSceneAdapter.State.QQS)
+            runCurrent()
+            verify(qsImpl!!).closeCustomizerImmediately()
+        }
+
+    @Test
+    fun customizing_moveToClosed_stopCustomizing() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+            underTest.setState(QSSceneAdapter.State.QS)
+            underTest.setCustomizerShowing(true)
+            runCurrent()
+
+            underTest.setState(QSSceneAdapter.State.CLOSED)
+            verify(qsImpl!!).closeCustomizerImmediately()
+        }
+
+    @Test
+    fun reinflation_previousStateDestroyed() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+            val oldQsImpl = qsImpl!!
+
+            underTest.inflate(context)
+            runCurrent()
+            val newQSImpl = qsImpl!!
+
+            assertThat(oldQsImpl).isNotSameInstanceAs(newQSImpl)
+            val inOrder = inOrder(oldQsImpl, newQSImpl)
+            val bundleArgCaptor = argumentCaptor<Bundle>()
+
+            inOrder.verify(oldQsImpl).onSaveInstanceState(capture(bundleArgCaptor))
+            inOrder.verify(oldQsImpl).onDestroy()
+            assertThat(newQSImpl).isSameInstanceAs(qsImplProvider.impls[1])
+            inOrder.verify(newQSImpl).onCreate(nullable())
+            inOrder
+                .verify(newQSImpl)
+                .onComponentCreated(
+                    qsSceneComponentFactory.components[1],
+                    bundleArgCaptor.value,
+                )
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index d582b9e..ef129c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -23,9 +23,12 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -53,6 +56,7 @@
     private val sceneInteractor = utils.sceneInteractor()
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
+    private val qsFlexiglassAdapter = FakeQSSceneAdapter { _, _ -> mock() }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -96,6 +100,7 @@
                         sceneInteractor = sceneInteractor,
                     ),
                 shadeHeaderViewModel = shadeHeaderViewModel,
+                qsSceneAdapter = qsFlexiglassAdapter,
             )
     }
 
@@ -124,4 +129,33 @@
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
+
+    @Test
+    fun destinationsNotCustomizing() =
+        testScope.runTest {
+            val destinations by collectLastValue(underTest.destinationScenes)
+            qsFlexiglassAdapter.setCustomizing(false)
+
+            assertThat(destinations)
+                .isEqualTo(
+                    mapOf(
+                        UserAction.Back to SceneModel(SceneKey.Shade),
+                        UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
+                    )
+                )
+        }
+
+    @Test
+    fun destinationsCustomizing() =
+        testScope.runTest {
+            val destinations by collectLastValue(underTest.destinationScenes)
+            qsFlexiglassAdapter.setCustomizing(true)
+
+            assertThat(destinations)
+                .isEqualTo(
+                    mapOf(
+                        UserAction.Back to SceneModel(SceneKey.QuickSettings),
+                    )
+                )
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index d1db9c1..cef888b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -20,6 +20,7 @@
 
 import android.telecom.TelecomManager
 import android.telephony.TelephonyManager
+import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.R
@@ -39,6 +40,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
@@ -182,6 +184,8 @@
 
     private var bouncerSceneJob: Job? = null
 
+    private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { _, _ -> mock<View>() })
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -232,6 +236,7 @@
                 applicationScope = testScope.backgroundScope,
                 deviceEntryInteractor = deviceEntryInteractor,
                 shadeHeaderViewModel = shadeHeaderViewModel,
+                qsSceneAdapter = qsFlexiglassAdapter,
             )
 
         utils.deviceEntryRepository.setUnlocked(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 4c8b562..5969bd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.flags.ReleasedFlag
 import com.android.systemui.flags.ResourceBooleanFlag
 import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -68,6 +69,7 @@
         listOf(
                 AconfigFlags.FLAG_SCENE_CONTAINER,
                 AconfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
+                KeyguardShadeMigrationNssl.FLAG_NAME,
             )
             .forEach { flagToken ->
                 setFlagsRule.enableFlags(flagToken)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 152be65..6e487cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -25,9 +25,9 @@
 import android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.activity.SingleActivityFactory
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -182,5 +182,15 @@
             brightnessControllerFactory,
             mainExecutor,
             accessibilityMgr
-        )
+        ) {
+        private var finishing = false
+
+        override fun isFinishing(): Boolean {
+            return finishing
+        }
+
+        override fun requestFinish() {
+            finishing = true
+        }
+    }
 }
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 f4c05e0..f1429b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -100,6 +100,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
@@ -371,12 +372,13 @@
         MockitoAnnotations.initMocks(this);
         mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false);
         mFeatureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false);
-        mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false);
         mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false);
-        mFeatureFlags.set(Flags.MIGRATE_NSSL, false);
         mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
-        mFeatureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false);
         mFeatureFlags.set(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT, false);
+
+        mSetFlagsRule.disableFlags(KeyguardShadeMigrationNssl.FLAG_NAME);
+        mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
+
         mMainDispatcher = getMainDispatcher();
         KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps =
                 KeyguardInteractorFactory.create();
@@ -423,7 +425,6 @@
                 mDozeParameters,
                 mScreenOffAnimationController,
                 mKeyguardLogger,
-                mFeatureFlags,
                 mInteractionJankMonitor,
                 mKeyguardInteractor,
                 mKeyguardTransitionInteractor,
@@ -770,7 +771,6 @@
                 mAccessibilityManager,
                 mLockscreenGestureLogger,
                 mMetricsLogger,
-                mFeatureFlags,
                 mInteractionJankMonitor,
                 mShadeLog,
                 mDumpManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index d573764..722fb2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -59,6 +59,7 @@
 import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
@@ -1105,7 +1106,7 @@
 
     @Test
     public void nsslFlagEnabled_allowOnlyExternalTouches() {
-        mFeatureFlags.set(Flags.MIGRATE_NSSL, true);
+        mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME);
 
         // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 7ad84d6..4df7ef5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.shade
 
-import android.os.VibrationEffect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.HapticFeedbackConstants
@@ -26,13 +25,10 @@
 import com.android.internal.util.CollectionUtils
 import com.android.keyguard.KeyguardClockSwitch.LARGE
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarState.KEYGUARD
 import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -62,9 +58,6 @@
 
     override fun getMainDispatcher() = Dispatchers.Main.immediate
 
-    private val ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT =
-        VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false)
-
     @Test
     fun testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() = runTest {
         launch(Dispatchers.Main.immediate) { givenViewAttached() }
@@ -158,31 +151,8 @@
     }
 
     @Test
-    fun doubleTapRequired_onKeyguard_oneWayHapticsDisabled_usesOldVibrate() = runTest {
+    fun doubleTapRequired_onKeyguard_usesPerformHapticFeedback() = runTest {
         launch(Dispatchers.Main.immediate) {
-            mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
-            val listener = getFalsingTapListener()
-            mStatusBarStateController.setState(KEYGUARD)
-
-            listener.onAdditionalTapRequired()
-            val packageName = mView.context.packageName
-            verify(mKeyguardIndicationController).showTransientIndication(anyInt())
-            verify(mVibratorHelper)
-                .vibrate(
-                    any(),
-                    eq(packageName),
-                    eq(ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT),
-                    eq("falsing-additional-tap-required"),
-                    eq(VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES)
-                )
-        }
-        advanceUntilIdle()
-    }
-
-    @Test
-    fun doubleTapRequired_onKeyguard_oneWayHapticsEnabled_usesPerformHapticFeedback() = runTest {
-        launch(Dispatchers.Main.immediate) {
-            mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
             val listener = getFalsingTapListener()
             mStatusBarStateController.setState(KEYGUARD)
 
@@ -208,32 +178,8 @@
     }
 
     @Test
-    fun doubleTapRequired_shadeLocked_oneWayHapticsDisabled_usesOldVibrate() = runTest {
+    fun doubleTapRequired_shadeLocked_usesPerformHapticFeedback() = runTest {
         launch(Dispatchers.Main.immediate) {
-            mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
-            val listener = getFalsingTapListener()
-            val packageName = mView.context.packageName
-            mStatusBarStateController.setState(SHADE_LOCKED)
-
-            listener.onAdditionalTapRequired()
-            verify(mVibratorHelper)
-                .vibrate(
-                    any(),
-                    eq(packageName),
-                    eq(ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT),
-                    eq("falsing-additional-tap-required"),
-                    eq(VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES)
-                )
-
-            verify(mTapAgainViewController).show()
-        }
-        advanceUntilIdle()
-    }
-
-    @Test
-    fun doubleTapRequired_shadeLocked_oneWayHapticsEnabled_usesPerformHapticFeedback() = runTest {
-        launch(Dispatchers.Main.immediate) {
-            mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
             val listener = getFalsingTapListener()
             mStatusBarStateController.setState(SHADE_LOCKED)
 
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 5459779..2dd0af7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -53,7 +53,6 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags.ALTERNATE_BOUNCER_VIEW
 import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
-import com.android.systemui.flags.Flags.MIGRATE_NSSL
 import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
 import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
 import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
@@ -67,6 +66,7 @@
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.log.BouncerLogger
@@ -198,7 +198,6 @@
         featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
         featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true)
         featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
-        featureFlagsClassic.set(MIGRATE_NSSL, false)
         featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false)
 
         mCommunalRepository = FakeCommunalRepository()
@@ -468,7 +467,7 @@
         // AND the lock icon wants the touch
         whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true)
 
-        featureFlagsClassic.set(MIGRATE_NSSL, true)
+        mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
 
         // THEN touch should NOT be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse()
@@ -487,7 +486,7 @@
         whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any()))
             .thenReturn(false)
 
-        featureFlagsClassic.set(MIGRATE_NSSL, true)
+        mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
 
         // THEN touch should NOT be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
@@ -506,7 +505,7 @@
         whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any()))
             .thenReturn(true)
 
-        featureFlagsClassic.set(MIGRATE_NSSL, true)
+        mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
 
         // THEN touch should NOT be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
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 a6ab6a5..4b62906 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -192,7 +192,6 @@
         featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
         featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
-        featureFlags.set(Flags.MIGRATE_NSSL, false)
         featureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false)
         testScope = TestScope()
         controller =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 778cfa6..88a47eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -65,7 +65,10 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-/** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */
+/**
+ * Uses Flags.KEYGUARD_STATUS_VIEW_MIGRATE_NSSL set to false. If all goes well, this set of tests
+ * will be deleted.
+ */
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -101,11 +104,7 @@
         MockitoAnnotations.initMocks(this)
         fakeSystemClock = FakeSystemClock()
         delayableExecutor = FakeExecutor(fakeSystemClock)
-        featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.MIGRATE_NSSL, false)
-                set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false)
-            }
+        featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false) }
         mContext.ensureTestableResources()
         whenever(view.context).thenReturn(mContext)
         whenever(view.resources).thenReturn(mContext.resources)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index 2342003..1f37ca0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.fragments.FragmentService
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
 import com.android.systemui.plugins.qs.QS
@@ -100,11 +101,8 @@
         MockitoAnnotations.initMocks(this)
         fakeSystemClock = FakeSystemClock()
         delayableExecutor = FakeExecutor(fakeSystemClock)
-        featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.MIGRATE_NSSL, true)
-                set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true)
-            }
+        mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
+        featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true) }
         mContext.ensureTestableResources()
         whenever(view.context).thenReturn(mContext)
         whenever(view.resources).thenReturn(mContext.resources)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 52643cd..ff110c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -353,7 +353,6 @@
                 mAccessibilityManager,
                 mLockscreenGestureLogger,
                 mMetricsLogger,
-                mFeatureFlags,
                 mInteractionJankMonitor,
                 mShadeLogger,
                 mDumpManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index fa849fe..0d3b2b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -76,6 +77,8 @@
             scope = testScope.backgroundScope,
         )
 
+    private val qsFlexiglassAdapter = FakeQSSceneAdapter { _, _ -> mock() }
+
     private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
 
     private lateinit var underTest: ShadeSceneViewModel
@@ -97,6 +100,7 @@
                 applicationScope = testScope.backgroundScope,
                 deviceEntryInteractor = deviceEntryInteractor,
                 shadeHeaderViewModel = shadeHeaderViewModel,
+                qsSceneAdapter = qsFlexiglassAdapter,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index 1a04a3e..87e9735 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -389,4 +389,31 @@
             assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
             assertThat(isolatedIcon?.isAnimating).isFalse()
         }
+
+    @Test
+    fun isolatedIcon_updateWhenIconDataChanges() =
+        testComponent.runTest {
+            val icon: Icon = mock()
+            val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+            runCurrent()
+
+            headsUpViewStateRepository.isolatedNotification.value = "notif1"
+            runCurrent()
+
+            activeNotificationsRepository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply {
+                        addIndividualNotif(
+                            activeNotificationModel(
+                                key = "notif1",
+                                groupKey = "group",
+                                statusBarIcon = icon
+                            )
+                        )
+                    }
+                    .build()
+            runCurrent()
+
+            assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index 1d2055e..e1581ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -44,7 +44,7 @@
                 statusBarStateController,
                 keyguardStateController,
                 headsUpManager,
-                logger,
+                oldLogger,
                 mainHandler,
                 flags,
                 keyguardNotificationVisibilityProvider,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index 722b170..1064475 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -37,7 +37,7 @@
             headsUpManager,
             keyguardNotificationVisibilityProvider,
             keyguardStateController,
-            logger,
+            newLogger,
             mainHandler,
             powerManager,
             statusBarStateController,
@@ -222,14 +222,14 @@
     private class TestCondition(
         types: Set<VisualInterruptionType>,
         val onShouldSuppress: () -> Boolean
-    ) : VisualInterruptionCondition(types = types, reason = "") {
+    ) : VisualInterruptionCondition(types = types, reason = "test condition") {
         override fun shouldSuppress(): Boolean = onShouldSuppress()
     }
 
     private class TestFilter(
         types: Set<VisualInterruptionType>,
         val onShouldSuppress: (NotificationEntry) -> Boolean = { true }
-    ) : VisualInterruptionFilter(types = types, reason = "") {
+    ) : VisualInterruptionFilter(types = types, reason = "test filter") {
         override fun shouldSuppress(entry: NotificationEntry) = onShouldSuppress(entry)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 0f29836..5e81156 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -20,7 +20,9 @@
 import android.app.Notification
 import android.app.Notification.BubbleMetadata
 import android.app.Notification.FLAG_BUBBLE
+import android.app.Notification.FLAG_FOREGROUND_SERVICE
 import android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED
+import android.app.Notification.FLAG_USER_INITIATED_JOB
 import android.app.Notification.GROUP_ALERT_ALL
 import android.app.Notification.GROUP_ALERT_CHILDREN
 import android.app.Notification.GROUP_ALERT_SUMMARY
@@ -47,6 +49,9 @@
 import android.provider.Settings.Global.HEADS_UP_ON
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.res.R
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.statusbar.FakeStatusBarStateController
@@ -76,19 +81,35 @@
 import org.mockito.Mockito.`when` as whenever
 
 abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
+    private val fakeLogBuffer =
+        LogBuffer(
+            name = "FakeLog",
+            maxSize = 1,
+            logcatEchoTracker =
+                object : LogcatEchoTracker {
+                    override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean =
+                        true
+
+                    override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true
+                },
+            systrace = false
+        )
+
     private val leakCheck = LeakCheckedTest.SysuiLeakCheck()
 
     protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context)
     protected val batteryController = FakeBatteryController(leakCheck)
     protected val deviceProvisionedController = FakeDeviceProvisionedController()
     protected val flags: NotifPipelineFlags = mock()
-    protected val globalSettings = FakeGlobalSettings()
+    protected val globalSettings =
+        FakeGlobalSettings().also { it.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON) }
     protected val headsUpManager: HeadsUpManager = mock()
     protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider =
         mock()
     protected val keyguardStateController = FakeKeyguardStateController(leakCheck)
-    protected val logger: NotificationInterruptLogger = mock()
     protected val mainHandler = FakeHandler(Looper.getMainLooper())
+    protected val newLogger = VisualInterruptionDecisionLogger(fakeLogBuffer)
+    protected val oldLogger = NotificationInterruptLogger(fakeLogBuffer)
     protected val powerManager: PowerManager = mock()
     protected val statusBarStateController = FakeStatusBarStateController()
     protected val systemClock = FakeSystemClock()
@@ -116,14 +137,9 @@
 
     @Before
     fun setUp() {
-        globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON)
-
         val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
         userTracker.set(listOf(user), /* currentUserIndex = */ 0)
 
-        whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
-            .thenReturn(false)
-
         provider.start()
     }
 
@@ -203,24 +219,64 @@
     }
 
     @Test
-    fun testShouldPeek_notQuiteOldEnoughWhen() {
+    fun testShouldPeek_oldWhen_now() {
+        ensurePeekState()
+        assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(0) })
+    }
+
+    @Test
+    fun testShouldPeek_oldWhen_notOldEnough() {
         ensurePeekState()
         assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS - 1) })
     }
 
     @Test
-    fun testShouldPeek_zeroWhen() {
+    fun testShouldPeek_oldWhen_zeroWhen() {
         ensurePeekState()
         assertShouldHeadsUp(buildPeekEntry { whenMs = 0L })
     }
 
     @Test
-    fun testShouldPeek_oldWhenButFsi() {
+    fun testShouldPeek_oldWhen_negativeWhen() {
+        ensurePeekState()
+        assertShouldHeadsUp(buildPeekEntry { whenMs = -1L })
+    }
+
+    @Test
+    fun testShouldPeek_oldWhen_fullScreenIntent() {
         ensurePeekState()
         assertShouldHeadsUp(buildFsiEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) })
     }
 
     @Test
+    fun testShouldPeek_oldWhen_foregroundService() {
+        ensurePeekState()
+        assertShouldHeadsUp(
+            buildPeekEntry {
+                whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS)
+                isForegroundService = true
+            }
+        )
+    }
+
+    @Test
+    fun testShouldPeek_oldWhen_userInitiatedJob() {
+        ensurePeekState()
+        assertShouldHeadsUp(
+            buildPeekEntry {
+                whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS)
+                isUserInitiatedJob = true
+            }
+        )
+    }
+
+    @Test
+    fun testShouldNotPeek_hiddenOnKeyguard() {
+        ensurePeekState({ keyguardShouldHideNotification = true })
+        assertShouldNotHeadsUp(buildPeekEntry())
+    }
+
+    @Test
     fun testShouldPeek_defaultLegacySuppressor() {
         ensurePeekState()
         withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPeekEntry()) }
@@ -257,36 +313,6 @@
     }
 
     @Test
-    fun testShouldPulse_defaultLegacySuppressor() {
-        ensurePulseState()
-        withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) }
-    }
-
-    @Test
-    fun testShouldNotPulse_legacySuppressInterruptions() {
-        ensurePulseState()
-        withLegacySuppressor(alwaysSuppressesInterruptions) {
-            assertShouldNotHeadsUp(buildPulseEntry())
-        }
-    }
-
-    @Test
-    fun testShouldPulse_legacySuppressAwakeInterruptions() {
-        ensurePulseState()
-        withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
-            assertShouldHeadsUp(buildPulseEntry())
-        }
-    }
-
-    @Test
-    fun testShouldPulse_legacySuppressAwakeHeadsUp() {
-        ensurePulseState()
-        withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) {
-            assertShouldHeadsUp(buildPulseEntry())
-        }
-    }
-
-    @Test
     fun testShouldNotPulse_disabled() {
         ensurePulseState { pulseOnNotificationsEnabled = false }
         assertShouldNotHeadsUp(buildPulseEntry())
@@ -318,6 +344,42 @@
         assertShouldNotHeadsUp(buildPulseEntry { importance = IMPORTANCE_LOW })
     }
 
+    @Test
+    fun testShouldNotPulse_hiddenOnKeyguard() {
+        ensurePulseState({ keyguardShouldHideNotification = true })
+        assertShouldNotHeadsUp(buildPulseEntry())
+    }
+
+    @Test
+    fun testShouldPulse_defaultLegacySuppressor() {
+        ensurePulseState()
+        withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) }
+    }
+
+    @Test
+    fun testShouldNotPulse_legacySuppressInterruptions() {
+        ensurePulseState()
+        withLegacySuppressor(alwaysSuppressesInterruptions) {
+            assertShouldNotHeadsUp(buildPulseEntry())
+        }
+    }
+
+    @Test
+    fun testShouldPulse_legacySuppressAwakeInterruptions() {
+        ensurePulseState()
+        withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
+            assertShouldHeadsUp(buildPulseEntry())
+        }
+    }
+
+    @Test
+    fun testShouldPulse_legacySuppressAwakeHeadsUp() {
+        ensurePulseState()
+        withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) {
+            assertShouldHeadsUp(buildPulseEntry())
+        }
+    }
+
     private fun withPeekAndPulseEntry(
         extendEntry: EntryBuilder.() -> Unit,
         block: (NotificationEntry) -> Unit
@@ -330,73 +392,7 @@
     }
 
     @Test
-    fun testShouldHeadsUp_groupedSummaryNotif_groupAlertAll() {
-        withPeekAndPulseEntry({
-            isGrouped = true
-            isGroupSummary = true
-            groupAlertBehavior = GROUP_ALERT_ALL
-        }) {
-            assertShouldHeadsUp(it)
-        }
-    }
-
-    @Test
-    fun testShouldHeadsUp_groupedSummaryNotif_groupAlertSummary() {
-        withPeekAndPulseEntry({
-            isGrouped = true
-            isGroupSummary = true
-            groupAlertBehavior = GROUP_ALERT_SUMMARY
-        }) {
-            assertShouldHeadsUp(it)
-        }
-    }
-
-    @Test
-    fun testShouldNotHeadsUp_groupedSummaryNotif_groupAlertChildren() {
-        withPeekAndPulseEntry({
-            isGrouped = true
-            isGroupSummary = true
-            groupAlertBehavior = GROUP_ALERT_CHILDREN
-        }) {
-            assertShouldNotHeadsUp(it)
-        }
-    }
-
-    @Test
-    fun testShouldHeadsUp_ungroupedSummaryNotif_groupAlertChildren() {
-        withPeekAndPulseEntry({
-            isGrouped = false
-            isGroupSummary = true
-            groupAlertBehavior = GROUP_ALERT_CHILDREN
-        }) {
-            assertShouldHeadsUp(it)
-        }
-    }
-
-    @Test
-    fun testShouldHeadsUp_groupedChildNotif_groupAlertAll() {
-        withPeekAndPulseEntry({
-            isGrouped = true
-            isGroupSummary = false
-            groupAlertBehavior = GROUP_ALERT_ALL
-        }) {
-            assertShouldHeadsUp(it)
-        }
-    }
-
-    @Test
-    fun testShouldHeadsUp_groupedChildNotif_groupAlertChildren() {
-        withPeekAndPulseEntry({
-            isGrouped = true
-            isGroupSummary = false
-            groupAlertBehavior = GROUP_ALERT_CHILDREN
-        }) {
-            assertShouldHeadsUp(it)
-        }
-    }
-
-    @Test
-    fun testShouldNotHeadsUp_groupedChildNotif_groupAlertSummary() {
+    fun testShouldNotHeadsUp_suppressiveGroupAlertBehavior() {
         withPeekAndPulseEntry({
             isGrouped = true
             isGroupSummary = false
@@ -407,7 +403,18 @@
     }
 
     @Test
-    fun testShouldHeadsUp_ungroupedChildNotif_groupAlertSummary() {
+    fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notSuppressive() {
+        withPeekAndPulseEntry({
+            isGrouped = true
+            isGroupSummary = false
+            groupAlertBehavior = GROUP_ALERT_CHILDREN
+        }) {
+            assertShouldHeadsUp(it)
+        }
+    }
+
+    @Test
+    fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notGrouped() {
         withPeekAndPulseEntry({
             isGrouped = false
             isGroupSummary = false
@@ -435,18 +442,41 @@
     }
 
     @Test
-    fun testShouldNotBubble_notAllowed() {
+    fun testShouldBubble_suppressiveGroupAlertBehavior() {
         ensureBubbleState()
-        assertShouldNotBubble(buildBubbleEntry { canBubble = false })
+        assertShouldBubble(
+            buildBubbleEntry {
+                isGrouped = true
+                isGroupSummary = false
+                groupAlertBehavior = GROUP_ALERT_SUMMARY
+            }
+        )
     }
 
     @Test
-    fun testShouldNotBubble_noBubbleMetadata() {
+    fun testShouldNotBubble_notABubble() {
+        ensureBubbleState()
+        assertShouldNotBubble(
+            buildBubbleEntry {
+                isBubble = false
+                hasBubbleMetadata = false
+            }
+        )
+    }
+
+    @Test
+    fun testShouldNotBubble_missingBubbleMetadata() {
         ensureBubbleState()
         assertShouldNotBubble(buildBubbleEntry { hasBubbleMetadata = false })
     }
 
     @Test
+    fun testShouldNotBubble_notAllowedToBubble() {
+        ensureBubbleState()
+        assertShouldNotBubble(buildBubbleEntry { canBubble = false })
+    }
+
+    @Test
     fun testShouldBubble_defaultLegacySuppressor() {
         ensureBubbleState()
         withLegacySuppressor(neverSuppresses) { assertShouldBubble(buildBubbleEntry()) }
@@ -477,13 +507,7 @@
     }
 
     @Test
-    fun testShouldNotAlert_hiddenOnKeyguard() {
-        ensurePeekState({ keyguardShouldHideNotification = true })
-        assertShouldNotHeadsUp(buildPeekEntry())
-
-        ensurePulseState({ keyguardShouldHideNotification = true })
-        assertShouldNotHeadsUp(buildPulseEntry())
-
+    fun testShouldNotBubble_hiddenOnKeyguard() {
         ensureBubbleState({ keyguardShouldHideNotification = true })
         assertShouldNotBubble(buildBubbleEntry())
     }
@@ -855,12 +879,12 @@
     }
 
     protected fun assertShouldHeadsUp(entry: NotificationEntry) =
-        provider.makeUnloggedHeadsUpDecision(entry).let {
+        provider.makeAndLogHeadsUpDecision(entry).let {
             assertTrue("unexpected suppressed HUN: ${it.logReason}", it.shouldInterrupt)
         }
 
     protected fun assertShouldNotHeadsUp(entry: NotificationEntry) =
-        provider.makeUnloggedHeadsUpDecision(entry).let {
+        provider.makeAndLogHeadsUpDecision(entry).let {
             assertFalse("unexpected unsuppressed HUN: ${it.logReason}", it.shouldInterrupt)
         }
 
@@ -876,6 +900,7 @@
 
     protected fun assertShouldFsi(entry: NotificationEntry) =
         provider.makeUnloggedFullScreenIntentDecision(entry).let {
+            provider.logFullScreenIntentDecision(it)
             assertTrue("unexpected suppressed FSI: ${it.logReason}", it.shouldInterrupt)
         }
 
@@ -884,10 +909,11 @@
         expectWouldInterruptWithoutDnd: Boolean? = null
     ) =
         provider.makeUnloggedFullScreenIntentDecision(entry).let {
+            provider.logFullScreenIntentDecision(it)
             assertFalse("unexpected unsuppressed FSI: ${it.logReason}", it.shouldInterrupt)
             if (expectWouldInterruptWithoutDnd != null) {
                 assertEquals(
-                    "unexpected unsuppressed-without-DND FSI: ${it.logReason}",
+                    "unexpected wouldInterruptWithoutDnd for FSI: ${it.logReason}",
                     expectWouldInterruptWithoutDnd,
                     it.wouldInterruptWithoutDnd
                 )
@@ -895,22 +921,35 @@
         }
 
     protected class EntryBuilder(val context: Context) {
-        var importance = IMPORTANCE_DEFAULT
-        var suppressedVisualEffects: Int? = null
-        var whenMs: Long? = null
-        var visibilityOverride: Int? = null
-        var hasFsi = false
-        var canBubble: Boolean? = null
-        var isBubble = false
-        var hasBubbleMetadata = false
+        // Set on BubbleMetadata:
         var bubbleIsShortcut = false
-        var bubbleSuppressesNotification: Boolean? = null
+        var bubbleSuppressesNotification = false
+
+        // Set on Notification.Builder:
+        var whenMs: Long? = null
         var isGrouped = false
-        var isGroupSummary: Boolean? = null
+        var isGroupSummary = false
         var groupAlertBehavior: Int? = null
-        var hasJustLaunchedFsi = false
+        var hasBubbleMetadata = false
+        var hasFsi = false
+
+        // Set on Notification:
+        var isForegroundService = false
+        var isUserInitiatedJob = false
+        var isBubble = false
         var isStickyAndNotDemoted = false
-        var packageSuspended: Boolean? = null
+
+        // Set on NotificationEntryBuilder:
+        var importance = IMPORTANCE_DEFAULT
+        var canBubble: Boolean? = null
+
+        // Set on NotificationEntry:
+        var hasJustLaunchedFsi = false
+
+        // Set on ModifiedRankingBuilder:
+        var packageSuspended = false
+        var visibilityOverride: Int? = null
+        var suppressedVisualEffects: Int? = null
 
         private fun buildBubbleMetadata(): BubbleMetadata {
             val builder =
@@ -928,71 +967,87 @@
                     )
                 }
 
-            bubbleSuppressesNotification?.let { builder.setSuppressNotification(it) }
+            if (bubbleSuppressesNotification) {
+                builder.setSuppressNotification(true)
+            }
 
             return builder.build()
         }
 
         fun build() =
             Notification.Builder(context, TEST_CHANNEL_ID)
-                .apply {
-                    setContentTitle(TEST_CONTENT_TITLE)
-                    setContentText(TEST_CONTENT_TEXT)
+                .also { nb ->
+                    nb.setContentTitle(TEST_CONTENT_TITLE)
+                    nb.setContentText(TEST_CONTENT_TEXT)
 
-                    if (hasFsi) {
-                        setFullScreenIntent(mock(), /* highPriority = */ true)
-                    }
-
-                    whenMs?.let { setWhen(it) }
-
-                    if (hasBubbleMetadata) {
-                        setBubbleMetadata(buildBubbleMetadata())
-                    }
+                    whenMs?.let { nb.setWhen(it) }
 
                     if (isGrouped) {
-                        setGroup(TEST_GROUP_KEY)
+                        nb.setGroup(TEST_GROUP_KEY)
                     }
 
-                    isGroupSummary?.let { setGroupSummary(it) }
+                    if (isGroupSummary) {
+                        nb.setGroupSummary(true)
+                    }
 
-                    groupAlertBehavior?.let { setGroupAlertBehavior(it) }
+                    groupAlertBehavior?.let { nb.setGroupAlertBehavior(it) }
+
+                    if (hasBubbleMetadata) {
+                        nb.setBubbleMetadata(buildBubbleMetadata())
+                    }
+
+                    if (hasFsi) {
+                        nb.setFullScreenIntent(mock(), /* highPriority = */ true)
+                    }
                 }
                 .build()
-                .apply {
+                .also { n ->
+                    if (isForegroundService) {
+                        n.flags = n.flags or FLAG_FOREGROUND_SERVICE
+                    }
+
+                    if (isUserInitiatedJob) {
+                        n.flags = n.flags or FLAG_USER_INITIATED_JOB
+                    }
+
                     if (isBubble) {
-                        flags = flags or FLAG_BUBBLE
+                        n.flags = n.flags or FLAG_BUBBLE
                     }
 
                     if (isStickyAndNotDemoted) {
-                        flags = flags or FLAG_FSI_REQUESTED_BUT_DENIED
+                        n.flags = n.flags or FLAG_FSI_REQUESTED_BUT_DENIED
                     }
                 }
                 .let { NotificationEntryBuilder().setNotification(it) }
-                .apply {
-                    setPkg(TEST_PACKAGE)
-                    setOpPkg(TEST_PACKAGE)
-                    setTag(TEST_TAG)
+                .also { neb ->
+                    neb.setPkg(TEST_PACKAGE)
+                    neb.setOpPkg(TEST_PACKAGE)
+                    neb.setTag(TEST_TAG)
 
-                    setImportance(importance)
-                    setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance))
+                    neb.setImportance(importance)
+                    neb.setChannel(
+                        NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
+                    )
 
-                    canBubble?.let { setCanBubble(it) }
+                    canBubble?.let { neb.setCanBubble(it) }
                 }
                 .build()!!
-                .also {
+                .also { ne ->
                     if (hasJustLaunchedFsi) {
-                        it.notifyFullScreenIntentLaunched()
+                        ne.notifyFullScreenIntentLaunched()
                     }
 
                     if (isStickyAndNotDemoted) {
-                        assertFalse(it.isDemoted)
+                        assertFalse(ne.isDemoted)
                     }
 
-                    modifyRanking(it)
-                        .apply {
-                            suppressedVisualEffects?.let { setSuppressedVisualEffects(it) }
-                            visibilityOverride?.let { setVisibilityOverride(it) }
-                            packageSuspended?.let { setSuspended(it) }
+                    modifyRanking(ne)
+                        .also { mrb ->
+                            if (packageSuspended) {
+                                mrb.setSuspended(true)
+                            }
+                            visibilityOverride?.let { mrb.setVisibilityOverride(it) }
+                            suppressedVisualEffects?.let { mrb.setSuppressedVisualEffects(it) }
                         }
                         .build()
                 }
@@ -1013,6 +1068,7 @@
     }
 
     protected fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+        isBubble = true
         canBubble = true
         hasBubbleMetadata = true
         run(block)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 86a5c52..027c11c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -121,6 +121,8 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shade.CameraLauncher;
@@ -329,6 +331,8 @@
     private final DumpManager mDumpManager = new DumpManager();
     private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
 
+    private final SceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
+
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -555,7 +559,8 @@
                 mAlternateBouncerInteractor,
                 mUserTracker,
                 () -> mFingerprintManager,
-                mActivityStarter
+                mActivityStarter,
+                mSceneContainerFlags
         );
         mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
         mCentralSurfaces.initShadeVisibilityListener();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
index 0a68406..f71114d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
@@ -16,7 +16,14 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -26,6 +33,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.FakeDisplayTracker;
@@ -81,4 +89,39 @@
         verify(mApplier).applyDarkIntensity(eq(0f));
     }
 
+    @Test
+    public void gestureNav_noForceNavButtons_expectNotSupportsIconTint() {
+        GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class);
+        doReturn(false).when(observer).areNavigationButtonForcedVisible();
+        mLightBarTransitionsController.setNavigationSettingsObserver(observer);
+        assertFalse(mLightBarTransitionsController.supportsIconTintForNavMode(
+                NAV_BAR_MODE_GESTURAL));
+    }
+
+    @Test
+    public void gestureNav_forceNavButtons_expectSupportsIconTint() {
+        GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class);
+        doReturn(true).when(observer).areNavigationButtonForcedVisible();
+        mLightBarTransitionsController.setNavigationSettingsObserver(observer);
+        assertTrue(mLightBarTransitionsController.supportsIconTintForNavMode(
+                NAV_BAR_MODE_GESTURAL));
+    }
+
+    @Test
+    public void buttonNav_noForceNavButtons_expectNotSupportsIconTint() {
+        GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class);
+        doReturn(false).when(observer).areNavigationButtonForcedVisible();
+        mLightBarTransitionsController.setNavigationSettingsObserver(observer);
+        assertTrue(mLightBarTransitionsController.supportsIconTintForNavMode(
+                NAV_BAR_MODE_3BUTTON));
+    }
+
+    @Test
+    public void buttonNav_forceNavButtons_expectSupportsIconTint() {
+        GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class);
+        doReturn(true).when(observer).areNavigationButtonForcedVisible();
+        mLightBarTransitionsController.setNavigationSettingsObserver(observer);
+        assertTrue(mLightBarTransitionsController.supportsIconTintForNavMode(
+                NAV_BAR_MODE_3BUTTON));
+    }
 }
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 9aafee4..6a0375d 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
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.phone
 
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import android.os.Handler
 import android.os.PowerManager
 import android.testing.AndroidTestingRunner
@@ -82,14 +80,9 @@
     @Mock
     private lateinit var handler: Handler
 
-    private lateinit var featureFlags: FakeFeatureFlags
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        featureFlags = FakeFeatureFlags().apply {
-            set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
-        }
         controller = UnlockedScreenOffAnimationController(
                 context,
                 wakefulnessLifecycle,
@@ -102,7 +95,6 @@
                 interactionJankMonitor,
                 powerManager,
                 handler = handler,
-                featureFlags,
         )
         controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index d1b9b8a..0b87fe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -58,6 +58,7 @@
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
@@ -703,6 +704,7 @@
                 mKeyguardStateController,
                 mShadeViewController,
                 mStatusBarStateController,
+                mock(StatusBarIconViewBindingFailureTracker.class),
                 mCommandQueue,
                 mCarrierConfigTracker,
                 new CollapsedStatusBarFragmentLogger(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
index d33806e..1250228 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.policy
 
-import com.android.systemui.flags.FakeFeatureFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.ViewUtils
@@ -78,13 +77,11 @@
     private lateinit var view: FrameLayout
     private lateinit var testableLooper: TestableLooper
     private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController
-    private lateinit var featureFlags: FakeFeatureFlags
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
-        featureFlags = FakeFeatureFlags()
 
         view = LayoutInflater.from(context)
                 .inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout
@@ -101,7 +98,6 @@
                 dozeParameters,
                 screenOffAnimationController,
                 userSwitchDialogController,
-                featureFlags,
                 uiEventLogger)
 
         ViewUtils.attachView(view)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt
new file mode 100644
index 0000000..004f679
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.policy.data.repository
+
+import android.app.NotificationManager
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ZenModeRepositoryImplTest : SysuiTestCase() {
+    @Mock lateinit var zenModeController: ZenModeController
+
+    lateinit var underTest: ZenModeRepositoryImpl
+
+    private val testPolicy = NotificationManager.Policy(0, 1, 0)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest = ZenModeRepositoryImpl(zenModeController)
+    }
+
+    @Test
+    fun zenMode_reflectsCurrentControllerState() = runTest {
+        whenever(zenModeController.zen).thenReturn(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+        val zenMode by collectLastValue(underTest.zenMode)
+        assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+    }
+
+    @Test
+    fun zenMode_updatesWhenControllerStateChanges() = runTest {
+        val zenMode by collectLastValue(underTest.zenMode)
+        runCurrent()
+        whenever(zenModeController.zen).thenReturn(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+        withArgCaptor { Mockito.verify(zenModeController).addCallback(capture()) }
+            .onZenChanged(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+        assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+    }
+
+    @Test
+    fun policy_reflectsCurrentControllerState() {
+        runTest {
+            whenever(zenModeController.consolidatedPolicy).thenReturn(testPolicy)
+            val policy by collectLastValue(underTest.consolidatedNotificationPolicy)
+            assertThat(policy).isEqualTo(testPolicy)
+        }
+    }
+
+    @Test
+    fun policy_updatesWhenControllerStateChanges() = runTest {
+        val policy by collectLastValue(underTest.consolidatedNotificationPolicy)
+        runCurrent()
+        whenever(zenModeController.consolidatedPolicy).thenReturn(testPolicy)
+        withArgCaptor { Mockito.verify(zenModeController).addCallback(capture()) }
+            .onConsolidatedPolicyChanged(testPolicy)
+        assertThat(policy).isEqualTo(testPolicy)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
new file mode 100644
index 0000000..78e7971
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -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.systemui.statusbar.policy.domain.interactor
+
+import android.app.NotificationManager.Policy
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.SysUITestComponent
+import com.android.SysUITestModule
+import com.android.collectLastValue
+import com.android.runCurrent
+import com.android.runTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository
+import com.android.systemui.user.domain.UserDomainLayerModule
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import org.junit.Test
+
+@SmallTest
+class ZenModeInteractorTest : SysuiTestCase() {
+    @SysUISingleton
+    @Component(
+        modules =
+            [
+                SysUITestModule::class,
+                UserDomainLayerModule::class,
+            ]
+    )
+    interface TestComponent : SysUITestComponent<ZenModeInteractor> {
+
+        val repository: FakeZenModeRepository
+
+        @Component.Factory
+        interface Factory {
+            fun create(@BindsInstance test: SysuiTestCase): TestComponent
+        }
+    }
+
+    private val testComponent: TestComponent =
+        DaggerZenModeInteractorTest_TestComponent.factory().create(test = this)
+
+    @Test
+    fun testIsZenModeEnabled_off() =
+        testComponent.runTest {
+            val enabled by collectLastValue(underTest.isZenModeEnabled)
+
+            repository.zenMode.value = Settings.Global.ZEN_MODE_OFF
+            runCurrent()
+
+            assertThat(enabled).isFalse()
+        }
+
+    @Test
+    fun testIsZenModeEnabled_alarms() =
+        testComponent.runTest {
+            val enabled by collectLastValue(underTest.isZenModeEnabled)
+
+            repository.zenMode.value = Settings.Global.ZEN_MODE_ALARMS
+            runCurrent()
+
+            assertThat(enabled).isTrue()
+        }
+
+    @Test
+    fun testIsZenModeEnabled_importantInterruptions() =
+        testComponent.runTest {
+            val enabled by collectLastValue(underTest.isZenModeEnabled)
+
+            repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+            runCurrent()
+
+            assertThat(enabled).isTrue()
+        }
+
+    @Test
+    fun testIsZenModeEnabled_noInterruptions() =
+        testComponent.runTest {
+            val enabled by collectLastValue(underTest.isZenModeEnabled)
+
+            repository.zenMode.value = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+            runCurrent()
+
+            assertThat(enabled).isTrue()
+        }
+
+    @Test
+    fun testIsZenModeEnabled_unknown() =
+        testComponent.runTest {
+            val enabled by collectLastValue(underTest.isZenModeEnabled)
+
+            repository.zenMode.value = 4 // this should fail if we ever add another zen mode type
+            runCurrent()
+
+            assertThat(enabled).isFalse()
+        }
+
+    @Test
+    fun testAreNotificationsHiddenInShade_noPolicy() =
+        testComponent.runTest {
+            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+            repository.consolidatedNotificationPolicy.value = null
+            repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+            runCurrent()
+
+            assertThat(hidden).isFalse()
+        }
+
+    @Test
+    fun testAreNotificationsHiddenInShade_zenOffShadeSuppressed() =
+        testComponent.runTest {
+            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+            repository.consolidatedNotificationPolicy.value =
+                policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+            repository.zenMode.value = Settings.Global.ZEN_MODE_OFF
+            runCurrent()
+
+            assertThat(hidden).isFalse()
+        }
+
+    @Test
+    fun testAreNotificationsHiddenInShade_zenOnShadeNotSuppressed() =
+        testComponent.runTest {
+            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+            repository.consolidatedNotificationPolicy.value =
+                policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR)
+            repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+            runCurrent()
+
+            assertThat(hidden).isFalse()
+        }
+
+    @Test
+    fun testAreNotificationsHiddenInShade_zenOnShadeSuppressed() =
+        testComponent.runTest {
+            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+            repository.consolidatedNotificationPolicy.value =
+                policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+            repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+            runCurrent()
+
+            assertThat(hidden).isTrue()
+        }
+}
+
+fun policyWithSuppressedVisualEffects(suppressedVisualEffects: Int) =
+    Policy(
+        /* priorityCategories = */ 0,
+        /* priorityCallSenders = */ 0,
+        /* priorityMessageSenders = */ 0,
+        /* suppressedVisualEffects = */ suppressedVisualEffects
+    )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 1e7e184..1466d24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -21,7 +21,6 @@
 import android.os.VibrationEffect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
@@ -33,7 +32,6 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId
 import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.ContentDescription
@@ -42,9 +40,8 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.temporarydisplay.TemporaryViewUiEvent
@@ -94,7 +91,6 @@
     private lateinit var fakeExecutor: FakeExecutor
     private lateinit var uiEventLoggerFake: UiEventLoggerFake
     private lateinit var uiEventLogger: TemporaryViewUiEventLogger
-    private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setUp() {
@@ -131,10 +127,8 @@
                 fakeWakeLockBuilder,
                 fakeClock,
                 uiEventLogger,
-                featureFlags
             )
         underTest.start()
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
     @Test
@@ -494,23 +488,6 @@
             )
     }
 
-    @Test
-    fun displayView_oneWayHapticsEnabled_usesPerformHapticFeedback() {
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
-        val constant: Int = HapticFeedbackConstants.CONFIRM
-        underTest.displayView(
-            createChipbarInfo(
-                Icon.Resource(R.id.check_box, null),
-                Text.Loaded("text"),
-                endItem = null,
-                vibrationEffect = null,
-                vibrationConstant = constant
-            )
-        )
-
-        verify(vibratorHelper).performHapticFeedback(any(), eq(constant))
-    }
-
     /** Regression test for b/266119467. */
     @Test
     fun displayView_animationFailure_viewsStillBecomeVisible() {
@@ -729,14 +706,12 @@
         endItem: ChipbarEndItem?,
         vibrationEffect: VibrationEffect? = null,
         allowSwipeToDismiss: Boolean = false,
-        vibrationConstant: Int = HapticFeedbackConstants.NO_HAPTICS,
     ): ChipbarInfo {
         return ChipbarInfo(
             TintedIcon(startIcon, tint = null),
             text,
             endItem,
             vibrationEffect,
-            vibrationConstant,
             allowSwipeToDismiss,
             windowTitle = WINDOW_TITLE,
             wakeReason = WAKE_REASON,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index 6e9363b..af1930e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -60,6 +60,10 @@
 
     override val minPatternLength: Int = 4
 
+    private val _isPinEnhancedPrivacyEnabled = MutableStateFlow(false)
+    override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> =
+        _isPinEnhancedPrivacyEnabled.asStateFlow()
+
     private var failedAttemptCount = 0
     private var throttlingEndTimestamp = 0L
     private var credentialOverride: List<Any>? = null
@@ -138,6 +142,10 @@
         }
     }
 
+    fun setPinEnhancedPrivacyEnabled(isEnabled: Boolean) {
+        _isPinEnhancedPrivacyEnabled.value = isEnabled
+    }
+
     private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
         return when (val credentialType = getCurrentCredentialType(securityMode)) {
             LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
new file mode 100644
index 0000000..2902c3f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.ui.adapter
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeQSSceneAdapter(
+    private val inflateDelegate: suspend (Context, ViewGroup?) -> View,
+) : QSSceneAdapter {
+    private val _customizing = MutableStateFlow(false)
+    override val isCustomizing: StateFlow<Boolean> = _customizing.asStateFlow()
+
+    private val _view = MutableStateFlow<View?>(null)
+    override val qsView: Flow<View> = _view.filterNotNull()
+
+    private val _state = MutableStateFlow<QSSceneAdapter.State?>(null)
+    val state = _state.filterNotNull()
+
+    override suspend fun inflate(context: Context, parent: ViewGroup?) {
+        _view.value = inflateDelegate(context, parent)
+    }
+
+    override fun setState(state: QSSceneAdapter.State) {
+        if (_view.value != null) {
+            _state.value = state
+        }
+    }
+
+    fun setCustomizing(value: Boolean) {
+        _customizing.value = value
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt
index 5aece1b..16dab40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt
@@ -16,7 +16,14 @@
 package com.android.systemui.statusbar.policy.data
 
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepositoryModule
+import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepositoryModule
 import dagger.Module
 
-@Module(includes = [FakeDeviceProvisioningRepositoryModule::class])
+@Module(
+    includes =
+        [
+            FakeDeviceProvisioningRepositoryModule::class,
+            FakeZenModeRepositoryModule::class,
+        ]
+)
 object FakeStatusBarPolicyDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt
new file mode 100644
index 0000000..4059930
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.policy.data.repository
+
+import android.app.NotificationManager
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class FakeZenModeRepository @Inject constructor() : ZenModeRepository {
+    override val zenMode: MutableStateFlow<Int> = MutableStateFlow(Settings.Global.ZEN_MODE_OFF)
+    override val consolidatedNotificationPolicy: MutableStateFlow<NotificationManager.Policy?> =
+        MutableStateFlow(
+            NotificationManager.Policy(
+                /* priorityCategories = */ 0,
+                /* priorityCallSenders = */ 0,
+                /* priorityMessageSenders = */ 0,
+            )
+        )
+}
+
+@Module
+interface FakeZenModeRepositoryModule {
+    @Binds fun bindFake(fake: FakeZenModeRepository): ZenModeRepository
+}
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 4c224fb..fb521e1 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -359,7 +359,7 @@
     }
 
     @Test
-    public void testUpdateWallpaperComponent_doesApplyLater() throws IOException {
+    public void testUpdateWallpaperComponent_systemAndLock() throws IOException {
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
                 /* which */ FLAG_LOCK | FLAG_SYSTEM);
@@ -377,7 +377,7 @@
     }
 
     @Test
-    public void testUpdateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen()
+    public void testUpdateWallpaperComponent_systemOnly()
             throws IOException {
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
 
@@ -617,7 +617,7 @@
 
         mWallpaperBackupAgent.onRestoreFinished();
 
-        // wallpaper will be applied to home & lock screen, a success for both screens in expected
+        // wallpaper will be applied to home & lock screen, a success for both screens is expected
         DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
                 mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
         assertThat(result).isNotNull();
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 6d82b74..f73b00c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -26,7 +26,6 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 import static android.view.accessibility.AccessibilityInteractionClient.CALL_STACK;
 import static android.view.accessibility.AccessibilityInteractionClient.IGNORE_CALL_STACK;
@@ -69,7 +68,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.provider.Settings;
 import android.util.Pair;
 import android.util.Slog;
@@ -93,7 +91,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
-import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
@@ -101,7 +98,6 @@
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
 import com.android.server.accessibility.magnification.MagnificationProcessor;
-import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
@@ -1993,20 +1989,7 @@
         }
     }
 
-    private void createImeSessionInternal() {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
-            try {
-                if (svcClientTracingEnabled()) {
-                    logTraceSvcClient("createImeSession", "");
-                }
-                AccessibilityCallback callback = new AccessibilityCallback();
-                listener.createImeSession(callback);
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG,
-                        "Error requesting IME session from " + mService, re);
-            }
-        }
+    protected void createImeSessionInternal() {
     }
 
     private void setImeSessionEnabledInternal(IAccessibilityInputMethodSession session,
@@ -2658,21 +2641,6 @@
         }
     }
 
-    private static final class AccessibilityCallback
-            extends IAccessibilityInputMethodSessionCallback.Stub {
-        @Override
-        public void sessionCreated(IAccessibilityInputMethodSession session, int id) {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AACS.sessionCreated");
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                InputMethodManagerInternal.get().onSessionForAccessibilityCreated(id, session);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
-    }
-
     @Override
     public void attachAccessibilityOverlayToDisplay(
             int interactionId,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index bb50a99..b5e8c84 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5378,19 +5378,37 @@
 
     @Override
     public void requestImeLocked(AbstractAccessibilityServiceConnection connection) {
+        if (!(connection instanceof AccessibilityServiceConnection)
+                || (connection instanceof ProxyAccessibilityServiceConnection)) {
+            if (DEBUG) {
+                Slog.d(LOG_TAG, "The connection should be a real connection but was "
+                        + connection);
+            }
+            return;
+        }
+        AccessibilityServiceConnection realConnection = (AccessibilityServiceConnection) connection;
         mMainHandler.sendMessage(obtainMessage(
-                AccessibilityManagerService::createSessionForConnection, this, connection));
+                AccessibilityManagerService::createSessionForConnection, this, realConnection));
         mMainHandler.sendMessage(obtainMessage(
-                AccessibilityManagerService::bindAndStartInputForConnection, this, connection));
+                AccessibilityManagerService::bindAndStartInputForConnection, this, realConnection));
     }
 
     @Override
     public void unbindImeLocked(AbstractAccessibilityServiceConnection connection) {
+        if (!(connection instanceof AccessibilityServiceConnection)
+                || (connection instanceof ProxyAccessibilityServiceConnection)) {
+            if (DEBUG) {
+                Slog.d(LOG_TAG, "The connection should be a real connection but was "
+                        + connection);
+            }
+            return;
+        }
+        AccessibilityServiceConnection realConnection = (AccessibilityServiceConnection) connection;
         mMainHandler.sendMessage(obtainMessage(
-                AccessibilityManagerService::unbindInputForConnection, this, connection));
+                AccessibilityManagerService::unbindInputForConnection, this, realConnection));
     }
 
-    private void createSessionForConnection(AbstractAccessibilityServiceConnection connection) {
+    private void createSessionForConnection(AccessibilityServiceConnection connection) {
         synchronized (mLock) {
             if (mInputSessionRequested) {
                 connection.createImeSessionLocked();
@@ -5398,7 +5416,7 @@
         }
     }
 
-    private void bindAndStartInputForConnection(AbstractAccessibilityServiceConnection connection) {
+    private void bindAndStartInputForConnection(AccessibilityServiceConnection connection) {
         synchronized (mLock) {
             if (mInputBound) {
                 connection.bindInputLocked();
@@ -5407,8 +5425,9 @@
         }
     }
 
-    private void unbindInputForConnection(AbstractAccessibilityServiceConnection connection) {
-        InputMethodManagerInternal.get().unbindAccessibilityFromCurrentClient(connection.mId);
+    private void unbindInputForConnection(AccessibilityServiceConnection connection) {
+        InputMethodManagerInternal.get()
+                .unbindAccessibilityFromCurrentClient(connection.mId, connection.mUserId);
         synchronized (mLock) {
             connection.unbindInputLocked();
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 7a2a602..40ca694 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -18,6 +18,7 @@
 
 import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_UNKNOWN;
 import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
@@ -27,6 +28,8 @@
 import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.TouchInteractionController;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -38,12 +41,15 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 import android.view.Display;
 import android.view.MotionEvent;
 
+import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -70,13 +76,38 @@
      Having the reference be null when being called is a very bad sign, but we check the condition.
     */
     final WeakReference<AccessibilityUserState> mUserStateWeakReference;
+    @UserIdInt
+    final int mUserId;
     final Intent mIntent;
     final ActivityTaskManagerInternal mActivityTaskManagerService;
 
     private final Handler mMainHandler;
 
-    AccessibilityServiceConnection(AccessibilityUserState userState, Context context,
-            ComponentName componentName,
+    private static final class AccessibilityInputMethodSessionCallback
+            extends IAccessibilityInputMethodSessionCallback.Stub {
+        @UserIdInt
+        private final int mUserId;
+
+        AccessibilityInputMethodSessionCallback(@UserIdInt int userId) {
+            mUserId = userId;
+        }
+
+        @Override
+        public void sessionCreated(IAccessibilityInputMethodSession session, int id) {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ASC.sessionCreated");
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                InputMethodManagerInternal.get()
+                        .onSessionForAccessibilityCreated(id, session, mUserId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    AccessibilityServiceConnection(@Nullable AccessibilityUserState userState,
+            Context context, ComponentName componentName,
             AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
             Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport,
             AccessibilityTrace trace, WindowManagerInternal windowManagerInternal,
@@ -86,6 +117,10 @@
                 securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerfomer,
                 awm);
         mUserStateWeakReference = new WeakReference<AccessibilityUserState>(userState);
+        // the user ID doesn't matter when userState is null, because it is null only when this is a
+        // ProxyAccessibilityServiceConnection, for which it never creates an IME session and uses
+        // the user ID.
+        mUserId = userState == null ? UserHandle.USER_NULL : userState.mUserId;
         mIntent = new Intent().setComponent(mComponentName);
         mMainHandler = mainHandler;
         mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
@@ -557,6 +592,24 @@
         return mRequestImeApis;
     }
 
+    @Override
+    protected void createImeSessionInternal() {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("createImeSession", "");
+                }
+                AccessibilityInputMethodSessionCallback
+                        callback = new AccessibilityInputMethodSessionCallback(mUserId);
+                listener.createImeSession(callback);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG,
+                        "Error requesting IME session from " + mService, re);
+            }
+        }
+    }
+
     private void notifyMotionEventInternal(MotionEvent event) {
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index fb8db21..550e17b 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -21,7 +21,6 @@
     defaults: ["platform_service_defaults"],
     srcs: [
         ":services.companion-sources",
-        ":VirtualCamera-aidl-sources",
     ],
     libs: [
         "app-compat-annotations",
@@ -30,13 +29,6 @@
     static_libs: [
         "ukey2_jni",
         "virtualdevice_flags_lib",
+        "virtual_camera_service_aidl-java",
     ],
 }
-
-filegroup {
-    name: "VirtualCamera-aidl-sources",
-    srcs: [
-        "java/com/android/server/companion/virtual/camera/*.aidl",
-    ],
-    path: "java",
-}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 2c60893..b9c269c 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -522,7 +522,8 @@
     private void notifyListeners(
             @UserIdInt int userId, @NonNull List<AssociationInfo> associations) {
         mListeners.broadcast((listener, callbackUserId) -> {
-            if ((int) callbackUserId == userId) {
+            int listenerUserId = (int) callbackUserId;
+            if (listenerUserId == userId || listenerUserId == UserHandle.USER_ALL) {
                 try {
                     listener.onAssociationsChanged(associations);
                 } catch (RemoteException ignored) {
@@ -660,6 +661,9 @@
 
             enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
 
+            if (userId == UserHandle.USER_ALL) {
+                return List.copyOf(mAssociationStore.getAssociations());
+            }
             return mAssociationStore.getAssociationsForUser(userId);
         }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index ae8fddf..118943d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -51,7 +51,7 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
-import android.companion.virtual.camera.IVirtualCamera;
+import android.companion.virtual.camera.VirtualCameraConfig;
 import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorEvent;
@@ -277,7 +277,7 @@
                 runningAppsChangedCallback,
                 params,
                 DisplayManagerGlobal.getInstance(),
-                Flags.virtualCamera() ? new VirtualCameraController(context) : null);
+                Flags.virtualCamera() ? new VirtualCameraController() : null);
     }
 
     @VisibleForTesting
@@ -304,7 +304,7 @@
         UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid());
         mContext = context.createContextAsUser(ownerUserHandle, 0);
         mAssociationInfo = associationInfo;
-        mPersistentDeviceId = PERSISTENT_ID_PREFIX_CDM_ASSOCIATION + associationInfo.getId();
+        mPersistentDeviceId = createPersistentDeviceId(associationInfo.getId());
         mService = service;
         mPendingTrampolineCallback = pendingTrampolineCallback;
         mActivityListener = activityListener;
@@ -380,6 +380,10 @@
         return mSensorController;
     }
 
+    static String createPersistentDeviceId(int associationId) {
+        return PERSISTENT_ID_PREFIX_CDM_ASSOCIATION + associationId;
+    }
+
     /**
      * Returns the flags that should be added to any virtual displays created on this virtual
      * device.
@@ -688,7 +692,8 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(),
-                    config.getProductId(), deviceToken, config.getAssociatedDisplayId());
+                    config.getProductId(), deviceToken,
+                    getTargetDisplayIdForInput(config.getAssociatedDisplayId()));
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -706,7 +711,8 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(),
-                    config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
+                    config.getProductId(), deviceToken,
+                    getTargetDisplayIdForInput(config.getAssociatedDisplayId()),
                     config.getLanguageTag(), config.getLayoutType());
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -772,7 +778,8 @@
         try {
             mInputController.createNavigationTouchpad(
                     config.getInputDeviceName(), config.getVendorId(),
-                    config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
+                    config.getProductId(), deviceToken,
+                    getTargetDisplayIdForInput(config.getAssociatedDisplayId()),
                     touchpadHeight, touchpadWidth);
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -950,13 +957,28 @@
         }
     }
 
+    @Override // Binder call
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
-    public void registerVirtualCamera(@NonNull IVirtualCamera camera) {
+    public void registerVirtualCamera(@NonNull VirtualCameraConfig cameraConfig)
+            throws RemoteException {
         super.registerVirtualCamera_enforcePermission();
+        Objects.requireNonNull(cameraConfig);
         if (mVirtualCameraController == null) {
-            return;
+            throw new UnsupportedOperationException("Virtual camera controller is not available");
         }
-        mVirtualCameraController.registerCamera(Objects.requireNonNull(camera));
+        mVirtualCameraController.registerCamera(Objects.requireNonNull(cameraConfig));
+    }
+
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void unregisterVirtualCamera(@NonNull VirtualCameraConfig cameraConfig)
+            throws RemoteException {
+        super.unregisterVirtualCamera_enforcePermission();
+        Objects.requireNonNull(cameraConfig);
+        if (mVirtualCameraController == null) {
+            throw new UnsupportedOperationException("Virtual camera controller is not available");
+        }
+        mVirtualCameraController.unregisterCamera(Objects.requireNonNull(cameraConfig));
     }
 
     @Override
@@ -983,6 +1005,20 @@
         }
     }
 
+    // For display mirroring, we want to dispatch all key events to the source (default) display,
+    // as the virtual display doesn't have any focused windows. Hence, call this for
+    // associating any input device to the source display if the input device emits any key events.
+    private int getTargetDisplayIdForInput(int displayId) {
+        if (!Flags.interactiveScreenMirror()) {
+            return displayId;
+        }
+
+        DisplayManagerInternal displayManager = LocalServices.getService(
+                DisplayManagerInternal.class);
+        int mirroredDisplayId = displayManager.getDisplayIdToMirror(displayId);
+        return mirroredDisplayId == Display.INVALID_DISPLAY ? displayId : mirroredDisplayId;
+    }
+
     @GuardedBy("mVirtualDeviceLock")
     private GenericWindowPolicyController createWindowPolicyControllerLocked(
             @NonNull Set<String> displayCategories) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 85b3c9a..215970e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -27,6 +27,7 @@
 import android.annotation.SuppressLint;
 import android.app.ActivityOptions;
 import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.IVirtualDeviceActivityListener;
@@ -94,6 +95,11 @@
 
     private static final String VIRTUAL_DEVICE_NATIVE_SERVICE = "virtualdevice_native";
 
+    private static final List<String> VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES = Arrays.asList(
+            AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION,
+            AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+            AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING);
+
     private final Object mVirtualDeviceManagerLock = new Object();
     private final VirtualDeviceManagerImpl mImpl;
     private final VirtualDeviceManagerNativeImpl mNativeImpl;
@@ -105,6 +111,9 @@
     private static AtomicInteger sNextUniqueIndex = new AtomicInteger(
             Context.DEVICE_ID_DEFAULT + 1);
 
+    @GuardedBy("mVirtualDeviceManagerLock")
+    private List<AssociationInfo> mActiveAssociations = new ArrayList<>();
+
     private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener =
             new CompanionDeviceManager.OnAssociationsChangedListener() {
                 @Override
@@ -161,6 +170,7 @@
             };
 
     @Override
+    @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
     public void onStart() {
         publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
         if (Flags.enableNativeVdm()) {
@@ -172,6 +182,21 @@
         activityTaskManagerInternal.registerActivityStartInterceptor(
                 VIRTUAL_DEVICE_SERVICE_ORDERED_ID,
                 mActivityInterceptorCallback);
+
+        if (Flags.persistentDeviceIdApi()) {
+            CompanionDeviceManager cdm =
+                    getContext().getSystemService(CompanionDeviceManager.class);
+            if (cdm != null) {
+                synchronized (mVirtualDeviceManagerLock) {
+                    mActiveAssociations = cdm.getAllAssociations(UserHandle.USER_ALL);
+                }
+                cdm.addOnAssociationsChangedListener(getContext().getMainExecutor(),
+                        this::onCdmAssociationsChanged, UserHandle.USER_ALL);
+            } else {
+                Slog.e(TAG, "Failed to find CompanionDeviceManager. No CDM association info "
+                        + " will be available.");
+            }
+        }
     }
 
     void onCameraAccessBlocked(int appUid) {
@@ -264,9 +289,11 @@
         try {
             getContext().sendBroadcastAsUser(i, UserHandle.ALL);
 
-            synchronized (mVirtualDeviceManagerLock) {
-                if (mVirtualDevices.size() == 0) {
-                    unregisterCdmAssociationListener();
+            if (!Flags.persistentDeviceIdApi()) {
+                synchronized (mVirtualDeviceManagerLock) {
+                    if (mVirtualDevices.size() == 0) {
+                        unregisterCdmAssociationListener();
+                    }
                 }
             }
         } finally {
@@ -316,6 +343,45 @@
         cdm.removeOnAssociationsChangedListener(mCdmAssociationListener);
     }
 
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    void onCdmAssociationsChanged(List<AssociationInfo> associations) {
+        Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>();
+        Set<String> removedPersistentDeviceIds = new HashSet<>();
+        synchronized (mVirtualDeviceManagerLock) {
+            Set<Integer> activeAssociationIds = new HashSet<>(associations.size());
+            for (int i = 0; i < associations.size(); ++i) {
+                activeAssociationIds.add(associations.get(i).getId());
+            }
+
+            for (int i = 0; i < mActiveAssociations.size(); ++i) {
+                AssociationInfo associationInfo = mActiveAssociations.get(i);
+                if (!activeAssociationIds.contains(associationInfo.getId())
+                        && VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(
+                                associationInfo.getDeviceProfile())) {
+                    removedPersistentDeviceIds.add(
+                            VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId()));
+                }
+            }
+
+            for (int i = 0; i < mVirtualDevices.size(); i++) {
+                VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
+                if (!activeAssociationIds.contains(virtualDevice.getAssociationId())) {
+                    virtualDevicesToRemove.add(virtualDevice);
+                }
+            }
+
+            mActiveAssociations = associations;
+        }
+
+        for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) {
+            virtualDevice.close();
+        }
+
+        if (!removedPersistentDeviceIds.isEmpty()) {
+            mLocalService.onPersistentDeviceIdsRemoved(removedPersistentDeviceIds);
+        }
+    }
+
     private ArrayList<VirtualDeviceImpl> getVirtualDevicesSnapshot() {
         synchronized (mVirtualDeviceManagerLock) {
             ArrayList<VirtualDeviceImpl> virtualDevices = new ArrayList<>(mVirtualDevices.size());
@@ -393,7 +459,7 @@
             }
 
             synchronized (mVirtualDeviceManagerLock) {
-                if (mVirtualDevices.size() == 0) {
+                if (!Flags.persistentDeviceIdApi() && mVirtualDevices.size() == 0) {
                     final long callindId = Binder.clearCallingIdentity();
                     try {
                         registerCdmAssociationListener();
@@ -623,8 +689,12 @@
 
     private final class LocalService extends VirtualDeviceManagerInternal {
         @GuardedBy("mVirtualDeviceManagerLock")
-        private final ArrayList<AppsOnVirtualDeviceListener>
-                mAppsOnVirtualDeviceListeners = new ArrayList<>();
+        private final ArrayList<AppsOnVirtualDeviceListener> mAppsOnVirtualDeviceListeners =
+                new ArrayList<>();
+        @GuardedBy("mVirtualDeviceManagerLock")
+        private final ArrayList<Consumer<String>> mPersistentDeviceIdRemovedListeners =
+                new ArrayList<>();
+
         @GuardedBy("mVirtualDeviceManagerLock")
         private final ArraySet<Integer> mAllUidsOnVirtualDevice = new ArraySet<>();
 
@@ -700,6 +770,22 @@
         }
 
         @Override
+        public void onPersistentDeviceIdsRemoved(Set<String> removedPersistentDeviceIds) {
+            final List<Consumer<String>> persistentDeviceIdRemovedListeners;
+            synchronized (mVirtualDeviceManagerLock) {
+                persistentDeviceIdRemovedListeners = List.copyOf(
+                        mPersistentDeviceIdRemovedListeners);
+            }
+            mHandler.post(() -> {
+                for (String persistentDeviceId : removedPersistentDeviceIds) {
+                    for (Consumer<String> listener : persistentDeviceIdRemovedListeners) {
+                        listener.accept(persistentDeviceId);
+                    }
+                }
+            });
+        }
+
+        @Override
         public void onAuthenticationPrompt(int uid) {
             synchronized (mVirtualDeviceManagerLock) {
                 for (int i = 0; i < mVirtualDevices.size(); i++) {
@@ -766,6 +852,10 @@
 
         @Override
         public @Nullable String getPersistentIdForDevice(int deviceId) {
+            if (deviceId == Context.DEVICE_ID_DEFAULT) {
+                return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
+            }
+
             VirtualDeviceImpl virtualDevice;
             synchronized (mVirtualDeviceManagerLock) {
                 virtualDevice = mVirtualDevices.get(deviceId);
@@ -788,6 +878,22 @@
                 mAppsOnVirtualDeviceListeners.remove(listener);
             }
         }
+
+        @Override
+        public void registerPersistentDeviceIdRemovedListener(
+                @NonNull Consumer<String> persistentDeviceIdRemovedListener) {
+            synchronized (mVirtualDeviceManagerLock) {
+                mPersistentDeviceIdRemovedListeners.add(persistentDeviceIdRemovedListener);
+            }
+        }
+
+        @Override
+        public void unregisterPersistentDeviceIdRemovedListener(
+                @NonNull Consumer<String> persistentDeviceIdRemovedListener) {
+            synchronized (mVirtualDeviceManagerLock) {
+                mPersistentDeviceIdRemovedListeners.remove(persistentDeviceIdRemovedListener);
+            }
+        }
     }
 
     private static final class PendingTrampolineMap {
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl b/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl
deleted file mode 100644
index a4c1c42..0000000
--- a/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl
+++ /dev/null
@@ -1,39 +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.server.companion.virtual.camera;
-
-import android.companion.virtual.camera.IVirtualCamera;
-import android.companion.virtual.camera.VirtualCameraHalConfig;
-
-/**
- * AIDL Interface to communicate with the VirtualCamera HAL
- * @hide
- */
-interface IVirtualCameraService {
-
-    /**
-     * Registers a new camera with the virtual camera hal.
-     * @return true if the camera was successfully registered
-     */
-    boolean registerCamera(in IVirtualCamera camera);
-
-    /**
-     * Unregisters the camera from the virtual camera hal. After this call the virtual camera won't
-     * be visible to the camera framework anymore.
-     */
-    void unregisterCamera(in IVirtualCamera camera);
-}
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 031d949..06be3f3 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -16,207 +16,127 @@
 
 package com.android.server.companion.virtual.camera;
 
+import static com.android.server.companion.virtual.camera.VirtualCameraConversionUtil.getServiceCameraConfiguration;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.companion.virtual.camera.IVirtualCamera;
-import android.companion.virtual.camera.VirtualCameraHalConfig;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
+import android.companion.virtual.camera.VirtualCameraConfig;
+import android.companion.virtualcamera.IVirtualCameraService;
+import android.companion.virtualcamera.VirtualCameraConfiguration;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.Set;
 
 /**
  * Manages the registration and removal of virtual camera from the server side.
  *
  * <p>This classes delegate calls to the virtual camera service, so it is dependent on the service
- * to be up and running
+ * to be up and running.
  */
-public class VirtualCameraController implements IBinder.DeathRecipient, ServiceConnection {
+public final class VirtualCameraController implements IBinder.DeathRecipient {
 
-    private static class VirtualCameraInfo {
-
-        private final IVirtualCamera mVirtualCamera;
-        private boolean mIsRegistered;
-
-        VirtualCameraInfo(IVirtualCamera virtualCamera) {
-            mVirtualCamera = virtualCamera;
-        }
-    }
-
+    private static final String VIRTUAL_CAMERA_SERVICE_NAME = "virtual_camera";
     private static final String TAG = "VirtualCameraController";
 
-    private static final String VIRTUAL_CAMERA_SERVICE_PACKAGE = "com.android.virtualcamera";
-    private static final String VIRTUAL_CAMERA_SERVICE_CLASS = ".VirtualCameraService";
-    private final Context mContext;
-
-    @Nullable private IVirtualCameraService mVirtualCameraService = null;
+    @Nullable private IVirtualCameraService mVirtualCameraService;
 
     @GuardedBy("mCameras")
-    private final Map<IVirtualCamera, VirtualCameraInfo> mCameras = new HashMap<>(1);
+    private final Set<VirtualCameraConfig> mCameras = new ArraySet<>();
 
-    public VirtualCameraController(Context context) {
-        mContext = context;
+    public VirtualCameraController() {
         connectVirtualCameraService();
     }
 
-    private void connectVirtualCameraService() {
-        final long callingId = Binder.clearCallingIdentity();
+    @VisibleForTesting
+    VirtualCameraController(IVirtualCameraService virtualCameraService) {
+        mVirtualCameraService = virtualCameraService;
+    }
+
+    /**
+     * Register a new virtual camera with the given config.
+     *
+     * @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
+     */
+    public void registerCamera(@NonNull VirtualCameraConfig cameraConfig) {
+        // Try to connect to service if not connected already.
+        if (mVirtualCameraService == null) {
+            connectVirtualCameraService();
+        }
+        // Throw exception if we are unable to connect to service.
+        if (mVirtualCameraService == null) {
+            throw new IllegalStateException("Virtual camera service is not connected.");
+        }
+
         try {
-            Intent intent = new Intent();
-            intent.setPackage(VIRTUAL_CAMERA_SERVICE_PACKAGE);
-            intent.setComponent(
-                    ComponentName.createRelative(
-                            VIRTUAL_CAMERA_SERVICE_PACKAGE, VIRTUAL_CAMERA_SERVICE_CLASS));
-            mContext.startServiceAsUser(intent, UserHandle.SYSTEM);
-            if (!mContext.bindServiceAsUser(
-                    intent,
-                    this,
-                    Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
-                    UserHandle.SYSTEM)) {
-                mContext.unbindService(this);
-                Log.w(
-                        TAG,
-                        "connectVirtualCameraService: Failed to connect to the virtual camera "
-                                + "service");
-            }
-        } finally {
-            Binder.restoreCallingIdentity(callingId);
-        }
-    }
-
-    private void forwardPendingRegistrations() {
-        IVirtualCameraService cameraService = mVirtualCameraService;
-        if (cameraService == null) {
-            return;
-        }
-        synchronized (mCameras) {
-            for (VirtualCameraInfo cameraInfo : mCameras.values()) {
-                if (cameraInfo.mIsRegistered) {
-                    continue;
-                }
-                try {
-                    cameraService.registerCamera(cameraInfo.mVirtualCamera);
-                    cameraInfo.mIsRegistered = true;
-                } catch (RemoteException e) {
-                    e.rethrowFromSystemServer();
-                }
-            }
-        }
-    }
-
-    /**
-     * Remove the virtual camera with the provided name
-     *
-     * @param camera The name of the camera to remove
-     */
-    public void unregisterCamera(@NonNull IVirtualCamera camera) {
-        IVirtualCameraService virtualCameraService = mVirtualCameraService;
-        if (virtualCameraService != null) {
-            try {
-                virtualCameraService.unregisterCamera(camera);
+            if (registerCameraWithService(cameraConfig)) {
                 synchronized (mCameras) {
-                    VirtualCameraInfo cameraInfo = mCameras.remove(camera);
-                    cameraInfo.mIsRegistered = false;
+                    mCameras.add(cameraConfig);
                 }
-            } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
+            } else {
+                // TODO(b/310857519): Revisit this to find a better way of indicating failure.
+                throw new RuntimeException("Failed to register virtual camera.");
             }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Register a new virtual camera with the provided characteristics.
+     * Unregister the virtual camera with the given config.
      *
-     * @param camera The {@link IVirtualCamera} producing the image to communicate with the client.
-     * @throws IllegalArgumentException if the characteristics could not be parsed.
+     * @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
      */
-    public void registerCamera(@NonNull IVirtualCamera camera) {
-        IVirtualCameraService service = mVirtualCameraService;
-        VirtualCameraInfo virtualCameraInfo = new VirtualCameraInfo(camera);
-        synchronized (mCameras) {
-            mCameras.put(camera, virtualCameraInfo);
-        }
-        if (service != null) {
-            try {
-                if (service.registerCamera(camera)) {
-                    virtualCameraInfo.mIsRegistered = true;
-                    return;
-                }
-            } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
+    public void unregisterCamera(@NonNull VirtualCameraConfig cameraConfig) {
+        try {
+            if (mVirtualCameraService == null) {
+                Slog.w(TAG, "Virtual camera service is not connected.");
+            } else {
+                mVirtualCameraService.unregisterCamera(cameraConfig.getCallback().asBinder());
             }
+            synchronized (mCameras) {
+                mCameras.remove(cameraConfig);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
         }
-
-        // Service was not available or registration failed, save the registration for later
-        connectVirtualCameraService();
     }
 
     @Override
     public void binderDied() {
-        Log.d(TAG, "binderDied");
+        Slog.d(TAG, "Virtual camera service died.");
         mVirtualCameraService = null;
-    }
-
-    @Override
-    public void onBindingDied(ComponentName name) {
-        mVirtualCameraService = null;
-        Log.d(TAG, "onBindingDied() called with: name = [" + name + "]");
-    }
-
-    @Override
-    public void onNullBinding(ComponentName name) {
-        mVirtualCameraService = null;
-        Log.d(TAG, "onNullBinding() called with: name = [" + name + "]");
-    }
-
-    @Override
-    public void onServiceConnected(ComponentName name, IBinder service) {
-        Log.d(TAG, "onServiceConnected: " + name.toString());
-        mVirtualCameraService = IVirtualCameraService.Stub.asInterface(service);
-        try {
-            service.linkToDeath(this, 0);
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
+        synchronized (mCameras) {
+            mCameras.clear();
         }
-        forwardPendingRegistrations();
-    }
-
-    @Override
-    public void onServiceDisconnected(ComponentName name) {
-        Log.d(TAG, "onServiceDisconnected() called with: name = [" + name + "]");
-        mVirtualCameraService = null;
     }
 
     /** Release resources associated with this controller. */
     public void close() {
-        if (mVirtualCameraService == null) {
-            return;
-        }
         synchronized (mCameras) {
-            mCameras.forEach(
-                    (name, cameraInfo) -> {
-                        try {
-                            mVirtualCameraService.unregisterCamera(name);
-                        } catch (RemoteException e) {
-                            Log.w(
-                                    TAG,
-                                    "close(): Camera failed to be removed on camera service.",
-                                    e);
-                        }
-                    });
+            if (mVirtualCameraService == null) {
+                Slog.w(TAG, "Virtual camera service is not connected.");
+            } else {
+                for (VirtualCameraConfig config : mCameras) {
+                    try {
+                        mVirtualCameraService.unregisterCamera(config.getCallback().asBinder());
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "close(): Camera failed to be removed on camera "
+                                + "service.", e);
+                    }
+                }
+            }
+            mCameras.clear();
         }
-        mContext.unbindService(this);
+        mVirtualCameraService = null;
     }
 
     /** Dumps information about this {@link VirtualCameraController} for debugging purposes. */
@@ -226,20 +146,34 @@
         fout.printf("%sService:%s\n", indent, mVirtualCameraService);
         synchronized (mCameras) {
             fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size());
-            for (VirtualCameraInfo info : mCameras.values()) {
-                VirtualCameraHalConfig config = null;
-                try {
-                    config = info.mVirtualCamera.getHalConfig();
-                } catch (RemoteException ex) {
-                    Log.w(TAG, ex);
-                }
-                fout.printf(
-                        "%s- %s isRegistered: %s, token: %s\n",
-                        indent,
-                        config == null ? "" : config.displayName,
-                        info.mIsRegistered,
-                        info.mVirtualCamera);
+            for (VirtualCameraConfig config : mCameras) {
+                fout.printf("%s token: %s\n", indent, config);
             }
         }
     }
+
+    private void connectVirtualCameraService() {
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            IBinder virtualCameraBinder =
+                    ServiceManager.waitForService(VIRTUAL_CAMERA_SERVICE_NAME);
+            if (virtualCameraBinder == null) {
+                Slog.e(TAG, "connectVirtualCameraService: Failed to connect to the virtual "
+                        + "camera service");
+                return;
+            }
+            virtualCameraBinder.linkToDeath(this, 0);
+            mVirtualCameraService = IVirtualCameraService.Stub.asInterface(virtualCameraBinder);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    private boolean registerCameraWithService(VirtualCameraConfig config) throws RemoteException {
+        VirtualCameraConfiguration serviceConfiguration = getServiceCameraConfiguration(config);
+        return mVirtualCameraService.registerCamera(config.getCallback().asBinder(),
+                serviceConfiguration);
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
new file mode 100644
index 0000000..202f68b
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
@@ -0,0 +1,94 @@
+/*
+ * 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.companion.virtual.camera;
+
+import android.annotation.NonNull;
+import android.companion.virtual.camera.IVirtualCameraCallback;
+import android.companion.virtual.camera.VirtualCameraConfig;
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+import android.companion.virtualcamera.IVirtualCameraService;
+import android.companion.virtualcamera.SupportedStreamConfiguration;
+import android.companion.virtualcamera.VirtualCameraConfiguration;
+import android.os.RemoteException;
+import android.view.Surface;
+
+/** Utilities to convert the client side classes to the virtual camera service ones. */
+public final class VirtualCameraConversionUtil {
+
+    /**
+     * Fetches the configuration of the provided virtual cameraConfig that was provided by its owner
+     * and convert it into the {@link IVirtualCameraService} types: {@link
+     * VirtualCameraConfiguration}.
+     *
+     * @param cameraConfig The cameraConfig sent by the client.
+     * @return The converted configuration to be sent to the {@link IVirtualCameraService}.
+     * @throws RemoteException If there was an issue fetching the configuration from the client.
+     */
+    @NonNull
+    public static android.companion.virtualcamera.VirtualCameraConfiguration
+            getServiceCameraConfiguration(@NonNull VirtualCameraConfig cameraConfig)
+                    throws RemoteException {
+        VirtualCameraConfiguration serviceConfiguration = new VirtualCameraConfiguration();
+
+        serviceConfiguration.supportedStreamConfigs =
+                cameraConfig.getStreamConfigs().stream()
+                        .map(VirtualCameraConversionUtil::convertSupportedStreamConfiguration)
+                        .toArray(SupportedStreamConfiguration[]::new);
+
+        serviceConfiguration.virtualCameraCallback = convertCallback(cameraConfig.getCallback());
+        return serviceConfiguration;
+    }
+
+    @NonNull
+    private static android.companion.virtualcamera.IVirtualCameraCallback convertCallback(
+            @NonNull IVirtualCameraCallback camera) {
+        return new android.companion.virtualcamera.IVirtualCameraCallback.Stub() {
+            @Override
+            public void onStreamConfigured(
+                    int streamId, Surface surface, int width, int height, int pixelFormat)
+                    throws RemoteException {
+                VirtualCameraStreamConfig streamConfig =
+                        createStreamConfig(width, height, pixelFormat);
+                camera.onStreamConfigured(streamId, surface, streamConfig);
+            }
+
+            @Override
+            public void onStreamClosed(int streamId) throws RemoteException {
+                camera.onStreamClosed(streamId);
+            }
+        };
+    }
+
+    @NonNull
+    private static VirtualCameraStreamConfig createStreamConfig(
+            int width, int height, int pixelFormat) {
+        return new VirtualCameraStreamConfig(width, height, pixelFormat);
+    }
+
+    @NonNull
+    private static SupportedStreamConfiguration convertSupportedStreamConfiguration(
+            VirtualCameraStreamConfig stream) {
+        SupportedStreamConfiguration supportedConfig = new SupportedStreamConfiguration();
+        supportedConfig.height = stream.getHeight();
+        supportedConfig.width = stream.getWidth();
+        supportedConfig.pixelFormat = stream.getFormat();
+        return supportedConfig;
+    }
+
+    private VirtualCameraConversionUtil() {
+    }
+}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 065a447..4b00434 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -43,6 +43,7 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.permission.persistence.RuntimePermissionsState;
 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
@@ -54,7 +55,6 @@
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedUserApi;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.mutate.PackageStateMutator;
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index cd9c53b..80c4c58 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -41,6 +41,18 @@
             "file_patterns": ["StorageManagerService\\.java"]
         },
         {
+            "name": "CtsScopedStorageBypassDatabaseOperationsTest",
+            "file_patterns": ["StorageManagerService\\.java"]
+        },
+        {
+            "name": "CtsScopedStorageGeneralTest",
+            "file_patterns": ["StorageManagerService\\.java"]
+        },
+        {
+            "name": "CtsScopedStorageRedactUriTest",
+            "file_patterns": ["StorageManagerService\\.java"]
+        },
+        {
             "name": "FrameworksMockingServicesTests",
             "options": [
                 {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 9bba08a..ddccce5 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2230,8 +2230,10 @@
                             || now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
                         // This service has seen some activity within
                         // recent memory, so we will keep its process ahead
-                        // of the background processes.
-                        if (adj > SERVICE_ADJ) {
+                        // of the background processes. This does not apply
+                        // to the SDK sandbox process since it should never
+                        // be more important than its corresponding app.
+                        if (!app.isSdkSandbox && adj > SERVICE_ADJ) {
                             adj = SERVICE_ADJ;
                             state.setAdjType("started-services");
                             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index e5f7637..7292ea6 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -148,6 +148,7 @@
 import com.android.internal.app.MessageSamplingConfig;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.os.Clock;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
@@ -165,7 +166,6 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
-import com.android.server.pm.pkg.component.ParsedAttribution;
 import com.android.server.policy.AppOpsPolicy;
 
 import dalvik.annotation.optimization.NeverCompile;
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 0d7f778..c629b2b 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -25,6 +25,7 @@
 import android.util.ArraySet;
 
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Virtual device manager local service interface.
@@ -46,6 +47,14 @@
     public abstract void unregisterAppsOnVirtualDeviceListener(
             @NonNull AppsOnVirtualDeviceListener listener);
 
+    /** Register a listener for removal of persistent device IDs. */
+    public abstract void registerPersistentDeviceIdRemovedListener(
+            @NonNull Consumer<String> persistentDeviceIdRemovedListener);
+
+    /** Unregister a listener for the removal of persistent device IDs. */
+    public abstract void unregisterPersistentDeviceIdRemovedListener(
+            @NonNull Consumer<String> persistentDeviceIdRemovedListener);
+
     /**
      * Notifies that the set of apps running on virtual devices has changed.
      * This method only notifies the listeners when the union of running UIDs on all virtual devices
@@ -59,6 +68,11 @@
     public abstract void onAuthenticationPrompt(int uid);
 
     /**
+     * Notifies the given persistent device IDs have been removed.
+     */
+    public abstract void onPersistentDeviceIdsRemoved(Set<String> removedPersistentDeviceIds);
+
+    /**
      * Gets the owner uid for a deviceId.
      *
      * @param deviceId which device we're asking about
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 d023913..fb6c9e3 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -724,8 +724,9 @@
                     || mode.getPhysicalHeight() > maxAllowedHeight
                     || mode.getPhysicalWidth() < outSummary.minWidth
                     || mode.getPhysicalHeight() < outSummary.minHeight
-                    || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate
-                    || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) {
+                    || mode.getRefreshRate() < (outSummary.minPhysicalRefreshRate - FLOAT_TOLERANCE)
+                    || mode.getRefreshRate() > (outSummary.maxPhysicalRefreshRate + FLOAT_TOLERANCE)
+            ) {
                 continue;
             }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index d3eedd7..1687157 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -191,9 +191,16 @@
         launchDeviceDiscovery();
         startQueuedActions();
         if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
-            mService.sendCecCommand(
-                    HdmiCecMessageBuilder.buildRequestActiveSource(
-                            getDeviceInfo().getLogicalAddress()));
+            addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() {
+                @Override
+                public void onComplete(int result) {
+                    if (result != HdmiControlManager.RESULT_SUCCESS) {
+                        mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
+                                getDeviceInfo().getLogicalAddress(),
+                                getDeviceInfo().getPhysicalAddress()));
+                    }
+                }
+            }));
         }
     }
 
@@ -1325,6 +1332,8 @@
         removeAction(TimerRecordingAction.class);
         removeAction(NewDeviceAction.class);
         removeAction(AbsoluteVolumeAudioStatusAction.class);
+        // Remove pending actions.
+        removeAction(RequestActiveSourceAction.class);
 
         // Keep SAM enabled if eARC is enabled, unless we're going to Standby.
         if (initiatedByCec || !mService.isEarcEnabled()){
diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
new file mode 100644
index 0000000..017c86d
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
@@ -0,0 +1,66 @@
+/*
+ * 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.hdmi;
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.util.Slog;
+
+/**
+ * Feature action that sends <Request Active Source> message and waits for <Active Source>.
+ */
+public class RequestActiveSourceAction extends HdmiCecFeatureAction {
+    private static final String TAG = "RequestActiveSourceAction";
+
+    // State to wait for the <Active Source> message.
+    private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 1;
+
+    RequestActiveSourceAction(HdmiCecLocalDevice source, IHdmiControlCallback callback) {
+        super(source, callback);
+    }
+
+    @Override
+    boolean start() {
+        Slog.v(TAG, "RequestActiveSourceAction started.");
+
+        sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+
+        mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
+        addTimer(mState, HdmiConfig.TIMEOUT_MS);
+        return true;
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        // The action finishes successfully if the <Active Source> message is received.
+        // {@link HdmiCecLocalDevice#onMessage} handles this message, so false is returned.
+        if (cmd.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) {
+            finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+        }
+        return false;
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            return;
+        }
+        if (mState == STATE_WAIT_FOR_ACTIVE_SOURCE) {
+            finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 3e46ee2..a46d719 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -171,17 +171,20 @@
      *
      * @param accessibilityConnectionId the connection id of the accessibility service
      * @param session                   the session passed back from the accessibility service
+     * @param userId                    the user ID to be queried
      */
     public abstract void onSessionForAccessibilityCreated(int accessibilityConnectionId,
-            IAccessibilityInputMethodSession session);
+            IAccessibilityInputMethodSession session, @UserIdInt int userId);
 
     /**
      * Unbind the accessibility service with the specified accessibilityConnectionId from current
      * client.
      *
      * @param accessibilityConnectionId the connection id of the accessibility service
+     * @param userId the user ID to be queried
      */
-    public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId);
+    public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
+            @UserIdInt int userId);
 
     /**
      * Switch the keyboard layout in response to a keyboard shortcut.
@@ -266,11 +269,12 @@
 
                 @Override
                 public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
-                        IAccessibilityInputMethodSession session) {
+                        IAccessibilityInputMethodSession session, @UserIdInt int userId) {
                 }
 
                 @Override
-                public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) {
+                public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
+                        @UserIdInt int userId) {
                 }
 
                 @Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d656f20..6cc0693 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5716,8 +5716,9 @@
 
         @Override
         public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
-                IAccessibilityInputMethodSession session) {
+                IAccessibilityInputMethodSession session, @UserIdInt int userId) {
             synchronized (ImfLock.class) {
+                // TODO(b/305829876): Implement user ID verification
                 if (mCurClient != null) {
                     clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
                     mCurClient.mAccessibilitySessions.put(accessibilityConnectionId,
@@ -5744,8 +5745,10 @@
         }
 
         @Override
-        public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) {
+        public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
+                @UserIdInt int userId) {
             synchronized (ImfLock.class) {
+                // TODO(b/305829876): Implement user ID verification
                 if (mCurClient != null) {
                     if (DEBUG) {
                         Slog.v(TAG, "unbindAccessibilityFromCurrentClientLocked: client="
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2ec3700..8c75367 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -360,6 +360,7 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
@@ -5245,13 +5246,23 @@
             }
         }
 
+        // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
         @Override
         public List<ZenModeConfig.ZenRule> getZenRules() throws RemoteException {
-            enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules");
+            enforcePolicyAccess(Binder.getCallingUid(), "getZenRules");
             return mZenModeHelper.getZenRules();
         }
 
         @Override
+        public Map<String, AutomaticZenRule> getAutomaticZenRules() {
+            if (!android.app.Flags.modesApi()) {
+                throw new IllegalStateException("getAutomaticZenRules called with flag off!");
+            }
+            enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules");
+            return mZenModeHelper.getAutomaticZenRules();
+        }
+
+        @Override
         public AutomaticZenRule getAutomaticZenRule(String id) throws RemoteException {
             Objects.requireNonNull(id, "Id is null");
             enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRule");
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index c637df2..a1704c6 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -106,7 +106,9 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -334,6 +336,7 @@
         return mZenMode;
     }
 
+    // TODO: b/310620812 - Make private (or inline) when MODES_API is inlined.
     public List<ZenRule> getZenRules() {
         List<ZenRule> rules = new ArrayList<>();
         synchronized (mConfigLock) {
@@ -347,6 +350,20 @@
         return rules;
     }
 
+    /**
+     * Get the list of {@link AutomaticZenRule} instances that the calling package can manage
+     * (which means the owned rules for a regular app, and every rule for system callers) together
+     * with their ids.
+     */
+    Map<String, AutomaticZenRule> getAutomaticZenRules() {
+        List<ZenRule> ruleList = getZenRules();
+        HashMap<String, AutomaticZenRule> rules = new HashMap<>(ruleList.size());
+        for (ZenRule rule : ruleList) {
+            rules.put(rule.id, zenRuleToAutomaticZenRule(rule));
+        }
+        return rules;
+    }
+
     public AutomaticZenRule getAutomaticZenRule(String id) {
         ZenRule rule;
         synchronized (mConfigLock) {
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 21284a0..659c36c 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -45,11 +45,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedApexSystemService;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.build.UnboundedSdkLevel;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedApexSystemService;
 import com.android.server.utils.TimingsTraceAndSlog;
 
 import com.google.android.collect.Lists;
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 21610c9..bdcec3a 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -58,6 +58,9 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.FgThread;
@@ -68,9 +71,6 @@
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedUserApi;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.utils.Snappable;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.Watchable;
diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java
index d38b83f..f3f64c5 100644
--- a/services/core/java/com/android/server/pm/AppsFilterUtils.java
+++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java
@@ -27,15 +27,15 @@
 import android.util.ArraySet;
 import android.util.Pair;
 
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedProvider;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedProvider;
 import com.android.server.utils.WatchedArraySet;
 import com.android.server.utils.WatchedSparseSetArray;
 
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 2617703..5e76ae5 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -123,6 +123,12 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.IndentingPrintWriter;
@@ -141,12 +147,6 @@
 import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.PackageUserStateUtils;
 import com.android.server.pm.pkg.SharedUserApi;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedService;
 import com.android.server.pm.resolution.ComponentResolverApi;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f8d27f1..d46d559 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -154,6 +154,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
 import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -177,11 +182,6 @@
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedLibraryWrapper;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.rollback.RollbackManagerInternal;
 import com.android.server.security.FileIntegrityService;
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index e749968..ea783b8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -47,13 +47,13 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUtils;
 import com.android.server.pm.pkg.SharedUserApi;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.mutate.PackageStateMutator;
 
 import java.io.IOException;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 434c00a..7d3d85d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -181,6 +181,8 @@
 import com.android.internal.content.F2fsUtils;
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.telephony.CarrierAppUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -233,8 +235,6 @@
 import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.SharedUserApi;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.mutate.PackageStateMutator;
 import com.android.server.pm.pkg.mutate.PackageStateWrite;
 import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index bcb7bde..cd34163 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -92,6 +92,7 @@
 
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.HexDump;
@@ -104,7 +105,6 @@
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.resolution.ComponentResolverApi;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 
diff --git a/services/core/java/com/android/server/pm/PackageProperty.java b/services/core/java/com/android/server/pm/PackageProperty.java
index 241f143..651bc5f 100644
--- a/services/core/java/com/android/server/pm/PackageProperty.java
+++ b/services/core/java/com/android/server/pm/PackageProperty.java
@@ -31,8 +31,8 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 
+import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedComponent;
 
 import java.util.ArrayList;
 import java.util.Iterator;
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index b055a3f..a8196f3 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -44,6 +44,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.parsing.PackageCacher;
@@ -52,7 +53,6 @@
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
 
 import java.io.File;
 import java.util.Collections;
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 7ea9e3f..22ee963 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -73,6 +73,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedProcess;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -82,11 +87,6 @@
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateUtils;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedProcess;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedService;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.WatchedArraySet;
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6338965..7c969ef 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -89,6 +89,10 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedProcess;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.IndentingPrintWriter;
@@ -113,10 +117,6 @@
 import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.SharedUserApi;
 import com.android.server.pm.pkg.SuspendParams;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedProcess;
 import com.android.server.pm.resolution.ComponentResolver;
 import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index 9376259..dddc6b0 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -25,13 +25,13 @@
 import android.util.ArraySet;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.pm.pkg.component.ParsedProcess;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.permission.LegacyPermissionState;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedUserApi;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedProcess;
 import com.android.server.pm.pkg.component.ParsedProcessImpl;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.Watchable;
diff --git a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java
index adac68b..2156467 100644
--- a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java
+++ b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java
@@ -29,9 +29,9 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedUsesPermission;
 
 import org.xmlpull.v1.XmlPullParser;
 
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 38cde3e..91a70a6 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -52,6 +52,17 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.component.ParsedProcess;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.SystemConfig;
 import com.android.server.pm.PackageArchiver;
@@ -65,17 +76,6 @@
 import com.android.server.pm.pkg.PackageUserStateUtils;
 import com.android.server.pm.pkg.SELinuxUtil;
 import com.android.server.pm.pkg.component.ComponentParseUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedAttribution;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
-import com.android.server.pm.pkg.component.ParsedProcess;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedService;
-import com.android.server.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
index 97d526d..8916efd7 100644
--- a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
@@ -20,8 +20,8 @@
 import android.annotation.Nullable;
 import android.util.Pair;
 
+import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.component.ParsedComponent;
 
 /**
  * For exposing internal fields to the rest of the server, enforcing that any overridden state from
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index e2acc17..0eb2bbd 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -29,16 +29,16 @@
 import android.os.incremental.IncrementalManager;
 
 import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.SystemConfig;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedService;
 import com.android.server.pm.pkg.parsing.ParsingPackageHidden;
 
 import java.io.IOException;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index 75dd67d..370d239 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -50,6 +50,19 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedApexSystemService;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.component.ParsedProcess;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
@@ -61,27 +74,15 @@
 import com.android.server.pm.pkg.AndroidPackageSplitImpl;
 import com.android.server.pm.pkg.SELinuxUtil;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.pkg.component.ParsedActivityImpl;
-import com.android.server.pm.pkg.component.ParsedApexSystemService;
 import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl;
-import com.android.server.pm.pkg.component.ParsedAttribution;
 import com.android.server.pm.pkg.component.ParsedAttributionImpl;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
 import com.android.server.pm.pkg.component.ParsedInstrumentationImpl;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
 import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl;
 import com.android.server.pm.pkg.component.ParsedPermissionImpl;
-import com.android.server.pm.pkg.component.ParsedProcess;
-import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedProcessImpl;
 import com.android.server.pm.pkg.component.ParsedProviderImpl;
-import com.android.server.pm.pkg.component.ParsedService;
 import com.android.server.pm.pkg.component.ParsedServiceImpl;
-import com.android.server.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageHidden;
@@ -3305,7 +3306,7 @@
         this.instrumentations = ParsingUtils.createTypedInterfaceList(in,
                 ParsedInstrumentationImpl.CREATOR);
         this.preferredActivityFilters = sForIntentInfoPairs.unparcel(in);
-        this.processes = in.readHashMap(ParsedProcess.class.getClassLoader());
+        this.processes = in.readHashMap(ParsedProcessImpl.class.getClassLoader());
         this.metaData = in.readBundle(boot);
         this.volumeUuid = sForInternedString.unparcel(in);
         this.signingDetails = in.readParcelable(boot, android.content.pm.SigningDetails.class);
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index c81d6d7..07ff0ee 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -28,9 +28,9 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.pkg.PackageState;
-import com.android.server.pm.pkg.component.ParsedPermission;
 
 import libcore.util.EmptyArray;
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 6764e08..883b066 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -119,6 +119,8 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.os.RoSystemProperties;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.IntPair;
@@ -142,8 +144,6 @@
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedUserApi;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
 import com.android.server.pm.pkg.component.ParsedPermissionUtils;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.SoftRestrictedPermissionPolicy;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionRegistry.java b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java
index 3a61704..61677eb 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionRegistry.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java
@@ -18,10 +18,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+
 import java.util.Collection;
 
 /**
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 91854fd..4d4efac 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -47,17 +47,17 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.R;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedApexSystemService;
-import com.android.server.pm.pkg.component.ParsedAttribution;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
-import com.android.server.pm.pkg.component.ParsedProcess;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedService;
-import com.android.server.pm.pkg.component.ParsedUsesPermission;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedApexSystemService;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.component.ParsedProcess;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import java.security.PublicKey;
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
index 1d2c5ec..20fcdb5 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
@@ -22,7 +22,7 @@
 import android.content.pm.PackageManager;
 import android.util.SparseArray;
 
-import com.android.server.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
 
 /** @hide */
 public class PackageStateUtils {
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
index cd3583b..fe80f74 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
@@ -27,7 +27,7 @@
 import android.util.DebugUtils;
 import android.util.Slog;
 
-import com.android.server.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
 
 /** @hide */
 public class PackageUserStateUtils {
diff --git a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java
index 063f577..411bded 100644
--- a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java
+++ b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java
@@ -22,8 +22,8 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import com.android.internal.pm.pkg.component.ParsedProcess;
 import com.android.server.pm.permission.LegacyPermissionState;
-import com.android.server.pm.pkg.component.ParsedProcess;
 
 import java.util.List;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java
index 1deb8d0..1964df0 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java
@@ -19,6 +19,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.component.ParsedProcess;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+
 /**
  * Contains mutation methods so that code doesn't have to cast to the Impl. Meant to eventually
  * be removed once all post-parsing mutation is moved to parsing.
diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java
index a8fb79a..041edaa 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java
@@ -29,6 +29,9 @@
 import android.content.res.XmlResourceParser;
 import android.text.TextUtils;
 
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateUtils;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
index 68d5428..f027901 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
@@ -36,7 +36,7 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
@@ -49,7 +49,6 @@
  * @hide
  **/
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
-@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class ParsedActivityImpl extends ParsedMainComponentImpl implements ParsedActivity,
         Parcelable {
 
@@ -133,7 +132,7 @@
      * should be invisible to user and user should not know or see it.
      */
     @NonNull
-    static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName,
+    public static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName,
             int uiOptions, String taskAffinity, boolean hardwareAccelerated) {
         ParsedActivityImpl activity = new ParsedActivityImpl();
         activity.setPackageName(packageName);
@@ -700,7 +699,7 @@
             time = 1669437519576L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java",
-            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.server.pm.pkg.component.ParsedActivity)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic  void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic  void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index ee793c8..5709cbb 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -48,6 +48,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
index 167aba3..cfed19a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
@@ -22,6 +22,7 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedApexSystemService;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
 
@@ -250,7 +251,7 @@
             time = 1643723578605L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java",
-            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate  int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.server.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
+            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate  int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
index ed9aa2e..d3fb29b 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
@@ -25,6 +25,8 @@
 import android.content.res.XmlResourceParser;
 import android.text.TextUtils;
 
+import com.android.internal.pm.pkg.component.ParsedApexSystemService;
+
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
index b59f511..62b9947 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
@@ -22,6 +22,7 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
 import com.android.internal.util.DataClass;
 
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java
index 98e94c5..411220a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java
@@ -26,6 +26,7 @@
 import android.util.ArraySet;
 
 import com.android.internal.R;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
index f8d678e..512e5c7 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
@@ -32,6 +32,8 @@
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
index 8a0d356..7bfad14 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
@@ -26,6 +26,7 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
index c63a689..9792a91 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
@@ -26,6 +26,7 @@
 import android.content.res.XmlResourceParser;
 
 import com.android.internal.R;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
 
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
index 5b6375d..ab94043 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
@@ -23,6 +23,7 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.util.DataClass;
 
 /**
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
index 4f0a504..5e67bbf 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -31,6 +31,7 @@
 import android.util.TypedValue;
 
 import com.android.internal.R;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
index c670e7c..f322eef 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
@@ -25,6 +25,7 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
index f52ad13..6c22f82 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
@@ -31,6 +31,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
index 59075de..afe37bc 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
@@ -21,6 +21,7 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
 import com.android.internal.util.DataClass;
 
 /**
@@ -174,7 +175,7 @@
             time = 1642132854167L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java",
-            inputSignatures = "private  int requestDetailRes\nprivate  int backgroundRequestRes\nprivate  int backgroundRequestDetailRes\nprivate  int requestRes\nprivate  int priority\npublic  java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.server.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
+            inputSignatures = "private  int requestDetailRes\nprivate  int backgroundRequestRes\nprivate  int backgroundRequestDetailRes\nprivate  int requestRes\nprivate  int priority\npublic  java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
index 4c831d3..69e33c8 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
@@ -24,6 +24,8 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
@@ -119,8 +121,8 @@
         this.requestRes = in.readInt();
         this.protectionLevel = in.readInt();
         this.tree = in.readBoolean();
-        this.parsedPermissionGroup = in.readParcelable(ParsedPermissionGroup.class.getClassLoader(),
-                ParsedPermissionGroupImpl.class);
+        this.parsedPermissionGroup = in.readParcelable(
+                ParsedPermissionGroupImpl.class.getClassLoader(), ParsedPermissionGroupImpl.class);
         this.knownCerts = sForStringSet.unparcel(in);
     }
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
index c6d17753..0f2b49b 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
@@ -31,6 +31,8 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
index 6d52f65..40e3670 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
@@ -25,7 +25,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedProcess;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
@@ -35,7 +35,6 @@
 /** @hide */
 @DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false,
         genBuilder = false)
-@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class ParsedProcessImpl implements ParsedProcess, Parcelable {
 
     @NonNull
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
index 4f4c2d5..766fb90 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
@@ -27,6 +27,7 @@
 import android.util.ArraySet;
 
 import com.android.internal.R;
+import com.android.internal.pm.pkg.component.ParsedProcess;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
index 6f4b4c8..81a3c17 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
@@ -28,6 +28,7 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedProvider;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
@@ -301,7 +302,7 @@
             time = 1642560323360L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate  boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate  boolean grantUriPermissions\nprivate  boolean forceUriPermissions\nprivate  boolean multiProcess\nprivate  int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic  com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic  com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate  boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate  boolean grantUriPermissions\nprivate  boolean forceUriPermissions\nprivate  boolean multiProcess\nprivate  int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic  com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic  com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
index 37bed15..b66db4f 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
@@ -34,6 +34,7 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.pm.pkg.component.ParsedProvider;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
index 47e993c..ca8c45d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
@@ -26,6 +26,8 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
index c15266f..1b42184 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
@@ -32,6 +32,7 @@
 import android.os.Build;
 
 import com.android.internal.R;
+import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
index 9b89373..78377a8 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
@@ -21,6 +21,7 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
 
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 699ccbd..408a531 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -32,18 +32,18 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.R;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedApexSystemService;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.component.ParsedProcess;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedApexSystemService;
-import com.android.server.pm.pkg.component.ParsedAttribution;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
-import com.android.server.pm.pkg.component.ParsedProcess;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedService;
-import com.android.server.pm.pkg.component.ParsedUsesPermission;
 
 import java.security.PublicKey;
 import java.util.List;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 061698a..417e3ae 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -89,6 +89,19 @@
 
 import com.android.internal.R;
 import com.android.internal.os.ClassLoaderFactory;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedApexSystemService;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.component.ParsedProcess;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.SharedUidMigration;
@@ -98,29 +111,17 @@
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
 import com.android.server.pm.pkg.component.ComponentParseUtils;
 import com.android.server.pm.pkg.component.InstallConstraintsTagParser;
-import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedActivityImpl;
 import com.android.server.pm.pkg.component.ParsedActivityUtils;
-import com.android.server.pm.pkg.component.ParsedApexSystemService;
 import com.android.server.pm.pkg.component.ParsedApexSystemServiceUtils;
-import com.android.server.pm.pkg.component.ParsedAttribution;
 import com.android.server.pm.pkg.component.ParsedAttributionUtils;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
 import com.android.server.pm.pkg.component.ParsedInstrumentationUtils;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
 import com.android.server.pm.pkg.component.ParsedIntentInfoUtils;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
 import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-import com.android.server.pm.pkg.component.ParsedProcess;
 import com.android.server.pm.pkg.component.ParsedProcessUtils;
-import com.android.server.pm.pkg.component.ParsedProvider;
 import com.android.server.pm.pkg.component.ParsedProviderUtils;
-import com.android.server.pm.pkg.component.ParsedService;
 import com.android.server.pm.pkg.component.ParsedServiceUtils;
-import com.android.server.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
 import com.android.server.pm.split.DefaultSplitAssetLoader;
 import com.android.server.pm.split.SplitAssetDependencyLoader;
@@ -2842,7 +2843,7 @@
         String taskAffinity = result.getResult();
 
         // Build custom App Details activity info instead of parsing it from xml
-        return input.success(ParsedActivity.makeAppDetailsActivity(packageName,
+        return input.success(ParsedActivityImpl.makeAppDetailsActivity(packageName,
                 pkg.getProcessName(), pkg.getUiOptions(), taskAffinity,
                 pkg.isHardwareAccelerated()));
     }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
index 0751285..2cfffb3 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
@@ -30,9 +30,9 @@
 import android.util.Pair;
 import android.util.Slog;
 
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.util.Parcelling;
 import com.android.internal.util.XmlUtils;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
 
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index 0ceda42..ed6d3b9 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -45,6 +45,12 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.IntentResolver;
 import com.android.server.pm.Computer;
@@ -57,13 +63,7 @@
 import com.android.server.pm.pkg.PackageStateUtils;
 import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedProvider;
 import com.android.server.pm.pkg.component.ParsedProviderImpl;
-import com.android.server.pm.pkg.component.ParsedService;
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.utils.Snappable;
 import com.android.server.utils.SnapshotCache;
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
index b8e4c8d..0f12ee1 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
@@ -25,11 +25,11 @@
 import android.content.pm.ResolveInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.server.pm.Computer;
 import com.android.server.pm.DumpState;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedService;
 
 import java.io.PrintWriter;
 import java.util.List;
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
index 80cde73..2bc926c 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
@@ -28,6 +28,11 @@
 import android.util.ArrayMap;
 import android.util.Pair;
 
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.server.pm.Computer;
 import com.android.server.pm.DumpState;
 import com.android.server.pm.UserManagerService;
@@ -36,11 +41,6 @@
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageUserStateInternal;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedService;
 import com.android.server.utils.WatchableImpl;
 
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
index 0c84f4c..add33b2 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
@@ -24,13 +24,13 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.server.pm.Computer;
 import com.android.server.pm.DumpState;
 import com.android.server.pm.PackageManagerTracedLock;
 import com.android.server.pm.UserManagerService;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedService;
 
 import java.io.PrintWriter;
 import java.util.List;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
index adef808..735f90f 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -27,11 +27,11 @@
 import android.util.ArraySet;
 import android.util.Patterns;
 
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.SystemConfig;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
 
 import java.util.List;
 import java.util.Objects;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 3d4d4ec..6150099 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -48,6 +48,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.util.CollectionUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -60,7 +61,6 @@
 import com.android.server.pm.pkg.PackageStateUtils;
 import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.PackageUserStateUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
 import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index dd39fb0..ee3b746 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -32,6 +32,8 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.os.WorkDuration;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseIntArray;
@@ -195,6 +197,9 @@
 
         private static native void nativeSetMode(long halPtr, int mode, boolean enabled);
 
+        private static native void nativeReportActualWorkDuration(long halPtr,
+                                                                  WorkDuration[] workDurations);
+
         /** Wrapper for HintManager.nativeInit */
         public void halInit() {
             nativeInit();
@@ -252,6 +257,10 @@
             nativeSetMode(halPtr, mode, enabled);
         }
 
+        /** Wrapper for HintManager.nativeReportActualWorkDuration */
+        public void halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations) {
+            nativeReportActualWorkDuration(halPtr, workDurations);
+        }
     }
 
     @VisibleForTesting
@@ -624,6 +633,52 @@
             }
         }
 
+        @Override
+        public void reportActualWorkDuration2(WorkDuration[] workDurations) {
+            synchronized (this) {
+                if (mHalSessionPtr == 0 || !mUpdateAllowed) {
+                    return;
+                }
+                Preconditions.checkArgument(workDurations.length != 0, "the count"
+                        + " of work durations shouldn't be 0.");
+                for (WorkDuration workDuration : workDurations) {
+                    validateWorkDuration(workDuration);
+                }
+                mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations);
+            }
+        }
+
+        void validateWorkDuration(WorkDuration workDuration) {
+            if (DEBUG) {
+                Slogf.d(TAG, "WorkDuration(" + workDuration.getTimestampNanos() + ", "
+                        + workDuration.getWorkPeriodStartTimestampNanos() + ", "
+                        + workDuration.getActualTotalDurationNanos() + ", "
+                        + workDuration.getActualCpuDurationNanos() + ", "
+                        + workDuration.getActualGpuDurationNanos() + ")");
+            }
+            if (workDuration.getWorkPeriodStartTimestampNanos() <= 0) {
+                throw new IllegalArgumentException(
+                    TextUtils.formatSimple(
+                            "Work period start timestamp (%d) should be greater than 0",
+                            workDuration.getWorkPeriodStartTimestampNanos()));
+            }
+            if (workDuration.getActualTotalDurationNanos() <= 0) {
+                throw new IllegalArgumentException(
+                    TextUtils.formatSimple("Actual total duration (%d) should be greater than 0",
+                            workDuration.getActualTotalDurationNanos()));
+            }
+            if (workDuration.getActualCpuDurationNanos() <= 0) {
+                throw new IllegalArgumentException(
+                    TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0",
+                            workDuration.getActualCpuDurationNanos()));
+            }
+            if (workDuration.getActualGpuDurationNanos() < 0) {
+                throw new IllegalArgumentException(
+                    TextUtils.formatSimple("Actual GPU duration (%d) should be non negative",
+                            workDuration.getActualGpuDurationNanos()));
+            }
+        }
+
         private void onProcStateChanged(boolean updateAllowed) {
             updateHintAllowed(updateAllowed);
         }
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index 6d580e9..8b57f87 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -280,12 +280,12 @@
                 return null;
             }
 
-            if (getSessionCountByUidLocked(callingUid) >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) {
+            if (getSessionCountByUidLocked(callingUid) == MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) {
                 Slog.w(TAG, "Number of sessions exceeded for uid: " + callingUid);
                 Counter.logIncrementWithUid(
                         "speech_recognition.value_exceed_session_count",
                         callingUid);
-                return null;
+                // TODO(b/297249772): return null early to refuse the new connection
             }
 
             if (servicesForClient != null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index bdcde66..f8078d2 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -869,8 +869,7 @@
                 if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) {
                     Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent
                             + ", reverting to built-in wallpaper!");
-                    int which = mWallpaper.mWhich;
-                    clearWallpaperLocked(which, mWallpaper.userId, false, null);
+                    clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null);
                 }
             }
         };
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 26f0d34..7b399c8 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1018,9 +1018,8 @@
         }
 
         try {
-            final ClientTransaction transaction = ClientTransaction.obtain(r.app.getThread());
-            transaction.addCallback(EnterPipRequestedItem.obtain(r.token));
-            mService.getLifecycleManager().scheduleTransaction(transaction);
+            mService.getLifecycleManager().scheduleTransaction(r.app.getThread(),
+                    EnterPipRequestedItem.obtain(r.token));
             return true;
         } catch (Exception e) {
             Slog.w(TAG, "Failed to send enter pip requested item: "
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index eeeca10..24d9938 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8185,6 +8185,12 @@
      *         aspect ratio.
      */
     boolean shouldCreateCompatDisplayInsets() {
+        if (mLetterboxUiController.shouldApplyUserFullscreenOverride()) {
+            // If the user has forced the applications aspect ratio to be fullscreen, don't use size
+            // compatibility mode in any situation. The user has been warned and therefore accepts
+            // the risk of the application misbehaving.
+            return false;
+        }
         switch (supportsSizeChanges()) {
             case SIZE_CHANGES_SUPPORTED_METADATA:
             case SIZE_CHANGES_SUPPORTED_OVERRIDE:
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 777b5cd..e196d46 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -936,7 +936,7 @@
 
                 final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
                 clientTransaction.addCallback(LaunchActivityItem.obtain(r.token,
-                        new Intent(r.intent), System.identityHashCode(r), r.info,
+                        r.intent, System.identityHashCode(r), r.info,
                         // TODO: Have this take the merged configuration instead of separate global
                         // and override configs.
                         mergedConfiguration.getGlobalConfiguration(),
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index a2f5a38..c2b5f88 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -583,7 +583,7 @@
                                 + " if the PI sender upgrades target_sdk to 34+! "
                                 + " (missing opt in by PI sender)! "
                                 + state.dump(resultForCaller, resultForRealCaller));
-                showBalBlockedToast("BAL would be blocked", state);
+                showBalRiskToast("BAL would be blocked", state);
                 return statsLog(resultForRealCaller, state);
             }
             Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 735cbc4..5518de7 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -254,7 +254,9 @@
     // Counter for ActivityRecord#setRequestedOrientation
     private int mSetOrientationRequestCounter = 0;
 
-    // The min aspect ratio override set by user
+    // The min aspect ratio override set by user. Stores the last selected aspect ratio after
+    // {@link #shouldApplyUserFullscreenOverride} or {@link #shouldApplyUserMinAspectRatioOverride}
+    // have been invoked.
     @PackageManager.UserMinAspectRatio
     private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
 
@@ -661,7 +663,9 @@
 
     @ScreenOrientation
     int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
-        if (shouldApplyUserFullscreenOverride()) {
+        if (shouldApplyUserFullscreenOverride()
+                && mActivityRecord.mDisplayContent != null
+                && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
             Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
                     + mActivityRecord + " is overridden to "
                     + screenOrientationToString(SCREEN_ORIENTATION_USER)
@@ -1171,9 +1175,7 @@
     boolean shouldApplyUserFullscreenOverride() {
         if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
                 || FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride)
-                || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
-                || mActivityRecord.mDisplayContent == null
-                || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
+                || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3e23fab..f700944 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -173,7 +173,6 @@
     private final TransitionController mController;
     private final BLASTSyncEngine mSyncEngine;
     private final Token mToken;
-    private IApplicationThread mRemoteAnimApp;
 
     private @Nullable ActivityRecord mPipActivity;
 
@@ -1485,13 +1484,14 @@
         return mForcePlaying;
     }
 
+    /** Adjusts the priority of the process which will run the transition animation. */
     void setRemoteAnimationApp(IApplicationThread app) {
-        mRemoteAnimApp = app;
-    }
-
-    /** Returns the app which will run the transition animation. */
-    IApplicationThread getRemoteAnimationApp() {
-        return mRemoteAnimApp;
+        final WindowProcessController wpc = mController.mAtm.getProcessController(app);
+        if (wpc != null) {
+            // This is an early prediction. If the process doesn't ack the animation in 200 ms,
+            // the priority will be restored.
+            mController.mRemotePlayer.update(wpc, true /* running */, true /* predict */);
+        }
     }
 
     void setNoAnimation(WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 1f01778..a736874 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1251,13 +1251,7 @@
         } else if (mPlayingTransitions.isEmpty()) {
             mTransitionPlayerProc.setRunningRemoteAnimation(false);
             mRemotePlayer.clear();
-            return;
         }
-        final IApplicationThread appThread = transition.getRemoteAnimationApp();
-        if (appThread == null || appThread == mTransitionPlayerProc.getThread()) return;
-        final WindowProcessController delegate = mAtm.getProcessController(appThread);
-        if (delegate == null) return;
-        mRemotePlayer.update(delegate, isPlaying, true /* predict */);
     }
 
     /** Called when a transition is aborted. This should only be called by {@link Transition} */
@@ -1483,7 +1477,7 @@
      * {@link #mTransitionPlayerProc}.
      */
     static class RemotePlayer {
-        private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 100;
+        private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 200;
         @GuardedBy("itself")
         private final ArrayMap<IBinder, DelegateProcess> mDelegateProcesses = new ArrayMap<>();
         private final ActivityTaskManagerService mAtm;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index f6c431f..2b77fff 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3950,7 +3950,7 @@
         return true;
     }
 
-    boolean useBLASTSync() {
+    boolean syncNextBuffer() {
         return mSyncState != SYNC_STATE_NONE;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0a986c8..0e13914 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -660,11 +660,6 @@
     @NonNull
     final RootWindowContainer mRoot;
 
-    // Whether the system should use BLAST for ViewRootImpl
-    final boolean mUseBLAST;
-    // Whether to enable BLASTSyncEngine Transaction passing.
-    static final boolean USE_BLAST_SYNC = true;
-
     final BLASTSyncEngine mSyncEngine;
 
     boolean mIsPc;
@@ -1219,8 +1214,6 @@
         mRoot = new RootWindowContainer(this);
 
         final ContentResolver resolver = context.getContentResolver();
-        mUseBLAST = Settings.Global.getInt(resolver,
-            Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_VR, 1) == 1;
 
         mSyncEngine = new BLASTSyncEngine(this);
 
@@ -1741,13 +1734,6 @@
             }
 
             // From now on, no exceptions or errors allowed!
-
-            res = ADD_OKAY;
-
-            if (mUseBLAST) {
-                res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST;
-            }
-
             if (displayContent.mCurrentFocus == null) {
                 displayContent.mWinAddedSinceNullFocus.add(win);
             }
@@ -2555,7 +2541,7 @@
 
             if (outSyncIdBundle != null) {
                 final int maybeSyncSeqId;
-                if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility == View.VISIBLE
+                if (win.syncNextBuffer() && viewVisibility == View.VISIBLE
                         && win.mSyncSeqId > lastSyncSeqId) {
                     maybeSyncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1;
                     win.markRedrawForSyncReported();
@@ -5818,15 +5804,6 @@
     }
 
     @Override
-    public boolean useBLAST() {
-        return mUseBLAST;
-    }
-
-    public boolean useBLASTSync() {
-        return USE_BLAST_SYNC;
-    }
-
-    @Override
     public void getInitialDisplaySize(int displayId, Point size) {
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4e17011..8ded38b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3368,7 +3368,7 @@
         mAnimatingExit = false;
         ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=destroySurface win=%s", this);
 
-        if (useBLASTSync()) {
+        if (syncNextBuffer()) {
             immediatelyNotifyBlastSync();
         }
     }
@@ -5799,7 +5799,7 @@
                 // Consume the transaction because the sync group will merge it.
                 postDrawTransaction = null;
             }
-        } else if (useBLASTSync()) {
+        } else if (syncNextBuffer()) {
             // Sync that is not using BLAST
             layoutNeeded = onSyncFinishedDrawing();
         }
@@ -5855,7 +5855,7 @@
             // drawing for being visible, then no need to request redraw.
             return false;
         }
-        return useBLASTSync();
+        return syncNextBuffer();
     }
 
     int getSyncMethod() {
@@ -5880,11 +5880,11 @@
      * it's next draw in to a transaction). If we have pending draw handlers, we are
      * looking for the client to sync.
      *
-     * See {@link WindowState#mPendingDrawHandlers}
+     * See {@link WindowState#mDrawHandlers}
      */
     @Override
-    boolean useBLASTSync() {
-        return super.useBLASTSync() || (mDrawHandlers.size() != 0);
+    boolean syncNextBuffer() {
+        return super.syncNextBuffer() || (mDrawHandlers.size() != 0);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 209d934..6c15c22 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -37,7 +37,6 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
 import android.view.WindowContentFrameStats;
-import android.view.WindowManager;
 
 import com.android.internal.protolog.common.ProtoLog;
 
@@ -73,7 +72,7 @@
         mWindowSession = win.mSession;
 
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
-        final SurfaceControl.Builder b = win.makeSurface()
+        mSurfaceControl = win.makeSurface()
                 .setParent(win.getSurfaceControl())
                 .setName(name)
                 .setFormat(format)
@@ -81,16 +80,8 @@
                 .setMetadata(METADATA_WINDOW_TYPE, windowType)
                 .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
                 .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
-                .setCallsite("WindowSurfaceController");
-
-        final boolean useBLAST = mService.mUseBLAST && ((win.getAttrs().privateFlags
-                & WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0);
-
-        if (useBLAST) {
-            b.setBLASTLayer();
-        }
-
-        mSurfaceControl = b.build();
+                .setCallsite("WindowSurfaceController")
+                .setBLASTLayer().build();
 
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index 7edf445..ccd9bd0 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -20,6 +20,7 @@
 
 #include <aidl/android/hardware/power/IPower.h>
 #include <android-base/stringprintf.h>
+#include <inttypes.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <powermanager/PowerHalController.h>
@@ -38,6 +39,15 @@
 
 namespace android {
 
+static struct {
+    jclass clazz{};
+    jfieldID workPeriodStartTimestampNanos{};
+    jfieldID actualTotalDurationNanos{};
+    jfieldID actualCpuDurationNanos{};
+    jfieldID actualGpuDurationNanos{};
+    jfieldID timestampNanos{};
+} gWorkDurationInfo;
+
 static power::PowerHalController gPowerHalController;
 static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
 static std::mutex gSessionMapLock;
@@ -180,6 +190,26 @@
     setMode(session_ptr, static_cast<SessionMode>(mode), enabled);
 }
 
+static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr,
+                                            jobjectArray jWorkDurations) {
+    int size = env->GetArrayLength(jWorkDurations);
+    std::vector<WorkDuration> workDurations(size);
+    for (int i = 0; i < size; i++) {
+        jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i);
+        workDurations[i].workPeriodStartTimestampNanos =
+                env->GetLongField(workDuration, gWorkDurationInfo.workPeriodStartTimestampNanos);
+        workDurations[i].durationNanos =
+                env->GetLongField(workDuration, gWorkDurationInfo.actualTotalDurationNanos);
+        workDurations[i].cpuDurationNanos =
+                env->GetLongField(workDuration, gWorkDurationInfo.actualCpuDurationNanos);
+        workDurations[i].gpuDurationNanos =
+                env->GetLongField(workDuration, gWorkDurationInfo.actualGpuDurationNanos);
+        workDurations[i].timeStampNanos =
+                env->GetLongField(workDuration, gWorkDurationInfo.timestampNanos);
+    }
+    reportActualWorkDuration(session_ptr, workDurations);
+}
+
 // ----------------------------------------------------------------------------
 static const JNINativeMethod sHintManagerServiceMethods[] = {
         /* name, signature, funcPtr */
@@ -194,9 +224,23 @@
         {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
         {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
         {"nativeSetMode", "(JIZ)V", (void*)nativeSetMode},
+        {"nativeReportActualWorkDuration", "(J[Landroid/os/WorkDuration;)V",
+         (void*)nativeReportActualWorkDuration2},
 };
 
 int register_android_server_HintManagerService(JNIEnv* env) {
+    gWorkDurationInfo.clazz = env->FindClass("android/os/WorkDuration");
+    gWorkDurationInfo.workPeriodStartTimestampNanos =
+            env->GetFieldID(gWorkDurationInfo.clazz, "mWorkPeriodStartTimestampNanos", "J");
+    gWorkDurationInfo.actualTotalDurationNanos =
+            env->GetFieldID(gWorkDurationInfo.clazz, "mActualTotalDurationNanos", "J");
+    gWorkDurationInfo.actualCpuDurationNanos =
+            env->GetFieldID(gWorkDurationInfo.clazz, "mActualCpuDurationNanos", "J");
+    gWorkDurationInfo.actualGpuDurationNanos =
+            env->GetFieldID(gWorkDurationInfo.clazz, "mActualGpuDurationNanos", "J");
+    gWorkDurationInfo.timestampNanos =
+            env->GetFieldID(gWorkDurationInfo.clazz, "mTimestampNanos", "J");
+
     return jniRegisterNativeMethods(env,
                                     "com/android/server/power/hint/"
                                     "HintManagerService$NativeWrapper",
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 12cd0f6..8d76fdd 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -24,6 +24,7 @@
 import android.os.Binder
 import android.os.UserHandle
 import android.util.ArrayMap
+import com.android.internal.pm.pkg.component.ParsedActivity
 import com.android.server.pm.AppsFilterImpl
 import com.android.server.pm.PackageManagerService
 import com.android.server.pm.PackageManagerServiceInjector
@@ -39,7 +40,6 @@
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.parsing.pkg.ParsedPackage
 import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.component.ParsedActivity
 import com.android.server.pm.resolution.ComponentResolver
 import com.android.server.pm.snapshot.PackageDataSnapshot
 import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
index d5cd6ef9..25146a8 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
@@ -49,16 +49,16 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.server.om.OverlayReferenceMapper;
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.pkg.component.ParsedActivityImpl;
 import com.android.server.pm.pkg.component.ParsedComponentImpl;
 import com.android.server.pm.pkg.component.ParsedInstrumentationImpl;
 import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
-import com.android.server.pm.pkg.component.ParsedPermission;
 import com.android.server.pm.pkg.component.ParsedPermissionImpl;
 import com.android.server.pm.pkg.component.ParsedProviderImpl;
 import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index 0eac4e6..7c28e13 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -58,6 +58,16 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedApexSystemService;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -69,24 +79,14 @@
 import com.android.server.pm.permission.CompatibilityPermissionInfo;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageUserStateInternal;
-import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.pkg.component.ParsedActivityImpl;
-import com.android.server.pm.pkg.component.ParsedApexSystemService;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
 import com.android.server.pm.pkg.component.ParsedInstrumentationImpl;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
 import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl;
 import com.android.server.pm.pkg.component.ParsedPermissionImpl;
 import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-import com.android.server.pm.pkg.component.ParsedProvider;
 import com.android.server.pm.pkg.component.ParsedProviderImpl;
-import com.android.server.pm.pkg.component.ParsedService;
 import com.android.server.pm.pkg.component.ParsedServiceImpl;
-import com.android.server.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
 
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 9e37164..7123c20 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -38,16 +38,16 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.pm.test.service.server.R;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.component.ParsedActivityUtils;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedPermission;
 import com.android.server.pm.pkg.component.ParsedPermissionUtils;
+import com.android.server.pm.test.service.server.R;
 
 import com.google.common.truth.Expect;
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index 0e2e35f..2646854 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import android.content.pm.ActivityInfo
-import com.android.server.pm.pkg.component.ParsedActivity
+import com.android.internal.pm.pkg.component.ParsedActivity
 import com.android.server.pm.pkg.component.ParsedActivityImpl
 import kotlin.contracts.ExperimentalContracts
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
index 4e44e96..52d5b3b 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.pm.test.parsing.parcelling
 
-import com.android.server.pm.pkg.component.ParsedAttribution
+import com.android.internal.pm.pkg.component.ParsedAttribution
 import com.android.server.pm.pkg.component.ParsedAttributionImpl
 import kotlin.contracts.ExperimentalContracts
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
index 058f6d6..af0c0de 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import android.content.pm.PackageManager
-import com.android.server.pm.pkg.component.ParsedComponent
+import com.android.internal.pm.pkg.component.ParsedComponent
 import com.android.server.pm.pkg.component.ParsedComponentImpl
 import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import android.os.Bundle
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
index eeb30b7..dc0f194 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.pm.test.parsing.parcelling
 
-import com.android.server.pm.pkg.component.ParsedInstrumentation
+import com.android.internal.pm.pkg.component.ParsedInstrumentation
 import com.android.server.pm.pkg.component.ParsedInstrumentationImpl
 import kotlin.contracts.ExperimentalContracts
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
index f27a51f..5224f23 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.pm.test.parsing.parcelling
 
-import com.android.server.pm.pkg.component.ParsedIntentInfo
+import com.android.internal.pm.pkg.component.ParsedIntentInfo
 import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import android.os.Parcelable
 import android.os.PatternMatcher
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
index a0d8c44..dfff602 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.pm.test.parsing.parcelling
 
-import com.android.server.pm.pkg.component.ParsedMainComponent
+import com.android.internal.pm.pkg.component.ParsedMainComponent
 import com.android.server.pm.pkg.component.ParsedMainComponentImpl
 import android.os.Parcelable
 import java.util.Arrays
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
index f266e76..ccbf558 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.pm.test.parsing.parcelling
 
-import com.android.server.pm.pkg.component.ParsedPermissionGroup
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup
 import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
 import kotlin.contracts.ExperimentalContracts
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
index c72a44e..2814783 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
@@ -16,8 +16,8 @@
 
 package com.android.server.pm.test.parsing.parcelling
 
-import com.android.server.pm.pkg.component.ParsedPermission
-import com.android.server.pm.pkg.component.ParsedPermissionGroup
+import com.android.internal.pm.pkg.component.ParsedPermission
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup
 import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
 import com.android.server.pm.pkg.component.ParsedPermissionImpl
 import kotlin.contracts.ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
index 8b9361a..2e96046 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.pm.test.parsing.parcelling
 
-import com.android.server.pm.pkg.component.ParsedProcess
+import com.android.internal.pm.pkg.component.ParsedProcess
 import com.android.server.pm.pkg.component.ParsedProcessImpl
 import android.util.ArrayMap
 import kotlin.contracts.ExperimentalContracts
@@ -45,7 +45,7 @@
     override fun extraParams() = listOf(
         getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission")),
         getter(ParsedProcess::getAppClassNamesByPackage, ArrayMap<String, String>().apply {
-            put("package1", "classname1");
+            put("package1", "classname1")
         }),
     )
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
index 0302d57..290dbd6 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
@@ -17,14 +17,14 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import android.content.pm.PathPermission
-import com.android.server.pm.pkg.component.ParsedProvider
+import com.android.internal.pm.pkg.component.ParsedProvider
 import com.android.server.pm.pkg.component.ParsedProviderImpl
 import android.os.PatternMatcher
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
-class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, ParsedProviderImpl::class) {
-
+class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, ParsedProviderImpl::class)
+{
     override val defaultImpl =
         ParsedProviderImpl()
     override val creator = ParsedProviderImpl.CREATOR
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
index e2c9439..3ae7e92 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.pm.test.parsing.parcelling
 
-import com.android.server.pm.pkg.component.ParsedService
+import com.android.internal.pm.pkg.component.ParsedService
 import com.android.server.pm.pkg.component.ParsedServiceImpl
 import kotlin.contracts.ExperimentalContracts
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
index ad60736..67dfc6d 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.pm.test.parsing.parcelling
 
-import com.android.server.pm.pkg.component.ParsedUsesPermission
+import com.android.internal.pm.pkg.component.ParsedUsesPermission
 import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl
 import kotlin.contracts.ExperimentalContracts
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
index d217d63..1da3a22 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
@@ -24,26 +24,25 @@
 import android.content.pm.VersionedPackage
 import android.os.PatternMatcher
 import android.util.ArraySet
+import com.android.internal.pm.pkg.component.ParsedActivity
+import com.android.internal.pm.pkg.component.ParsedInstrumentation
+import com.android.internal.pm.pkg.component.ParsedPermission
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup
+import com.android.internal.pm.pkg.component.ParsedProcess
+import com.android.internal.pm.pkg.component.ParsedProvider
+import com.android.internal.pm.pkg.component.ParsedService
 import com.android.server.pm.PackageSetting
 import com.android.server.pm.PackageSettingBuilder
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageState
 import com.android.server.pm.pkg.PackageUserState
-import com.android.server.pm.pkg.PackageUserStateImpl
-import com.android.server.pm.pkg.component.ParsedActivity
 import com.android.server.pm.pkg.component.ParsedActivityImpl
 import com.android.server.pm.pkg.component.ParsedComponentImpl
-import com.android.server.pm.pkg.component.ParsedInstrumentation
 import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
-import com.android.server.pm.pkg.component.ParsedPermission
-import com.android.server.pm.pkg.component.ParsedPermissionGroup
 import com.android.server.pm.pkg.component.ParsedPermissionImpl
-import com.android.server.pm.pkg.component.ParsedProcess
 import com.android.server.pm.pkg.component.ParsedProcessImpl
-import com.android.server.pm.pkg.component.ParsedProvider
 import com.android.server.pm.pkg.component.ParsedProviderImpl
-import com.android.server.pm.pkg.component.ParsedService
 import com.android.server.pm.test.parsing.parcelling.AndroidPackageTest
 import com.google.common.truth.Expect
 import org.junit.Rule
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
index ec84bc3..316f338 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
@@ -26,6 +26,8 @@
 import android.util.ArrayMap
 import android.util.SparseArray
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.pm.pkg.component.ParsedPermission
+import com.android.internal.pm.pkg.component.ParsedPermissionGroup
 import com.android.modules.utils.testing.ExtendedMockitoRule
 import com.android.server.extendedtestutils.wheneverStatic
 import com.android.server.permission.access.MutableAccessState
@@ -39,8 +41,6 @@
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageState
 import com.android.server.pm.pkg.PackageUserState
-import com.android.server.pm.pkg.component.ParsedPermission
-import com.android.server.pm.pkg.component.ParsedPermissionGroup
 import com.android.server.testutils.any
 import com.android.server.testutils.mock
 import com.android.server.testutils.whenever
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 6a95d5c..499e700 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
@@ -229,7 +229,19 @@
                                     LIMIT_MODE_70.getPhysicalWidth(),
                                     LIMIT_MODE_70.getPhysicalHeight(),
                                     0, APP_MODE_65.getRefreshRate())),
-                        /*displayResolutionRangeVotingEnabled*/ true}});
+                        /*displayResolutionRangeVotingEnabled*/ true},
+                {/*expectedBaseModeId*/ APP_MODE_65.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ 64.99f,
+                        /*expectedAppRequestedRefreshRate*/ 64.99f,
+                        /*votesWithPriorities*/ Map.of(
+                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                                Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()),
+                                Vote.PRIORITY_APP_REQUEST_SIZE,
+                                Vote.forSize(APP_MODE_65.getPhysicalWidth(),
+                                        APP_MODE_65.getPhysicalHeight()),
+                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.forPhysicalRefreshRates(
+                                        0, 64.99f))}});
 
         final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2);
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java
deleted file mode 100644
index 8f77e9b..0000000
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java
+++ /dev/null
@@ -1,224 +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.server.companion.virtual;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.Manifest;
-import android.companion.virtual.VirtualDeviceParams;
-import android.companion.virtual.camera.IVirtualCamera;
-import android.companion.virtual.camera.IVirtualCameraSession;
-import android.companion.virtual.camera.VirtualCamera;
-import android.companion.virtual.camera.VirtualCameraConfig;
-import android.companion.virtual.camera.VirtualCameraHalConfig;
-import android.companion.virtual.camera.VirtualCameraSession;
-import android.companion.virtual.camera.VirtualCameraStreamConfig;
-import android.companion.virtual.flags.Flags;
-import android.content.ComponentName;
-import android.graphics.ImageFormat;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
-import android.testing.TestableLooper;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.AdoptShellPermissionsRule;
-import com.android.server.companion.virtual.camera.IVirtualCameraService;
-import com.android.server.companion.virtual.camera.VirtualCameraController;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashSet;
-import java.util.Set;
-
-@Presubmit
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class VirtualCameraTest {
-
-    private static final String PKG = "com.android.virtualcamera";
-    private static final String CLS = ".VirtualCameraService";
-    public static final String CAMERA_DISPLAY_NAME = "testCamera";
-
-    private final TestableContext mContext =
-            new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
-    private FakeVirtualCameraService mFakeVirtualCameraService;
-    private VirtualCameraController mVirtualCameraController;
-
-    @Rule public final VirtualDeviceRule mVirtualDeviceRule = new VirtualDeviceRule(mContext);
-
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
-    @Rule
-    public AdoptShellPermissionsRule mAdoptShellPermissionsRule =
-            new AdoptShellPermissionsRule(
-                    InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                    Manifest.permission.CREATE_VIRTUAL_DEVICE);
-
-    @Before
-    public void setUp() {
-        mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> mVirtualCameraController);
-        mFakeVirtualCameraService = new FakeVirtualCameraService();
-        connectFakeService();
-        mVirtualCameraController = new VirtualCameraController(mContext);
-    }
-
-    private VirtualDeviceImpl createVirtualDevice() {
-        return mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build());
-    }
-
-    private void connectFakeService() {
-        mContext.addMockService(
-                ComponentName.createRelative(PKG, CLS), mFakeVirtualCameraService.asBinder());
-    }
-
-    @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
-    @Test
-    public void addVirtualCamera() {
-        VirtualDeviceImpl virtualDevice = createVirtualDevice();
-        VirtualCameraConfig config = createVirtualCameraConfig(null);
-        IVirtualCamera.Default camera = new IVirtualCamera.Default();
-        virtualDevice.registerVirtualCamera(camera);
-
-        assertThat(mFakeVirtualCameraService.mCameras).contains(camera);
-    }
-
-    @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
-    @Test
-    public void addVirtualCamera_serviceNotReady() {
-        TestableContext context =
-                new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
-        VirtualCameraController virtualCameraController = new VirtualCameraController(context);
-        mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> virtualCameraController);
-
-        VirtualDeviceImpl virtualDevice =
-                mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build());
-        IVirtualCamera.Default camera = new IVirtualCamera.Default();
-        VirtualCameraConfig config = createVirtualCameraConfig(null);
-        virtualDevice.registerVirtualCamera(camera);
-        FakeVirtualCameraService fakeVirtualCameraService = new FakeVirtualCameraService();
-
-        // Only add the service after connecting the camera
-        virtualCameraController.onServiceConnected(
-                ComponentName.createRelative(PKG, CLS), fakeVirtualCameraService.asBinder());
-
-        assertThat(fakeVirtualCameraService.mCameras).contains(camera);
-    }
-
-    @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
-    @Test
-    public void getCameraConfiguration() {
-        VirtualDeviceImpl virtualDevice = createVirtualDevice();
-        VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {};
-        VirtualCameraConfig config =
-                new VirtualCameraConfig.Builder()
-                        .addStreamConfiguration(10, 10, ImageFormat.RGB_565)
-                        .setDisplayName(CAMERA_DISPLAY_NAME)
-                        .setCallback(
-                                new HandlerExecutor(new Handler(Looper.getMainLooper())),
-                                () -> virtualCameraSession)
-                        .build();
-
-        VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config);
-
-        VirtualCameraConfig returnedConfig = virtualCamera.getConfig();
-        assertThat(returnedConfig).isNotNull();
-        assertThat(returnedConfig.getDisplayName()).isEqualTo(CAMERA_DISPLAY_NAME);
-        Set<VirtualCameraStreamConfig> streamConfigs = returnedConfig.getStreamConfigs();
-        assertThat(streamConfigs).hasSize(1);
-        VirtualCameraStreamConfig streamConfig =
-                streamConfigs.toArray(new VirtualCameraStreamConfig[0])[0];
-        assertThat(streamConfig.format).isEqualTo(ImageFormat.RGB_565);
-        assertThat(streamConfig.width).isEqualTo(10);
-        assertThat(streamConfig.height).isEqualTo(10);
-
-        VirtualCameraHalConfig halConfig = virtualCamera.getHalConfig();
-        assertThat(halConfig).isNotNull();
-        assertThat(halConfig.displayName).isEqualTo(CAMERA_DISPLAY_NAME);
-        assertThat(halConfig.streamConfigs).asList().hasSize(1);
-        assertThat(halConfig.streamConfigs[0].format).isEqualTo(ImageFormat.RGB_565);
-        assertThat(halConfig.streamConfigs[0].width).isEqualTo(10);
-        assertThat(halConfig.streamConfigs[0].height).isEqualTo(10);
-    }
-
-    @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
-    @Test
-    public void createCameraWithVirtualCameraInstance() {
-        VirtualDeviceImpl virtualDevice = createVirtualDevice();
-
-        VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {};
-        VirtualCameraConfig config = createVirtualCameraConfig(virtualCameraSession);
-        VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config);
-
-        assertThat(mFakeVirtualCameraService.mCameras).contains(virtualCamera);
-        assertThat(virtualCamera.open()).isInstanceOf(IVirtualCameraSession.class);
-    }
-
-    @RequiresFlagsDisabled(Flags.FLAG_VIRTUAL_CAMERA)
-    @Test
-    public void createCameraDoesNothingWhenControllerIsNull() {
-        mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> null);
-        VirtualDeviceImpl virtualDevice = createVirtualDevice();
-        IVirtualCamera.Default camera = new IVirtualCamera.Default();
-        VirtualCameraConfig config = createVirtualCameraConfig(null);
-        virtualDevice.registerVirtualCamera(camera);
-
-        assertThat(mFakeVirtualCameraService.mCameras).doesNotContain(camera);
-    }
-
-    @NonNull
-    private static VirtualCameraConfig createVirtualCameraConfig(
-            VirtualCameraSession virtualCameraSession) {
-        return new VirtualCameraConfig.Builder()
-                .addStreamConfiguration(10, 10, ImageFormat.RGB_565)
-                .setDisplayName(CAMERA_DISPLAY_NAME)
-                .setCallback(
-                        new HandlerExecutor(new Handler(Looper.getMainLooper())),
-                        () -> virtualCameraSession)
-                .build();
-    }
-
-    private static class FakeVirtualCameraService extends IVirtualCameraService.Stub {
-
-        final Set<IVirtualCamera> mCameras = new HashSet<>();
-
-        @Override
-        public boolean registerCamera(IVirtualCamera camera) throws RemoteException {
-            mCameras.add(camera);
-            return true;
-        }
-
-        @Override
-        public void unregisterCamera(IVirtualCamera camera) throws RemoteException {
-            mCameras.remove(camera);
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index cdff623..30300ec 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -52,9 +52,11 @@
 import android.app.WindowConfiguration;
 import android.app.admin.DevicePolicyManager;
 import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
 import android.companion.virtual.IVirtualDeviceActivityListener;
 import android.companion.virtual.IVirtualDeviceIntentInterceptor;
 import android.companion.virtual.IVirtualDeviceSoundEffectListener;
+import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
@@ -134,6 +136,8 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -261,6 +265,8 @@
     @Mock
     private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener mAppsOnVirtualDeviceListener;
     @Mock
+    private Consumer<String> mPersistentDeviceIdRemovedListener;
+    @Mock
     IPowerManager mIPowerManagerMock;
     @Mock
     IThermalService mIThermalServiceMock;
@@ -372,9 +378,8 @@
         mCameraAccessController =
                 new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);
 
-        mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null,
-                null, MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false,
-                0, 0, -1);
+        mAssociationInfo = createAssociationInfo(
+                /* associationId= */ 1, AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
 
         mVdms = new VirtualDeviceManagerService(mContext);
         mLocalService = mVdms.getLocalServiceInstance();
@@ -722,6 +727,39 @@
     }
 
     @Test
+    public void onPersistentDeviceIdsRemoved_listenersNotified() {
+        mLocalService.registerPersistentDeviceIdRemovedListener(mPersistentDeviceIdRemovedListener);
+        mLocalService.onPersistentDeviceIdsRemoved(Set.of(mDeviceImpl.getPersistentDeviceId()));
+        TestableLooper.get(this).processAllMessages();
+
+        verify(mPersistentDeviceIdRemovedListener).accept(mDeviceImpl.getPersistentDeviceId());
+    }
+
+    @Test
+    public void onCdmAssociationsChanged_persistentDeviceIdRemovedListenersNotified() {
+        mLocalService.registerPersistentDeviceIdRemovedListener(mPersistentDeviceIdRemovedListener);
+        mVdms.onCdmAssociationsChanged(List.of(mAssociationInfo));
+        TestableLooper.get(this).processAllMessages();
+
+        mVdms.onCdmAssociationsChanged(List.of(
+                createAssociationInfo(2, AssociationRequest.DEVICE_PROFILE_APP_STREAMING),
+                createAssociationInfo(3, AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION),
+                createAssociationInfo(4, AssociationRequest.DEVICE_PROFILE_WATCH)));
+        TestableLooper.get(this).processAllMessages();
+
+        verify(mPersistentDeviceIdRemovedListener).accept(mDeviceImpl.getPersistentDeviceId());
+
+        mVdms.onCdmAssociationsChanged(Collections.emptyList());
+        TestableLooper.get(this).processAllMessages();
+
+        verify(mPersistentDeviceIdRemovedListener)
+                .accept(VirtualDeviceImpl.createPersistentDeviceId(2));
+        verify(mPersistentDeviceIdRemovedListener)
+                .accept(VirtualDeviceImpl.createPersistentDeviceId(3));
+        verifyNoMoreInteractions(mPersistentDeviceIdRemovedListener);
+    }
+
+    @Test
     public void onAppsOnVirtualDeviceChanged_singleVirtualDevice_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
@@ -1860,11 +1898,16 @@
     @Test
     public void getPersistentIdForDevice_invalidDeviceId_returnsNull() {
         assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_INVALID)).isNull();
-        assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_DEFAULT)).isNull();
         assertThat(mLocalService.getPersistentIdForDevice(VIRTUAL_DEVICE_ID_2)).isNull();
     }
 
     @Test
+    public void getPersistentIdForDevice_defaultDeviceId() {
+        assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_DEFAULT)).isEqualTo(
+                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+    }
+
+    @Test
     public void getPersistentIdForDevice_returnsCorrectId() {
         assertThat(mLocalService.getPersistentIdForDevice(VIRTUAL_DEVICE_ID_1))
                 .isEqualTo(mDeviceImpl.getPersistentDeviceId());
@@ -1897,7 +1940,7 @@
                         mRunningAppsChangedCallback,
                         params,
                         new DisplayManagerGlobal(mIDisplayManager),
-                        new VirtualCameraController(mContext));
+                        new VirtualCameraController());
         mVdms.addVirtualDevice(virtualDeviceImpl);
         assertThat(virtualDeviceImpl.getAssociationId()).isEqualTo(mAssociationInfo.getId());
         assertThat(virtualDeviceImpl.getPersistentDeviceId())
@@ -1919,6 +1962,14 @@
         return intent.resolveActivity(packageManager);
     }
 
+    private AssociationInfo createAssociationInfo(int associationId, String deviceProfile) {
+        return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null,
+                /* tag= */ null, MacAddress.BROADCAST_ADDRESS, /* displayName= */ "", deviceProfile,
+                /* associatedDevice= */ null, /* selfManaged= */ true,
+                /* notifyOnDeviceNearby= */ false, /* revoked= */false,  /* timeApprovedMs= */0,
+                /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
+    }
+
     /** Helper class to drop permissions temporarily and restore them at the end of a test. */
     static final class DropShellPermissionsTemporarily implements AutoCloseable {
         DropShellPermissionsTemporarily() {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java
deleted file mode 100644
index dbd6c88..0000000
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java
+++ /dev/null
@@ -1,222 +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.server.companion.virtual;
-
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-
-import android.app.admin.DevicePolicyManager;
-import android.companion.AssociationInfo;
-import android.companion.virtual.IVirtualDeviceActivityListener;
-import android.companion.virtual.IVirtualDeviceSoundEffectListener;
-import android.companion.virtual.VirtualDeviceParams;
-import android.companion.virtual.flags.Flags;
-import android.content.AttributionSource;
-import android.content.Context;
-import android.hardware.display.DisplayManagerGlobal;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.IDisplayManager;
-import android.net.MacAddress;
-import android.os.Binder;
-import android.testing.TestableContext;
-import android.util.ArraySet;
-import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.WindowManager;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.LocalServices;
-import com.android.server.companion.virtual.camera.VirtualCameraController;
-import com.android.server.input.InputManagerInternal;
-import com.android.server.sensors.SensorManagerInternal;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Objects;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/** Test rule to generate instances of {@link VirtualDeviceImpl}. */
-public class VirtualDeviceRule implements TestRule {
-
-    private static final int DEVICE_OWNER_UID = 50;
-    private static final int VIRTUAL_DEVICE_ID = 42;
-
-    private final Context mContext;
-    private InputController mInputController;
-    private CameraAccessController mCameraAccessController;
-    private AssociationInfo mAssociationInfo;
-    private VirtualDeviceManagerService mVdms;
-    private VirtualDeviceManagerInternal mLocalService;
-    private VirtualDeviceLog mVirtualDeviceLog;
-
-    // Mocks
-    @Mock private InputController.NativeWrapper mNativeWrapperMock;
-    @Mock private DisplayManagerInternal mDisplayManagerInternalMock;
-    @Mock private IDisplayManager mIDisplayManager;
-    @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
-    @Mock private DevicePolicyManager mDevicePolicyManagerMock;
-    @Mock private InputManagerInternal mInputManagerInternalMock;
-    @Mock private SensorManagerInternal mSensorManagerInternalMock;
-    @Mock private IVirtualDeviceActivityListener mActivityListener;
-    @Mock private IVirtualDeviceSoundEffectListener mSoundEffectListener;
-    @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
-    @Mock private CameraAccessController.CameraAccessBlockedCallback mCameraAccessBlockedCallback;
-
-    // Test instance suppliers
-    private Supplier<VirtualCameraController> mVirtualCameraControllerSupplier;
-
-    /**
-     * Create a new {@link VirtualDeviceRule}
-     *
-     * @param context The context to be used with the rule.
-     */
-    public VirtualDeviceRule(@NonNull Context context) {
-        Objects.requireNonNull(context);
-        mContext = context;
-    }
-
-    /**
-     * Sets a supplier that will supply an instance of {@link VirtualCameraController}. If the
-     * supplier returns null, a new instance will be created.
-     */
-    public VirtualDeviceRule withVirtualCameraControllerSupplier(
-            Supplier<VirtualCameraController> virtualCameraControllerSupplier) {
-        mVirtualCameraControllerSupplier = virtualCameraControllerSupplier;
-        return this;
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                init(new TestableContext(mContext));
-                base.evaluate();
-            }
-        };
-    }
-
-    private void init(@NonNull TestableContext context) {
-        MockitoAnnotations.initMocks(this);
-
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
-
-        doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
-        doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
-        doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
-        LocalServices.removeServiceForTest(InputManagerInternal.class);
-        LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
-
-        LocalServices.removeServiceForTest(SensorManagerInternal.class);
-        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
-
-        final DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.uniqueId = "uniqueId";
-        doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
-        doReturn(Display.INVALID_DISPLAY).when(mDisplayManagerInternalMock)
-                .getDisplayIdToMirror(anyInt());
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
-
-        context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManagerMock);
-
-        // Allow virtual devices to be created on the looper thread for testing.
-        final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
-        mInputController =
-                new InputController(
-                        mNativeWrapperMock,
-                        InstrumentationRegistry.getInstrumentation()
-                                .getContext()
-                                .getMainThreadHandler(),
-                        context.getSystemService(WindowManager.class),
-                        threadVerifier);
-        mCameraAccessController =
-                new CameraAccessController(context, mLocalService, mCameraAccessBlockedCallback);
-
-        mAssociationInfo =
-                new AssociationInfo(
-                        /* associationId= */ 1,
-                        0,
-                        null,
-                        null,
-                        MacAddress.BROADCAST_ADDRESS,
-                        "",
-                        null,
-                        null,
-                        true,
-                        false,
-                        false,
-                        0,
-                        0,
-                        -1);
-
-        mVdms = new VirtualDeviceManagerService(context);
-        mLocalService = mVdms.getLocalServiceInstance();
-        mVirtualDeviceLog = new VirtualDeviceLog(context);
-    }
-
-    /**
-     * Create a {@link VirtualDeviceImpl} with the required mocks
-     *
-     * @param params See {@link
-     *     android.companion.virtual.VirtualDeviceManager#createVirtualDevice(int,
-     *     VirtualDeviceParams)}
-     */
-    public VirtualDeviceImpl createVirtualDevice(VirtualDeviceParams params) {
-        VirtualCameraController virtualCameraController = mVirtualCameraControllerSupplier.get();
-        if (Flags.virtualCamera()) {
-            if (virtualCameraController == null) {
-                virtualCameraController = new VirtualCameraController(mContext);
-            }
-        }
-
-        VirtualDeviceImpl virtualDeviceImpl =
-                new VirtualDeviceImpl(
-                        mContext,
-                        mAssociationInfo,
-                        mVdms,
-                        mVirtualDeviceLog,
-                        new Binder(),
-                        new AttributionSource(
-                                DEVICE_OWNER_UID,
-                                "com.android.virtualdevice.test",
-                                "virtualdevicerule"),
-                        VIRTUAL_DEVICE_ID,
-                        mInputController,
-                        mCameraAccessController,
-                        mPendingTrampolineCallback,
-                        mActivityListener,
-                        mSoundEffectListener,
-                        mRunningAppsChangedCallback,
-                        params,
-                        new DisplayManagerGlobal(mIDisplayManager),
-                        virtualCameraController);
-        mVdms.addVirtualDevice(virtualDeviceImpl);
-        return virtualDeviceImpl;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
new file mode 100644
index 0000000..2583023
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.companion.virtual.camera;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.companion.virtual.camera.VirtualCameraCallback;
+import android.companion.virtual.camera.VirtualCameraConfig;
+import android.companion.virtual.camera.VirtualCameraMetadata;
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+import android.companion.virtualcamera.IVirtualCameraService;
+import android.companion.virtualcamera.VirtualCameraConfiguration;
+import android.graphics.ImageFormat;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class VirtualCameraControllerTest {
+
+    private static final int CAMERA_DISPLAY_NAME_RES_ID_1 = 10;
+    private static final int CAMERA_WIDTH_1 = 100;
+    private static final int CAMERA_HEIGHT_1 = 200;
+    private static final int CAMERA_FORMAT_1 = ImageFormat.RGB_565;
+
+    private static final int CAMERA_DISPLAY_NAME_RES_ID_2 = 11;
+    private static final int CAMERA_WIDTH_2 = 400;
+    private static final int CAMERA_HEIGHT_2 = 600;
+    private static final int CAMERA_FORMAT_2 = ImageFormat.YUY2;
+
+    @Mock
+    private IVirtualCameraService mVirtualCameraServiceMock;
+
+    private VirtualCameraController mVirtualCameraController;
+    private final HandlerExecutor mCallbackHandler =
+            new HandlerExecutor(new Handler(Looper.getMainLooper()));
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mVirtualCameraController = new VirtualCameraController(mVirtualCameraServiceMock);
+        when(mVirtualCameraServiceMock.registerCamera(any(), any())).thenReturn(true);
+    }
+
+    @Test
+    public void registerCamera_registersCamera() throws Exception {
+        mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1));
+
+        ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
+                ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
+        verify(mVirtualCameraServiceMock).registerCamera(any(), configurationCaptor.capture());
+        VirtualCameraConfiguration virtualCameraConfiguration = configurationCaptor.getValue();
+        assertThat(virtualCameraConfiguration.supportedStreamConfigs.length).isEqualTo(1);
+        assertVirtualCameraConfiguration(virtualCameraConfiguration, CAMERA_WIDTH_1,
+                CAMERA_HEIGHT_1, CAMERA_FORMAT_1);
+    }
+
+    @Test
+    public void unregisterCamera_unregistersCamera() throws Exception {
+        VirtualCameraConfig config = createVirtualCameraConfig(
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1);
+        mVirtualCameraController.unregisterCamera(config);
+
+        verify(mVirtualCameraServiceMock).unregisterCamera(any());
+    }
+
+    @Test
+    public void close_unregistersAllCameras() throws Exception {
+        mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1));
+        mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+                CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_DISPLAY_NAME_RES_ID_2));
+
+        ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
+                ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
+        mVirtualCameraController.close();
+        verify(mVirtualCameraServiceMock, times(2)).registerCamera(any(),
+                configurationCaptor.capture());
+        List<VirtualCameraConfiguration> virtualCameraConfigurations =
+                configurationCaptor.getAllValues();
+        assertThat(virtualCameraConfigurations).hasSize(2);
+        assertVirtualCameraConfiguration(virtualCameraConfigurations.get(0), CAMERA_WIDTH_1,
+                CAMERA_HEIGHT_1, CAMERA_FORMAT_1);
+        assertVirtualCameraConfiguration(virtualCameraConfigurations.get(1), CAMERA_WIDTH_2,
+                CAMERA_HEIGHT_2, CAMERA_FORMAT_2);
+    }
+
+    private VirtualCameraConfig createVirtualCameraConfig(
+            int width, int height, int format, int displayNameResId) {
+        return new VirtualCameraConfig.Builder()
+                .addStreamConfig(width, height, format)
+                .setDisplayNameStringRes(displayNameResId)
+                .setVirtualCameraCallback(mCallbackHandler, createNoOpCallback())
+                .build();
+    }
+
+    private static void assertVirtualCameraConfiguration(
+            VirtualCameraConfiguration configuration, int width, int height, int format) {
+        assertThat(configuration.supportedStreamConfigs[0].width).isEqualTo(width);
+        assertThat(configuration.supportedStreamConfigs[0].height).isEqualTo(height);
+        assertThat(configuration.supportedStreamConfigs[0].pixelFormat).isEqualTo(format);
+    }
+
+    private static VirtualCameraCallback createNoOpCallback() {
+        return new VirtualCameraCallback() {
+
+            @Override
+            public void onStreamConfigured(
+                    int streamId,
+                    @NonNull Surface surface,
+                    @NonNull VirtualCameraStreamConfig streamConfig) {}
+
+            @Override
+            public void onProcessCaptureRequest(
+                    int streamId, long frameId, @Nullable VirtualCameraMetadata metadata) {}
+
+            @Override
+            public void onStreamClosed(int streamId) {}
+        };
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index c632727f..9e5bea7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -1680,4 +1680,47 @@
         assertThat(mHdmiControlService.isSystemAudioActivated()).isTrue();
 
     }
+
+    @Test
+    public void onAddressAllocated_startRequestActiveSourceAction_playbackActiveSource() {
+        HdmiCecMessage requestActiveSource =
+                HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+        HdmiCecMessage activeSourceFromPlayback =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x1000);
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+
+        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+        mNativeWrapper.clearResultMessages();
+        mNativeWrapper.onCecMessage(activeSourceFromPlayback);
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+    }
+
+    @Test
+    public void onAddressAllocated_startRequestActiveSourceAction_noActiveSource() {
+        HdmiCecMessage requestActiveSource =
+                HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+
+        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSourceFromTv);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index d09aa89..3748527 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -44,6 +44,7 @@
 import android.os.IHintSession;
 import android.os.PerformanceHintManager;
 import android.os.Process;
+import android.os.WorkDuration;
 import android.util.Log;
 
 import com.android.server.FgThread;
@@ -89,6 +90,11 @@
     private static final long[] DURATIONS_ZERO = new long[] {};
     private static final long[] TIMESTAMPS_ZERO = new long[] {};
     private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
+    private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] {
+        new WorkDuration(1L, 11L, 8L, 4L, 1L),
+        new WorkDuration(2L, 13L, 8L, 6L, 2L),
+        new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L),
+    };
 
     @Mock private Context mContext;
     @Mock private HintManagerService.NativeWrapper mNativeWrapperMock;
@@ -593,4 +599,55 @@
         }
         a.close();
     }
+
+    @Test
+    public void testReportActualWorkDuration2() throws Exception {
+        HintManagerService service = createService();
+        IBinder token = new Binder();
+
+        AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+                .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+
+        a.updateTargetWorkDuration(100L);
+        a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
+        verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(),
+                eq(WORK_DURATIONS_THREE));
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            a.reportActualWorkDuration2(new WorkDuration[] {});
+        });
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(0L, 11L, 8L, 4L, 1L)});
+        });
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 0L, 8L, 4L, 1L)});
+        });
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)});
+        });
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            a.reportActualWorkDuration2(
+                    new WorkDuration[] {new WorkDuration(1L, 11L, 8L, -1L, 1L)});
+        });
+
+        reset(mNativeWrapperMock);
+        // Set session to background, then the duration would not be updated.
+        service.mUidObserver.onUidStateChanged(
+                a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+
+        // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
+        final CountDownLatch latch = new CountDownLatch(1);
+        FgThread.getHandler().post(() -> {
+            latch.countDown();
+        });
+        latch.await();
+
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
+        a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
+        verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
new file mode 100644
index 0000000..8dcf89b
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.service.notification.ZenDeviceEffects;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ZenDeviceEffectsTest extends UiServiceTestCase {
+
+    @Test
+    public void builder() {
+        ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
+                .setShouldDimWallpaper(true)
+                .setShouldDisableTapToWake(true).setShouldDisableTapToWake(false)
+                .setShouldDisableTiltToWake(true)
+                .setShouldMaximizeDoze(true)
+                .setShouldUseNightMode(false)
+                .setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true)
+                .build();
+
+        assertThat(deviceEffects.shouldDimWallpaper()).isTrue();
+        assertThat(deviceEffects.shouldDisableAutoBrightness()).isFalse();
+        assertThat(deviceEffects.shouldDisableTapToWake()).isFalse();
+        assertThat(deviceEffects.shouldDisableTiltToWake()).isTrue();
+        assertThat(deviceEffects.shouldDisableTouch()).isFalse();
+        assertThat(deviceEffects.shouldDisplayGrayscale()).isFalse();
+        assertThat(deviceEffects.shouldMaximizeDoze()).isTrue();
+        assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse();
+        assertThat(deviceEffects.shouldUseNightMode()).isFalse();
+        assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue();
+    }
+
+    @Test
+    public void builder_fromInstance() {
+        ZenDeviceEffects original = new ZenDeviceEffects.Builder()
+                .setShouldDimWallpaper(true)
+                .setShouldDisableTiltToWake(true)
+                .setShouldUseNightMode(true)
+                .setShouldSuppressAmbientDisplay(true)
+                .build();
+
+        ZenDeviceEffects modified = new ZenDeviceEffects.Builder(original)
+                .setShouldDisplayGrayscale(true)
+                .setShouldUseNightMode(false)
+                .build();
+
+        assertThat(modified.shouldDimWallpaper()).isTrue(); // from original
+        assertThat(modified.shouldDisableTiltToWake()).isTrue(); // from original
+        assertThat(modified.shouldDisplayGrayscale()).isTrue(); // updated
+        assertThat(modified.shouldUseNightMode()).isFalse(); // updated
+        assertThat(modified.shouldSuppressAmbientDisplay()).isTrue(); // from original
+    }
+
+    @Test
+    public void writeToParcel_parcelsAndUnparcels() {
+        ZenDeviceEffects source = new ZenDeviceEffects.Builder()
+                .setShouldDimWallpaper(true)
+                .setShouldDisableTouch(true)
+                .setShouldMinimizeRadioUsage(true)
+                .setShouldUseNightMode(true)
+                .setShouldSuppressAmbientDisplay(true)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        ZenDeviceEffects copy;
+        try {
+            source.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            copy = ZenDeviceEffects.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+
+        assertThat(copy.shouldDimWallpaper()).isTrue();
+        assertThat(copy.shouldDisableTouch()).isTrue();
+        assertThat(copy.shouldMinimizeRadioUsage()).isTrue();
+        assertThat(copy.shouldUseNightMode()).isTrue();
+        assertThat(copy.shouldSuppressAmbientDisplay()).isTrue();
+        assertThat(copy.shouldDisplayGrayscale()).isFalse();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 0c996e0..1776ba5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -195,6 +195,8 @@
         setBooted(mAtm);
         // Because the booted state is set, avoid starting real home if there is no task.
         doReturn(false).when(mRootWindowContainer).resumeHomeActivity(any(), anyString(), any());
+        // Do not execute the transaction, because we can't verify the parameter after it recycles.
+        doNothing().when(mClientLifecycleManager).scheduleTransaction(any());
     }
 
     private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() {
@@ -262,7 +264,7 @@
                 pauseFound.value = true;
             }
             return null;
-        }).when(activity.app.getThread()).scheduleTransaction(any());
+        }).when(mClientLifecycleManager).scheduleTransaction(any());
 
         activity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
 
@@ -477,7 +479,7 @@
                 .build();
         final Task task = activity.getTask();
         activity.setState(DESTROYED, "Testing");
-        clearInvocations(mAtm.getLifecycleManager());
+        clearInvocations(mClientLifecycleManager);
 
         final Configuration newConfig = new Configuration(task.getConfiguration());
         newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT
@@ -487,7 +489,7 @@
 
         ensureActivityConfiguration(activity);
 
-        verify(mAtm.getLifecycleManager(), never())
+        verify(mClientLifecycleManager, never())
                 .scheduleTransaction(any(), isA(ActivityConfigurationChangeItem.class));
     }
 
@@ -500,7 +502,7 @@
         // test properly.
         activity.finishRelaunching();
         // Clear out any calls to scheduleTransaction from launching the activity.
-        reset(mAtm.getLifecycleManager());
+        reset(mClientLifecycleManager);
 
         final Task task = activity.getTask();
         activity.setState(RESUMED, "Testing");
@@ -517,7 +519,7 @@
         // The configuration change is still sent to the activity, even if it doesn't relaunch.
         final ActivityConfigurationChangeItem expected =
                 ActivityConfigurationChangeItem.obtain(activity.token, newConfig);
-        verify(mAtm.getLifecycleManager()).scheduleTransaction(
+        verify(mClientLifecycleManager).scheduleTransaction(
                 eq(activity.app.getThread()), eq(expected));
     }
 
@@ -558,19 +560,7 @@
         activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
                 activity.getConfiguration()));
 
-        clearInvocations(mAtm.getLifecycleManager());
-        final Configuration newConfig = new Configuration(activity.getConfiguration());
-        final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
-        final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp);
-        if (newConfig.orientation == ORIENTATION_PORTRAIT) {
-            newConfig.orientation = ORIENTATION_LANDSCAPE;
-            newConfig.screenWidthDp = longSide;
-            newConfig.screenHeightDp = shortSide;
-        } else {
-            newConfig.orientation = ORIENTATION_PORTRAIT;
-            newConfig.screenWidthDp = shortSide;
-            newConfig.screenHeightDp = longSide;
-        }
+        clearInvocations(mClientLifecycleManager);
 
         // Mimic the behavior that display doesn't handle app's requested orientation.
         final DisplayContent dc = activity.getTask().getDisplayContent();
@@ -578,12 +568,15 @@
         doReturn(false).when(dc).handlesOrientationChangeFromDescendant(anyInt());
 
         final int requestedOrientation;
-        switch (newConfig.orientation) {
-            case ORIENTATION_LANDSCAPE:
-                requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE;
-                break;
+        final int expectedOrientation;
+        switch (activity.getConfiguration().orientation) {
             case ORIENTATION_PORTRAIT:
+                requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE;
+                expectedOrientation = ORIENTATION_LANDSCAPE;
+                break;
+            case ORIENTATION_LANDSCAPE:
                 requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+                expectedOrientation = ORIENTATION_PORTRAIT;
                 break;
             default:
                 throw new IllegalStateException("Orientation in new config should be either"
@@ -595,11 +588,11 @@
 
         activity.setRequestedOrientation(requestedOrientation);
 
+        final Configuration currentConfig = activity.getConfiguration();
+        assertEquals(expectedOrientation, currentConfig.orientation);
         final ActivityConfigurationChangeItem expected =
-                ActivityConfigurationChangeItem.obtain(activity.token, newConfig);
-        verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()),
-                eq(expected));
-
+                ActivityConfigurationChangeItem.obtain(activity.token, currentConfig);
+        verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected);
         verify(displayRotation).onSetRequestedOrientation();
     }
 
@@ -788,7 +781,7 @@
 
         final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         try {
-            clearInvocations(mAtm.getLifecycleManager());
+            clearInvocations(mClientLifecycleManager);
             doReturn(false).when(stack).isTranslucent(any());
             assertTrue(task.shouldBeVisible(null /* starting */));
 
@@ -796,7 +789,10 @@
                     activity.getConfiguration()));
 
             final Configuration newConfig = new Configuration(activity.getConfiguration());
-            final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
+            final int shortSide = newConfig.screenWidthDp == newConfig.screenHeightDp
+                    // To avoid the case where it is always portrait because of width == height.
+                    ? newConfig.screenWidthDp - 1
+                    : Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
             final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp);
             if (newConfig.orientation == ORIENTATION_PORTRAIT) {
                 newConfig.orientation = ORIENTATION_LANDSCAPE;
@@ -811,12 +807,12 @@
             task.onConfigurationChanged(newConfig);
 
             activity.ensureActivityConfiguration(0 /* globalChanges */,
-                    false /* preserveWindow */, true /* ignoreStopState */);
+                    false /* preserveWindow */, true /* ignoreVisibility */);
 
             final ActivityConfigurationChangeItem expected =
-                    ActivityConfigurationChangeItem.obtain(activity.token, newConfig);
-            verify(mAtm.getLifecycleManager()).scheduleTransaction(
-                    eq(activity.app.getThread()), eq(expected));
+                    ActivityConfigurationChangeItem.obtain(activity.token,
+                            activity.getConfiguration());
+            verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected);
         } finally {
             stack.getDisplayArea().removeChild(stack);
         }
@@ -1259,12 +1255,12 @@
         targetActivity.resultTo = sourceActivity;
         targetActivity.setForceSendResultForMediaProjection();
 
-        clearInvocations(mAtm.getLifecycleManager());
+        clearInvocations(mClientLifecycleManager);
 
         targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
 
         try {
-            verify(mAtm.getLifecycleManager(), atLeastOnce()).scheduleTransaction(
+            verify(mClientLifecycleManager, atLeastOnce()).scheduleTransaction(
                     any(ClientTransaction.class));
         } catch (RemoteException ignored) {
         }
@@ -1283,7 +1279,7 @@
         targetActivity.setState(RESUMED, "test");
         targetActivity.resultTo = resultToActivity;
 
-        clearInvocations(mAtm.getLifecycleManager());
+        clearInvocations(mClientLifecycleManager);
         targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
         waitUntilHandlersIdle();
 
@@ -1786,10 +1782,10 @@
             final ActivityRecord activity = createActivityWithTask();
             final WindowProcessController wpc = activity.app;
             setup.accept(activity);
-            clearInvocations(mAtm.getLifecycleManager());
+            clearInvocations(mClientLifecycleManager);
             activity.getTask().removeImmediately("test");
             try {
-                verify(mAtm.getLifecycleManager()).scheduleTransaction(any(),
+                verify(mClientLifecycleManager).scheduleTransaction(any(),
                         isA(DestroyActivityItem.class));
             } catch (RemoteException ignored) {
             }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index e7ebd7db..3c027ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -39,9 +39,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.never;
 
@@ -51,7 +52,7 @@
 import android.app.ActivityManager;
 import android.app.IApplicationThread;
 import android.app.PictureInPictureParams;
-import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.EnterPipRequestedItem;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -90,9 +91,6 @@
 @RunWith(WindowTestRunner.class)
 public class ActivityTaskManagerServiceTests extends WindowTestsBase {
 
-    private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor =
-            ArgumentCaptor.forClass(ClientTransaction.class);
-
     private static final String DEFAULT_PACKAGE_NAME = "my.application.package";
     private static final int DEFAULT_USER_ID = 100;
 
@@ -123,53 +121,42 @@
         final ClientLifecycleManager mockLifecycleManager = mock(ClientLifecycleManager.class);
         doReturn(mockLifecycleManager).when(mAtm).getLifecycleManager();
         doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
+        clearInvocations(mClientLifecycleManager);
 
         mAtm.mActivityClientController.requestPictureInPictureMode(activity);
 
-        verify(mockLifecycleManager).scheduleTransaction(mClientTransactionCaptor.capture());
-        final ClientTransaction transaction = mClientTransactionCaptor.getValue();
+        final ArgumentCaptor<ClientTransactionItem> clientTransactionItemCaptor =
+                ArgumentCaptor.forClass(ClientTransactionItem.class);
+        verify(mockLifecycleManager).scheduleTransaction(any(),
+                clientTransactionItemCaptor.capture());
+        final ClientTransactionItem transactionItem = clientTransactionItemCaptor.getValue();
         // Check that only an enter pip request item callback was scheduled.
-        assertEquals(1, transaction.getCallbacks().size());
-        assertTrue(transaction.getCallbacks().get(0) instanceof EnterPipRequestedItem);
-        // Check the activity lifecycle state remains unchanged.
-        assertNull(transaction.getLifecycleStateRequest());
+        assertTrue(transactionItem instanceof EnterPipRequestedItem);
     }
 
     @Test
     public void testOnPictureInPictureRequested_cannotEnterPip() throws RemoteException {
         final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
-        ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
         doReturn(false).when(activity).inPinnedWindowingMode();
         doReturn(false).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
+        clearInvocations(mClientLifecycleManager);
 
         mAtm.mActivityClientController.requestPictureInPictureMode(activity);
 
-        verify(lifecycleManager, atLeast(0))
-                .scheduleTransaction(mClientTransactionCaptor.capture());
-        final ClientTransaction transaction = mClientTransactionCaptor.getValue();
-        // Check that none are enter pip request items.
-        transaction.getCallbacks().forEach(clientTransactionItem -> {
-            assertFalse(clientTransactionItem instanceof EnterPipRequestedItem);
-        });
+        verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any());
     }
 
     @Test
     public void testOnPictureInPictureRequested_alreadyInPIPMode() throws RemoteException {
         final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
-        ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
         doReturn(true).when(activity).inPinnedWindowingMode();
+        clearInvocations(mClientLifecycleManager);
 
         mAtm.mActivityClientController.requestPictureInPictureMode(activity);
 
-        verify(lifecycleManager, atLeast(0))
-                .scheduleTransaction(mClientTransactionCaptor.capture());
-        final ClientTransaction transaction = mClientTransactionCaptor.getValue();
-        // Check that none are enter pip request items.
-        transaction.getCallbacks().forEach(clientTransactionItem -> {
-            assertFalse(clientTransactionItem instanceof EnterPipRequestedItem);
-        });
+        verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 0d4c443..c6796dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -787,22 +787,44 @@
     public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() {
         spyOn(mController);
         doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
+        mDisplayContent.setIgnoreOrientationRequest(true);
 
         assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
                 /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
     }
+    @Test
+    public void testOverrideOrientationIfNeeded_respectOrientationRequestOverUserFullScreen() {
+        spyOn(mController);
+        doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
+        mDisplayContent.setIgnoreOrientationRequest(false);
+
+        assertNotEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
+    }
 
     @Test
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION})
     public void testOverrideOrientationIfNeeded_userFullScreenOverrideOverSystem_returnsUser() {
         spyOn(mController);
         doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
+        mDisplayContent.setIgnoreOrientationRequest(true);
 
         assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
                 /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
     }
 
     @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION})
+    public void testOverrideOrientationIfNeeded_respectOrientationReqOverUserFullScreenAndSystem() {
+        spyOn(mController);
+        doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
+        mDisplayContent.setIgnoreOrientationRequest(false);
+
+        assertNotEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+    }
+
+    @Test
     public void testOverrideOrientationIfNeeded_userFullScreenOverrideDisabled_returnsUnchanged() {
         spyOn(mController);
         doReturn(false).when(mController).shouldApplyUserFullscreenOverride();
@@ -872,14 +894,6 @@
     }
 
     @Test
-    public void testShouldApplyUserFullscreenOverride_disabledIgnoreOrientationRequest() {
-        prepareActivityThatShouldApplyUserFullscreenOverride();
-        mDisplayContent.setIgnoreOrientationRequest(false);
-
-        assertFalse(mController.shouldApplyUserFullscreenOverride());
-    }
-
-    @Test
     public void testShouldApplyUserFullscreenOverride_returnsTrue() {
         prepareActivityThatShouldApplyUserFullscreenOverride();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 491d5b5..8de45b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -202,8 +202,7 @@
                 any() /* starting */, anyInt() /* configChanges */,
                 anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
         doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt());
-        ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
-        doNothing().when(lifecycleManager).scheduleTransaction(any());
+        doNothing().when(mClientLifecycleManager).scheduleTransaction(any());
 
         startRecentsActivity();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index a7c14c3..c3102e0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -31,6 +31,7 @@
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -1301,6 +1302,27 @@
     }
 
     @Test
+    public void testShouldCreateCompatDisplayUserAspectRatioFullscreenOverride() {
+        setUpDisplaySizeWithApp(1000, 2500);
+
+        // Make the task root resizable.
+        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
+
+        // Create an activity on the same task.
+        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
+                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+
+        // Simulate the user selecting the fullscreen user aspect ratio override
+        spyOn(activity.mWmService.mLetterboxConfiguration);
+        spyOn(activity.mLetterboxUiController);
+        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
+                .isUserAppAspectRatioFullscreenEnabled();
+        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(activity.mLetterboxUiController)
+                .getUserMinAspectRatioOverrideCode();
+        assertFalse(activity.shouldCreateCompatDisplayInsets());
+    }
+
+    @Test
     @EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS})
     public void testNeverSandboxDisplayApis_configEnabled_sandboxingNotApplied() {
         setUpDisplaySizeWithApp(1000, 1200);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 4a33594..6655932 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -31,7 +31,9 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -143,6 +145,15 @@
         secureWindow.mAttrs.flags |= FLAG_SECURE;
         assertEquals(SNAPSHOT_MODE_APP_THEME,
                 mWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask()));
+
+        // Verifies that if the snapshot can be cached, then getSnapshotMode should be respected.
+        // Otherwise a real snapshot can be taken even if the activity disables recents screenshot.
+        spyOn(mWm.mTaskSnapshotController);
+        final int disabledInRecentsTaskId = disabledWindow.getTask().mTaskId;
+        mAtm.takeTaskSnapshot(disabledInRecentsTaskId, true /* updateCache */);
+        verify(mWm.mTaskSnapshotController, never()).prepareTaskSnapshot(any(), any());
+        mAtm.takeTaskSnapshot(disabledInRecentsTaskId, false /* updateCache */);
+        verify(mWm.mTaskSnapshotController).prepareTaskSnapshot(any(), any());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index a83caa4..dade3b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -636,6 +636,7 @@
         transition.collect(app);
         controller.requestStartTransition(transition, null /* startTask */, remoteTransition,
                 null /* displayChange */);
+        assertTrue(delegateProc.isRunningRemoteTransition());
         testPlayer.startTransition();
         app.onStartingWindowDrawn();
         // The task appeared event should be deferred until transition ready.
@@ -643,7 +644,6 @@
         testPlayer.onTransactionReady(app.getSyncTransaction());
         assertTrue(task.taskAppearedReady());
         assertTrue(playerProc.isRunningRemoteTransition());
-        assertTrue(delegateProc.isRunningRemoteTransition());
         assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
         assertTrue(app.isVisible());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 1494f94..28e0c6b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1337,8 +1337,8 @@
 
         // Since we have a window we have to wait for it to draw to finish sync.
         verify(mockCallback, never()).onTransactionReady(anyInt(), any());
-        assertTrue(w1.useBLASTSync());
-        assertTrue(w2.useBLASTSync());
+        assertTrue(w1.syncNextBuffer());
+        assertTrue(w2.syncNextBuffer());
 
         // Make second (bottom) ready. If we started with the top, since activities fillsParent
         // by default, the sync would be considered finished.
@@ -1348,16 +1348,16 @@
 
         assertEquals(SYNC_STATE_READY, w2.mSyncState);
         // Even though one Window finished drawing, both windows should still be using blast sync
-        assertTrue(w1.useBLASTSync());
-        assertTrue(w2.useBLASTSync());
+        assertTrue(w1.syncNextBuffer());
+        assertTrue(w2.syncNextBuffer());
 
         // A drawn window can complete the sync state automatically.
         w1.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
         makeLastConfigReportedToClient(w1, true /* visible */);
         mWm.mSyncEngine.onSurfacePlacement();
         verify(mockCallback).onTransactionReady(anyInt(), any());
-        assertFalse(w1.useBLASTSync());
-        assertFalse(w2.useBLASTSync());
+        assertFalse(w1.syncNextBuffer());
+        assertFalse(w2.syncNextBuffer());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index b89182d..46cff8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -306,29 +306,28 @@
 
     @Test
     public void testCachedStateConfigurationChange() throws RemoteException {
-        final ClientLifecycleManager clientManager = mAtm.getLifecycleManager();
-        doNothing().when(clientManager).scheduleTransaction(any(), any());
+        doNothing().when(mClientLifecycleManager).scheduleTransaction(any(), any());
         final IApplicationThread thread = mWpc.getThread();
         final Configuration newConfig = new Configuration(mWpc.getConfiguration());
         newConfig.densityDpi += 100;
         // Non-cached state will send the change directly.
         mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
-        clearInvocations(clientManager);
+        clearInvocations(mClientLifecycleManager);
         mWpc.onConfigurationChanged(newConfig);
-        verify(clientManager).scheduleTransaction(eq(thread), any());
+        verify(mClientLifecycleManager).scheduleTransaction(eq(thread), any());
 
         // Cached state won't send the change.
-        clearInvocations(clientManager);
+        clearInvocations(mClientLifecycleManager);
         mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
         newConfig.densityDpi += 100;
         mWpc.onConfigurationChanged(newConfig);
-        verify(clientManager, never()).scheduleTransaction(eq(thread), any());
+        verify(mClientLifecycleManager, never()).scheduleTransaction(eq(thread), any());
 
         // Cached -> non-cached will send the previous deferred config immediately.
         mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
         final ArgumentCaptor<ConfigurationChangeItem> captor =
                 ArgumentCaptor.forClass(ConfigurationChangeItem.class);
-        verify(clientManager).scheduleTransaction(eq(thread), captor.capture());
+        verify(mClientLifecycleManager).scheduleTransaction(eq(thread), captor.capture());
         final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
         captor.getValue().preExecute(client);
         final ArgumentCaptor<Configuration> configCaptor =
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 014d57d..67384b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -551,7 +551,7 @@
         final SurfaceControl.Transaction[] handledT = { null };
         // The normal case that the draw transaction is applied with finishing drawing.
         win.applyWithNextDraw(t -> handledT[0] = t);
-        assertTrue(win.useBLASTSync());
+        assertTrue(win.syncNextBuffer());
         final SurfaceControl.Transaction drawT = new StubTransaction();
         final SurfaceControl.Transaction currT = win.getSyncTransaction();
         clearInvocations(currT);
@@ -560,12 +560,12 @@
         // The draw transaction should be merged to current transaction even if the state is hidden.
         verify(currT).merge(eq(drawT));
         assertEquals(drawT, handledT[0]);
-        assertFalse(win.useBLASTSync());
+        assertFalse(win.syncNextBuffer());
 
         // If the window is gone before reporting drawn, the sync state should be cleared.
         win.applyWithNextDraw(t -> handledT[0] = t);
         win.destroySurfaceUnchecked();
-        assertFalse(win.useBLASTSync());
+        assertFalse(win.syncNextBuffer());
         assertNotEquals(drawT, handledT[0]);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index e0ed642..df4af11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -152,6 +152,7 @@
     ActivityTaskManagerService mAtm;
     RootWindowContainer mRootWindowContainer;
     ActivityTaskSupervisor mSupervisor;
+    ClientLifecycleManager mClientLifecycleManager;
     WindowManagerService mWm;
     private final IWindow mIWindow = new TestIWindow();
     private Session mTestSession;
@@ -215,6 +216,7 @@
         mAtm = mSystemServicesTestRule.getActivityTaskManagerService();
         mSupervisor = mAtm.mTaskSupervisor;
         mRootWindowContainer = mAtm.mRootWindowContainer;
+        mClientLifecycleManager = mAtm.getLifecycleManager();
         mWm = mSystemServicesTestRule.getWindowManagerService();
         mOriginalPerDisplayFocusEnabled = mWm.mPerDisplayFocusEnabled;
         SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java
index b1a7d81..8c6e101 100644
--- a/telecomm/java/android/telecom/CallAttributes.java
+++ b/telecomm/java/android/telecom/CallAttributes.java
@@ -24,6 +24,8 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -83,6 +85,7 @@
 
     /** @hide */
     @IntDef(value = {DIRECTION_INCOMING, DIRECTION_OUTGOING})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface Direction {
     }
     /**
@@ -96,6 +99,7 @@
 
     /** @hide */
     @IntDef(value = {AUDIO_CALL, VIDEO_CALL})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface CallType {
     }
     /**
@@ -110,6 +114,7 @@
 
     /** @hide */
     @IntDef(value = {SUPPORTS_SET_INACTIVE, SUPPORTS_STREAM, SUPPORTS_TRANSFER}, flag = true)
+    @Retention(RetentionPolicy.SOURCE)
     public @interface CallCapability {
     }
     /**
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 3158ad8..287aa65 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -7,10 +7,12 @@
 tgunn@google.com
 huiwang@google.com
 jayachandranc@google.com
-chinmayd@google.com
 amruthr@google.com
 sasindran@google.com
 
 # Requiring TL ownership for new carrier config keys.
 per-file CarrierConfigManager.java=set noparent
 per-file CarrierConfigManager.java=amruthr@google.com,tgunn@google.com,rgreenwalt@google.com,satk@google.com
+
+#Domain Selection is jointly owned, add additional owners for domain selection specific files
+per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index baeff06..c124079 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8875,6 +8875,19 @@
                 KEY_PREFIX + "child_session_aes_ctr_key_size_int_array";
 
         /**
+         * List of supported key sizes for AES Galois/Counter Mode (GCM) encryption mode
+         * of child session.
+         * Possible values are:
+         * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED},
+         * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128},
+         * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192},
+         * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256}
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS)
+        public static final String KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY =
+                KEY_PREFIX + "child_session_aes_gcm_key_size_int_array";
+
+        /**
          * List of supported encryption algorithms for child session. Possible values are
          * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC},
          * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR}
@@ -8883,6 +8896,16 @@
                 KEY_PREFIX + "supported_child_session_encryption_algorithms_int_array";
 
         /**
+         * List of supported AEAD algorithms for child session. Possible values are
+         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_8},
+         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_12},
+         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_16}
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS)
+        public static final String KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY =
+                KEY_PREFIX + "supported_child_session_aead_algorithms_int_array";
+
+        /**
          * Time in seconds after which the IKE session is terminated if rekey procedure is not
          * successful. If not set or set to <= 0, default value is 3600 seconds.
          */
@@ -8919,6 +8942,19 @@
                  KEY_PREFIX + "ike_session_encryption_aes_ctr_key_size_int_array";
 
         /**
+         * List of supported key sizes for AES Galois/Counter Mode (GCM) encryption mode
+         * of IKE session.
+         * Possible values -
+         * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED},
+         * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128},
+         * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192},
+         * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256}
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS)
+        public static final String KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY =
+                KEY_PREFIX + "ike_session_encryption_aes_gcm_key_size_int_array";
+
+        /**
          * List of supported encryption algorithms for IKE session. Possible values are
          * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC},
          * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR}
@@ -8927,6 +8963,16 @@
                 KEY_PREFIX + "supported_ike_session_encryption_algorithms_int_array";
 
         /**
+         * List of supported AEAD algorithms for IKE session. Possible values are
+         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_8},
+         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_12},
+         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_16}
+         */
+        @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS)
+        public static final String KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY =
+                KEY_PREFIX + "supported_ike_session_aead_algorithms_int_array";
+
+        /**
          * List of supported integrity algorithms for IKE session. Possible values are
          * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_NONE},
          * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_HMAC_SHA1_96},
@@ -9156,9 +9202,13 @@
                     KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY,
                     new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC});
             defaults.putIntArray(
+                    KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {});
+            defaults.putIntArray(
                     KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY,
                     new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC});
             defaults.putIntArray(
+                    KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {});
+            defaults.putIntArray(
                     KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY,
                     new int[] {
                         SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96,
@@ -9207,6 +9257,10 @@
                       SaProposal.KEY_LEN_AES_192,
                       SaProposal.KEY_LEN_AES_256});
             defaults.putIntArray(
+                    KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {});
+            defaults.putIntArray(
+                    KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {});
+            defaults.putIntArray(
                     KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY,
                     new int[] {EPDG_ADDRESS_PLMN, EPDG_ADDRESS_STATIC});
             defaults.putIntArray(
diff --git a/telephony/java/android/telephony/NumberVerificationCallback.java b/telephony/java/android/telephony/NumberVerificationCallback.java
index b00c573..71df1f2 100644
--- a/telephony/java/android/telephony/NumberVerificationCallback.java
+++ b/telephony/java/android/telephony/NumberVerificationCallback.java
@@ -20,6 +20,9 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A callback for number verification. After a request for number verification is received,
  * the system will call {@link #onCallReceived(String)} if a phone call was received from a number
@@ -34,6 +37,7 @@
             REASON_TOO_MANY_CALLS, REASON_CONCURRENT_REQUESTS, REASON_IN_ECBM,
             REASON_IN_EMERGENCY_CALL},
             prefix = {"REASON_"})
+    @Retention(RetentionPolicy.SOURCE)
     @interface NumberVerificationFailureReason {}
 
     /**
diff --git a/telephony/java/android/telephony/PinResult.java b/telephony/java/android/telephony/PinResult.java
index b8c1ffe..14713c7 100644
--- a/telephony/java/android/telephony/PinResult.java
+++ b/telephony/java/android/telephony/PinResult.java
@@ -25,6 +25,8 @@
 
 import com.android.internal.telephony.PhoneConstants;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -46,6 +48,7 @@
             PIN_RESULT_TYPE_FAILURE,
             PIN_RESULT_TYPE_ABORTED,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface PinResultType {}
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 1c5761d..b96914e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3400,6 +3400,7 @@
                     SIM_STATE_LOADED,
                     SIM_STATE_PRESENT,
             })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface SimState {}
 
     /**
@@ -10170,6 +10171,7 @@
                 CALL_COMPOSER_STATUS_ON,
                 CALL_COMPOSER_STATUS_OFF,
             })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface CallComposerStatus {}
 
     /**
@@ -13157,7 +13159,7 @@
             CARRIER_RESTRICTION_STATUS_RESTRICTED,
             CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER
     })
-
+    @Retention(RetentionPolicy.SOURCE)
     public @interface CarrierRestrictionStatus {
     }
 
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 4b1a726..3e87872 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -951,8 +951,8 @@
      * See 3GPP TS 23.501 section 5.6.13
      *
      * @return True if the PDU session for this APN should always be on and false otherwise
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG)
     public boolean isAlwaysOn() {
         return mAlwaysOn;
     }
@@ -2282,9 +2282,9 @@
          * See 3GPP TS 23.501 section 5.6.13
          *
          * @param alwaysOn the always on status to set for this APN
-         * @hide
          */
-        public Builder setAlwaysOn(boolean alwaysOn) {
+        @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG)
+        public @NonNull Builder setAlwaysOn(boolean alwaysOn) {
             this.mAlwaysOn = alwaysOn;
             return this;
         }
diff --git a/telephony/java/android/telephony/data/ThrottleStatus.java b/telephony/java/android/telephony/data/ThrottleStatus.java
index 0335c68..0dff6ff 100644
--- a/telephony/java/android/telephony/data/ThrottleStatus.java
+++ b/telephony/java/android/telephony/data/ThrottleStatus.java
@@ -27,6 +27,8 @@
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -52,6 +54,7 @@
             ThrottleStatus.THROTTLE_TYPE_NONE,
             ThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface ThrottleType {
     }
 
@@ -76,6 +79,7 @@
             ThrottleStatus.RETRY_TYPE_NEW_CONNECTION,
             ThrottleStatus.RETRY_TYPE_HANDOVER,
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface RetryType {
     }
 
diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.java b/telephony/java/android/telephony/ims/MediaQualityStatus.java
index 76394fe..e2df0d4 100644
--- a/telephony/java/android/telephony/ims/MediaQualityStatus.java
+++ b/telephony/java/android/telephony/ims/MediaQualityStatus.java
@@ -24,6 +24,8 @@
 import android.os.Parcelable;
 import android.telephony.AccessNetworkConstants.TransportType;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -49,6 +51,7 @@
                     MEDIA_SESSION_TYPE_AUDIO,
                     MEDIA_SESSION_TYPE_VIDEO,
             })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface MediaSessionType {}
 
     /**
diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
index f367e40..39c9d8b 100644
--- a/telephony/java/android/telephony/ims/RcsClientConfiguration.java
+++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
@@ -22,6 +22,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -35,6 +37,7 @@
     /**@hide*/
     @StringDef(prefix = "RCS_PROFILE_",
             value = {RCS_PROFILE_1_0, RCS_PROFILE_2_3, RCS_PROFILE_2_4})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface StringRcsProfile {}
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 15a20cb..4c53f8a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3147,4 +3147,16 @@
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
     void unregisterForSatelliteCapabilitiesChanged(int subId,
             in ISatelliteCapabilitiesCallback callback);
+
+    /**
+     * This API can be used by only CTS to override the cached value for the device overlay config
+     * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
+     * outgoing satellite datagrams should be sent to modem in demo mode.
+     *
+     * @param shouldSendToDemoMode Whether send datagram in demo mode should be sent to satellite
+     * modem or not.
+     *
+     * @return {@code true} if the operation is successful, {@code false} otherwise.
+     */
+    boolean setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode);
 }
diff --git a/tests/CtsSurfaceControlTestsStaging/OWNERS b/tests/CtsSurfaceControlTestsStaging/OWNERS
index 438b7f2..8ad3446 100644
--- a/tests/CtsSurfaceControlTestsStaging/OWNERS
+++ b/tests/CtsSurfaceControlTestsStaging/OWNERS
@@ -1 +1 @@
-include platform/cts:/tests/tests/graphics/OWNERS
\ No newline at end of file
+include platform/cts:/tests/tests/graphics/src/android/graphics/OWNERS
\ No newline at end of file
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 159c6fd..c638873 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -2514,6 +2514,28 @@
     }
   }
 
+  // Parse the feature flag values. An argument that starts with '@' points to a file to read flag
+  // values from.
+  std::vector<std::string> all_feature_flags_args;
+  for (const std::string& arg : feature_flags_args_) {
+    if (util::StartsWith(arg, "@")) {
+      const std::string path = arg.substr(1, arg.size() - 1);
+      std::string error;
+      if (!file::AppendArgsFromFile(path, &all_feature_flags_args, &error)) {
+        context.GetDiagnostics()->Error(android::DiagMessage(path) << error);
+        return 1;
+      }
+    } else {
+      all_feature_flags_args.push_back(arg);
+    }
+  }
+
+  for (const std::string& arg : all_feature_flags_args) {
+    if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
+      return 1;
+    }
+  }
+
   if (context.GetPackageType() != PackageType::kStaticLib && stable_id_file_path_) {
     if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path_.value(),
         &options_.stable_id_map)) {
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index a08f385..26713fd 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -17,11 +17,17 @@
 #ifndef AAPT2_LINK_H
 #define AAPT2_LINK_H
 
+#include <optional>
 #include <regex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
 
 #include "Command.h"
 #include "Resource.h"
 #include "androidfw/IDiagnostics.h"
+#include "cmd/Util.h"
 #include "format/binary/TableFlattener.h"
 #include "format/proto/ProtoSerialize.h"
 #include "link/ManifestFixer.h"
@@ -72,6 +78,7 @@
   bool use_sparse_encoding = false;
   std::unordered_set<std::string> extensions_to_not_compress;
   std::optional<std::regex> regex_to_not_compress;
+  FeatureFlagValues feature_flag_values;
 
   // Static lib options.
   bool no_static_lib_packages = false;
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index a92f24b..678d846 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -113,6 +113,56 @@
   return std::move(filter);
 }
 
+bool ParseFeatureFlagsParameter(StringPiece arg, android::IDiagnostics* diag,
+                                FeatureFlagValues* out_feature_flag_values) {
+  if (arg.empty()) {
+    return true;
+  }
+
+  for (StringPiece flag_and_value : util::Tokenize(arg, ',')) {
+    std::vector<std::string> parts = util::Split(flag_and_value, '=');
+    if (parts.empty()) {
+      continue;
+    }
+
+    if (parts.size() > 2) {
+      diag->Error(android::DiagMessage()
+                  << "Invalid feature flag and optional value '" << flag_and_value
+                  << "'. Must be in the format 'flag_name[=true|false]");
+      return false;
+    }
+
+    StringPiece flag_name = util::TrimWhitespace(parts[0]);
+    if (flag_name.empty()) {
+      diag->Error(android::DiagMessage() << "No name given for one or more flags in: " << arg);
+      return false;
+    }
+
+    std::optional<bool> flag_value = {};
+    if (parts.size() == 2) {
+      StringPiece str_flag_value = util::TrimWhitespace(parts[1]);
+      if (!str_flag_value.empty()) {
+        flag_value = ResourceUtils::ParseBool(parts[1]);
+        if (!flag_value.has_value()) {
+          diag->Error(android::DiagMessage() << "Invalid value for feature flag '" << flag_and_value
+                                             << "'. Value must be 'true' or 'false'");
+          return false;
+        }
+      }
+    }
+
+    if (auto [it, inserted] =
+            out_feature_flag_values->try_emplace(std::string(flag_name), flag_value);
+        !inserted) {
+      // We are allowing the same flag to appear multiple times, last value wins.
+      diag->Note(android::DiagMessage()
+                 << "Value for feature flag '" << flag_name << "' was given more than once");
+      it->second = flag_value;
+    }
+  }
+  return true;
+}
+
 // Adjust the SplitConstraints so that their SDK version is stripped if it
 // is less than or equal to the minSdk. Otherwise the resources that have had
 // their SDK version stripped due to minSdk won't ever match.
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 712c07b..9ece5dd 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -17,8 +17,13 @@
 #ifndef AAPT_SPLIT_UTIL_H
 #define AAPT_SPLIT_UTIL_H
 
+#include <functional>
+#include <map>
+#include <memory>
+#include <optional>
 #include <regex>
 #include <set>
+#include <string>
 #include <unordered_set>
 
 #include "AppInfo.h"
@@ -32,6 +37,8 @@
 
 namespace aapt {
 
+using FeatureFlagValues = std::map<std::string, std::optional<bool>, std::less<>>;
+
 // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc).
 // Returns Nothing and logs a human friendly error message if the string was not legal.
 std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg,
@@ -48,6 +55,13 @@
 std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args,
                                                            android::IDiagnostics* diag);
 
+// Parses a feature flags parameter, which can contain one or more pairs of flag names and optional
+// values, and fills in `out_feature_flag_values` with the parsed values. The pairs in the argument
+// are separated by ',' and the name is separated from the value by '=' if there is a value given.
+// Example arg: "flag1=true,flag2=false,flag3=,flag4" where flag3 and flag4 have no given value.
+bool ParseFeatureFlagsParameter(android::StringPiece arg, android::IDiagnostics* diag,
+                                FeatureFlagValues* out_feature_flag_values);
+
 // Adjust the SplitConstraints so that their SDK version is stripped if it
 // is less than or equal to the min_sdk. Otherwise the resources that have had
 // their SDK version stripped due to min_sdk won't ever match.
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 139bfbc..723d87e 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -25,6 +25,7 @@
 #include "util/Files.h"
 
 using ::android::ConfigDescription;
+using testing::Pair;
 using testing::UnorderedElementsAre;
 
 namespace aapt {
@@ -354,6 +355,51 @@
   EXPECT_CONFIG_EQ(constraints, expected_configuration);
 }
 
+TEST(UtilTest, ParseFeatureFlagsParameter_Empty) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_TRUE(ParseFeatureFlagsParameter("", diagnostics, &feature_flag_values));
+  EXPECT_TRUE(feature_flag_values.empty());
+}
+
+TEST(UtilTest, ParseFeatureFlagsParameter_TooManyParts) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_FALSE(ParseFeatureFlagsParameter("foo=bar=baz", diagnostics, &feature_flag_values));
+}
+
+TEST(UtilTest, ParseFeatureFlagsParameter_NoNameGiven) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,=false", diagnostics, &feature_flag_values));
+}
+
+TEST(UtilTest, ParseFeatureFlagsParameter_InvalidValue) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,bar=42", diagnostics, &feature_flag_values));
+}
+
+TEST(UtilTest, ParseFeatureFlagsParameter_DuplicateFlag) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_TRUE(
+      ParseFeatureFlagsParameter("foo=true,bar=true,foo=false", diagnostics, &feature_flag_values));
+  EXPECT_THAT(feature_flag_values, UnorderedElementsAre(Pair("foo", std::optional<bool>(false)),
+                                                        Pair("bar", std::optional<bool>(true))));
+}
+
+TEST(UtilTest, ParseFeatureFlagsParameter_Valid) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_TRUE(ParseFeatureFlagsParameter("foo= true, bar =FALSE,baz=, quux", diagnostics,
+                                         &feature_flag_values));
+  EXPECT_THAT(feature_flag_values,
+              UnorderedElementsAre(Pair("foo", std::optional<bool>(true)),
+                                   Pair("bar", std::optional<bool>(false)),
+                                   Pair("baz", std::nullopt), Pair("quux", std::nullopt)));
+}
+
 TEST (UtilTest, AdjustSplitConstraintsForMinSdk) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
 
diff --git a/tools/lint/README.md b/tools/lint/README.md
index b235ad6..ff8e442 100644
--- a/tools/lint/README.md
+++ b/tools/lint/README.md
@@ -103,10 +103,15 @@
 
 As noted above, this baseline file contains warnings too, which might be undesirable. For example,
 CI tools might surface these warnings in code reviews. In order to create this file without
-warnings, we need to pass another flag to lint: `--nowarn`. The easiest way to do this is to
-locally change the soong code in
-[lint.go](http://cs/aosp-master/build/soong/java/lint.go;l=451;rcl=2e778d5bc4a8d1d77b4f4a3029a4a254ad57db75)
-adding `cmd.Flag("--nowarn")` and running lint again.
+warnings, we need to pass another flag to lint: `--nowarn`. One option is to add the flag to your
+Android.bp file and then run lint again:
+
+```
+  lint: {
+    extra_check_modules: ["AndroidFrameworkLintChecker"],
+    flags: ["--nowarn"],
+  }
+```
 
 # Documentation
 
diff --git a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
index d41fee3..24d203f 100644
--- a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
+++ b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
@@ -24,33 +24,31 @@
 import org.jetbrains.uast.UMethod
 
 /**
- * Given a UMethod, determine if this method is
- * the entrypoint to an interface generated by AIDL,
- * returning the interface name if so, otherwise returning null
+ * Given a UMethod, determine if this method is the entrypoint to an interface
+ * generated by AIDL, returning the interface name if so, otherwise returning
+ * null
  */
 fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? {
-    if (!isContainedInSubclassOfStub(context, node)) return null
-    for (superMethod in node.findSuperMethods()) {
-        for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
-            ?: continue) {
-            if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
-                return superMethod.containingClass?.name
-            }
-        }
+    val containingStub = containingStub(context, node) ?: return null
+    val superMethod = node.findSuperMethods(containingStub)
+    if (superMethod.isEmpty()) return null
+    return containingStub.containingClass?.name
+}
+
+/* Returns the containing Stub class if any. This is not sufficient to infer
+ * that the method itself extends an AIDL generated method. See
+ * getContainingAidlInterface for that purpose.
+ */
+fun containingStub(context: JavaContext, node: UMethod?): PsiClass? {
+    var superClass = node?.containingClass?.superClass
+    while (superClass != null) {
+        if (isStub(context, superClass)) return superClass
+        superClass = superClass.superClass
     }
     return null
 }
 
-fun isContainedInSubclassOfStub(context: JavaContext, node: UMethod?): Boolean {
-    var superClass = node?.containingClass?.superClass
-    while (superClass != null) {
-        if (isStub(context, superClass)) return true
-        superClass = superClass.superClass
-    }
-    return false
-}
-
-fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean {
+private fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean {
     if (psiClass == null) return false
     if (psiClass.name != "Stub") return false
     if (!context.evaluator.isStatic(psiClass)) return false
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 935bade..624a198 100644
--- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -20,6 +20,7 @@
 import com.android.tools.lint.client.api.Vendor
 import com.android.tools.lint.detector.api.CURRENT_API
 import com.google.android.lint.parcel.SaferParcelChecker
+import com.google.android.lint.aidl.PermissionAnnotationDetector
 import com.google.auto.service.AutoService
 
 @AutoService(IssueRegistry::class)
@@ -37,6 +38,7 @@
         SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
         // TODO: Currently crashes due to OOM issue
         // PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
+        PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION,
         PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE,
         PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD,
     )
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt
new file mode 100644
index 0000000..6b50cfd
--- /dev/null
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UMethod
+
+/**
+ * Ensures all AIDL-generated methods are annotated.
+ *
+ * This detector is run on system_server to validate that any method that may
+ * be exposed via an AIDL interface is permission-annotated. That is, it must
+ * have one of the following annotation:
+ *   - @EnforcePermission
+ *   - @RequiresNoPermission
+ *   - @PermissionManuallyEnforced
+ */
+class PermissionAnnotationDetector : AidlImplementationDetector() {
+
+    override fun visitAidlMethod(
+      context: JavaContext,
+      node: UMethod,
+      interfaceName: String,
+      body: UBlockExpression
+    ) {
+        if (context.evaluator.isAbstract(node)) return
+
+        if (AIDL_PERMISSION_ANNOTATIONS.any { node.hasAnnotation(it) }) return
+
+        context.report(
+            ISSUE_MISSING_PERMISSION_ANNOTATION,
+            node,
+            context.getLocation(node),
+            "The method ${node.name} is not permission-annotated."
+        )
+    }
+
+    companion object {
+
+        private val EXPLANATION_MISSING_PERMISSION_ANNOTATION = """
+          Interfaces that are exposed by system_server are required to have an annotation which
+          denotes the type of permission enforced. There are 3 possible options:
+            - @EnforcePermission
+            - @RequiresNoPermission
+            - @PermissionManuallyEnforced
+          See the documentation of each annotation for further details.
+
+          The annotation on the Java implementation must be the same that the AIDL interface
+          definition. This is verified by a lint in the build system.
+          """.trimIndent()
+
+        @JvmField
+        val ISSUE_MISSING_PERMISSION_ANNOTATION = Issue.create(
+            id = "MissingPermissionAnnotation",
+            briefDescription = "No permission annotation on exposed AIDL interface.",
+            explanation = EXPLANATION_MISSING_PERMISSION_ANNOTATION,
+            category = Category.CORRECTNESS,
+            priority = 5,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                PermissionAnnotationDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+            enabledByDefault = false
+        )
+    }
+}
diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt
new file mode 100644
index 0000000..bce848a
--- /dev/null
+++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class PermissionAnnotationDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = PermissionAnnotationDetector()
+
+    override fun getIssues(): List<Issue> = listOf(
+        PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION,
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    /** No issue scenario */
+
+    fun testDoesNotDetectIssuesInCorrectScenario() {
+        lint().files(
+            java(
+            """
+            public class Foo extends IFoo.Stub {
+                @Override
+                @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                public void testMethod() { }
+            }
+            """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testMissingAnnotation() {
+        lint().files(
+            java(
+            """
+            public class Bar extends IBar.Stub {
+                public void testMethod() { }
+            }
+            """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Bar.java:2: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation]
+                    public void testMethod() { }
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    fun testNoIssueWhenExtendingWithAnotherSubclass() {
+        lint().files(
+            java(
+            """
+            public class Foo extends IFoo.Stub {
+                @Override
+                @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                public void testMethod() { }
+                // not an AIDL method, just another method
+                public void someRandomMethod() { }
+            }
+            """).indented(),
+            java(
+            """
+            public class Baz extends Bar {
+              @Override
+              public void someRandomMethod() { }
+            }
+            """).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    /* Stubs */
+
+    // A service with permission annotation on the method.
+    private val interfaceIFoo: TestFile = java(
+        """
+        public interface IFoo extends android.os.IInterface {
+         public static abstract class Stub extends android.os.Binder implements IFoo {
+          }
+          @Override
+          @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+          public void testMethod();
+          @Override
+          @android.annotation.RequiresNoPermission
+          public void testMethodNoPermission();
+          @Override
+          @android.annotation.PermissionManuallyEnforced
+          public void testMethodManual();
+        }
+        """
+    ).indented()
+
+    // A service with no permission annotation.
+    private val interfaceIBar: TestFile = java(
+        """
+        public interface IBar extends android.os.IInterface {
+         public static abstract class Stub extends android.os.Binder implements IBar {
+          }
+          public void testMethod();
+        }
+        """
+    ).indented()
+
+    private val stubs = arrayOf(interfaceIFoo, interfaceIBar)
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 83b8f16..4455a9c 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -168,7 +168,7 @@
             annotationInfo.origin == AnnotationOrigin.METHOD) {
             /* Ignore implementations that are not a sub-class of Stub (i.e., Proxy). */
             val uMethod = element as? UMethod ?: return
-            if (!isContainedInSubclassOfStub(context, uMethod)) {
+            if (getContainingAidlInterface(context, uMethod) == null) {
                 return
             }
             val overridingMethod = element.sourcePsi as PsiMethod
@@ -184,7 +184,8 @@
             if (context.evaluator.isAbstract(node)) return
             if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
 
-            if (!isContainedInSubclassOfStub(context, node)) {
+            val stubClass = containingStub(context, node)
+            if (stubClass == null) {
                 context.report(
                     ISSUE_MISUSING_ENFORCE_PERMISSION,
                     node,
@@ -196,7 +197,7 @@
 
             /* Check that we are connected to the super class */
             val overridingMethod = node as PsiMethod
-            val parents = overridingMethod.findSuperMethods()
+            val parents = overridingMethod.findSuperMethods(stubClass)
             if (parents.isEmpty()) {
                 context.report(
                     ISSUE_MISUSING_ENFORCE_PERMISSION,
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index d8afcb9..2afca05 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -176,6 +176,29 @@
                 """.addLineContinuation())
     }
 
+    fun testDetectNoIssuesAnnotationOnNonStubMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass43 extends IFooMethod.Stub {
+                public void aRegularMethodNotPartOfStub() {
+                }
+            }
+            """).indented(), java(
+            """
+            package test.pkg;
+            public class TestClass44 extends TestClass43 {
+              @Override
+              public void aRegularMethodNotPartOfStub() {
+              }
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
     fun testDetectIssuesEmptyAnnotationOnMethod() {
         lint().files(java(
             """
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
index ebda6f1..4f5e0e4 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -70,14 +70,8 @@
     private List<HotspotNetwork> mHotspotNetworks = Collections.emptyList();
     private List<KnownNetwork> mKnownNetworks = Collections.emptyList();
     private SharedConnectivitySettingsState mSettingsState = null;
-    private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus =
-            new HotspotNetworkConnectionStatus.Builder()
-                    .setStatus(HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
-                    .setExtras(Bundle.EMPTY).build();
-    private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus =
-            new KnownNetworkConnectionStatus.Builder()
-                    .setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
-                    .setExtras(Bundle.EMPTY).build();
+    private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus = null;
+    private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus = null;
     // Used for testing
     private CountDownLatch mCountDownLatch;
 
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
index c6f6798..48ac82d 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
@@ -394,6 +394,26 @@
         verify(mCallback, never()).onKnownNetworkConnectionStatusChanged(any());
     }
 
+    @Test
+    public void getHotspotNetworkConnectionStatus_withoutUpdate_returnsNull()
+            throws RemoteException {
+        SharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+
+        assertThat(binder.getHotspotNetworkConnectionStatus()).isNull();
+    }
+
+    @Test
+    public void getKnownNetworkConnectionStatus_withoutUpdate_returnsNull()
+            throws RemoteException {
+        SharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+
+        assertThat(binder.getKnownNetworkConnectionStatus()).isNull();
+    }
+
     private FakeSharedConnectivityService createService() {
         FakeSharedConnectivityService service = new FakeSharedConnectivityService();
         service.attachBaseContext(mContext);