Merge "Add unaudited exported flag to exposed runtime receivers"
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java b/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java
index 530dc9d..15a65ce 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java
@@ -130,7 +130,8 @@
         IntentSender getIntentSender(int sessionId) {
             String action = BROADCAST_ACTION + "." + sessionId;
             IntentFilter filter = new IntentFilter(action);
-            mContext.registerReceiver(this, filter);
+            mContext.registerReceiver(this, filter,
+                    Context.RECEIVER_EXPORTED_UNAUDITED);
 
             Intent intent = new Intent(action);
             PendingIntent pending = PendingIntent.getBroadcast(mContext, sessionId, intent,
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index c251529a..e5b0742 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -125,7 +125,7 @@
      * will not be invoked.
      *
      * @param params Parameters specifying info about this job, including the optional
-     *     extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle).
+     *     extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle)}.
      *     This object serves to identify this specific running job instance when calling
      *     {@link #jobFinished(JobParameters, boolean)}.
      * @return {@code true} if your service will continue running, using a separate thread
diff --git a/core/api/current.txt b/core/api/current.txt
index 98d3a91..0162e14 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -16525,6 +16525,7 @@
     method @NonNull public static android.graphics.RenderEffect createColorFilterEffect(@NonNull android.graphics.ColorFilter);
     method @NonNull public static android.graphics.RenderEffect createOffsetEffect(float, float);
     method @NonNull public static android.graphics.RenderEffect createOffsetEffect(float, float, @NonNull android.graphics.RenderEffect);
+    method @NonNull public static android.graphics.RenderEffect createRuntimeShaderEffect(@NonNull android.graphics.RuntimeShader, @NonNull String);
     method @NonNull public static android.graphics.RenderEffect createShaderEffect(@NonNull android.graphics.Shader);
   }
 
@@ -51384,9 +51385,9 @@
     method public int getRecordCount();
     method public int getWindowChanges();
     method public void initFromParcel(android.os.Parcel);
-    method public static android.view.accessibility.AccessibilityEvent obtain(int);
-    method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
-    method public static android.view.accessibility.AccessibilityEvent obtain();
+    method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(int);
+    method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
+    method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain();
     method public void setAction(int);
     method public void setContentChangeTypes(int);
     method public void setEventTime(long);
@@ -51574,13 +51575,13 @@
     method public boolean isShowingHintText();
     method public boolean isTextEntryKey();
     method public boolean isVisibleToUser();
-    method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
-    method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
-    method public static android.view.accessibility.AccessibilityNodeInfo obtain();
-    method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.accessibility.AccessibilityNodeInfo);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain();
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.accessibility.AccessibilityNodeInfo);
     method public boolean performAction(int);
     method public boolean performAction(int, android.os.Bundle);
-    method public void recycle();
+    method @Deprecated public void recycle();
     method public boolean refresh();
     method public boolean refreshWithExtraData(String, android.os.Bundle);
     method @Deprecated public void removeAction(int);
@@ -51759,8 +51760,8 @@
     method public int getRowCount();
     method public int getSelectionMode();
     method public boolean isHierarchical();
-    method public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean);
-    method public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean, int);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean, int);
     field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
     field public static final int SELECTION_MODE_NONE = 0; // 0x0
     field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
@@ -51777,9 +51778,9 @@
     method @Nullable public String getRowTitle();
     method @Deprecated public boolean isHeading();
     method public boolean isSelected();
-    method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean);
-    method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean);
-    method @NonNull public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean);
+    method @Deprecated @NonNull public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean);
   }
 
   public static final class AccessibilityNodeInfo.CollectionItemInfo.Builder {
@@ -51807,7 +51808,7 @@
     method public float getMax();
     method public float getMin();
     method public int getType();
-    method public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float);
     field public static final int RANGE_TYPE_FLOAT = 1; // 0x1
     field public static final int RANGE_TYPE_INT = 0; // 0x0
     field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
@@ -51861,9 +51862,9 @@
     method public boolean isFullScreen();
     method public boolean isPassword();
     method public boolean isScrollable();
-    method public static android.view.accessibility.AccessibilityRecord obtain(android.view.accessibility.AccessibilityRecord);
-    method public static android.view.accessibility.AccessibilityRecord obtain();
-    method public void recycle();
+    method @Deprecated public static android.view.accessibility.AccessibilityRecord obtain(android.view.accessibility.AccessibilityRecord);
+    method @Deprecated public static android.view.accessibility.AccessibilityRecord obtain();
+    method @Deprecated public void recycle();
     method public void setAddedCount(int);
     method public void setBeforeText(CharSequence);
     method public void setChecked(boolean);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b37c938..8ae6e4c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -59,6 +59,7 @@
   public static final class R.bool {
     field public static final int config_assistantOnTopOfDream = 17891333; // 0x1110005
     field public static final int config_perDisplayFocusEnabled = 17891332; // 0x1110004
+    field public static final int config_preventImeStartupUnlessTextEditor;
     field public static final int config_remoteInsetsControllerControlsSystemBars = 17891334; // 0x1110006
   }
 
@@ -2837,7 +2838,6 @@
     method public void addChild(@NonNull android.os.IBinder);
     method public long getSourceNodeId();
     method public void setLeashedParent(@Nullable android.os.IBinder, int);
-    method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
     method public void writeToParcelNoRecycle(android.os.Parcel, int);
   }
 
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index bb537dd..2a8ff51 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -189,7 +189,7 @@
     @NonNull
     @SystemApi
     public static final ParcelUuid CAP =
-            ParcelUuid.fromString("00008FE0-0000-1000-8000-00805F9B34FB");
+            ParcelUuid.fromString("00001853-0000-1000-8000-00805F9B34FB");
     /** @hide */
     @NonNull
     @SystemApi
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 3f02aa2..78d5137 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -197,6 +197,20 @@
         return macAddress.equals(mDeviceMacAddress);
     }
 
+    /** @hide */
+    public @NonNull String toShortString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("id=").append(mId);
+        if (mDeviceMacAddress != null) {
+            sb.append(", addr=").append(getDeviceMacAddressAsString());
+        }
+        if (mSelfManaged) {
+            sb.append(", self-managed");
+        }
+        sb.append(", pkg=u").append(mUserId).append('/').append(mPackageName);
+        return sb.toString();
+    }
+
     @Override
     public String toString() {
         return "Association{"
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 117f305..120a0a6 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3615,9 +3615,8 @@
      *          {@link #BIND_ADJUST_WITH_ACTIVITY}.
      * @return {@code true} if the system is in the process of bringing up a
      *         service that your client has permission to bind to; {@code false}
-     *         if the system couldn't find the service. If this value is {@code true}, you
-     *         should later call {@link #unbindService} to release the
-     *         connection.
+     *         if the system couldn't find the service. You should call {@link #unbindService}
+     *         to release the connection even if this method returned {@code false}.
      *
      * @throws SecurityException if the client does not have the required permission to bind.
      */
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 282f1d3..3a8513b 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -464,7 +464,8 @@
 
             IntentFilter filter = new IntentFilter("dynamic_sensor_change");
             filter.addAction(Intent.ACTION_DYNAMIC_SENSOR_CHANGED);
-            mContext.registerReceiver(mDynamicSensorBroadcastReceiver, filter);
+            mContext.registerReceiver(mDynamicSensorBroadcastReceiver, filter,
+                    Context.RECEIVER_NOT_EXPORTED);
         }
     }
 
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 01833fd..e731165 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -965,7 +965,7 @@
 
     private static final class DisplayListenerDelegate extends Handler {
         public final DisplayListener mListener;
-        public long mEventsMask;
+        public volatile long mEventsMask;
 
         private final DisplayInfo mDisplayInfo = new DisplayInfo();
 
@@ -985,12 +985,12 @@
             removeCallbacksAndMessages(null);
         }
 
-        public synchronized void setEventsMask(@EventsMask long newEventsMask) {
+        public void setEventsMask(@EventsMask long newEventsMask) {
             mEventsMask = newEventsMask;
         }
 
         @Override
-        public synchronized void handleMessage(Message msg) {
+        public void handleMessage(Message msg) {
             switch (msg.what) {
                 case EVENT_DISPLAY_ADDED:
                     if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index be841c0..afaa085 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -51,7 +51,6 @@
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
-import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -94,6 +93,7 @@
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
 import android.util.proto.ProtoOutputStream;
+import android.view.Gravity;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -1340,26 +1340,42 @@
                 Context.LAYOUT_INFLATER_SERVICE);
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow");
         mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
-        mWindow.getWindow().getAttributes().setFitInsetsTypes(statusBars() | navigationBars());
-        mWindow.getWindow().getAttributes().setFitInsetsSides(Side.all() & ~Side.BOTTOM);
-        mWindow.getWindow().getAttributes().receiveInsetsIgnoringZOrder = true;
 
-        // Automotive devices may request the navigation bar to be hidden when the IME shows up
-        // (controlled via config_automotiveHideNavBarForKeyboard) in order to maximize the visible
-        // screen real estate. When this happens, the IME window should animate from the bottom of
-        // the screen to reduce the jank that happens from the lack of synchronization between the
-        // bottom system window and the IME window.
-        if (mIsAutomotive && mAutomotiveHideNavBarForKeyboard) {
-            mWindow.getWindow().setDecorFitsSystemWindows(false);
+        {
+            final Window window = mWindow.getWindow();
+            {
+                final WindowManager.LayoutParams lp = window.getAttributes();
+                lp.setTitle("InputMethod");
+                lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+                lp.width = WindowManager.LayoutParams.MATCH_PARENT;
+                lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
+                lp.gravity = Gravity.BOTTOM;
+                lp.setFitInsetsTypes(statusBars() | navigationBars());
+                lp.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
+                lp.receiveInsetsIgnoringZOrder = true;
+                window.setAttributes(lp);
+            }
+
+            // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set
+            // by default (but IME developers can opt this out later if they want a new behavior).
+            final int windowFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+            final int windowFlagsMask = windowFlags
+                    | WindowManager.LayoutParams.FLAG_DIM_BEHIND;  // to be unset
+            window.setFlags(windowFlags, windowFlagsMask);
+
+            // Automotive devices may request the navigation bar to be hidden when the IME shows up
+            // (controlled via config_automotiveHideNavBarForKeyboard) in order to maximize the
+            // visible screen real estate. When this happens, the IME window should animate from the
+            // bottom of the screen to reduce the jank that happens from the lack of synchronization
+            // between the bottom system window and the IME window.
+            if (mIsAutomotive && mAutomotiveHideNavBarForKeyboard) {
+                window.setDecorFitsSystemWindows(false);
+            }
         }
 
-        // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set
-        // by default (but IME developers can opt this out later if they want a new behavior).
-        mWindow.getWindow().setFlags(
-                FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
-
         initViews();
-        mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
         mInlineSuggestionSessionController = new InlineSuggestionSessionController(
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 25cdb2b..66288d6 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -137,7 +137,6 @@
     SoftInputWindow(Context context, int theme, KeyEvent.DispatcherState dispatcherState) {
         super(context, theme);
         mDispatcherState = dispatcherState;
-        initDockWindow();
     }
 
     @Override
@@ -162,27 +161,6 @@
         }
     }
 
-    private void initDockWindow() {
-        WindowManager.LayoutParams lp = getWindow().getAttributes();
-
-        lp.setTitle("InputMethod");
-        lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-        lp.width = WindowManager.LayoutParams.MATCH_PARENT;
-        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
-        lp.gravity = Gravity.BOTTOM;
-
-        getWindow().setAttributes(lp);
-
-        final int windowModFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
-
-        final int windowSetFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-
-        getWindow().setFlags(windowSetFlags, windowModFlags);
-    }
-
     @Override
     public void show() {
         switch (mWindowState) {
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index a9c52f1..f4b427959f 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -369,11 +369,13 @@
 
         try {
             final NetworkTemplate.Builder builder = new NetworkTemplate.Builder(matchRule)
-                    .setWifiNetworkKey(wifiNetworkKey)
                     .setMeteredness(metered);
             if (subscriberId != null) {
                 builder.setSubscriberIds(Set.of(subscriberId));
             }
+            if (wifiNetworkKey != null) {
+                builder.setWifiNetworkKeys(Set.of(wifiNetworkKey));
+            }
             return builder.build();
         } catch (IllegalArgumentException e) {
             throw new BackupUtils.BadVersionException(
@@ -393,7 +395,7 @@
             case MATCH_MOBILE:
                 return !template.getSubscriberIds().isEmpty();
             case MATCH_WIFI:
-                if (Objects.equals(template.getWifiNetworkKey(), null)
+                if (template.getWifiNetworkKeys().isEmpty()
                         && template.getSubscriberIds().isEmpty()) {
                     return false;
                 }
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index e2f5908..17f57a8 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -218,7 +218,8 @@
     /**
      * Checks whether a given data access chain described by the given {@link AttributionSource}
      * has a given permission. Call this method if you are the datasource which would not blame you
-     * for access to the data since you are the data.
+     * for access to the data since you are the data. Use this API if you are the datasource of the
+     * protected state.
      *
      * <strong>NOTE:</strong> Use this method only for permission checks at the
      * point where you will deliver the permission protected data to clients.
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index f0e6624..658e033 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -318,7 +318,7 @@
             String permGroup = usedPermGroups.get(permGroupNum);
 
             ArrayMap<OpUsage, CharSequence> usagesWithLabels =
-                    getUniqueUsagesWithLabels(rawUsages.get(permGroup));
+                    getUniqueUsagesWithLabels(permGroup, rawUsages.get(permGroup));
 
             if (permGroup.equals(OPSTR_PHONE_CALL_MICROPHONE)) {
                 isPhone = true;
@@ -439,7 +439,8 @@
         return ListFormatter.getInstance().format(labels);
     }
 
-    private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(List<OpUsage> usages) {
+    private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(String permGroup,
+            List<OpUsage> usages) {
         ArrayMap<OpUsage, CharSequence> usagesAndLabels = new ArrayMap<>();
 
         if (usages == null || usages.isEmpty()) {
@@ -474,7 +475,7 @@
             // If this usage has a proxy, but is not a proxy, it is the end of a chain.
             // TODO remove once camera converted
             if (!proxies.containsKey(usageAttr) && usage.proxy != null
-                    && !usage.op.equals(OPSTR_RECORD_AUDIO)) {
+                    && !MICROPHONE.equals(permGroup)) {
                 proxyLabels.put(usage, new ArrayList<>());
                 proxyPackages.add(usage.getPackageIdHash());
             }
@@ -546,7 +547,7 @@
 
             // TODO ntmyren: remove this proxy logic once camera is converted to AttributionSource
             // For now: don't add mic proxy usages
-            if (!start.op.equals(OPSTR_RECORD_AUDIO)) {
+            if (!MICROPHONE.equals(permGroup)) {
                 usagesAndLabels.put(start,
                         proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList));
             }
@@ -560,7 +561,8 @@
                 // if the list is empty or incomplete, do not show it.
                 if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd()
                         || !usageList.get(0).isStart()
-                        || !usageList.get(lastVisible).usage.op.equals(OPSTR_RECORD_AUDIO)) {
+                        || !permGroup.equals(getGroupForOp(usageList.get(0).usage.op))
+                        || !MICROPHONE.equals(permGroup)) {
                     continue;
                 }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f5777ed..190b8f6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6495,6 +6495,27 @@
         public static final String ALLOW_MOCK_LOCATION = "mock_location";
 
         /**
+         * This is used by Bluetooth Manager to store adapter name
+         * @hide
+         */
+        @Readable(maxTargetSdk = Build.VERSION_CODES.S)
+        public static final String BLUETOOTH_NAME = "bluetooth_name";
+
+        /**
+         * This is used by Bluetooth Manager to store adapter address
+         * @hide
+         */
+        @Readable(maxTargetSdk = Build.VERSION_CODES.S)
+        public static final String BLUETOOTH_ADDRESS = "bluetooth_address";
+
+        /**
+         * This is used by Bluetooth Manager to store whether adapter address is valid
+         * @hide
+         */
+        @Readable(maxTargetSdk = Build.VERSION_CODES.S)
+        public static final String BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid";
+
+        /**
          * Setting to indicate that on device captions are enabled.
          *
          * @hide
@@ -17037,6 +17058,15 @@
              */
             public static final String CLOCKWORK_SYSUI_MAIN_ACTIVITY =
                     "clockwork_sysui_main_activity";
+
+            /**
+             * Setting to disable power button long press launching Assistant. It's boolean, i.e.
+             * enabled = 1, disabled = 0. By default, this setting is enabled.
+             *
+             * @hide
+             */
+            public static final String CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED =
+                    "clockwork_long_press_to_assistant_enabled";
         }
     }
 
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index c1a5636..ae323226 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -585,8 +585,10 @@
      * Returns a list of {@code Rect}s, each of which is the bounding rectangle for a non-functional
      * area on the display.
      *
-     * There will be at most one non-functional area per short edge of the device, and none on
-     * the long edges.
+     * There will be at most one non-functional area per edge of the device.
+     *
+     * <p>Note that there is no bounding rectangle for waterfall cutout since it just represents the
+     * curved areas of the display but not the non-functional areas.</p>
      *
      * @return a list of bounding {@code Rect}s, one for each display cutout area. No empty Rect is
      * returned.
@@ -607,8 +609,10 @@
      * functional area on the display. Ordinal value of BoundPosition is used as an index of
      * the array.
      *
-     * There will be at most one non-functional area per short edge of the device, and none on
-     * the long edges.
+     * There will be at most one non-functional area per edge of the device.
+     *
+     * <p>Note that there is no bounding rectangle for waterfall cutout since it just represents the
+     * curved areas of the display but not the non-functional areas.</p>
      *
      * @return an array of bounding {@code Rect}s, one for each display cutout area. This might
      * contain ZERO_RECT, which means there is no cutout area at the position.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1d652af..748e551 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1973,26 +1973,29 @@
        return mBoundsLayer;
     }
 
-    Surface getOrCreateBLASTSurface() {
+    void updateBlastSurfaceIfNeeded() {
         if (!mSurfaceControl.isValid()) {
-            return null;
+            return;
         }
 
-        Surface ret = null;
-        if (mBlastBufferQueue == null) {
-            mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
-                mSurfaceSize.x, mSurfaceSize.y,
-                mWindowAttributes.format);
-            // We only return the Surface the first time, as otherwise
-            // it hasn't changed and there is no need to update.
-            ret = mBlastBufferQueue.createSurface();
-        } else {
+        if (mBlastBufferQueue != null && mBlastBufferQueue.isSameSurfaceControl(mSurfaceControl)) {
             mBlastBufferQueue.update(mSurfaceControl,
                 mSurfaceSize.x, mSurfaceSize.y,
                 mWindowAttributes.format);
+            return;
         }
 
-        return ret;
+        // If the SurfaceControl has been updated, destroy and recreate the BBQ to reset the BQ and
+        // BBQ states.
+        if (mBlastBufferQueue != null) {
+            mBlastBufferQueue.destroy();
+        }
+        mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
+                mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
+        Surface blastSurface = mBlastBufferQueue.createSurface();
+        // Only call transferFrom if the surface has changed to prevent inc the generation ID and
+        // causing EGL resources to be recreated.
+        mSurface.transferFrom(blastSurface);
     }
 
     private void setBoundsLayerCrop(Transaction t) {
@@ -2856,7 +2859,10 @@
                 if (mSurfaceControl.isValid()) {
                     updateOpacity(mWindowAttributes, dragResizing,
                             surfaceControlChanged /*forceUpdate */);
-                    if (surfaceControlChanged) {
+                    // No need to updateDisplayDecoration if it's a new SurfaceControl and
+                    // mDisplayDecorationCached is false, since that's the default for a new
+                    // SurfaceControl.
+                    if (surfaceControlChanged && mDisplayDecorationCached) {
                         updateDisplayDecoration();
                     }
                 }
@@ -7890,13 +7896,7 @@
             if (!useBLAST()) {
                 mSurface.copyFrom(mSurfaceControl);
             } else {
-                final Surface blastSurface = getOrCreateBLASTSurface();
-                // If blastSurface == null that means it hasn't changed since the last time we
-                // called. In this situation, avoid calling transferFrom as we would then
-                // inc the generation ID and cause EGL resources to be recreated.
-                if (blastSurface != null) {
-                    mSurface.transferFrom(blastSurface);
-                }
+                updateBlastSurfaceIfNeeded();
             }
             if (mAttachInfo.mThreadedRenderer != null) {
                 mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f94125d..cd9f3eb6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2757,7 +2757,7 @@
          *
          * <p>Applications that target {@link android.os.Build.VERSION_CODES#P} and later, this flag
          * is ignored unless there is a focused view that returns {@code true} from
-         * {@link View#isInEditMode()} when the window is focused.</p>
+         * {@link View#onCheckIsTextEditor()} when the window is focused.</p>
          */
         public static final int SOFT_INPUT_STATE_VISIBLE = 4;
 
@@ -2767,7 +2767,7 @@
          *
          * <p>Applications that target {@link android.os.Build.VERSION_CODES#P} and later, this flag
          * is ignored unless there is a focused view that returns {@code true} from
-         * {@link View#isInEditMode()} when the window is focused.</p>
+         * {@link View#onCheckIsTextEditor()} when the window is focused.</p>
          */
         public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
 
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 83712b4..a427ab8 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -24,7 +24,6 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pools.SynchronizedPool;
 
 import com.android.internal.util.BitUtils;
 
@@ -806,10 +805,6 @@
      */
     public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
 
-    private static final int MAX_POOL_SIZE = 10;
-    private static final SynchronizedPool<AccessibilityEvent> sPool =
-            new SynchronizedPool<>(MAX_POOL_SIZE);
-
     @UnsupportedAppUsage
     private @EventType int mEventType;
     private CharSequence mPackageName;
@@ -1170,7 +1165,7 @@
      */
     public static AccessibilityEvent obtainWindowsChangedEvent(
             int windowId, int windowChangeTypes) {
-        final AccessibilityEvent event = AccessibilityEvent.obtain(TYPE_WINDOWS_CHANGED);
+        final AccessibilityEvent event = new AccessibilityEvent(TYPE_WINDOWS_CHANGED);
         event.setWindowId(windowId);
         event.setWindowChanges(windowChangeTypes);
         event.setImportantForAccessibility(true);
@@ -1178,69 +1173,58 @@
     }
 
     /**
-     * Returns a cached instance if such is available or a new one is
-     * instantiated with its type property set.
+     * Instantiates a new AccessibilityEvent instance with its type property set.
      *
-     * <p>In most situations object pooling is not beneficial. Create a new instance using the
-     * constructor {@link #AccessibilityEvent(int)} instead.
-     *
+     * @deprecated Object pooling has been discontinued. Create a new instance using the
+     * constructor {@link #AccessibilityEvent()} instead.
      * @param eventType The event type.
      * @return An instance.
      */
+    @Deprecated
     public static AccessibilityEvent obtain(int eventType) {
-        AccessibilityEvent event = AccessibilityEvent.obtain();
+        AccessibilityEvent event = new AccessibilityEvent();
         event.setEventType(eventType);
         return event;
     }
 
     /**
-     * Returns a cached instance if such is available or a new one is
-     * created. The returned instance is initialized from the given
+     * Instantiates a new AccessibilityEvent instance.
+     * The returned instance is initialized from the given
      * <code>event</code>.
      *
-     * <p>In most situations object pooling is not beneficial. Create a new instance using the
-     * constructor {@link #AccessibilityEvent(AccessibilityEvent)} instead.
-     *
+     * @deprecated Object pooling has been discontinued. Create a new instance using the
+     * constructor {@link #AccessibilityEvent()} instead.
      * @param event The other event.
      * @return An instance.
      */
+    @Deprecated
     public static AccessibilityEvent obtain(AccessibilityEvent event) {
-        AccessibilityEvent eventClone = AccessibilityEvent.obtain();
+        AccessibilityEvent eventClone = new AccessibilityEvent();
         eventClone.init(event);
         return eventClone;
     }
 
     /**
-     * Returns a cached instance if such is available or a new one is
-     * instantiated.
+     * Instantiates a new AccessibilityEvent instance.
      *
-     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * @deprecated Object pooling has been discontinued. Create a new instance using the
      * constructor {@link #AccessibilityEvent()} instead.
-     *
      * @return An instance.
      */
+    @Deprecated
     public static AccessibilityEvent obtain() {
-        AccessibilityEvent event = sPool.acquire();
-        if (event == null) event = new AccessibilityEvent();
-        if (DEBUG_ORIGIN) event.originStackTrace = Thread.currentThread().getStackTrace();
-        return event;
+        return new AccessibilityEvent();
     }
 
     /**
-     * Recycles an instance back to be reused.
-     * <p>
-     *   <b>Note: You must not touch the object after calling this function.</b>
-     * </p>
+     * Previously would recycle an instance back to be reused.
      *
-     * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
-     *
-     * @throws IllegalStateException If the event is already recycled.
+     * @deprecated Object pooling has been discontinued. Calling this function now will have
+     * no effect.
      */
     @Override
-    public void recycle() {
-        clear();
-        sPool.release(this);
-    }
+    @Deprecated
+    public void recycle() {}
 
     /**
      * Clears the state of this instance.
@@ -1260,7 +1244,6 @@
         if (mRecords != null) {
             while (!mRecords.isEmpty()) {
                 AccessibilityRecord record = mRecords.remove(0);
-                record.recycle();
             }
         }
         if (DEBUG_ORIGIN) originStackTrace = null;
@@ -1288,7 +1271,7 @@
         if (recordCount > 0) {
             mRecords = new ArrayList<>(recordCount);
             for (int i = 0; i < recordCount; i++) {
-                AccessibilityRecord record = AccessibilityRecord.obtain();
+                AccessibilityRecord record = new AccessibilityRecord();
                 readAccessibilityRecordFromParcel(record, parcel);
                 record.mConnectionId = mConnectionId;
                 mRecords.add(record);
@@ -1527,7 +1510,7 @@
     public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityEvent> CREATOR =
             new Parcelable.Creator<AccessibilityEvent>() {
         public AccessibilityEvent createFromParcel(Parcel parcel) {
-            AccessibilityEvent event = AccessibilityEvent.obtain();
+            AccessibilityEvent event = new AccessibilityEvent();
             event.initFromParcel(parcel);
             return event;
         }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 9511958..db7c663 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -50,7 +50,6 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.LongArray;
-import android.util.Pools.SynchronizedPool;
 import android.util.Size;
 import android.util.TypedValue;
 import android.view.SurfaceView;
@@ -68,7 +67,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class represents a node of the window content as well as actions that
@@ -723,9 +721,6 @@
      */
     private static final int VIRTUAL_DESCENDANT_ID_SHIFT = 32;
 
-    // TODO(b/129300068): Remove sNumInstancesInUse.
-    private static AtomicInteger sNumInstancesInUse;
-
     /**
      * Gets the accessibility view id which identifies a View in the view three.
      *
@@ -769,11 +764,6 @@
         return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId;
     }
 
-    // Housekeeping.
-    private static final int MAX_POOL_SIZE = 50;
-    private static final SynchronizedPool<AccessibilityNodeInfo> sPool =
-            new SynchronizedPool<>(MAX_POOL_SIZE);
-
     private static final AccessibilityNodeInfo DEFAULT = new AccessibilityNodeInfo();
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -869,7 +859,7 @@
      * @param info The other info.
      */
     public AccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
-        init(info, false /* usePoolingInfo */);
+        init(info);
     }
 
     /**
@@ -1009,13 +999,7 @@
         if (refreshedInfo == null) {
             return false;
         }
-        // Hard-to-reproduce bugs seem to be due to some tools recycling a node on another
-        // thread. If that happens, the init will re-seal the node, which then is in a bad state
-        // when it is obtained. Enforce sealing again before we init to fail when a node has been
-        // recycled during a refresh to catch such errors earlier.
-        enforceSealed();
-        init(refreshedInfo, true /* usePoolingInfo */);
-        refreshedInfo.recycle();
+        init(refreshedInfo);
         return true;
     }
 
@@ -3599,25 +3583,23 @@
      * Returns a cached instance if such is available otherwise a new one
      * and sets the source.
      *
-     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * @deprecated Object pooling has been discontinued. Create a new instance using the
      * constructor {@link #AccessibilityNodeInfo(View)} instead.
-     *
      * @param source The source view.
      * @return An instance.
      *
      * @see #setSource(View)
      */
+    @Deprecated
     public static AccessibilityNodeInfo obtain(View source) {
-        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
-        info.setSource(source);
-        return info;
+        return new AccessibilityNodeInfo(source);
     }
 
     /**
      * Returns a cached instance if such is available otherwise a new one
      * and sets the source.
      *
-     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * @deprecated Object pooling has been discontinued. Create a new instance using the
      * constructor {@link #AccessibilityNodeInfo(View, int)} instead.
      *
      * @param root The root of the virtual subtree.
@@ -3626,71 +3608,45 @@
      *
      * @see #setSource(View, int)
      */
+    @Deprecated
     public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) {
-        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
-        info.setSource(root, virtualDescendantId);
-        return info;
+        return new AccessibilityNodeInfo(root, virtualDescendantId);
     }
 
     /**
-     * Returns a cached instance if such is available otherwise a new one.
+     * Instantiates a new AccessibilityNodeInfo.
      *
-     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * @deprecated Object pooling has been discontinued. Create a new instance using the
      * constructor {@link #AccessibilityNodeInfo()} instead.
-     *
      * @return An instance.
      */
+    @Deprecated
     public static AccessibilityNodeInfo obtain() {
-        AccessibilityNodeInfo info = sPool.acquire();
-        if (sNumInstancesInUse != null) {
-            sNumInstancesInUse.incrementAndGet();
-        }
-        return (info != null) ? info : new AccessibilityNodeInfo();
+        return new AccessibilityNodeInfo();
     }
 
     /**
-     * Returns a cached instance if such is available or a new one is
-     * create. The returned instance is initialized from the given
+     * Instantiates a new AccessibilityNodeInfo initialized from the given
      * <code>info</code>.
      *
-     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * @deprecated Object pooling has been discontinued. Create a new instance using the
      * constructor {@link #AccessibilityNodeInfo(AccessibilityNodeInfo)} instead.
-     *
      * @param info The other info.
      * @return An instance.
      */
+    @Deprecated
     public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
-        AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain();
-        infoClone.init(info, true /* usePoolingInfo */);
-        return infoClone;
+        return new AccessibilityNodeInfo(info);
     }
 
     /**
-     * Return an instance back to be reused.
-     * <p>
-     * <strong>Note:</strong> You must not touch the object after calling this function.
+     * Would previously return an instance back to be reused.
      *
-     * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
-     *
-     * @throws IllegalStateException If the info is already recycled.
+     * @deprecated Object pooling has been discontinued. Calling this function now will have
+     * no effect.
      */
-    public void recycle() {
-        clear();
-        sPool.release(this);
-        if (sNumInstancesInUse != null) {
-            sNumInstancesInUse.decrementAndGet();
-        }
-    }
-
-    /**
-     * Specify a counter that will be incremented on obtain() and decremented on recycle()
-     *
-     * @hide
-     */
-    @TestApi
-    public static void setNumInstancesInUseCounter(AtomicInteger counter) {
-        sNumInstancesInUse = counter;
-    }
+    @Deprecated
+    public void recycle() {}
 
     /**
      * {@inheritDoc}
@@ -3704,7 +3660,6 @@
         writeToParcelNoRecycle(parcel, flags);
         // Since instances of this class are fetched via synchronous i.e. blocking
         // calls in IPCs we always recycle as soon as the instance is marshaled.
-        recycle();
     }
 
     /** @hide */
@@ -4000,9 +3955,8 @@
      * Initializes this instance from another one.
      *
      * @param other The other instance.
-     * @param usePoolingInfos whether using pooled object internally or not
      */
-    private void init(AccessibilityNodeInfo other, boolean usePoolingInfos) {
+    private void init(AccessibilityNodeInfo other) {
         mSealed = other.mSealed;
         mSourceNodeId = other.mSourceNodeId;
         mParentNodeId = other.mParentNodeId;
@@ -4062,11 +4016,7 @@
 
         mExtras = other.mExtras != null ? new Bundle(other.mExtras) : null;
 
-        if (usePoolingInfos) {
-            initPoolingInfos(other);
-        } else {
-            initCopyInfos(other);
-        }
+        initCopyInfos(other);
 
         final TouchDelegateInfo otherInfo = other.mTouchDelegateInfo;
         mTouchDelegateInfo = (otherInfo != null)
@@ -4077,21 +4027,6 @@
         mLeashedParentNodeId = other.mLeashedParentNodeId;
     }
 
-    private void initPoolingInfos(AccessibilityNodeInfo other) {
-        if (mRangeInfo != null) mRangeInfo.recycle();
-        mRangeInfo = (other.mRangeInfo != null)
-                ? RangeInfo.obtain(other.mRangeInfo) : null;
-        if (mCollectionInfo != null) mCollectionInfo.recycle();
-        mCollectionInfo = (other.mCollectionInfo != null)
-                ? CollectionInfo.obtain(other.mCollectionInfo) : null;
-        if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
-        mCollectionItemInfo =  (other.mCollectionItemInfo != null)
-                ? CollectionItemInfo.obtain(other.mCollectionItemInfo) : null;
-        if (mExtraRenderingInfo != null) mExtraRenderingInfo.recycle();
-        mExtraRenderingInfo = (other.mExtraRenderingInfo != null)
-                ? ExtraRenderingInfo.obtain(other.mExtraRenderingInfo) : null;
-    }
-
     private void initCopyInfos(AccessibilityNodeInfo other) {
         RangeInfo ri = other.mRangeInfo;
         mRangeInfo = (ri == null) ? null
@@ -4205,27 +4140,24 @@
                 ? parcel.readBundle()
                 : null;
 
-        if (mRangeInfo != null) mRangeInfo.recycle();
         mRangeInfo = isBitSet(nonDefaultFields, fieldIndex++)
-                ? RangeInfo.obtain(
+                ? new RangeInfo(
                         parcel.readInt(),
                         parcel.readFloat(),
                         parcel.readFloat(),
                         parcel.readFloat())
                 : null;
 
-        if (mCollectionInfo != null) mCollectionInfo.recycle();
         mCollectionInfo = isBitSet(nonDefaultFields, fieldIndex++)
-                ? CollectionInfo.obtain(
+                ? new CollectionInfo(
                         parcel.readInt(),
                         parcel.readInt(),
                         parcel.readInt() == 1,
                         parcel.readInt())
                 : null;
 
-        if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
         mCollectionItemInfo = isBitSet(nonDefaultFields, fieldIndex++)
-                ? CollectionItemInfo.obtain(
+                ? new CollectionItemInfo(
                         parcel.readString(),
                         parcel.readInt(),
                         parcel.readInt(),
@@ -4241,8 +4173,7 @@
         }
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
-            if (mExtraRenderingInfo != null) mExtraRenderingInfo.recycle();
-            mExtraRenderingInfo = ExtraRenderingInfo.obtain();
+            mExtraRenderingInfo = new ExtraRenderingInfo(null);
             mExtraRenderingInfo.mLayoutSize = (Size) parcel.readValue(null);
             mExtraRenderingInfo.mTextSizeInPx = parcel.readFloat();
             mExtraRenderingInfo.mTextSizeUnit = parcel.readInt();
@@ -4265,7 +4196,7 @@
      * Clears the state of this instance.
      */
     private void clear() {
-        init(DEFAULT, true /* usePoolingInfo */);
+        init(DEFAULT);
     }
 
     private static boolean isDefaultStandardAction(AccessibilityAction action) {
@@ -5235,7 +5166,6 @@
      * handled by the {@link AccessibilityNodeInfo} to which this object is attached.
      */
     public static final class RangeInfo {
-        private static final int MAX_POOL_SIZE = 10;
 
         /** Range type: integer. */
         public static final int RANGE_TYPE_INT = 0;
@@ -5244,35 +5174,16 @@
         /** Range type: percent with values from zero to one hundred. */
         public static final int RANGE_TYPE_PERCENT = 2;
 
-        private static final SynchronizedPool<RangeInfo> sPool =
-                new SynchronizedPool<AccessibilityNodeInfo.RangeInfo>(MAX_POOL_SIZE);
-
         private int mType;
         private float mMin;
         private float mMax;
         private float mCurrent;
-
         /**
-         * Obtains a pooled instance that is a clone of another one.
+         * Instantiates a new RangeInfo.
          *
-         * <p>In most situations object pooling is not beneficial. Create a new instance using the
-         * constructor {@link AccessibilityNodeInfo.RangeInfo#RangeInfo(int,
-         * float, float, float)} instead.
-         *
-         * @param other The instance to clone.
-         *
-         * @hide
-         */
-        public static RangeInfo obtain(RangeInfo other) {
-            return obtain(other.mType, other.mMin, other.mMax, other.mCurrent);
-        }
-
-        /**
-         * Obtains a pooled instance.
-         *
-         * <p>In most situations object pooling is not beneficial. Create a new instance using the
-         * constructor {@link AccessibilityNodeInfo.RangeInfo#RangeInfo(int,
-         * float, float, float)} instead.
+         * @deprecated Object pooling has been discontinued. Create a new instance using the
+         * constructor {@link AccessibilityNodeInfo.RangeInfo#RangeInfo(int, float, float,
+         * float)} instead.
          *
          * @param type The type of the range.
          * @param min The minimum value. Use {@code Float.NEGATIVE_INFINITY} if the range has no
@@ -5281,17 +5192,9 @@
          *            maximum.
          * @param current The current value.
          */
+        @Deprecated
         public static RangeInfo obtain(int type, float min, float max, float current) {
-            RangeInfo info = sPool.acquire();
-            if (info == null) {
-                return new RangeInfo(type, min, max, current);
-            }
-
-            info.mType = type;
-            info.mMin = min;
-            info.mMax = max;
-            info.mCurrent = current;
-            return info;
+            return new RangeInfo(type, min, max, current);
         }
 
         /**
@@ -5354,12 +5257,11 @@
         /**
          * Recycles this instance.
          *
-         * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+         * @deprecated Object pooling has been discontinued. Calling this function now will have
+         * no effect.
          */
-        void recycle() {
-            clear();
-            sPool.release(this);
-        }
+        @Deprecated
+        void recycle() {}
 
         private void clear() {
             mType = 0;
@@ -5392,20 +5294,15 @@
         /** Selection mode where multiple items may be selected. */
         public static final int SELECTION_MODE_MULTIPLE = 2;
 
-        private static final int MAX_POOL_SIZE = 20;
-
-        private static final SynchronizedPool<CollectionInfo> sPool =
-                new SynchronizedPool<>(MAX_POOL_SIZE);
-
         private int mRowCount;
         private int mColumnCount;
         private boolean mHierarchical;
         private int mSelectionMode;
 
         /**
-         * Obtains a pooled instance that is a clone of another one.
+         * Instantiates a CollectionInfo that is a clone of another one.
          *
-         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * @deprecated Object pooling has been discontinued. Create a new instance using the
          * constructor {@link
          * AccessibilityNodeInfo.CollectionInfo#CollectionInfo} instead.
          *
@@ -5413,14 +5310,14 @@
          * @hide
          */
         public static CollectionInfo obtain(CollectionInfo other) {
-            return CollectionInfo.obtain(other.mRowCount, other.mColumnCount, other.mHierarchical,
+            return new CollectionInfo(other.mRowCount, other.mColumnCount, other.mHierarchical,
                     other.mSelectionMode);
         }
 
         /**
          * Obtains a pooled instance.
          *
-         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * @deprecated Object pooling has been discontinued. Create a new instance using the
          * constructor {@link
          * AccessibilityNodeInfo.CollectionInfo#CollectionInfo(int, int,
          * boolean)} instead.
@@ -5431,13 +5328,13 @@
          */
         public static CollectionInfo obtain(int rowCount, int columnCount,
                 boolean hierarchical) {
-            return obtain(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE);
+            return new CollectionInfo(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE);
         }
 
         /**
          * Obtains a pooled instance.
          *
-         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * @deprecated Object pooling has been discontinued. Create a new instance using the
          * constructor {@link
          * AccessibilityNodeInfo.CollectionInfo#CollectionInfo(int, int,
          * boolean, int)} instead.
@@ -5454,16 +5351,7 @@
          */
         public static CollectionInfo obtain(int rowCount, int columnCount,
                 boolean hierarchical, int selectionMode) {
-           final CollectionInfo info = sPool.acquire();
-            if (info == null) {
-                return new CollectionInfo(rowCount, columnCount, hierarchical, selectionMode);
-            }
-
-            info.mRowCount = rowCount;
-            info.mColumnCount = columnCount;
-            info.mHierarchical = hierarchical;
-            info.mSelectionMode = selectionMode;
-            return info;
+            return new CollectionInfo(rowCount, columnCount, hierarchical, selectionMode);
         }
 
         /**
@@ -5535,14 +5423,13 @@
         }
 
         /**
-         * Recycles this instance.
+         * Previously would recycle this instance.
          *
-         * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+         * @deprecated Object pooling has been discontinued. Calling this function now will have
+         * no effect.
          */
-        void recycle() {
-            clear();
-            sPool.release(this);
-        }
+        @Deprecated
+        void recycle() {}
 
         private void clear() {
             mRowCount = 0;
@@ -5566,15 +5453,10 @@
      * </p>
      */
     public static final class CollectionItemInfo {
-        private static final int MAX_POOL_SIZE = 20;
-
-        private static final SynchronizedPool<CollectionItemInfo> sPool =
-                new SynchronizedPool<>(MAX_POOL_SIZE);
-
         /**
-         * Obtains a pooled instance that is a clone of another one.
+         * Instantiates a CollectionItemInfo that is a clone of another one.
          *
-         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * @deprecated Object pooling has been discontinued. Create a new instance using the
          * constructor {@link
          * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo}
          * instead.
@@ -5582,20 +5464,20 @@
          * @param other The instance to clone.
          * @hide
          */
+        @Deprecated
         public static CollectionItemInfo obtain(CollectionItemInfo other) {
-            return CollectionItemInfo.obtain(other.mRowTitle, other.mRowIndex, other.mRowSpan,
-                    other.mColumnTitle, other.mColumnIndex, other.mColumnSpan, other.mHeading,
-                    other.mSelected);
+            return new CollectionItemInfo(other.mRowTitle, other.mRowIndex, other.mRowSpan,
+                other.mColumnTitle, other.mColumnIndex, other.mColumnSpan, other.mHeading,
+                other.mSelected);
         }
 
         /**
-         * Obtains a pooled instance.
+         * Instantiates a new CollectionItemInfo.
          *
-         * <p>In most situations object pooling is not beneficial. Create a new instance using the
+         * @deprecated Object pooling has been discontinued. Create a new instance using the
          * constructor {@link
          * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int,
          * int, int, int, boolean)} instead.
-         *
          * @param rowIndex The row index at which the item is located.
          * @param rowSpan The number of rows the item spans.
          * @param columnIndex The column index at which the item is located.
@@ -5603,37 +5485,39 @@
          * @param heading Whether the item is a heading. (Prefer
          *                {@link AccessibilityNodeInfo#setHeading(boolean)}).
          */
+        @Deprecated
         public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
                 int columnIndex, int columnSpan, boolean heading) {
-            return obtain(rowIndex, rowSpan, columnIndex, columnSpan, heading, false);
+            return new CollectionItemInfo(rowIndex, rowSpan, columnIndex, columnSpan, heading,
+                false);
         }
 
         /**
-         * Obtains a pooled instance.
+         * Instantiates a new CollectionItemInfo.
          *
-         * <p>In most situations object pooling is not beneficial. Creates a new instance using the
+         * @deprecated Object pooling has been discontinued. Create a new instance using the
          * constructor {@link
          * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int,
-         * int, int, int, boolean, boolean)} instead.
-         *
+         * int, int, int, boolean)} instead.
          * @param rowIndex The row index at which the item is located.
          * @param rowSpan The number of rows the item spans.
          * @param columnIndex The column index at which the item is located.
          * @param columnSpan The number of columns the item spans.
          * @param heading Whether the item is a heading. (Prefer
-         *                {@link AccessibilityNodeInfo#setHeading(boolean)})
+         *                {@link AccessibilityNodeInfo#setHeading(boolean)}).
          * @param selected Whether the item is selected.
          */
+        @Deprecated
         public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
                 int columnIndex, int columnSpan, boolean heading, boolean selected) {
-            return obtain(null, rowIndex, rowSpan, null, columnIndex,
-                    columnSpan, heading, selected);
+            return new CollectionItemInfo(rowIndex, rowSpan, columnIndex, columnSpan, heading,
+                selected);
         }
 
         /**
-         * Obtains a pooled instance.
+         * Instantiates a new CollectionItemInfo.
          *
-         * <p>In most situations object pooling is not beneficial. Creates a new instance using the
+         * @deprecated Object pooling has been discontinued. Creates a new instance using the
          * constructor {@link
          * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int,
          * int, int, int, boolean, boolean)} instead.
@@ -5648,25 +5532,13 @@
          *                {@link AccessibilityNodeInfo#setHeading(boolean)})
          * @param selected Whether the item is selected.
          */
+        @Deprecated
         @NonNull
         public static CollectionItemInfo obtain(@Nullable String rowTitle, int rowIndex,
                 int rowSpan, @Nullable String columnTitle, int columnIndex, int columnSpan,
                 boolean heading, boolean selected) {
-            final CollectionItemInfo info = sPool.acquire();
-            if (info == null) {
-                return new CollectionItemInfo(rowTitle, rowIndex, rowSpan, columnTitle,
-                        columnIndex, columnSpan, heading, selected);
-            }
-
-            info.mRowIndex = rowIndex;
-            info.mRowSpan = rowSpan;
-            info.mColumnIndex = columnIndex;
-            info.mColumnSpan = columnSpan;
-            info.mHeading = heading;
-            info.mSelected = selected;
-            info.mRowTitle = rowTitle;
-            info.mColumnTitle = columnTitle;
-            return info;
+            return new CollectionItemInfo(rowTitle, rowIndex, rowSpan, columnTitle, columnIndex,
+                columnSpan, heading, selected);
         }
 
         private boolean mHeading;
@@ -5817,12 +5689,11 @@
         /**
          * Recycles this instance.
          *
-         * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+         * @deprecated Object pooling has been discontinued. Calling this function now will have
+         * no effect.
          */
-        void recycle() {
-            clear();
-            sPool.release(this);
-        }
+        @Deprecated
+        void recycle() {}
 
         private void clear() {
             mColumnIndex = 0;
@@ -6151,34 +6022,34 @@
      */
     public static final class ExtraRenderingInfo {
         private static final int UNDEFINED_VALUE = -1;
-        private static final int MAX_POOL_SIZE = 20;
-        private static final SynchronizedPool<ExtraRenderingInfo> sPool =
-                new SynchronizedPool<>(MAX_POOL_SIZE);
 
         private Size mLayoutSize;
         private float mTextSizeInPx = UNDEFINED_VALUE;
         private int mTextSizeUnit = UNDEFINED_VALUE;
 
         /**
-         * Obtains a pooled instance.
+         * Instantiates an ExtraRenderingInfo, by copying an existing one.
+         *
          * @hide
+         * @deprecated Object pooling has been discontinued. Create a new instance using the
+         * constructor {@link #ExtraRenderingInfo(ExtraRenderingInfo)} instead.
          */
+        @Deprecated
         @NonNull
         public static ExtraRenderingInfo obtain() {
-            final ExtraRenderingInfo info = sPool.acquire();
-            if (info == null) {
-                return new ExtraRenderingInfo(null);
-            }
-            return info;
+            return new ExtraRenderingInfo(null);
         }
 
-        /** Obtains a pooled instance that is a clone of another one. */
+        /**
+         * Instantiates an ExtraRenderingInfo, by copying an existing one.
+         *
+         * @deprecated Object pooling has been discontinued. Create a new instance using the
+         * constructor {@link #ExtraRenderingInfo(ExtraRenderingInfo)} instead.
+         * @param other
+         */
+        @Deprecated
         private static ExtraRenderingInfo obtain(ExtraRenderingInfo other) {
-            ExtraRenderingInfo extraRenderingInfo = ExtraRenderingInfo.obtain();
-            extraRenderingInfo.mLayoutSize = other.mLayoutSize;
-            extraRenderingInfo.mTextSizeInPx = other.mTextSizeInPx;
-            extraRenderingInfo.mTextSizeUnit = other.mTextSizeUnit;
-            return extraRenderingInfo;
+            return new ExtraRenderingInfo(other);
         }
 
         /**
@@ -6268,14 +6139,13 @@
         }
 
         /**
-         * Recycles this instance.
+         * Previously would recycle this instance.
          *
-         * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
+         * @deprecated Object pooling has been discontinued. Calling this function now will have
+         * no effect.
          */
-        void recycle() {
-            clear();
-            sPool.release(this);
-        }
+        @Deprecated
+        void recycle() {}
 
         private void clear() {
             mLayoutSize = null;
@@ -6291,7 +6161,7 @@
             new Parcelable.Creator<AccessibilityNodeInfo>() {
         @Override
         public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
-            AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+            AccessibilityNodeInfo info = new AccessibilityNodeInfo();
             info.initFromParcel(parcel);
             return info;
         }
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index f26abb2..426a3f4 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -78,13 +78,6 @@
         | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
         | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
 
-    // Housekeeping
-    private static final int MAX_POOL_SIZE = 10;
-    private static final Object sPoolLock = new Object();
-    private static AccessibilityRecord sPool;
-    private static int sPoolSize;
-    private AccessibilityRecord mNext;
-    private boolean mIsInPool;
 
     @UnsupportedAppUsage
     boolean mSealed;
@@ -821,15 +814,14 @@
     }
 
     /**
-     * Returns a cached instance if such is available or a new one is
-     * instantiated. The instance is initialized with data from the
+     * Instantiates a new record initialized with data from the
      * given record.
      *
-     * <p>In most situations object pooling is not beneficial. Create a new instance using the
-     * constructor {@link #AccessibilityRecord(AccessibilityRecord)} instead.
-     *
+     * @deprecated Object pooling has been discontinued. Create a new instance using the
+     * constructor {@link #AccessibilityRecord()} instead.
      * @return An instance.
      */
+    @Deprecated
     public static AccessibilityRecord obtain(AccessibilityRecord record) {
        AccessibilityRecord clone = AccessibilityRecord.obtain();
        clone.init(record);
@@ -837,51 +829,25 @@
     }
 
     /**
-     * Returns a cached instance if such is available or a new one is
-     * instantiated.
+     * Instantiates a new record.
      *
-     * <p>In most situations object pooling is not beneficial. Create a new instance using the
+     * @deprecated Object pooling has been discontinued. Create a new instance using the
      * constructor {@link #AccessibilityRecord()} instead.
-     *
      * @return An instance.
      */
+    @Deprecated
     public static AccessibilityRecord obtain() {
-        synchronized (sPoolLock) {
-            if (sPool != null) {
-                AccessibilityRecord record = sPool;
-                sPool = sPool.mNext;
-                sPoolSize--;
-                record.mNext = null;
-                record.mIsInPool = false;
-                return record;
-            }
-            return new AccessibilityRecord();
-        }
+        return new AccessibilityRecord();
     }
 
     /**
-     * Return an instance back to be reused.
-     * <p>
-     * <strong>Note:</strong> You must not touch the object after calling this function.
+     * Would previously return an instance back to be reused.
      *
-     * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
-     *
-     * @throws IllegalStateException If the record is already recycled.
+     * @deprecated Object pooling has been discontinued. Calling this function now will have
+     * no effect.
      */
-    public void recycle() {
-        if (mIsInPool) {
-            throw new IllegalStateException("Record already recycled!");
-        }
-        clear();
-        synchronized (sPoolLock) {
-            if (sPoolSize <= MAX_POOL_SIZE) {
-                mNext = sPool;
-                sPool = this;
-                mIsInPool = true;
-                sPoolSize++;
-            }
-        }
-    }
+    @Deprecated
+    public void recycle() { }
 
     /**
      * Initialize this record from another one.
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 862829b..dbf3570 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -74,6 +74,9 @@
  * <p>
  * Note that toasts being sent from the background are rate limited, so avoid sending such toasts
  * in quick succession.
+ * <p>
+ * Starting with Android 12 (API level 31), apps targeting Android 12 or newer will have
+ * their toasts limited to two lines.
  *
  * <div class="special reference">
  * <h3>Developer Guides</h3>
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index bc3c2f5..025f711 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -190,7 +190,7 @@
      * the handover intent.
      * TODO: investigate whether the privileged query is necessary to determine the availability.
      */
-    protected static final String EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE =
+    public static final String EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE =
             "com.android.internal.app.ChooserActivity.EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE";
 
     /**
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 88425be..c1111ec 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -21,11 +21,13 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
+import android.os.Parcel;
 import android.os.SystemClock;
 import android.os.UidBatteryConsumer;
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -133,9 +135,12 @@
      */
     @VisibleForTesting
     public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) {
-        return getBatteryUsageStats(query, currentTimeMillis());
+        synchronized (mStats) {
+            return getBatteryUsageStats(query, currentTimeMillis());
+        }
     }
 
+    @GuardedBy("mStats")
     private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query,
             long currentTimeMs) {
         if (query.getToTimestamp() == 0) {
@@ -145,6 +150,7 @@
         }
     }
 
+    @GuardedBy("mStats")
     private BatteryUsageStats getCurrentBatteryUsageStats(BatteryUsageStatsQuery query,
             long currentTimeMs) {
         final long realtimeUs = elapsedRealtime() * 1000;
@@ -189,7 +195,12 @@
             }
 
             BatteryStatsImpl batteryStatsImpl = (BatteryStatsImpl) mStats;
-            batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.mHistoryBuffer);
+
+            // Make a copy of battery history to avoid concurrent modification.
+            Parcel historyBuffer = Parcel.obtain();
+            historyBuffer.appendFrom(batteryStatsImpl.mHistoryBuffer, 0,
+                    batteryStatsImpl.mHistoryBuffer.dataSize());
+            batteryUsageStatsBuilder.setBatteryHistory(historyBuffer);
         }
 
         return batteryUsageStatsBuilder.build();
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index 76aa7a0..36b7ee5 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -15,6 +15,7 @@
  */
 package com.android.internal.policy;
 
+import android.content.Intent;
 import com.android.internal.policy.IKeyguardDrawnCallback;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IKeyguardStateCallback;
@@ -113,7 +114,20 @@
 
     /**
      * Notifies the Keyguard that the power key was pressed while locked and launched Home rather
-     * than putting the device to sleep or waking up.
+     * than putting the device to sleep or waking up. Note that it's called only if the device is
+     * interactive.
      */
     void onShortPowerPressedGoHome();
+
+    /**
+     * Notifies the Keyguard that it needs to bring up a bouncer and then launch the intent as soon
+     * as user unlocks the watch.
+     */
+    void dismissKeyguardToLaunch(in Intent intentToLaunch);
+
+    /**
+     * Notifies the Keyguard that a key was pressed while locked so the Keyguard can handle it.
+     * Note that it's called only if the device is interactive.
+     */
+    void onSystemKeyPressed(int keycode);
 }
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 78e5adc..55f1369 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -37,16 +37,6 @@
     return reinterpret_cast<jlong>(queue.get());
 }
 
-static jlong nativeCreateAndUpdate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
-                                   jlong width, jlong height, jint format) {
-    ScopedUtfChars name(env, jName);
-    sp<BLASTBufferQueue> queue =
-            new BLASTBufferQueue(name.c_str(), reinterpret_cast<SurfaceControl*>(surfaceControl),
-                                 width, height, format);
-    queue->incStrong((void*)nativeCreate);
-    return reinterpret_cast<jlong>(queue.get());
-}
-
 static void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
     sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
     queue->decStrong((void*)nativeCreate);
@@ -91,11 +81,15 @@
     queue->applyPendingTransactions(frameNum);
 }
 
+static bool nativeIsSameSurfaceControl(JNIEnv* env, jclass clazz, jlong ptr, jlong surfaceControl) {
+    sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+    return queue->isSameSurfaceControl(reinterpret_cast<SurfaceControl*>(surfaceControl));
+}
+
 static const JNINativeMethod gMethods[] = {
         /* name, signature, funcPtr */
         // clang-format off
         {"nativeCreate", "(Ljava/lang/String;)J", (void*)nativeCreate},
-        {"nativeCreateAndUpdate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreateAndUpdate},
         {"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface},
         {"nativeDestroy", "(J)V", (void*)nativeDestroy},
         {"nativeSetSyncTransaction", "(JJZ)V", (void*)nativeSetSyncTransaction},
@@ -103,6 +97,7 @@
         {"nativeMergeWithNextTransaction", "(JJJ)V", (void*)nativeMergeWithNextTransaction},
         {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum},
         {"nativeApplyPendingTransactions", "(JJ)V", (void*)nativeApplyPendingTransactions},
+        {"nativeIsSameSurfaceControl", "(JJ)Z", (void*)nativeIsSameSurfaceControl},
         // clang-format on
 };
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index bd0604e..7d8bcea 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2897,6 +2897,10 @@
     <!-- Make the IME killable by the lowmemorykiller by raising its oom_score_adj. -->
     <bool name="config_killableInputMethods">false</bool>
 
+    <!-- Prevent the InputMethodManagerService from starting up the IME unless
+     the currently focused view is a text editor. -->
+    <bool name="config_preventImeStartupUnlessTextEditor">false</bool>
+
     <!-- The list of classes that should be added to the notification ranking pipeline.
      See {@link com.android.server.notification.NotificationSignalExtractor}
       If you add a new extractor to this list make sure to update
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index bc127d9..1f560f4 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3313,6 +3313,8 @@
   <staging-public-group type="bool" first-id="0x01cf0000">
     <!-- @hide @SystemApi -->
     <public name="config_systemCaptionsServiceCallsEnabled" />
+    <!-- @hide @TestApi -->
+    <public name="config_preventImeStartupUnlessTextEditor" />
   </staging-public-group>
 
   <staging-public-group type="fraction" first-id="0x01ce0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7687b93..ba4aa81 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2250,6 +2250,7 @@
   <java-symbol type="bool" name="config_autoResetAirplaneMode" />
   <java-symbol type="string" name="config_notificationAccessConfirmationActivity" />
   <java-symbol type="bool" name="config_killableInputMethods" />
+  <java-symbol type="bool" name="config_preventImeStartupUnlessTextEditor" />
 
   <java-symbol type="layout" name="resolver_list" />
   <java-symbol type="id" name="resolver_list" />
diff --git a/core/tests/coretests/src/android/net/NetworkPolicyTest.kt b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt
index 121caef..3c8f90c 100644
--- a/core/tests/coretests/src/android/net/NetworkPolicyTest.kt
+++ b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt
@@ -32,14 +32,14 @@
 import kotlin.test.assertTrue
 
 private const val TEST_IMSI1 = "TESTIMSI1"
-private const val TEST_SSID1 = "TESTISSID1"
+private const val TEST_WIFI_NETWORK_KEY1 = "TESTKEY1"
 
 @RunWith(AndroidJUnit4::class)
 class NetworkPolicyTest {
     @Test
     fun testTemplateBackupRestore() {
         assertPolicyBackupRestore(createTestPolicyForTemplate(
-                NetworkTemplate.buildTemplateWifi(TEST_SSID1)))
+                NetworkTemplate.buildTemplateWifi(TEST_WIFI_NETWORK_KEY1)))
         assertPolicyBackupRestore(createTestPolicyForTemplate(
                 NetworkTemplate.buildTemplateMobileAll(TEST_IMSI1)))
         assertPolicyBackupRestore(createTestPolicyForTemplate(
@@ -79,6 +79,6 @@
 
         // Verify wifi template can be persistable if the Wifi Network Key is supplied.
         assertTrue(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(MATCH_WIFI)
-                .setWifiNetworkKey(TEST_SSID1).build()))
+                .setWifiNetworkKeys(setOf(TEST_WIFI_NETWORK_KEY1)).build()))
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
index 2c8c385..6df9002 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
@@ -42,14 +42,14 @@
     // and assertAccessibilityEventCleared
 
     /** The number of properties of the {@link AccessibilityEvent} class. */
-    private static final int A11Y_EVENT_NON_STATIC_FIELD_COUNT = 34;
+    private static final int A11Y_EVENT_NON_STATIC_FIELD_COUNT = 32;
 
     // The number of fields tested in the corresponding CTS AccessibilityRecordTest:
     // assertAccessibilityRecordCleared, fullyPopulateAccessibilityRecord,
     // and assertEqualAccessibilityRecord
 
     /** The number of properties of the {@link AccessibilityRecord} class. */
-    private static final int A11Y_RECORD_NON_STATIC_FIELD_COUNT = 25;
+    private static final int A11Y_RECORD_NON_STATIC_FIELD_COUNT = 23;
 
     @Test
     public void testImportantForAccessibiity_getSetWorkAcrossParceling() {
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index 212fdca..bb1a3b18 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -17,7 +17,6 @@
 package android.view.accessibility;
 
 import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertSame;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -188,17 +187,6 @@
     }
 
     @Test
-    public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception {
-        AccessibilityEvent sentEvent = AccessibilityEvent.obtain(
-                AccessibilityEvent.TYPE_ANNOUNCEMENT);
-
-        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
-        manager.sendAccessibilityEvent(sentEvent);
-
-        assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain());
-    }
-
-    @Test
     public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception {
         AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
 
diff --git a/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java
deleted file mode 100644
index 11f4e3c..0000000
--- a/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 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.view.accessibility;
-
-import androidx.test.filters.SmallTest;
-
-import junit.framework.TestCase;
-
-/**
- * This class exercises the caching and recycling of {@link AccessibilityEvent}s.
- */
-public class RecycleAccessibilityEventTest extends TestCase {
-
-    private static final String CLASS_NAME = "foo.bar.baz.Test";
-    private static final String PACKAGE_NAME = "foo.bar.baz";
-    private static final String TEXT = "Some stuff";
-
-    private static final String CONTENT_DESCRIPTION = "Content description";
-    private static final int ITEM_COUNT = 10;
-    private static final int CURRENT_ITEM_INDEX = 1;
-
-    private static final int FROM_INDEX = 1;
-    private static final int ADDED_COUNT = 2;
-    private static final int REMOVED_COUNT = 1;
-
-    /**
-     * If an {@link AccessibilityEvent} is marshaled/unmarshaled correctly
-     */
-    @SmallTest
-    public void testAccessibilityEventViewTextChangedType() {
-        AccessibilityEvent first =
-            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
-        assertNotNull(first);
-
-        first.setClassName(CLASS_NAME);
-        first.setPackageName(PACKAGE_NAME);
-        first.getText().add(TEXT);
-        first.setFromIndex(FROM_INDEX);
-        first.setAddedCount(ADDED_COUNT);
-        first.setRemovedCount(REMOVED_COUNT);
-        first.setChecked(true);
-        first.setContentDescription(CONTENT_DESCRIPTION);
-        first.setItemCount(ITEM_COUNT);
-        first.setCurrentItemIndex(CURRENT_ITEM_INDEX);
-        first.setEnabled(true);
-        first.setPassword(true);
-
-        first.recycle();
-
-        assertNotNull(first);
-        assertNull(first.getClassName());
-        assertNull(first.getPackageName());
-        assertEquals(0, first.getText().size());
-        assertFalse(first.isChecked());
-        assertNull(first.getContentDescription());
-        assertEquals(-1, first.getItemCount());
-        assertEquals(AccessibilityEvent.INVALID_POSITION, first.getCurrentItemIndex());
-        assertFalse(first.isEnabled());
-        assertFalse(first.isPassword());
-        assertEquals(-1, first.getFromIndex());
-        assertEquals(-1, first.getAddedCount());
-        assertEquals(-1, first.getRemovedCount());
-
-        // get another event from the pool (this must be the recycled first)
-        AccessibilityEvent second = AccessibilityEvent.obtain();
-        assertEquals(first, second);
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index c0ced6c..69ff7c6 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -180,6 +180,16 @@
         return clientIntent;
     }
 
+    /**
+     * Whether {@code #testIsAppPredictionServiceAvailable} should verify the behavior after
+     * changing the availability conditions at runtime. In the unbundled chooser, the availability
+     * is cached at start and will never be re-evaluated.
+     * TODO: remove when we no longer want to test the system's on-the-fly evaluation.
+     */
+    protected boolean shouldTestTogglingAppPredictionServiceAvailabilityAtRuntime() {
+        return true;
+    }
+
     /* --------
      * The code in this section is unorthodox and can be simplified/reverted when we no longer need
      * to support the parallel chooser implementations.
@@ -784,7 +794,8 @@
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
-        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).packageName, is(
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
         assertThat(logger.get(1).appProvidedApp, is(0));
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
@@ -802,7 +813,7 @@
         assertThat(logger.event(4).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
 
-        // SHARESHEET_EDIT_TARGET_SELECTED:
+        // SHARESHEET_NEARBY_TARGET_SELECTED:
         assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
         assertThat(logger.get(5).targetType,
                 is(ChooserActivityLogger
@@ -814,7 +825,7 @@
 
 
 
-    @Test
+    @Test @Ignore
     public void testEditImageLogs() throws Exception {
         Intent sendIntent = createSendImageIntent(
                 Uri.parse("android.resource://com.android.frameworks.coretests/"
@@ -853,7 +864,8 @@
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("image/png"));
-        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).packageName, is(
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
         assertThat(logger.get(1).appProvidedApp, is(0));
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
@@ -1321,6 +1333,10 @@
         } else {
             assertThat(activity.isAppPredictionServiceAvailable(), is(true));
 
+            if (!shouldTestTogglingAppPredictionServiceAvailabilityAtRuntime()) {
+                return;
+            }
+
             ChooserActivityOverrideData.getInstance().resources =
                     Mockito.spy(activity.getResources());
             when(
@@ -2101,7 +2117,8 @@
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
-        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).packageName, is(
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
         assertThat(logger.get(1).appProvidedApp, is(0));
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
@@ -2119,7 +2136,7 @@
         assertThat(logger.event(4).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
 
-        // SHARESHEET_EDIT_TARGET_SELECTED:
+        // SHARESHEET_APP_TARGET_SELECTED:
         assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
         assertThat(logger.get(5).targetType,
                 is(ChooserActivityLogger
@@ -2197,7 +2214,8 @@
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
-        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).packageName, is(
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
         assertThat(logger.get(1).appProvidedApp, is(0));
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
@@ -2215,7 +2233,7 @@
                         .SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId()));
     }
 
-    @Test
+    @Test @Ignore
     public void testEmptyDirectRowLogging() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
         // We need app targets for direct targets to get displayed
@@ -2259,7 +2277,8 @@
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
-        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).packageName, is(
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
         assertThat(logger.get(1).appProvidedApp, is(0));
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
@@ -2320,7 +2339,8 @@
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
-        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).packageName, is(
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
         assertThat(logger.get(1).appProvidedApp, is(0));
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
@@ -2338,7 +2358,7 @@
         assertThat(logger.event(4).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
 
-        // SHARESHEET_EDIT_TARGET_SELECTED:
+        // SHARESHEET_COPY_TARGET_SELECTED:
         assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
         assertThat(logger.get(5).targetType,
                 is(ChooserActivityLogger
@@ -2386,7 +2406,8 @@
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is(TEST_MIME_TYPE));
-        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).packageName, is(
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
         assertThat(logger.get(1).appProvidedApp, is(0));
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 8d3eadb..a9e730d 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -27,8 +27,6 @@
     // Note: This field is accessed by native code.
     public long mNativeObject; // BLASTBufferQueue*
 
-    private static native long nativeCreateAndUpdate(String name, long surfaceControl, long width,
-            long height, int format);
     private static native long nativeCreate(String name);
     private static native void nativeDestroy(long ptr);
     private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle);
@@ -40,11 +38,13 @@
                                                               long frameNumber);
     private static native long nativeGetLastAcquiredFrameNum(long ptr);
     private static native void nativeApplyPendingTransactions(long ptr, long frameNumber);
+    private static native boolean nativeIsSameSurfaceControl(long ptr, long surfaceControlPtr);
 
     /** Create a new connection with the surface flinger. */
     public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
             @PixelFormat.Format int format) {
-        mNativeObject = nativeCreateAndUpdate(name, sc.mNativeObject, width, height, format);
+        this(name);
+        update(sc, width, height, format);
     }
 
     public BLASTBufferQueue(String name) {
@@ -152,4 +152,11 @@
     public long getLastAcquiredFrameNum() {
         return nativeGetLastAcquiredFrameNum(mNativeObject);
     }
+
+    /**
+     * @return True if the associated SurfaceControl has the same handle as {@param sc}.
+     */
+    public boolean isSameSurfaceControl(SurfaceControl sc) {
+        return nativeIsSameSurfaceControl(mNativeObject, sc.mNativeObject);
+    }
 }
diff --git a/graphics/java/android/graphics/RenderEffect.java b/graphics/java/android/graphics/RenderEffect.java
index ad4c3fe..b8a4685 100644
--- a/graphics/java/android/graphics/RenderEffect.java
+++ b/graphics/java/android/graphics/RenderEffect.java
@@ -290,6 +290,22 @@
         return new RenderEffect(nativeCreateShaderEffect(shader.getNativeInstance()));
     }
 
+    /**
+     * Create a {@link RenderEffect} that executes the provided {@link RuntimeShader} and passes
+     * the contents of the {@link android.graphics.RenderNode} that this RenderEffect is installed
+     * on as an input to the shader.
+     * @param shader the runtime shader that will bind the inputShaderName to the RenderEffect input
+     * @param uniformShaderName the uniform name defined in the RuntimeShader's program to which
+     *                         the contents of the RenderNode will be bound
+     */
+    @NonNull
+    public static RenderEffect createRuntimeShaderEffect(
+            @NonNull RuntimeShader shader, @NonNull String uniformShaderName) {
+        return new RenderEffect(
+                nativeCreateRuntimeShaderEffect(shader.getNativeShaderBuilder(),
+                        uniformShaderName));
+    }
+
     private final long mNativeRenderEffect;
 
     /* only constructed from static factory methods */
@@ -318,5 +334,7 @@
     private static native long nativeCreateBlendModeEffect(long dst, long src, int blendmode);
     private static native long nativeCreateChainEffect(long outer, long inner);
     private static native long nativeCreateShaderEffect(long shader);
+    private static native long nativeCreateRuntimeShaderEffect(
+            long shaderBuilder, String inputShaderName);
     private static native long nativeGetFinalizer();
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index d70857a4..4f01dc6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -309,10 +309,11 @@
             PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
             PipBoundsState pipBoundsState, PipTransitionState pipTransitionState,
             PhonePipMenuController pipMenuController,
-            PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+            Optional<SplitScreenController> splitScreenOptional) {
         return new PipTransition(context, pipBoundsState, pipTransitionState, pipMenuController,
                 pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer,
-                pipSurfaceTransactionHelper);
+                pipSurfaceTransactionHelper, splitScreenOptional);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 667d166..a8d4d1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -25,6 +25,8 @@
 
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
 import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
 import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START;
@@ -40,6 +42,10 @@
 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
 import static com.android.wm.shell.pip.PipAnimationController.isRemovePipDirection;
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -394,6 +400,18 @@
         mPipUiEventLoggerLogger.log(
                 PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+        if (ENABLE_SHELL_TRANSITIONS) {
+            if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
+                mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo,
+                        isPipTopLeft()
+                                ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+                mPipTransitionController.startExitTransition(
+                        TRANSIT_EXIT_PIP_TO_SPLIT, wct, null /* destinationBounds */);
+                return;
+            }
+        }
+
         final Rect destinationBounds = mPipBoundsState.getDisplayBounds();
         final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
                 ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
@@ -414,7 +432,7 @@
         mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            mPipTransitionController.startTransition(destinationBounds, wct);
+            mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
             return;
         }
         mSyncTransactionQueue.queue(wct);
@@ -479,7 +497,8 @@
             wct.setBounds(mToken, null);
             wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
             wct.reorder(mToken, false);
-            mPipTransitionController.startTransition(null, wct);
+            mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
+                    null /* destinationBounds */);
             return;
         }
 
@@ -1280,7 +1299,8 @@
     public void applyFinishBoundsResize(@NonNull WindowContainerTransaction wct,
             @PipAnimationController.TransitionDirection int direction, boolean wasPipTopLeft) {
         if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
-            mSplitScreenOptional.get().enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct);
+            mSplitScreenOptional.ifPresent(splitScreenController ->
+                    splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct));
         } else {
             mTaskOrganizer.applyTransaction(wct);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index c909652f..e440feb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -30,8 +30,9 @@
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
 
 import android.app.ActivityManager;
 import android.app.TaskInfo;
@@ -51,8 +52,11 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
+import java.util.Optional;
+
 /**
  * Implementation of transitions for PiP on phone. Responsible for enter (alpha, bounds) and
  * exit animation.
@@ -64,6 +68,7 @@
     private final PipTransitionState mPipTransitionState;
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+    private final Optional<SplitScreenController> mSplitScreenOptional;
     private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
     private Transitions.TransitionFinishCallback mFinishCallback;
     private Rect mExitDestinationBounds = new Rect();
@@ -77,13 +82,15 @@
             PipAnimationController pipAnimationController,
             Transitions transitions,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
-            PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+            Optional<SplitScreenController> splitScreenOptional) {
         super(pipBoundsState, pipMenuController, pipBoundsAlgorithm,
                 pipAnimationController, transitions, shellTaskOrganizer);
         mPipTransitionState = pipTransitionState;
         mEnterExitAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
         mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
+        mSplitScreenOptional = splitScreenOptional;
     }
 
     @Override
@@ -101,13 +108,12 @@
     }
 
     @Override
-    public void startTransition(Rect destinationBounds, WindowContainerTransaction out) {
+    public void startExitTransition(int type, WindowContainerTransaction out,
+            @Nullable Rect destinationBounds) {
         if (destinationBounds != null) {
             mExitDestinationBounds.set(destinationBounds);
-            mExitTransition = mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this);
-        } else {
-            mTransitions.startTransition(TRANSIT_REMOVE_PIP, out, this);
         }
+        mExitTransition = mTransitions.startTransition(type, out, this);
     }
 
     @Override
@@ -116,9 +122,15 @@
             @android.annotation.NonNull SurfaceControl.Transaction startTransaction,
             @android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
             @android.annotation.NonNull Transitions.TransitionFinishCallback finishCallback) {
-
-        if (mExitTransition == transition || info.getType() == TRANSIT_EXIT_PIP) {
+        final int type = info.getType();
+        if (mExitTransition == transition) {
             mExitTransition = null;
+
+            if (type == TRANSIT_EXIT_PIP_TO_SPLIT) {
+                return startExitToSplitAnimation(
+                        info, startTransaction, finishTransaction, finishCallback);
+            }
+
             if (info.getChanges().size() == 1) {
                 if (mFinishCallback != null) {
                     mFinishCallback.onTransitionFinished(null, null);
@@ -138,7 +150,7 @@
             }
         }
 
-        if (info.getType() == TRANSIT_REMOVE_PIP) {
+        if (type == TRANSIT_REMOVE_PIP) {
             if (mFinishCallback != null) {
                 mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
                 mFinishCallback = null;
@@ -154,7 +166,7 @@
 
         // We only support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps
         // that enter PiP instantly on opening, mostly from CTS/Flicker tests)
-        if (info.getType() != TRANSIT_PIP && info.getType() != TRANSIT_OPEN) {
+        if (type != TRANSIT_PIP && type != TRANSIT_OPEN) {
             // In case the PIP window is part of rotation transition, reset the bounds and rounded
             // corner.
             for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -368,6 +380,40 @@
         return true;
     }
 
+    private boolean startExitToSplitAnimation(TransitionInfo info,
+            SurfaceControl.Transaction startTransaction,
+            SurfaceControl.Transaction finishTransaction,
+            Transitions.TransitionFinishCallback finishCallback) {
+        final int changeSize = info.getChanges().size();
+        if (changeSize < 4) {
+            throw new RuntimeException(
+                    "Got an exit-pip-to-split transition with unexpected change-list");
+        }
+        for (int i = changeSize - 1; i >= 0; i--) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            final int mode = change.getMode();
+
+            if (mode == TRANSIT_CHANGE && change.getParent() != null) {
+                // TODO: perform resize/expand animation for reparented child task.
+                continue;
+            }
+
+            if (isOpeningType(mode) && change.getParent() == null) {
+                final SurfaceControl leash = change.getLeash();
+                final Rect endBounds = change.getEndAbsBounds();
+                startTransaction
+                        .show(leash)
+                        .setAlpha(leash, 1f)
+                        .setPosition(leash, endBounds.left, endBounds.top)
+                        .setWindowCrop(leash, endBounds.width(), endBounds.height());
+            }
+        }
+        mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction);
+        startTransaction.apply();
+        finishCallback.onTransitionFinished(null, null);
+        return true;
+    }
+
     private void finishResizeForMenu(Rect destinationBounds) {
         mPipMenuController.movePipMenu(null, null, destinationBounds);
         mPipMenuController.updateMenuBounds(destinationBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 376f329..1c8b9bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -20,6 +20,7 @@
 
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
 
+import android.annotation.Nullable;
 import android.app.PictureInPictureParams;
 import android.app.TaskInfo;
 import android.content.ComponentName;
@@ -98,9 +99,10 @@
     }
 
     /**
-     * Called when the Shell wants to starts a transition/animation.
+     * Called when the Shell wants to start an exit Pip transition/animation.
      */
-    public void startTransition(Rect destinationBounds, WindowContainerTransaction out) {
+    public void startExitTransition(int type, WindowContainerTransaction out,
+            @Nullable Rect destinationBounds) {
         // Default implementation does nothing.
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 3de59b4..7decb54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -292,6 +292,7 @@
          * Invalidates this instance, preventing future calls from updating the controller.
          */
         void invalidate() {
+            Slog.d("b/206648922", "invalidating controller: " + mController);
             mController = null;
         }
 
@@ -317,7 +318,8 @@
                     (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId)
                             .toArray(new GroupedRecentTaskInfo[0]),
                     true /* blocking */);
-            Slog.d("b/206648922", "getRecentTasks(" + maxNum + "): " + out[0]);
+            Slog.d("b/206648922", "getRecentTasks(" + maxNum + "): " + out[0]
+                    + " mController=" + mController);
             return out[0];
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index fd1d0f9..6921448 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -239,6 +239,15 @@
         enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction());
     }
 
+    public void prepareEnterSplitScreen(WindowContainerTransaction wct,
+            ActivityManager.RunningTaskInfo taskInfo, int startPosition) {
+        mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition);
+    }
+
+    public void finishEnterSplitScreen(SurfaceControl.Transaction t) {
+        mStageCoordinator.finishEnterSplitScreen(t);
+    }
+
     public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
         final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE;
         final int stagePosition =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 54d8ece..0aa8d7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -28,7 +28,8 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
-import static com.android.wm.shell.transition.Transitions.isOpeningType;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -148,7 +149,7 @@
                 t.setWindowCrop(leash, change.getEndAbsBounds().width(),
                         change.getEndAbsBounds().height());
             }
-            boolean isOpening = isOpeningType(info.getType());
+            boolean isOpening = isOpeningTransition(info);
             if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
                 // fade in
                 startExampleAnimation(leash, true /* show */);
@@ -305,6 +306,12 @@
         mTransitions.getAnimExecutor().execute(va::start);
     }
 
+    private boolean isOpeningTransition(TransitionInfo info) {
+        return Transitions.isOpeningType(info.getType())
+                || info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE
+                || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+    }
+
     /** Bundled information of dismiss transition. */
     static class DismissTransition {
         IBinder mTransition;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e2bba76..4c28be0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -46,6 +46,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
 import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
 import static com.android.wm.shell.transition.Transitions.isClosingType;
 import static com.android.wm.shell.transition.Transitions.isOpeningType;
@@ -323,7 +324,14 @@
         if (!evictWct.isEmpty()) {
             wct.merge(evictWct, true /* transfer */);
         }
-        mTaskOrganizer.applyTransaction(wct);
+
+        if (ENABLE_SHELL_TRANSITIONS) {
+            prepareEnterSplitScreen(wct);
+            mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE,
+                    wct, null, this);
+        } else {
+            mTaskOrganizer.applyTransaction(wct);
+        }
         return true;
     }
 
@@ -720,12 +728,47 @@
      * an existing WindowContainerTransaction (rather than applying immediately). This is intended
      * to be used when exiting split might be bundled with other window operations.
      */
-    void prepareExitSplitScreen(@StageType int stageToTop,
+    private void prepareExitSplitScreen(@StageType int stageToTop,
             @NonNull WindowContainerTransaction wct) {
+        if (!mMainStage.isActive()) return;
         mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
         mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
     }
 
+    private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
+        prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED);
+    }
+
+    /**
+     * Prepare transaction to active split screen. If there's a task indicated, the task will be put
+     * into side stage.
+     */
+    void prepareEnterSplitScreen(WindowContainerTransaction wct,
+            @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+        if (mMainStage.isActive()) return;
+
+        if (taskInfo != null) {
+            setSideStagePosition(startPosition, wct);
+            mSideStage.addTask(taskInfo, wct);
+        }
+        mMainStage.activate(getMainStageBounds(), wct, true /* includingTopTask */);
+        mSideStage.moveToTop(getSideStageBounds(), wct);
+    }
+
+    void finishEnterSplitScreen(SurfaceControl.Transaction t) {
+        mSplitLayout.init();
+        setDividerVisibility(true, t);
+        setSplitsVisible(true);
+        mShouldUpdateRecents = true;
+        updateRecentTasksSplitPair();
+        if (!mLogger.hasStartedSession()) {
+            mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
+                    getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+                    getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+                    mSplitLayout.isLandscape());
+        }
+    }
+
     void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
         outTopOrLeftBounds.set(mSplitLayout.getBounds1());
         outBottomOrRightBounds.set(mSplitLayout.getBounds2());
@@ -932,8 +975,7 @@
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             mSplitLayout.init();
             // Make sure the main stage is active.
-            mMainStage.activate(getMainStageBounds(), wct, true /* reparent */);
-            mSideStage.moveToTop(getSideStageBounds(), wct);
+            prepareEnterSplitScreen(wct);
             mSyncQueue.queue(wct);
             mSyncQueue.runInSync(t -> {
                 updateSurfaceBounds(mSplitLayout, t);
@@ -1237,8 +1279,7 @@
             if (isOpening && getStageOfTask(triggerTask) != null) {
                 // One task is appearing into split, prepare to enter split screen.
                 out = new WindowContainerTransaction();
-                mMainStage.activate(getMainStageBounds(), out, true /* includingTopTask */);
-                mSideStage.moveToTop(getSideStageBounds(), out);
+                prepareEnterSplitScreen(out);
                 mSplitTransitions.mPendingEnter = transition;
             }
         }
@@ -1250,18 +1291,7 @@
         // Once the pending enter transition got merged, make sure to bring divider bar visible and
         // clear the pending transition from cache to prevent mess-up the following state.
         if (transition == mSplitTransitions.mPendingEnter) {
-            mSplitLayout.init();
-            setDividerVisibility(true, null /* transaction */);
-            setSplitsVisible(true);
-            mShouldUpdateRecents = true;
-            updateRecentTasksSplitPair();
-
-            if (!mLogger.hasStartedSession()) {
-                mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
-                        getMainStagePosition(), mMainStage.getTopChildTaskUid(),
-                        getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                        mSplitLayout.isLandscape());
-            }
+            finishEnterSplitScreen(null);
             mSplitTransitions.mPendingEnter = null;
         }
     }
@@ -1349,47 +1379,40 @@
                 sideChild = change;
             }
         }
-        if (mainChild == null || sideChild == null) {
-            throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
-                    + " 2 tasks in transition. Possibly one of them failed to launch");
-            // TODO: fallback logic. Probably start a new transition to exit split before
-            //       applying anything here. Ideally consolidate with transition-merging.
+
+        // TODO: fallback logic. Probably start a new transition to exit split before applying
+        //       anything here. Ideally consolidate with transition-merging.
+        if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
+            if (mainChild == null && sideChild == null) {
+                throw new IllegalStateException("Launched a task in split, but didn't receive any"
+                        + " task in transition.");
+            }
+        } else {
+            if (mainChild == null || sideChild == null) {
+                throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+                        + " 2 tasks in transition. Possibly one of them failed to launch");
+            }
         }
 
-        // Update local states (before animating).
-        mSplitLayout.init();
-        setDividerVisibility(true, t);
-        setSplitsVisible(true);
-
-        addDividerBarToTransition(info, t, true /* show */);
-
         // Make some noise if things aren't totally expected. These states shouldn't effect
         // transitions locally, but remotes (like Launcher) may get confused if they were
         // depending on listener callbacks. This can happen because task-organizer callbacks
         // aren't serialized with transition callbacks.
         // TODO(b/184679596): Find a way to either include task-org information in
         //                    the transition, or synchronize task-org callbacks.
-        if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
+        if (mainChild != null && !mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
             Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
                     + " to have been called with " + mainChild.getTaskInfo().taskId
                     + " before startAnimation().");
         }
-        if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
+        if (sideChild != null && !mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
             Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
                     + " to have been called with " + sideChild.getTaskInfo().taskId
                     + " before startAnimation().");
         }
 
-        mShouldUpdateRecents = true;
-        updateRecentTasksSplitPair();
-
-        if (!mLogger.hasStartedSession()) {
-            mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
-                    getMainStagePosition(), mMainStage.getTopChildTaskUid(),
-                    getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                    mSplitLayout.isLandscape());
-        }
-
+        finishEnterSplitScreen(t);
+        addDividerBarToTransition(info, t, true /* show */);
         return true;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 04e20db..83534c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -302,6 +302,11 @@
     }
 
     void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
+        // Clear overridden bounds and windowing mode to make sure the child task can inherit
+        // windowing mode and bounds from split root.
+        wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
+                .setBounds(task.token, null);
+
         wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index b8cbfd9..711510d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -74,23 +74,25 @@
     public static final boolean ENABLE_SHELL_TRANSITIONS =
             SystemProperties.getBoolean("persist.debug.shell_transit", false);
 
-    /** Transition type for dismissing split-screen via dragging the divider off the screen. */
-    public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 1;
-
-    /** Transition type for launching 2 tasks simultaneously. */
-    public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 2;
-
     /** Transition type for exiting PIP via the Shell, via pressing the expand button. */
-    public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 3;
+    public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 1;
+
+    public static final int TRANSIT_EXIT_PIP_TO_SPLIT =  TRANSIT_FIRST_CUSTOM + 2;
 
     /** Transition type for removing PIP via the Shell, either via Dismiss bubble or Close. */
-    public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 4;
+    public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 3;
+
+    /** Transition type for launching 2 tasks simultaneously. */
+    public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 4;
 
     /** Transition type for entering split by opening an app into side-stage. */
     public static final int TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE = TRANSIT_FIRST_CUSTOM + 5;
 
+    /** Transition type for dismissing split-screen via dragging the divider off the screen. */
+    public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 6;
+
     /** Transition type for dismissing split-screen. */
-    public static final int TRANSIT_SPLIT_DISMISS = TRANSIT_FIRST_CUSTOM + 6;
+    public static final int TRANSIT_SPLIT_DISMISS = TRANSIT_FIRST_CUSTOM + 7;
 
     private final WindowOrganizer mOrganizer;
     private final Context mContext;
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index c4be785..68b0b4e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -17,11 +17,11 @@
 @file:JvmName("CommonAssertions")
 package com.android.wm.shell.flicker
 
-import android.graphics.Region
 import android.view.Surface
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.region.Region
 
 fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() {
     assertLayersEnd {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
index 623055f6..efae207 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -17,23 +17,23 @@
 package com.android.wm.shell.flicker.helpers
 
 import android.app.Instrumentation
-import android.graphics.Region
 import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.region.Region
 
 class AppPairsHelper(
     instrumentation: Instrumentation,
     activityLabel: String,
     component: FlickerComponentName
 ) : BaseAppHelper(instrumentation, activityLabel, component) {
-    fun getPrimaryBounds(dividerBounds: Region): android.graphics.Region {
+    fun getPrimaryBounds(dividerBounds: Region): Region {
         val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right,
                 dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset)
         return primaryAppBounds
     }
 
-    fun getSecondaryBounds(dividerBounds: Region): android.graphics.Region {
+    fun getSecondaryBounds(dividerBounds: Region): Region {
         val displayBounds = WindowUtils.displayBounds
         val secondaryAppBounds = Region(0,
                 dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index 2357b0d..8e6fa5f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.helpers
 
 import android.app.Instrumentation
-import android.graphics.Rect
 import android.media.session.MediaController
 import android.media.session.MediaSessionManager
 import android.os.SystemClock
@@ -26,6 +25,7 @@
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
 import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
+import com.android.server.wm.traces.common.Rect
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index 0448ec8..7d7add4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.legacysplitscreen
 
-import android.graphics.Region
 import android.util.Rational
 import android.view.Surface
 import androidx.test.filters.FlakyTest
@@ -41,6 +40,7 @@
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.region.Region
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.helpers.SimpleAppHelper
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 0cbfc92..d3bb008 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -128,8 +128,8 @@
     @Presubmit
     @Test
     fun pipWindowRemainInsideVisibleBounds() {
-        testSpec.assertWm {
-            coversAtMost(displayBounds, pipApp.component)
+        testSpec.assertWmVisibleRegion(pipApp.component) {
+            coversAtMost(displayBounds)
         }
     }
 
@@ -140,8 +140,8 @@
     @Presubmit
     @Test
     fun pipLayerRemainInsideVisibleBounds() {
-        testSpec.assertLayers {
-            coversAtMost(displayBounds, pipApp.component)
+        testSpec.assertLayersVisibleRegion(pipApp.component) {
+            coversAtMost(displayBounds)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 3e7e2f5..f8a3aff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -45,8 +45,8 @@
     @Presubmit
     @Test
     open fun pipAppWindowRemainInsideVisibleBounds() {
-        testSpec.assertWm {
-            coversAtMost(displayBounds, pipApp.component)
+        testSpec.assertWmVisibleRegion(pipApp.component) {
+            coversAtMost(displayBounds)
         }
     }
 
@@ -57,8 +57,8 @@
     @Presubmit
     @Test
     open fun pipAppLayerRemainInsideVisibleBounds() {
-        testSpec.assertLayers {
-            coversAtMost(displayBounds, pipApp.component)
+        testSpec.assertLayersVisibleRegion(pipApp.component) {
+            coversAtMost(displayBounds)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index cba677b..52177c2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -78,8 +78,8 @@
     @Presubmit
     @Test
     fun pipWindowRemainInsideVisibleBounds() {
-        testSpec.assertWm {
-            coversAtMost(displayBounds, pipApp.component)
+        testSpec.assertWmVisibleRegion(pipApp.component) {
+            coversAtMost(displayBounds)
         }
     }
 
@@ -90,8 +90,8 @@
     @Presubmit
     @Test
     fun pipLayerRemainInsideVisibleBounds() {
-        testSpec.assertLayers {
-            coversAtMost(displayBounds, pipApp.component)
+        testSpec.assertLayersVisibleRegion(pipApp.component) {
+            coversAtMost(displayBounds)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index 7ed0c49..f9e180e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -25,8 +25,8 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.flicker.traces.RegionSubject
 import org.junit.Assume.assumeFalse
+import com.android.server.wm.flicker.traces.region.RegionSubject
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index 6e0324c..0499e7d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -19,7 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.traces.RegionSubject
+import com.android.server.wm.flicker.traces.region.RegionSubject
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import org.junit.Test
 
@@ -66,8 +66,8 @@
     @Presubmit
     @Test
     open fun pipWindowRemainInsideVisibleBounds() {
-        testSpec.assertWm {
-            coversAtMost(displayBounds, pipApp.component)
+        testSpec.assertWmVisibleRegion(pipApp.component) {
+            coversAtMost(displayBounds)
         }
     }
 
@@ -78,8 +78,8 @@
     @Presubmit
     @Test
     open fun pipLayerRemainInsideVisibleBounds() {
-        testSpec.assertLayers {
-            coversAtMost(displayBounds, pipApp.component)
+        testSpec.assertLayersVisibleRegion(pipApp.component) {
+            coversAtMost(displayBounds)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index 56c0949..b7bfa1b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -24,8 +24,8 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.traces.region.RegionSubject
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.flicker.traces.RegionSubject
 import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 62e230f..c36dfda 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -88,9 +88,9 @@
     @Presubmit
     @Test
     fun pipInVisibleBounds() {
-        testSpec.assertWm {
+        testSpec.assertWmVisibleRegion(pipApp.component) {
             val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
-            coversAtMost(displayBounds, pipApp.component)
+            coversAtMost(displayBounds)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
index e3f544a..df58194 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
@@ -101,8 +101,8 @@
     @FlakyTest(bugId = 161435597)
     @Test
     fun pipWindowInsideDisplayBounds() {
-        testSpec.assertWm {
-            coversAtMost(displayBounds, pipApp.component)
+        testSpec.assertWmVisibleRegion(pipApp.component) {
+            coversAtMost(displayBounds)
         }
     }
 
@@ -119,8 +119,8 @@
     @FlakyTest(bugId = 161435597)
     @Test
     fun pipLayerInsideDisplayBounds() {
-        testSpec.assertLayers {
-            coversAtMost(displayBounds, pipApp.component)
+        testSpec.assertLayersVisibleRegion(pipApp.component) {
+            coversAtMost(displayBounds)
         }
     }
 
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index a48d7f7..213f35a 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -127,6 +127,32 @@
     return reinterpret_cast<jlong>(shaderFilter.release());
 }
 
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+    va_end(args);
+    return ret;
+}
+
+static jlong createRuntimeShaderEffect(JNIEnv* env, jobject, jlong shaderBuilderHandle,
+                                       jstring inputShaderName) {
+    SkRuntimeShaderBuilder* builder =
+            reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilderHandle);
+    ScopedUtfChars name(env, inputShaderName);
+
+    if (builder->child(name.c_str()).fChild == nullptr) {
+        ThrowIAEFmt(env,
+                    "unable to find a uniform with the name '%s' of the correct "
+                    "type defined by the provided RuntimeShader",
+                    name.c_str());
+        return 0;
+    }
+
+    sk_sp<SkImageFilter> filter = SkImageFilters::RuntimeShader(*builder, name.c_str(), nullptr);
+    return reinterpret_cast<jlong>(filter.release());
+}
+
 static void RenderEffect_safeUnref(SkImageFilter* filter) {
     SkSafeUnref(filter);
 }
@@ -136,15 +162,16 @@
 }
 
 static const JNINativeMethod gRenderEffectMethods[] = {
-    {"nativeGetFinalizer", "()J", (void*)getRenderEffectFinalizer},
-    {"nativeCreateOffsetEffect", "(FFJ)J", (void*)createOffsetEffect},
-    {"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect},
-    {"nativeCreateBitmapEffect", "(JFFFFFFFF)J", (void*)createBitmapEffect},
-    {"nativeCreateColorFilterEffect", "(JJ)J", (void*)createColorFilterEffect},
-    {"nativeCreateBlendModeEffect", "(JJI)J", (void*)createBlendModeEffect},
-    {"nativeCreateChainEffect", "(JJ)J", (void*)createChainEffect},
-    {"nativeCreateShaderEffect", "(J)J", (void*)createShaderEffect}
-};
+        {"nativeGetFinalizer", "()J", (void*)getRenderEffectFinalizer},
+        {"nativeCreateOffsetEffect", "(FFJ)J", (void*)createOffsetEffect},
+        {"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect},
+        {"nativeCreateBitmapEffect", "(JFFFFFFFF)J", (void*)createBitmapEffect},
+        {"nativeCreateColorFilterEffect", "(JJ)J", (void*)createColorFilterEffect},
+        {"nativeCreateBlendModeEffect", "(JJI)J", (void*)createBlendModeEffect},
+        {"nativeCreateChainEffect", "(JJ)J", (void*)createChainEffect},
+        {"nativeCreateShaderEffect", "(J)J", (void*)createShaderEffect},
+        {"nativeCreateRuntimeShaderEffect", "(JLjava/lang/String;)J",
+         (void*)createRuntimeShaderEffect}};
 
 int register_android_graphics_RenderEffect(JNIEnv* env) {
     android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect",
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 9993ce9..85e49cc 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -1187,6 +1187,9 @@
                     case AudioSystem.STREAM_ACCESSIBILITY:
                         mContentType = CONTENT_TYPE_SPEECH;
                         break;
+                    case AudioSystem.STREAM_ASSISTANT:
+                        mContentType = CONTENT_TYPE_SPEECH;
+                        break;
                     default:
                         Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes");
                 }
@@ -1611,6 +1614,8 @@
                 return USAGE_VOICE_COMMUNICATION_SIGNALLING;
             case AudioSystem.STREAM_ACCESSIBILITY:
                 return USAGE_ASSISTANCE_ACCESSIBILITY;
+            case AudioSystem.STREAM_ASSISTANT:
+                return USAGE_ASSISTANT;
             case AudioSystem.STREAM_TTS:
             default:
                 return USAGE_UNKNOWN;
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index c912759..1f89f99 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -145,10 +145,8 @@
             final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
             switch (match_rule) {
                 case RULE_MATCH_ATTRIBUTE_USAGE:
-                    dest.writeInt(mAttr.getSystemUsage());
-                    break;
                 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
-                    dest.writeInt(mAttr.getCapturePreset());
+                    mAttr.writeToParcel(dest, AudioAttributes.FLATTEN_TAGS/*flags*/);
                     break;
                 case RULE_MATCH_UID:
                 case RULE_MATCH_USERID:
@@ -266,12 +264,14 @@
     public boolean isForCallRedirection() {
         for (AudioMixMatchCriterion criterion : mCriteria) {
             if (criterion.mAttr != null
-                    && (criterion.mRule == RULE_MATCH_ATTRIBUTE_USAGE
-                        && criterion.mAttr.getUsage() == AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                    && criterion.mAttr.isForCallRedirection()
+                    && ((criterion.mRule == RULE_MATCH_ATTRIBUTE_USAGE
+                        && (criterion.mAttr.getUsage() == AudioAttributes.USAGE_VOICE_COMMUNICATION
+                            || criterion.mAttr.getUsage()
+                                == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING))
                     || (criterion.mRule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET
-                        && criterion.mAttr.getCapturePreset()
-                            == MediaRecorder.AudioSource.VOICE_COMMUNICATION)
-                    && criterion.mAttr.isForCallRedirection()) {
+                        && (criterion.mAttr.getCapturePreset()
+                            == MediaRecorder.AudioSource.VOICE_COMMUNICATION)))) {
                 return true;
             }
         }
@@ -713,19 +713,8 @@
             Integer intProp = null;
             switch (match_rule) {
                 case RULE_MATCH_ATTRIBUTE_USAGE:
-                    int usage = in.readInt();
-                    if (AudioAttributes.isSystemUsage(usage)) {
-                        attr = new AudioAttributes.Builder()
-                                .setSystemUsage(usage).build();
-                    } else {
-                        attr = new AudioAttributes.Builder()
-                                .setUsage(usage).build();
-                    }
-                    break;
                 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
-                    int preset = in.readInt();
-                    attr = new AudioAttributes.Builder()
-                            .setInternalCapturePreset(preset).build();
+                    attr =  AudioAttributes.CREATOR.createFromParcel(in);
                     break;
                 case RULE_MATCH_UID:
                 case RULE_MATCH_USERID:
diff --git a/media/java/android/media/tv/AdRequest.aidl b/media/java/android/media/tv/AdRequest.aidl
new file mode 100644
index 0000000..ebfd748
--- /dev/null
+++ b/media/java/android/media/tv/AdRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+parcelable AdRequest;
diff --git a/media/java/android/media/tv/AdRequest.java b/media/java/android/media/tv/AdRequest.java
new file mode 100644
index 0000000..536baf2
--- /dev/null
+++ b/media/java/android/media/tv/AdRequest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @hide */
+public final class AdRequest implements Parcelable {
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(prefix = "REQUEST_TYPE_", value = {
+            REQUEST_TYPE_START,
+            REQUEST_TYPE_STOP
+    })
+    public @interface RequestType {}
+
+    public static final String REQUEST_TYPE_START = "START";
+    public static final String REQUEST_TYPE_STOP = "STOP";
+
+    public static final @NonNull Parcelable.Creator<AdRequest> CREATOR =
+            new Parcelable.Creator<AdRequest>() {
+                @Override
+                public AdRequest createFromParcel(Parcel source) {
+                    return new AdRequest(source);
+                }
+
+                @Override
+                public AdRequest[] newArray(int size) {
+                    return new AdRequest[size];
+                }
+            };
+
+    private final int mId;
+    private final @RequestType String mRequestType;
+    private final ParcelFileDescriptor mFileDescriptor;
+    private final long mStartTime;
+    private final long mStopTime;
+    private final long mEchoInterval;
+    private final String mMediaFileType;
+    private final Bundle mMetadata;
+
+    public AdRequest(int id, @RequestType String requestType, ParcelFileDescriptor fileDescriptor,
+            long startTime, long stopTime, long echoInterval, String mediaFileType,
+            Bundle metadata) {
+        mId = id;
+        mRequestType = requestType;
+        mFileDescriptor = fileDescriptor;
+        mStartTime = startTime;
+        mStopTime = stopTime;
+        mEchoInterval = echoInterval;
+        mMediaFileType = mediaFileType;
+        mMetadata = metadata;
+    }
+
+    private AdRequest(Parcel source) {
+        mId = source.readInt();
+        mRequestType = source.readString();
+        mFileDescriptor = source.readFileDescriptor();
+        mStartTime = source.readLong();
+        mStopTime = source.readLong();
+        mEchoInterval = source.readLong();
+        mMediaFileType = source.readString();
+        mMetadata = source.readBundle();
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public @RequestType String getRequestType() {
+        return mRequestType;
+    }
+
+    public ParcelFileDescriptor getFileDescriptor() {
+        return mFileDescriptor;
+    }
+
+    public long getStartTime() {
+        return mStartTime;
+    }
+
+    public long getStopTime() {
+        return mStopTime;
+    }
+
+    public long getEchoInterval() {
+        return mEchoInterval;
+    }
+
+    public String getMediaFileType() {
+        return mMediaFileType;
+    }
+
+    public Bundle getMetadata() {
+        return mMetadata;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mId);
+        dest.writeString(mRequestType);
+        mFileDescriptor.writeToParcel(dest, flags);
+        dest.writeLong(mStartTime);
+        dest.writeLong(mStopTime);
+        dest.writeLong(mEchoInterval);
+        dest.writeString(mMediaFileType);
+        dest.writeBundle(mMetadata);
+    }
+}
diff --git a/media/java/android/media/tv/AdResponse.aidl b/media/java/android/media/tv/AdResponse.aidl
new file mode 100644
index 0000000..9c09a0a
--- /dev/null
+++ b/media/java/android/media/tv/AdResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+parcelable AdResponse;
diff --git a/media/java/android/media/tv/AdResponse.java b/media/java/android/media/tv/AdResponse.java
new file mode 100644
index 0000000..28cf5ac
--- /dev/null
+++ b/media/java/android/media/tv/AdResponse.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @hide */
+public final class AdResponse implements Parcelable {
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(prefix = "RESPONSE_TYPE_", value = {
+            RESPONSE_TYPE_PLAYING,
+            RESPONSE_TYPE_FINISHED,
+            RESPONSE_TYPE_STOPPED,
+            RESPONSE_TYPE_ERROR
+    })
+    public @interface ResponseType {}
+
+    public static final String RESPONSE_TYPE_PLAYING = "PLAYING";
+    public static final String RESPONSE_TYPE_FINISHED = "FINISHED";
+    public static final String RESPONSE_TYPE_STOPPED = "STOPPED";
+    public static final String RESPONSE_TYPE_ERROR = "ERROR";
+
+    public static final @NonNull Parcelable.Creator<AdResponse> CREATOR =
+            new Parcelable.Creator<AdResponse>() {
+                @Override
+                public AdResponse createFromParcel(Parcel source) {
+                    return new AdResponse(source);
+                }
+
+                @Override
+                public AdResponse[] newArray(int size) {
+                    return new AdResponse[size];
+                }
+            };
+
+    private final int mId;
+    private final @ResponseType String mResponseType;
+    private final Long mElapsedTime;
+
+    public AdResponse(int id, @ResponseType String responseType, @Nullable Long elapsedTime) {
+        mId = id;
+        mResponseType = responseType;
+        mElapsedTime = elapsedTime;
+    }
+
+    private AdResponse(Parcel source) {
+        mId = source.readInt();
+        mResponseType = source.readString();
+        mElapsedTime = (Long) source.readValue(Long.class.getClassLoader());
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public @ResponseType String getResponseType() {
+        return mResponseType;
+    }
+
+    public Long getElapsedTime() {
+        return mElapsedTime;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mId);
+        dest.writeString(mResponseType);
+        dest.writeValue(mElapsedTime);
+    }
+}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index de13fc1..f4f55e4 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -17,6 +17,7 @@
 package android.media.tv;
 
 import android.content.ComponentName;
+import android.media.tv.AdResponse;
 import android.media.tv.AitInfo;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.ITvInputSession;
@@ -54,4 +55,7 @@
 
     // For broadcast info
     void onBroadcastInfoResponse(in BroadcastInfoResponse response, int seq);
+
+    // For ad response
+    void onAdResponse(in AdResponse response, int seq);
 }
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index a6e0217..d34e636 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.media.PlaybackParams;
+import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.DvbDeviceInfo;
 import android.media.tv.ITvInputClient;
@@ -106,6 +107,9 @@
     void requestBroadcastInfo(in IBinder sessionToken, in BroadcastInfoRequest request, int userId);
     void removeBroadcastInfo(in IBinder sessionToken, int id, int userId);
 
+    // For ad request
+    void requestAd(in IBinder sessionToken, in AdRequest request, int userId);
+
     // For TV input hardware binding
     List<TvInputHardwareInfo> getHardwareList();
     ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 025e6f1c..f427501 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect;
 import android.media.PlaybackParams;
+import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.TvTrackInfo;
 import android.net.Uri;
@@ -67,4 +68,7 @@
     // For broadcast info
     void requestBroadcastInfo(in BroadcastInfoRequest request);
     void removeBroadcastInfo(int id);
+
+    // For ad request
+    void requestAd(in AdRequest request);
 }
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index 9a0aaa3..9830e78 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.media.tv.AdResponse;
 import android.media.tv.AitInfo;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.ITvInputSession;
@@ -51,4 +52,7 @@
 
     // For broadcast info
     void onBroadcastInfoResponse(in BroadcastInfoResponse response);
+
+    // For ad response
+    void onAdResponse(in AdResponse response);
 }
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index ef6ef91..418ab2c 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -73,6 +73,7 @@
     private static final int DO_REQUEST_BROADCAST_INFO = 24;
     private static final int DO_REMOVE_BROADCAST_INFO = 25;
     private static final int DO_SET_IAPP_NOTIFICATION_ENABLED = 26;
+    private static final int DO_REQUEST_AD = 27;
 
     private final boolean mIsRecordingSession;
     private final HandlerCaller mCaller;
@@ -249,6 +250,10 @@
                 mTvInputSessionImpl.setIAppNotificationEnabled((Boolean) msg.obj);
                 break;
             }
+            case DO_REQUEST_AD: {
+                mTvInputSessionImpl.requestAd((AdRequest) msg.obj);
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -414,6 +419,11 @@
         mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_REMOVE_BROADCAST_INFO, requestId));
     }
 
+    @Override
+    public void requestAd(AdRequest request) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REQUEST_AD, request));
+    }
+
     private final class TvInputEventReceiver extends InputEventReceiver {
         public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
             super(inputChannel, looper);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index cde2f5a..ad86002 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -894,6 +894,19 @@
                 });
             }
         }
+
+        void postAdResponse(final AdResponse response) {
+            if (mSession.mIAppNotificationEnabled) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mSession.getIAppSession() != null) {
+                            mSession.getIAppSession().notifyAdResponse(response);
+                        }
+                    }
+                });
+            }
+        }
     }
 
     /**
@@ -1317,6 +1330,18 @@
                     record.postBroadcastInfoResponse(response);
                 }
             }
+
+            @Override
+            public void onAdResponse(AdResponse response, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postAdResponse(response);
+                }
+            }
         };
         ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() {
             @Override
@@ -3033,6 +3058,18 @@
             }
         }
 
+        public void requestAd(AdRequest request) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.requestAd(mToken, request, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         private final class InputEventHandler extends Handler {
             public static final int MSG_SEND_INPUT_EVENT = 1;
             public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index ee33e5c..bd5a343 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -849,7 +849,7 @@
         /**
          * Notifies response for broadcast info.
          *
-         * @param response
+         * @param response broadcast info response.
          * @hide
          */
         public void notifyBroadcastInfoResponse(@NonNull final BroadcastInfoResponse response) {
@@ -869,6 +869,29 @@
             });
         }
 
+        /**
+         * Notifies response for advertisement.
+         *
+         * @param response advertisement response.
+         * @hide
+         */
+        public void notifyAdResponse(@NonNull final AdResponse response) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) Log.d(TAG, "notifyAdResponse");
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onAdResponse(response);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifyAdResponse", e);
+                    }
+                }
+            });
+        }
+
         private void notifyTimeShiftStartPositionChanged(final long timeMs) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
@@ -1038,6 +1061,8 @@
         public abstract void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume);
 
         /**
+         * called when broadcast info is requested.
+         *
          * @param request broadcast info request
          * @hide
          */
@@ -1051,6 +1076,15 @@
         }
 
         /**
+         * called when advertisement is requested.
+         *
+         * @param request advertisement request
+         * @hide
+         */
+        public void onRequestAd(@NonNull AdRequest request) {
+        }
+
+        /**
          * Tunes to a given channel.
          *
          * <p>No video will be displayed until {@link #notifyVideoAvailable()} is called.
@@ -1663,6 +1697,10 @@
             onRemoveBroadcastInfo(requestId);
         }
 
+        void requestAd(AdRequest request) {
+            onRequestAd(request);
+        }
+
         /**
          * Takes care of dispatching incoming input events and tells whether the event was handled.
          */
diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
index 00c464f..892a800 100644
--- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.graphics.Rect;
+import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.net.Uri;
 import android.os.Bundle;
@@ -41,4 +42,5 @@
     void onRequestCurrentChannelLcn(int seq);
     void onRequestStreamVolume(int seq);
     void onRequestTrackInfoList(int seq);
+    void onAdRequest(in AdRequest request, int Seq);
 }
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
index 15a2eace..23201fa 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.graphics.Rect;
+import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
 import android.media.tv.interactive.ITvIAppClient;
@@ -55,6 +56,7 @@
             int userId);
     void notifyBroadcastInfoResponse(in IBinder sessionToken, in BroadcastInfoResponse response,
             int UserId);
+    void notifyAdResponse(in IBinder sessionToken, in AdResponse response, int UserId);
 
     void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
             int userId);
@@ -63,4 +65,4 @@
 
     void registerCallback(in ITvIAppManagerCallback callback, int userId);
     void unregisterCallback(in ITvIAppManagerCallback callback, int userId);
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
index 4a85fef..52f9a87 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
@@ -19,6 +19,7 @@
 import android.graphics.Rect;
 import android.media.tv.BroadcastInfoResponse;
 import android.net.Uri;
+import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
 import android.os.Bundle;
@@ -44,8 +45,9 @@
     void setSurface(in Surface surface);
     void dispatchSurfaceChanged(int format, int width, int height);
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
+    void notifyAdResponse(in AdResponse response);
 
     void createMediaView(in IBinder windowToken, in Rect frame);
     void relayoutMediaView(in Rect frame);
     void removeMediaView();
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
index b09f787..9b9e6af 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
@@ -17,9 +17,9 @@
 package android.media.tv.interactive;
 
 import android.graphics.Rect;
+import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.interactive.ITvIAppSession;
-import android.media.tv.BroadcastInfoRequest;
 import android.net.Uri;
 import android.os.Bundle;
 
@@ -41,4 +41,5 @@
     void onRequestCurrentChannelLcn();
     void onRequestStreamVolume();
     void onRequestTrackInfoList();
+    void onAdRequest(in AdRequest request);
 }
diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.java b/media/java/android/media/tv/interactive/TvIAppInfo.java
index 6d5e561..79d94cc 100644
--- a/media/java/android/media/tv/interactive/TvIAppInfo.java
+++ b/media/java/android/media/tv/interactive/TvIAppInfo.java
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.annotation.NonNull;
+import android.annotation.StringDef;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -35,6 +36,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.List;
 
@@ -46,6 +49,21 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "TvIAppInfo";
 
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(prefix = { "INTERACTIVE_APP_TYPE_" }, value = {
+            INTERACTIVE_APP_TYPE_HBBTV,
+            INTERACTIVE_APP_TYPE_ATSC,
+            INTERACTIVE_APP_TYPE_GINGA,
+    })
+    @interface InteractiveAppType {}
+
+    /** HbbTV interactive app type */
+    public static final String INTERACTIVE_APP_TYPE_HBBTV = "hbbtv";
+    /** ATSC interactive app type */
+    public static final String INTERACTIVE_APP_TYPE_ATSC = "atsc";
+    /** Ginga interactive app type */
+    public static final String INTERACTIVE_APP_TYPE_GINGA = "ginga";
+
     private final ResolveInfo mService;
     private final String mId;
     private List<String> mTypes = new ArrayList<>();
@@ -107,6 +125,13 @@
     }
 
     /**
+     * Gets supported interactive app types
+     */
+    public List<String> getSupportedTypes() {
+        return new ArrayList<>(mTypes);
+    }
+
+    /**
      * A convenience builder for creating {@link TvIAppInfo} objects.
      */
     public static final class Builder {
@@ -186,7 +211,7 @@
                 CharSequence[] types = sa.getTextArray(
                         com.android.internal.R.styleable.TvIAppService_supportedTypes);
                 for (CharSequence cs : types) {
-                    mTypes.add(cs.toString());
+                    mTypes.add(cs.toString().toLowerCase());
                 }
 
                 sa.recycle();
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
index 4c4dffb..d1fd1df 100644
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -22,6 +22,8 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.graphics.Rect;
+import android.media.tv.AdRequest;
+import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvInputManager;
@@ -282,6 +284,18 @@
             }
 
             @Override
+            public void onAdRequest(AdRequest request, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postAdRequest(request);
+                }
+            }
+
+            @Override
             public void onRequestCurrentChannelUri(int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -945,6 +959,23 @@
         }
 
         /**
+         * Notifies of any advertisement response passed in from TIS.
+         *
+         * @param response response passed in from TIS.
+         */
+        public void notifyAdResponse(AdResponse response) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyAdResponse(mToken, response, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Releases this session.
          */
         public void release() {
@@ -1322,6 +1353,17 @@
             });
         }
 
+        void postAdRequest(final AdRequest request) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mSession.getInputSession() != null) {
+                        mSession.getInputSession().requestAd(request);
+                    }
+                }
+            });
+        }
+
         void postSessionStateChanged(int state) {
             mHandler.post(new Runnable() {
                 @Override
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
index a0f8413..1480ff64 100644
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -27,6 +27,8 @@
 import android.content.Intent;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.media.tv.AdRequest;
+import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
@@ -434,6 +436,13 @@
         }
 
         /**
+         * Called when an advertisement response is received.
+         * @hide
+         */
+        public void onAdResponse(AdResponse response) {
+        }
+
+        /**
          * TODO: JavaDoc of APIs related to input events.
          * @hide
          */
@@ -703,6 +712,29 @@
             });
         }
 
+        /**
+         * requests an advertisement request to be processed by the related TV input.
+         * @param request advertisement request
+         */
+        public void requestAd(@NonNull final AdRequest request) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "requestAd (id=" + request.getId() + ")");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onAdRequest(request);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in requestAd", e);
+                    }
+                }
+            });
+        }
+
         void startIApp() {
             onStartIApp();
         }
@@ -784,6 +816,16 @@
         }
 
         /**
+         * Calls {@link #onAdResponse}.
+         */
+        void notifyAdResponse(AdResponse response) {
+            if (DEBUG) {
+                Log.d(TAG, "notifyAdResponse (requestId=" + response.getId() + ")");
+            }
+            onAdResponse(response);
+        }
+
+        /**
          * Notifies when the session state is changed.
          * @param state the current state.
          */
@@ -1142,6 +1184,11 @@
         }
 
         @Override
+        public void notifyAdResponse(AdResponse response) {
+            mSessionImpl.notifyAdResponse(response);
+        }
+
+        @Override
         public void createMediaView(IBinder windowToken, Rect frame) {
             mSessionImpl.createMediaView(windowToken, frame);
         }
diff --git a/media/java/android/media/tv/interactive/TvIAppView.java b/media/java/android/media/tv/interactive/TvIAppView.java
index bc015d1..1ce14ae 100644
--- a/media/java/android/media/tv/interactive/TvIAppView.java
+++ b/media/java/android/media/tv/interactive/TvIAppView.java
@@ -113,6 +113,10 @@
         this(context, null, 0);
     }
 
+    public TvIAppView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
     public TvIAppView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         int sourceResId = Resources.getAttributeSetSourceResId(attrs);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
index eb8f43e..8f1115e 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
@@ -21,7 +21,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
 import android.service.NetworkIdentityProto;
 import android.telephony.Annotation.NetworkType;
 import android.util.proto.ProtoOutputStream;
@@ -228,11 +227,11 @@
         final int oemManaged = getOemBitfield(snapshot.getNetworkCapabilities());
 
         if (legacyType == TYPE_WIFI) {
-            networkId = snapshot.getNetworkCapabilities().getSsid();
-            if (networkId == null) {
-                final WifiManager wifi = context.getSystemService(WifiManager.class);
-                final WifiInfo info = wifi.getConnectionInfo();
-                networkId = info != null ? info.getSSID() : null;
+            final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
+                    .getTransportInfo();
+            if (transportInfo instanceof WifiInfo) {
+                final WifiInfo info = (WifiInfo) transportInfo;
+                networkId = info != null ? info.getCurrentNetworkKey() : null;
             }
         }
 
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
index 591605d9..b64fbdb 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_REMOVED;
 import static android.net.TrafficStats.UID_TETHERING;
@@ -106,7 +107,7 @@
 
     /** Returns the {@link NetworkStatsAccess.Level} for the given caller. */
     public static @NetworkStatsAccess.Level int checkAccessLevel(
-            Context context, int callingUid, String callingPackage) {
+            Context context, int callingPid, int callingUid, String callingPackage) {
         final DevicePolicyManager mDpm = context.getSystemService(DevicePolicyManager.class);
         final TelephonyManager tm = (TelephonyManager)
                 context.getSystemService(Context.TELEPHONY_SERVICE);
@@ -123,8 +124,12 @@
         final boolean isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage);
         final int appId = UserHandle.getAppId(callingUid);
 
+        final boolean isNetworkStack = context.checkPermission(
+                android.Manifest.permission.NETWORK_STACK, callingPid, callingUid)
+                == PERMISSION_GRANTED;
+
         if (hasCarrierPrivileges || isDeviceOwner
-                || appId == Process.SYSTEM_UID || appId == Process.NETWORK_STACK_UID) {
+                || appId == Process.SYSTEM_UID || isNetworkStack) {
             // Carrier-privileged apps and device owners, and the system (including the
             // network stack) can access data usage for all apps on the device.
             return NetworkStatsAccess.Level.DEVICE;
@@ -155,6 +160,8 @@
      */
     public static boolean isAccessibleToUser(int uid, int callerUid,
             @NetworkStatsAccess.Level int accessLevel) {
+        final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+        final int callerUserId = UserHandle.getUserHandleForUid(callerUid).getIdentifier();
         switch (accessLevel) {
             case NetworkStatsAccess.Level.DEVICE:
                 // Device-level access - can access usage for any uid.
@@ -165,13 +172,13 @@
                 // anonymized uids
                 return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
                         || uid == UID_TETHERING || uid == UID_ALL
-                        || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
+                        || userId == callerUserId;
             case NetworkStatsAccess.Level.USER:
                 // User-level access - can access usage for any app running in the same user, along
                 // with some special uids (system, removed, or tethering).
                 return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
                         || uid == UID_TETHERING
-                        || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
+                        || userId == callerUserId;
             case NetworkStatsAccess.Level.DEFAULT:
             default:
                 // Default access level - can only access one's own usage.
@@ -185,8 +192,8 @@
             AppOpsManager appOps = (AppOpsManager) context.getSystemService(
                     Context.APP_OPS_SERVICE);
 
-            final int mode = appOps.noteOp(AppOpsManager.OP_GET_USAGE_STATS,
-                    callingUid, callingPackage);
+            final int mode = appOps.noteOp(AppOpsManager.OPSTR_GET_USAGE_STATS,
+                    callingUid, callingPackage, null /* attributionTag */, null /* message */);
             if (mode == AppOpsManager.MODE_DEFAULT) {
                 // The default behavior here is to check if PackageManager has given the app
                 // permission.
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
index 9fa777d..659ad06 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
@@ -35,7 +35,6 @@
 import static android.net.NetworkStats.ROAMING_ALL;
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.ROAMING_YES;
-import static android.net.wifi.WifiInfo.sanitizeSsid;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -57,6 +56,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -213,11 +213,14 @@
     public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId,
             @NetworkType int ratType, int metered) {
         if (TextUtils.isEmpty(subscriberId)) {
-            return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null, null,
-                    metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
+            return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null /* subscriberId */,
+                    null /* matchSubscriberIds */,
+                    new String[0] /* matchWifiNetworkKeys */, metered, ROAMING_ALL,
+                    DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
                     NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
         }
-        return new NetworkTemplate(MATCH_MOBILE, subscriberId, new String[]{subscriberId}, null,
+        return new NetworkTemplate(MATCH_MOBILE, subscriberId, new String[] { subscriberId },
+                new String[0] /* matchWifiNetworkKeys */,
                 metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
                 NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
@@ -235,7 +238,7 @@
 
     /**
      * Template to match all metered {@link ConnectivityManager#TYPE_WIFI} networks,
-     * regardless of SSID.
+     * regardless of key of the wifi network.
      *
      * @hide
      */
@@ -255,33 +258,40 @@
 
     /**
      * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
-     * given SSID.
+     * given key of the wifi network.
      *
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+     *                  to know details about the key.
      * @hide
      */
-    public static NetworkTemplate buildTemplateWifi(@NonNull String networkId) {
-        Objects.requireNonNull(networkId);
+    public static NetworkTemplate buildTemplateWifi(@NonNull String wifiNetworkKey) {
+        Objects.requireNonNull(wifiNetworkKey);
         return new NetworkTemplate(MATCH_WIFI, null /* subscriberId */,
                 new String[] { null } /* matchSubscriberIds */,
-                networkId, METERED_ALL, ROAMING_ALL,
+                new String[] { wifiNetworkKey }, METERED_ALL, ROAMING_ALL,
                 DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
                 NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL);
     }
 
     /**
-     * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given SSID,
-     * and IMSI.
+     * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given
+     * key of the wifi network and IMSI.
      *
-     * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code networkId} to get result regardless
-     * of SSID.
+     * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code wifiNetworkKey} to get result regardless
+     * of key of the wifi network.
+     *
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+     *                  to know details about the key.
+     * @param subscriberId the IMSI associated to this wifi network.
      *
      * @hide
      */
-    public static NetworkTemplate buildTemplateWifi(@Nullable String networkId,
+    public static NetworkTemplate buildTemplateWifi(@Nullable String wifiNetworkKey,
             @Nullable String subscriberId) {
         return new NetworkTemplate(MATCH_WIFI, subscriberId, new String[] { subscriberId },
-                networkId, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
+                wifiNetworkKey != null
+                        ? new String[] { wifiNetworkKey } : new String[0],
+                METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
                 NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
@@ -324,7 +334,9 @@
     public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) {
         Objects.requireNonNull(subscriberId);
         return new NetworkTemplate(MATCH_CARRIER, subscriberId,
-                new String[] { subscriberId }, null /* networkId */, METERED_YES, ROAMING_ALL,
+                new String[] { subscriberId },
+                new String[0] /* matchWifiNetworkKeys */,
+                METERED_YES, ROAMING_ALL,
                 DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
                 NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
@@ -342,8 +354,8 @@
      */
     private final String[] mMatchSubscriberIds;
 
-    // TODO: Change variable name to match the Api surface.
-    private final String mNetworkId;
+    @NonNull
+    private final String[] mMatchWifiNetworkKeys;
 
     // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*.
     private final int mMetered;
@@ -377,18 +389,19 @@
     /** @hide */
     // TODO: Deprecate this constructor, mark it @UnsupportedAppUsage(maxTargetSdk = S)
     @UnsupportedAppUsage
-    public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
-        this(matchRule, subscriberId, new String[] { subscriberId }, networkId);
+    public NetworkTemplate(int matchRule, String subscriberId, String wifiNetworkKey) {
+        this(matchRule, subscriberId, new String[] { subscriberId }, wifiNetworkKey);
     }
 
     /** @hide */
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
-            String networkId) {
+            String wifiNetworkKey) {
         // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates
         // to metered networks. It is now possible to match mobile with any meteredness, but
         // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this
         //constructor passes METERED_YES for these types.
-        this(matchRule, subscriberId, matchSubscriberIds, networkId,
+        this(matchRule, subscriberId, matchSubscriberIds,
+                wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
                 (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD) ? METERED_YES
                 : METERED_ALL , ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
                 OEM_MANAGED_ALL, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
@@ -397,23 +410,25 @@
     /** @hide */
     // TODO: Remove it after updating all of the caller.
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
-            String networkId, int metered, int roaming, int defaultNetwork, int subType,
+            String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int subType,
             int oemManaged) {
-        this(matchRule, subscriberId, matchSubscriberIds, networkId, metered, roaming,
-                defaultNetwork, subType, oemManaged,
+        this(matchRule, subscriberId, matchSubscriberIds,
+                wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
+                metered, roaming, defaultNetwork, subType, oemManaged,
                 NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
     /** @hide */
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
-            String networkId, int metered, int roaming, int defaultNetwork, int subType,
-            int oemManaged, int subscriberIdMatchRule) {
+            String[] matchWifiNetworkKeys, int metered, int roaming,
+            int defaultNetwork, int subType, int oemManaged, int subscriberIdMatchRule) {
+        Objects.requireNonNull(matchWifiNetworkKeys);
         mMatchRule = matchRule;
         mSubscriberId = subscriberId;
         // TODO: Check whether mMatchSubscriberIds = null or mMatchSubscriberIds = {null} when
         // mSubscriberId is null
         mMatchSubscriberIds = matchSubscriberIds;
-        mNetworkId = networkId;
+        mMatchWifiNetworkKeys = matchWifiNetworkKeys;
         mMetered = metered;
         mRoaming = roaming;
         mDefaultNetwork = defaultNetwork;
@@ -431,7 +446,7 @@
         mMatchRule = in.readInt();
         mSubscriberId = in.readString();
         mMatchSubscriberIds = in.createStringArray();
-        mNetworkId = in.readString();
+        mMatchWifiNetworkKeys = in.createStringArray();
         mMetered = in.readInt();
         mRoaming = in.readInt();
         mDefaultNetwork = in.readInt();
@@ -445,7 +460,7 @@
         dest.writeInt(mMatchRule);
         dest.writeString(mSubscriberId);
         dest.writeStringArray(mMatchSubscriberIds);
-        dest.writeString(mNetworkId);
+        dest.writeStringArray(mMatchWifiNetworkKeys);
         dest.writeInt(mMetered);
         dest.writeInt(mRoaming);
         dest.writeInt(mDefaultNetwork);
@@ -471,9 +486,7 @@
             builder.append(", matchSubscriberIds=").append(
                     Arrays.toString(NetworkIdentityUtils.scrubSubscriberIds(mMatchSubscriberIds)));
         }
-        if (mNetworkId != null) {
-            builder.append(", networkId=").append(mNetworkId);
-        }
+        builder.append(", matchWifiNetworkKeys=").append(Arrays.toString(mMatchWifiNetworkKeys));
         if (mMetered != METERED_ALL) {
             builder.append(", metered=").append(NetworkStats.meteredToString(mMetered));
         }
@@ -497,8 +510,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mMatchRule, mSubscriberId, mNetworkId, mMetered, mRoaming,
-                mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule);
+        return Objects.hash(mMatchRule, mSubscriberId, Arrays.hashCode(mMatchWifiNetworkKeys),
+                mMetered, mRoaming, mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule);
     }
 
     @Override
@@ -507,13 +520,13 @@
             final NetworkTemplate other = (NetworkTemplate) obj;
             return mMatchRule == other.mMatchRule
                     && Objects.equals(mSubscriberId, other.mSubscriberId)
-                    && Objects.equals(mNetworkId, other.mNetworkId)
                     && mMetered == other.mMetered
                     && mRoaming == other.mRoaming
                     && mDefaultNetwork == other.mDefaultNetwork
                     && mSubType == other.mSubType
                     && mOemManaged == other.mOemManaged
-                    && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule;
+                    && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule
+                    && Arrays.equals(mMatchWifiNetworkKeys, other.mMatchWifiNetworkKeys);
         }
         return false;
     }
@@ -579,14 +592,22 @@
      */
     @Nullable
     public String getWifiNetworkKey() {
-        return mNetworkId;
+        return CollectionUtils.isEmpty(mMatchWifiNetworkKeys) ? null : mMatchWifiNetworkKeys[0];
+    }
+
+    /**
+     * Get set of Wifi Network Keys of the template.
+     */
+    @Nullable
+    public Set<String> getWifiNetworkKeys() {
+        return new ArraySet<>(Arrays.asList(mMatchWifiNetworkKeys));
     }
 
     /** @hide */
     // TODO: Remove this and replace all callers with {@link #getWifiNetworkKey()}.
     @Nullable
     public String getNetworkId() {
-        return mNetworkId;
+        return getWifiNetworkKey();
     }
 
     /**
@@ -707,16 +728,21 @@
     }
 
     /**
-     * Check if network with matching SSID. Returns true when the SSID matches, or when
-     * {@code mNetworkId} is {@code WIFI_NETWORK_KEY_ALL}.
+     * Check if network matches key of the wifi network.
+     * Returns true when the key matches, or when {@code mMatchWifiNetworkKeys} is
+     * empty.
+     *
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+     *                  to know details about the key.
      */
-    private boolean matchesWifiNetworkId(@Nullable String networkId) {
-        return Objects.equals(mNetworkId, WIFI_NETWORK_KEY_ALL)
-                || Objects.equals(sanitizeSsid(mNetworkId), sanitizeSsid(networkId));
+    private boolean matchesWifiNetworkKey(@NonNull String wifiNetworkKey) {
+        Objects.requireNonNull(wifiNetworkKey);
+        return CollectionUtils.isEmpty(mMatchWifiNetworkKeys)
+                || CollectionUtils.contains(mMatchWifiNetworkKeys, wifiNetworkKey);
     }
 
     /**
-     * Check if mobile network with matching IMSI.
+     * Check if mobile network matches IMSI.
      */
     private boolean matchesMobile(NetworkIdentity ident) {
         if (ident.mType == TYPE_WIMAX) {
@@ -814,7 +840,7 @@
         switch (ident.mType) {
             case TYPE_WIFI:
                 return matchesSubscriberId(ident.mSubscriberId)
-                        && matchesWifiNetworkId(ident.mNetworkId);
+                        && matchesWifiNetworkKey(ident.mNetworkId);
             default:
                 return false;
         }
@@ -956,8 +982,10 @@
             if (CollectionUtils.contains(merged, template.mSubscriberId)) {
                 // Requested template subscriber is part of the merge group; return
                 // a template that matches all merged subscribers.
+                final String[] matchWifiNetworkKeys = template.mMatchWifiNetworkKeys;
                 return new NetworkTemplate(template.mMatchRule, merged[0], merged,
-                        template.mNetworkId);
+                        CollectionUtils.isEmpty(matchWifiNetworkKeys)
+                                ? null : matchWifiNetworkKeys[0]);
             }
         }
 
@@ -984,9 +1012,10 @@
         private final int mMatchRule;
         // Use a SortedSet to provide a deterministic order when fetching the first one.
         @NonNull
-        private final SortedSet<String> mMatchSubscriberIds = new TreeSet<>();
-        @Nullable
-        private String mWifiNetworkKey;
+        private final SortedSet<String> mMatchSubscriberIds =
+                new TreeSet<>(Comparator.nullsFirst(Comparator.naturalOrder()));
+        @NonNull
+        private final SortedSet<String> mMatchWifiNetworkKeys = new TreeSet<>();
 
         // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*.
         private int mMetered;
@@ -1006,7 +1035,6 @@
             assertRequestableMatchRule(matchRule);
             // Initialize members with default values.
             mMatchRule = matchRule;
-            mWifiNetworkKey = WIFI_NETWORK_KEY_ALL;
             mMetered = METERED_ALL;
             mRoaming = ROAMING_ALL;
             mDefaultNetwork = DEFAULT_NETWORK_ALL;
@@ -1030,15 +1058,28 @@
         }
 
         /**
-         * Set the Wifi Network Key.
+         * Set the Wifi Network Keys. Calling this function with an empty set represents
+         * the intention of matching any Wifi Network Key.
          *
-         * @param wifiNetworkKey the Wifi Network Key, see {@link WifiInfo#getCurrentNetworkKey()}.
-         *                       Or null to match all networks.
+         * @param wifiNetworkKeys the list of Wifi Network Key,
+         *                        see {@link WifiInfo#getCurrentNetworkKey()}.
+         *                        Or an empty list to match all networks.
+         *                        Note that {@code getCurrentNetworkKey()} might get null key
+         *                        when wifi disconnects. However, the caller should never invoke
+         *                        this function with a null Wifi Network Key since such statistics
+         *                        never exists.
          * @return this builder.
          */
         @NonNull
-        public Builder setWifiNetworkKey(@Nullable String wifiNetworkKey) {
-            mWifiNetworkKey = wifiNetworkKey;
+        public Builder setWifiNetworkKeys(@NonNull Set<String> wifiNetworkKeys) {
+            Objects.requireNonNull(wifiNetworkKeys);
+            for (String key : wifiNetworkKeys) {
+                if (key == null) {
+                    throw new IllegalArgumentException("Null is not a valid key");
+                }
+            }
+            mMatchWifiNetworkKeys.clear();
+            mMatchWifiNetworkKeys.addAll(wifiNetworkKeys);
             return this;
         }
 
@@ -1122,9 +1163,17 @@
         }
 
         private void assertRequestableParameters() {
+            validateWifiNetworkKeys();
             // TODO: Check all the input are legitimate.
         }
 
+        private void validateWifiNetworkKeys() {
+            if (mMatchRule != MATCH_WIFI && !mMatchWifiNetworkKeys.isEmpty()) {
+                throw new IllegalArgumentException("Trying to build non wifi match rule: "
+                        + mMatchRule + " with wifi network keys");
+            }
+        }
+
         /**
          * For backward compatibility, deduce match rule to a wildcard match rule
          * if the Subscriber Ids are empty.
@@ -1133,7 +1182,7 @@
             if (mMatchRule == MATCH_MOBILE && mMatchSubscriberIds.isEmpty()) {
                 return MATCH_MOBILE_WILDCARD;
             } else if (mMatchRule == MATCH_WIFI && mMatchSubscriberIds.isEmpty()
-                    && mWifiNetworkKey == WIFI_NETWORK_KEY_ALL) {
+                    && mMatchWifiNetworkKeys.isEmpty()) {
                 return MATCH_WIFI_WILDCARD;
             }
             return mMatchRule;
@@ -1153,8 +1202,8 @@
             return new NetworkTemplate(getWildcardDeducedMatchRule(),
                     mMatchSubscriberIds.isEmpty() ? null : mMatchSubscriberIds.iterator().next(),
                     mMatchSubscriberIds.toArray(new String[0]),
-                    mWifiNetworkKey, mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged,
-                    subscriberIdMatchRule);
+                    mMatchWifiNetworkKeys.toArray(new String[0]), mMetered, mRoaming,
+                    mDefaultNetwork, mRatType, mOemManaged, subscriberIdMatchRule);
         }
     }
 }
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
index e6433db..c7c8893 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
@@ -35,8 +35,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ProcFileReader;
+import com.android.net.module.util.CollectionUtils;
 
 import libcore.io.IoUtils;
 
@@ -434,7 +434,7 @@
                 entry.txBytes = reader.nextLong();
                 entry.txPackets = reader.nextLong();
 
-                if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface))
+                if ((limitIfaces == null || CollectionUtils.contains(limitIfaces, entry.iface))
                         && (limitUid == UID_ALL || limitUid == entry.uid)
                         && (limitTag == TAG_ALL || limitTag == entry.tag)) {
                     stats.insertEntry(entry);
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
index 1a0866d..b57a4f9 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
@@ -18,8 +18,6 @@
 
 import static android.app.usage.NetworkStatsManager.MIN_THRESHOLD_BYTES;
 
-import static com.android.internal.util.Preconditions.checkArgument;
-
 import android.app.usage.NetworkStatsManager;
 import android.net.DataUsageRequest;
 import android.net.NetworkIdentitySet;
@@ -38,7 +36,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArrayMap;
-import android.util.Slog;
+import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -83,7 +81,7 @@
         RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid,
                 accessLevel);
 
-        if (LOGV) Slog.v(TAG, "Registering observer for " + request);
+        if (LOGV) Log.v(TAG, "Registering observer for " + request);
         getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
         return request;
     }
@@ -116,7 +114,7 @@
         if (mHandler == null) {
             synchronized (this) {
                 if (mHandler == null) {
-                    if (LOGV) Slog.v(TAG, "Creating handler");
+                    if (LOGV) Log.v(TAG, "Creating handler");
                     mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback);
                 }
             }
@@ -172,15 +170,15 @@
         RequestInfo requestInfo;
         requestInfo = mDataUsageRequests.get(request.requestId);
         if (requestInfo == null) {
-            if (LOGV) Slog.v(TAG, "Trying to unregister unknown request " + request);
+            if (LOGV) Log.v(TAG, "Trying to unregister unknown request " + request);
             return;
         }
         if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
-            Slog.w(TAG, "Caller uid " + callingUid + " is not owner of " + request);
+            Log.w(TAG, "Caller uid " + callingUid + " is not owner of " + request);
             return;
         }
 
-        if (LOGV) Slog.v(TAG, "Unregistering " + request);
+        if (LOGV) Log.v(TAG, "Unregistering " + request);
         mDataUsageRequests.remove(request.requestId);
         requestInfo.unlinkDeathRecipient();
         requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
@@ -201,7 +199,7 @@
         // Cap the minimum threshold to a safe default to avoid too many callbacks
         long thresholdInBytes = Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes);
         if (thresholdInBytes < request.thresholdInBytes) {
-            Slog.w(TAG, "Threshold was too low for " + request
+            Log.w(TAG, "Threshold was too low for " + request
                     + ". Overriding to a safer default of " + thresholdInBytes + " bytes");
         }
         return new DataUsageRequest(mNextDataUsageRequestId.incrementAndGet(),
@@ -216,7 +214,10 @@
                     accessLevel);
         } else {
             // Safety check in case a new access level is added and we forgot to update this
-            checkArgument(accessLevel >= NetworkStatsAccess.Level.DEVICESUMMARY);
+            if (accessLevel < NetworkStatsAccess.Level.DEVICESUMMARY) {
+                throw new IllegalArgumentException(
+                        "accessLevel " + accessLevel + " is less than DEVICESUMMARY.");
+            }
             return new NetworkUsageRequestInfo(this, request, messenger, binder, callingUid,
                     accessLevel);
         }
@@ -255,8 +256,9 @@
 
         @Override
         public void binderDied() {
-            if (LOGV) Slog.v(TAG, "RequestInfo binderDied("
-                    + mRequest + ", " + mBinder + ")");
+            if (LOGV) {
+                Log.v(TAG, "RequestInfo binderDied(" + mRequest + ", " + mBinder + ")");
+            }
             mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
             callCallback(NetworkStatsManager.CALLBACK_RELEASED);
         }
@@ -299,13 +301,13 @@
             msg.setData(bundle);
             try {
                 if (LOGV) {
-                    Slog.v(TAG, "sending notification " + callbackTypeToName(callbackType)
+                    Log.v(TAG, "sending notification " + callbackTypeToName(callbackType)
                             + " for " + mRequest);
                 }
                 mMessenger.send(msg);
             } catch (RemoteException e) {
                 // May occur naturally in the race of binder death.
-                Slog.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
+                Log.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
             }
         }
 
@@ -341,7 +343,7 @@
         protected boolean checkStats() {
             long bytesSoFar = getTotalBytesForNetwork(mRequest.template);
             if (LOGV) {
-                Slog.v(TAG, bytesSoFar + " bytes so far since notification for "
+                Log.v(TAG, bytesSoFar + " bytes so far since notification for "
                         + mRequest.template);
             }
             if (bytesSoFar > mRequest.thresholdInBytes) {
@@ -416,7 +418,7 @@
                 return history.getTotalBytes();
             } catch (SecurityException e) {
                 if (LOGV) {
-                    Slog.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid "
+                    Log.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid "
                             + uid);
                 }
                 return 0;
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java
index 5e27c77..c371f08 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java
@@ -32,15 +32,12 @@
 import android.os.Binder;
 import android.os.DropBoxManager;
 import android.service.NetworkStatsRecorderProto;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
-import android.util.MathUtils;
-import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.FileRotator;
-import com.android.internal.util.IndentingPrintWriter;
-
-import com.google.android.collect.Sets;
+import com.android.net.module.util.NetworkStatsUtils;
 
 import libcore.io.IoUtils;
 
@@ -132,8 +129,8 @@
     }
 
     public void setPersistThreshold(long thresholdBytes) {
-        if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes);
-        mPersistThresholdBytes = MathUtils.constrain(
+        if (LOGV) Log.v(TAG, "setPersistThreshold() with " + thresholdBytes);
+        mPersistThresholdBytes = NetworkStatsUtils.constrain(
                 thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
     }
 
@@ -185,7 +182,7 @@
     }
 
     private NetworkStatsCollection loadLocked(long start, long end) {
-        if (LOGD) Slog.d(TAG, "loadLocked() reading from disk for " + mCookie);
+        if (LOGD) Log.d(TAG, "loadLocked() reading from disk for " + mCookie);
         final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration);
         try {
             mRotator.readMatching(res, start, end);
@@ -207,7 +204,7 @@
      */
     public void recordSnapshotLocked(NetworkStats snapshot,
             Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
-        final HashSet<String> unknownIfaces = Sets.newHashSet();
+        final HashSet<String> unknownIfaces = new HashSet<>();
 
         // skip recording when snapshot missing
         if (snapshot == null) return;
@@ -272,7 +269,7 @@
         mLastSnapshot = snapshot;
 
         if (LOGV && unknownIfaces.size() > 0) {
-            Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
+            Log.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
         }
     }
 
@@ -296,7 +293,7 @@
     public void forcePersistLocked(long currentTimeMillis) {
         Objects.requireNonNull(mRotator, "missing FileRotator");
         if (mPending.isDirty()) {
-            if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
+            if (LOGD) Log.d(TAG, "forcePersistLocked() writing for " + mCookie);
             try {
                 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
                 mRotator.maybeRotate(currentTimeMillis);
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index df066c21..a3de9e4 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -25,7 +25,6 @@
 import static android.content.Intent.EXTRA_UID;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.NetworkIdentity.SUBTYPE_COMBINED;
-import static android.net.NetworkStack.checkNetworkStackPermission;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.IFACE_VT;
@@ -141,28 +140,29 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
-import android.util.MathUtils;
-import android.util.Slog;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FileRotator;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.NetworkStatsUtils;
+import com.android.net.module.util.PermissionUtils;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.time.Clock;
 import java.time.ZoneOffset;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
@@ -646,7 +646,7 @@
         try {
             mNetworkManager.setGlobalAlert(mGlobalAlertBytes);
         } catch (IllegalStateException e) {
-            Slog.w(TAG, "problem registering for global alert: " + e);
+            Log.w(TAG, "problem registering for global alert: " + e);
         } catch (RemoteException e) {
             // ignored; service lives in system_server
         }
@@ -764,7 +764,7 @@
                     return stats;
                 } catch (NullPointerException e) {
                     // TODO: Track down and fix the cause of this crash and remove this catch block.
-                    Slog.wtf(TAG, "NullPointerException in getSummaryForAllUid", e);
+                    Log.wtf(TAG, "NullPointerException in getSummaryForAllUid", e);
                     throw e;
                 }
             }
@@ -809,7 +809,7 @@
 
     private @NetworkStatsAccess.Level int checkAccessLevel(String callingPackage) {
         return NetworkStatsAccess.checkAccessLevel(
-                mContext, Binder.getCallingUid(), callingPackage);
+                mContext, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage);
     }
 
     /**
@@ -821,7 +821,7 @@
         SubscriptionPlan plan = null;
         if ((flags & NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN) != 0
                 && mSettings.getAugmentEnabled()) {
-            if (LOGD) Slog.d(TAG, "Resolving plan for " + template);
+            if (LOGD) Log.d(TAG, "Resolving plan for " + template);
             final long token = Binder.clearCallingIdentity();
             try {
                 plan = LocalServices.getService(NetworkPolicyManagerInternal.class)
@@ -829,7 +829,7 @@
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
-            if (LOGD) Slog.d(TAG, "Resolved to plan " + plan);
+            if (LOGD) Log.d(TAG, "Resolved to plan " + plan);
         }
         return plan;
     }
@@ -932,7 +932,7 @@
     @Override
     public String[] getMobileIfaces() {
         // TODO (b/192758557): Remove debug log.
-        if (ArrayUtils.contains(mMobileIfaces, null)) {
+        if (CollectionUtils.contains(mMobileIfaces, null)) {
             throw new NullPointerException(
                     "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
         }
@@ -981,7 +981,7 @@
             @NonNull NetworkStateSnapshot[] networkStates,
             @Nullable String activeIface,
             @NonNull UnderlyingNetworkInfo[] underlyingNetworkInfos) {
-        checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -1010,9 +1010,10 @@
 
     private void advisePersistThreshold(long thresholdBytes) {
         // clamp threshold into safe range
-        mPersistThreshold = MathUtils.constrain(thresholdBytes, 128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
+        mPersistThreshold = NetworkStatsUtils.constrain(thresholdBytes,
+                128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
         if (LOGV) {
-            Slog.v(TAG, "advisePersistThreshold() given " + thresholdBytes + ", clamped to "
+            Log.v(TAG, "advisePersistThreshold() given " + thresholdBytes + ", clamped to "
                     + mPersistThreshold);
         }
 
@@ -1196,13 +1197,13 @@
             // On background handler thread, and USER_REMOVED is protected
             // broadcast.
 
-            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-            if (userId == -1) return;
+            final UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
+            if (userHandle == null) return;
 
             synchronized (mStatsLock) {
                 mWakeLock.acquire();
                 try {
-                    removeUserLocked(userId);
+                    removeUserLocked(userHandle);
                 } finally {
                     mWakeLock.release();
                 }
@@ -1227,7 +1228,7 @@
         @Override
         public void limitReached(String limitName, String iface) {
             // only someone like NMS should be calling us
-            NetworkStack.checkNetworkStackPermission(mContext);
+            PermissionUtils.enforceNetworkStackPermission(mContext);
 
             if (LIMIT_GLOBAL_ALERT.equals(limitName)) {
                 // kick off background poll to collect network stats unless there is already
@@ -1275,7 +1276,7 @@
     private void handleNotifyNetworkStatusLocked(@NonNull Network[] defaultNetworks,
             @NonNull NetworkStateSnapshot[] snapshots) {
         if (!mSystemReady) return;
-        if (LOGV) Slog.v(TAG, "handleNotifyNetworkStatusLocked()");
+        if (LOGV) Log.v(TAG, "handleNotifyNetworkStatusLocked()");
 
         // take one last stats snapshot before updating iface mapping. this
         // isn't perfect, since the kernel may already be counting traffic from
@@ -1299,7 +1300,8 @@
             final int displayTransport =
                     getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes());
             final boolean isMobile = (NetworkCapabilities.TRANSPORT_CELLULAR == displayTransport);
-            final boolean isDefault = ArrayUtils.contains(mDefaultNetworks, snapshot.getNetwork());
+            final boolean isDefault = CollectionUtils.contains(
+                    mDefaultNetworks, snapshot.getNetwork());
             final int subType = combineSubtypeEnabled ? SUBTYPE_COMBINED
                     : getSubTypeForStateSnapshot(snapshot);
             final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot,
@@ -1382,7 +1384,7 @@
 
         mMobileIfaces = mobileIfaces.toArray(new String[0]);
         // TODO (b/192758557): Remove debug log.
-        if (ArrayUtils.contains(mMobileIfaces, null)) {
+        if (CollectionUtils.contains(mMobileIfaces, null)) {
             throw new NullPointerException(
                     "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
         }
@@ -1397,7 +1399,7 @@
         if (spec instanceof TelephonyNetworkSpecifier) {
              return ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
         } else {
-            Slog.wtf(TAG, "getSubIdForState invalid NetworkSpecifier");
+            Log.wtf(TAG, "getSubIdForState invalid NetworkSpecifier");
             return INVALID_SUBSCRIPTION_ID;
         }
     }
@@ -1481,7 +1483,7 @@
         try {
             recordSnapshotLocked(currentTime);
         } catch (IllegalStateException e) {
-            Slog.w(TAG, "problem reading network stats: " + e);
+            Log.w(TAG, "problem reading network stats: " + e);
         } catch (RemoteException e) {
             // ignored; service lives in system_server
         }
@@ -1506,7 +1508,7 @@
     @GuardedBy("mStatsLock")
     private void performPollLocked(int flags) {
         if (!mSystemReady) return;
-        if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
+        if (LOGV) Log.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
         Trace.traceBegin(TRACE_TAG_NETWORK, "performPollLocked");
 
         final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
@@ -1628,7 +1630,7 @@
      */
     @GuardedBy("mStatsLock")
     private void removeUidsLocked(int... uids) {
-        if (LOGV) Slog.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids));
+        if (LOGV) Log.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids));
 
         // Perform one last poll before removing
         performPollLocked(FLAG_PERSIST_ALL);
@@ -1646,20 +1648,20 @@
      * Clean up {@link #mUidRecorder} after user is removed.
      */
     @GuardedBy("mStatsLock")
-    private void removeUserLocked(int userId) {
-        if (LOGV) Slog.v(TAG, "removeUserLocked() for userId=" + userId);
+    private void removeUserLocked(@NonNull UserHandle userHandle) {
+        if (LOGV) Log.v(TAG, "removeUserLocked() for UserHandle=" + userHandle);
 
         // Build list of UIDs that we should clean up
-        int[] uids = new int[0];
+        final ArrayList<Integer> uids = new ArrayList<>();
         final List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
                 PackageManager.MATCH_ANY_USER
                 | PackageManager.MATCH_DISABLED_COMPONENTS);
         for (ApplicationInfo app : apps) {
-            final int uid = UserHandle.getUid(userId, app.uid);
-            uids = ArrayUtils.appendInt(uids, uid);
+            final int uid = userHandle.getUid(app.uid);
+            uids.add(uid);
         }
 
-        removeUidsLocked(uids);
+        removeUidsLocked(CollectionUtils.toIntArray(uids));
     }
 
     private class NetworkStatsManagerInternalImpl extends NetworkStatsManagerInternal {
@@ -1702,7 +1704,7 @@
         public void setStatsProviderWarningAndLimitAsync(
                 @NonNull String iface, long warning, long limit) {
             if (LOGV) {
-                Slog.v(TAG, "setStatsProviderWarningAndLimitAsync("
+                Log.v(TAG, "setStatsProviderWarningAndLimitAsync("
                         + iface + "," + warning + "," + limit + ")");
             }
             invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface,
@@ -1712,7 +1714,7 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter rawWriter, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, rawWriter)) return;
+        if (!PermissionUtils.checkDumpPermission(mContext, TAG, rawWriter)) return;
 
         long duration = DateUtils.DAY_IN_MILLIS;
         final HashSet<String> argSet = new HashSet<String>();
@@ -1773,15 +1775,15 @@
 
             pw.println("Configs:");
             pw.increaseIndent();
-            pw.printPair(NETSTATS_COMBINE_SUBTYPE_ENABLED, mSettings.getCombineSubtypeEnabled());
+            pw.print(NETSTATS_COMBINE_SUBTYPE_ENABLED, mSettings.getCombineSubtypeEnabled());
             pw.println();
             pw.decreaseIndent();
 
             pw.println("Active interfaces:");
             pw.increaseIndent();
             for (int i = 0; i < mActiveIfaces.size(); i++) {
-                pw.printPair("iface", mActiveIfaces.keyAt(i));
-                pw.printPair("ident", mActiveIfaces.valueAt(i));
+                pw.print("iface", mActiveIfaces.keyAt(i));
+                pw.print("ident", mActiveIfaces.valueAt(i));
                 pw.println();
             }
             pw.decreaseIndent();
@@ -1789,8 +1791,8 @@
             pw.println("Active UID interfaces:");
             pw.increaseIndent();
             for (int i = 0; i < mActiveUidIfaces.size(); i++) {
-                pw.printPair("iface", mActiveUidIfaces.keyAt(i));
-                pw.printPair("ident", mActiveUidIfaces.valueAt(i));
+                pw.print("iface", mActiveUidIfaces.keyAt(i));
+                pw.print("ident", mActiveUidIfaces.valueAt(i));
                 pw.println();
             }
             pw.decreaseIndent();
@@ -1863,7 +1865,7 @@
 
     @GuardedBy("mStatsLock")
     private void dumpProtoLocked(FileDescriptor fd) {
-        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+        final ProtoOutputStream proto = new ProtoOutputStream(new FileOutputStream(fd));
 
         // TODO Right now it writes all history.  Should it limit to the "since-boot" log?
 
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
index 5646c75..93d0ae7 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -33,7 +33,7 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.CollectionUtils;
+import com.android.net.module.util.CollectionUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -99,18 +99,19 @@
         // prevent binder call to telephony when querying RAT. Keep listener registration with empty
         // IMSI is meaningless since the RAT type changed is ambiguous for multi-SIM if reported
         // with empty IMSI. So filter the subs w/o a valid IMSI to prevent such registration.
-        final List<Pair<Integer, String>> filteredNewSubs =
-                CollectionUtils.mapNotNull(newSubs, subId -> {
-                    final String subscriberId = mTeleManager.getSubscriberId(subId);
-                    return TextUtils.isEmpty(subscriberId) ? null : new Pair(subId, subscriberId);
-                });
+        final List<Pair<Integer, String>> filteredNewSubs = new ArrayList<>();
+        for (final int subId : newSubs) {
+            final String subscriberId = mTeleManager.getSubscriberId(subId);
+            if (!TextUtils.isEmpty(subscriberId)) {
+                filteredNewSubs.add(new Pair(subId, subscriberId));
+            }
+        }
 
         for (final Pair<Integer, String> sub : filteredNewSubs) {
             // Fully match listener with subId and IMSI, since in some rare cases, IMSI might be
             // suddenly change regardless of subId, such as switch IMSI feature in modem side.
             // If that happens, register new listener with new IMSI and remove old one later.
-            if (CollectionUtils.find(mRatListeners,
-                    it -> it.equalsKey(sub.first, sub.second)) != null) {
+            if (CollectionUtils.any(mRatListeners, it -> it.equalsKey(sub.first, sub.second))) {
                 continue;
             }
 
@@ -126,8 +127,8 @@
 
         for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
             // If there is no subId and IMSI matched the listener, removes it.
-            if (CollectionUtils.find(filteredNewSubs,
-                    it -> listener.equalsKey(it.first, it.second)) == null) {
+            if (!CollectionUtils.any(filteredNewSubs,
+                    it -> listener.equalsKey(it.first, it.second))) {
                 handleRemoveRatTypeListener(listener);
             }
         }
@@ -148,9 +149,10 @@
      * @return collapsed RatType for the given subscriberId
      */
     public int getRatTypeForSubscriberId(@NonNull String subscriberId) {
-        final RatTypeListener match = CollectionUtils.find(mRatListeners,
+        final int index = CollectionUtils.indexOf(mRatListeners,
                 it -> TextUtils.equals(subscriberId, it.mSubscriberId));
-        return match != null ? match.mLastCollapsedRatType : TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        return index != -1 ? mRatListeners.get(index).mLastCollapsedRatType
+                : TelephonyManager.NETWORK_TYPE_UNKNOWN;
     }
 
     /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
index 7e0490a..063d789 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
@@ -264,7 +264,8 @@
         String action = ACTION_INSTALL_COMMIT + "." + packageName;
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(action);
-        mContext.registerReceiver(broadcastReceiver, intentFilter);
+        mContext.registerReceiver(broadcastReceiver, intentFilter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
 
         // Create a matching PendingIntent and use it to generate the IntentSender
         Intent broadcastIntent = new Intent(action);
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index ab7b54d..c5e1c8d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -57,6 +58,7 @@
         public boolean isActive;
         public ComponentName componentName;
         public ComponentName settingsComponentName;
+        public CharSequence description;
 
         @Override
         public String toString() {
@@ -123,6 +125,7 @@
             DreamInfo dreamInfo = new DreamInfo();
             dreamInfo.caption = resolveInfo.loadLabel(pm);
             dreamInfo.icon = resolveInfo.loadIcon(pm);
+            dreamInfo.description = getDescription(resolveInfo, pm);
             dreamInfo.componentName = getDreamComponentName(resolveInfo);
             dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
             dreamInfo.settingsComponentName = getSettingsComponentName(pm, resolveInfo);
@@ -132,9 +135,25 @@
         return dreamInfos;
     }
 
+    private static CharSequence getDescription(ResolveInfo resolveInfo, PackageManager pm) {
+        String packageName = resolveInfo.resolvePackageName;
+        ApplicationInfo applicationInfo = null;
+        if (packageName == null) {
+            packageName = resolveInfo.serviceInfo.packageName;
+            applicationInfo = resolveInfo.serviceInfo.applicationInfo;
+        }
+        if (resolveInfo.serviceInfo.descriptionRes != 0) {
+            return pm.getText(packageName,
+                    resolveInfo.serviceInfo.descriptionRes,
+                    applicationInfo);
+        }
+        return null;
+    }
+
     public ComponentName getDefaultDream() {
-        if (mDreamManager == null)
+        if (mDreamManager == null) {
             return null;
+        }
         try {
             return mDreamManager.getDefaultDreamComponentForUser(mContext.getUserId());
         } catch (RemoteException e) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index bf5ab1c..426ea42 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -342,7 +342,8 @@
 
         resumeScanning();
         if (!mRegistered) {
-            mContext.registerReceiver(mReceiver, mFilter, null /* permission */, mWorkHandler);
+            mContext.registerReceiver(mReceiver, mFilter, null /* permission */, mWorkHandler,
+                    Context.RECEIVER_EXPORTED_UNAUDITED);
             // NetworkCallback objects cannot be reused. http://b/20701525 .
             mNetworkCallback = new WifiTrackerNetworkCallback();
             mConnectivityManager.registerNetworkCallback(
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index ed813a0..368dda1 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -657,6 +657,7 @@
                     Settings.Global.Wearable.WRIST_ORIENTATION_MODE,
                     Settings.Global.Wearable.CLOCKWORK_SYSUI_PACKAGE,
                     Settings.Global.Wearable.CLOCKWORK_SYSUI_MAIN_ACTIVITY,
+                    Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED,
                     Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 1e9a41e..e907efb 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -260,6 +260,8 @@
     <uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" />
     <!-- For handling silent audio recordings -->
     <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
+    <!-- For asking AudioManager audio information -->
+    <uses-permission android:name="android.permission.QUERY_AUDIO_STATE"/>
 
     <!-- to read and change hvac values in a car -->
     <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b64f81a..8482044 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1273,6 +1273,10 @@
 
     <!-- [CHAR LIMIT=NONE] Importance Tuner setting title -->
     <string name="tuner_full_importance_settings">Power notification controls</string>
+
+    <!-- [CHAR LIMIT=NONE] Notification camera based rotation enabled description -->
+    <string name="rotation_lock_camera_rotation_on">On - Face-based</string>
+
     <string name="power_notification_controls_description">With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications.
         \n\n<b>Level 5</b>
         \n- Show at the top of the notification list
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 4e852a8..726f865 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -43,15 +43,18 @@
     public static final BooleanFlag NEW_NOTIFICATION_PIPELINE_RENDERING =
             new BooleanFlag(101, false);
 
-    public static final BooleanFlag NOTIFICATION_UPDATES =
-            new BooleanFlag(102, true);
-
     public static final BooleanFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
             new BooleanFlag(103, false);
 
     public static final ResourceBooleanFlag NOTIFICATION_SHADE_DRAG =
             new ResourceBooleanFlag(104, R.bool.config_enableNotificationShadeDrag);
 
+    public static final BooleanFlag NSSL_DEBUG_LINES =
+            new BooleanFlag(105, false);
+
+    public static final BooleanFlag NSSL_DEBUG_REMOVE_ANIMATION =
+            new BooleanFlag(106, false);
+
     /***************************************/
     // 200 - keyguard/lockscreen
     public static final BooleanFlag KEYGUARD_LAYOUT =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 22a69d4..fb31d88 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -583,6 +583,18 @@
             checkPermission();
             mKeyguardViewMediator.onShortPowerPressedGoHome();
         }
+
+        @Override
+        public void dismissKeyguardToLaunch(Intent intentToLaunch) {
+            checkPermission();
+            mKeyguardViewMediator.dismissKeyguardToLaunch(intentToLaunch);
+        }
+
+        @Override
+        public void onSystemKeyPressed(int keycode) {
+            checkPermission();
+            mKeyguardViewMediator.onSystemKeyPressed(keycode);
+        }
     };
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0512d48..8f4a28c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2750,6 +2750,14 @@
         // do nothing
     }
 
+    public void dismissKeyguardToLaunch(Intent intentToLaunch) {
+        // do nothing
+    }
+
+    public void onSystemKeyPressed(int keycode) {
+        // do nothing
+    }
+
     public ViewMediatorCallback getViewMediatorCallback() {
         return mViewMediatorCallback;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 99b283e..5a86723 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -71,20 +71,32 @@
             when (args[1]) {
                 MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> {
                     mediaTttChipControllerSender.displayChip(
-                        MoveCloserToTransfer(appIconDrawable, otherDeviceName)
+                        MoveCloserToTransfer(
+                            appIconDrawable, APP_ICON_CONTENT_DESCRIPTION, otherDeviceName
+                        )
                     )
                 }
                 TRANSFER_INITIATED_COMMAND_NAME -> {
                     val futureTask = FutureTask { fakeUndoRunnable }
                     mediaTttChipControllerSender.displayChip(
-                        TransferInitiated(appIconDrawable, otherDeviceName, futureTask)
+                        TransferInitiated(
+                            appIconDrawable,
+                            APP_ICON_CONTENT_DESCRIPTION,
+                            otherDeviceName,
+                            futureTask
+                        )
                     )
                     mainExecutor.executeDelayed({ futureTask.run() }, FUTURE_WAIT_TIME)
 
                 }
                 TRANSFER_SUCCEEDED_COMMAND_NAME -> {
                     mediaTttChipControllerSender.displayChip(
-                        TransferSucceeded(appIconDrawable, otherDeviceName, fakeUndoRunnable)
+                        TransferSucceeded(
+                            appIconDrawable,
+                            APP_ICON_CONTENT_DESCRIPTION,
+                            otherDeviceName,
+                            fakeUndoRunnable
+                        )
                     )
                 }
                 else -> {
@@ -118,7 +130,9 @@
     /** A command to DISPLAY the media ttt chip on the RECEIVER device. */
     inner class AddChipCommandReceiver : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
-            mediaTttChipControllerReceiver.displayChip(ChipStateReceiver(appIconDrawable))
+            mediaTttChipControllerReceiver.displayChip(
+                ChipStateReceiver(appIconDrawable, APP_ICON_CONTENT_DESCRIPTION)
+            )
         }
         override fun help(pw: PrintWriter) {
             pw.println("Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_RECEIVER_TAG")
@@ -156,4 +170,5 @@
 val TRANSFER_SUCCEEDED_COMMAND_NAME = TransferSucceeded::class.simpleName!!
 
 private const val FUTURE_WAIT_TIME = 2000L
+private const val APP_ICON_CONTENT_DESCRIPTION = "Fake media app icon"
 private const val TAG = "MediaTapToTransferCli"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 3b429c8..67721a5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -99,6 +99,7 @@
     internal fun setIcon(chipState: T, currentChipView: ViewGroup) {
         currentChipView.findViewById<CachingIconView>(R.id.app_icon).apply {
             this.setImageDrawable(chipState.appIconDrawable)
+            this.contentDescription = chipState.appIconContentDescription
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
index 1e475a5..c510cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
@@ -22,7 +22,9 @@
  * A superclass chip state that will be subclassed by the sender chip and receiver chip.
  *
  * @property appIconDrawable a drawable representing the icon of the app playing the media.
+ * @property appIconContentDescription a string to use as the content description for the icon.
  */
 open class MediaTttChipState(
-    internal val appIconDrawable: Drawable
+    internal val appIconDrawable: Drawable,
+    internal val appIconContentDescription: String
 )
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
index 5397235..df6b934 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
@@ -24,5 +24,6 @@
  * the receiver device.
  */
 class ChipStateReceiver(
-    appIconDrawable: Drawable
-) : MediaTttChipState(appIconDrawable)
+    appIconDrawable: Drawable,
+    appIconContentDescription: String
+) : MediaTttChipState(appIconDrawable, appIconContentDescription)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 24943b9..b1f6faa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -34,9 +34,10 @@
  */
 sealed class ChipStateSender(
     appIconDrawable: Drawable,
+    appIconContentDescription: String,
     @StringRes internal val chipText: Int,
     internal val otherDeviceName: String,
-) : MediaTttChipState(appIconDrawable)
+) : MediaTttChipState(appIconDrawable, appIconContentDescription)
 
 /**
  * A state representing that the two devices are close but not close enough to initiate a transfer.
@@ -44,8 +45,14 @@
  */
 class MoveCloserToTransfer(
     appIconDrawable: Drawable,
+    appIconContentDescription: String,
     otherDeviceName: String,
-) : ChipStateSender(appIconDrawable, R.string.media_move_closer_to_transfer, otherDeviceName)
+) : ChipStateSender(
+    appIconDrawable,
+    appIconContentDescription,
+    R.string.media_move_closer_to_transfer,
+    otherDeviceName
+)
 
 /**
  * A state representing that a transfer has been initiated (but not completed).
@@ -57,9 +64,15 @@
  */
 class TransferInitiated(
     appIconDrawable: Drawable,
+    appIconContentDescription: String,
     otherDeviceName: String,
     val future: Future<Runnable?>
-) : ChipStateSender(appIconDrawable, R.string.media_transfer_playing, otherDeviceName)
+) : ChipStateSender(
+    appIconDrawable,
+    appIconContentDescription,
+    R.string.media_transfer_playing,
+    otherDeviceName
+)
 
 /**
  * A state representing that a transfer has been successfully completed.
@@ -69,6 +82,11 @@
  */
 class TransferSucceeded(
     appIconDrawable: Drawable,
+    appIconContentDescription: String,
     otherDeviceName: String,
     val undoRunnable: Runnable? = null
-) : ChipStateSender(appIconDrawable, R.string.media_transfer_playing, otherDeviceName)
+) : ChipStateSender(appIconDrawable,
+    appIconContentDescription,
+    R.string.media_transfer_playing,
+    otherDeviceName
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index fce4b98..77d3d70 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -93,7 +93,10 @@
                 mainExecutor.execute {
                     displayChip(
                         TransferSucceeded(
-                            chipState.appIconDrawable, chipState.otherDeviceName, undoRunnable
+                            chipState.appIconDrawable,
+                            chipState.appIconContentDescription,
+                            chipState.otherDeviceName,
+                            undoRunnable
                         )
                     )
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 0bbb5bd..082dc5c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -16,12 +16,18 @@
 
 package com.android.systemui.qs.tiles;
 
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import static com.android.systemui.statusbar.policy.RotationLockControllerImpl.hasSufficientPermission;
+
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.service.quicksettings.Tile;
 import android.view.View;
 import android.widget.Switch;
@@ -38,18 +44,25 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.RotationLockController;
 import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
+import com.android.systemui.util.settings.SecureSettings;
 
 import javax.inject.Inject;
 
 /** Quick settings tile: Rotation **/
-public class RotationLockTile extends QSTileImpl<BooleanState> {
+public class RotationLockTile extends QSTileImpl<BooleanState> implements
+        BatteryController.BatteryStateChangeCallback {
 
     private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate);
     private final RotationLockController mController;
+    private final SensorPrivacyManager mPrivacyManager;
+    private final BatteryController mBatteryController;
+    private final SettingObserver mSetting;
 
     @Inject
     public RotationLockTile(
@@ -61,12 +74,41 @@
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
             QSLogger qsLogger,
-            RotationLockController rotationLockController
+            RotationLockController rotationLockController,
+            SensorPrivacyManager privacyManager,
+            BatteryController batteryController,
+            SecureSettings secureSettings
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = rotationLockController;
         mController.observe(this, mCallback);
+        mPrivacyManager = privacyManager;
+        mBatteryController = batteryController;
+        int currentUser = host.getUserContext().getUserId();
+        mSetting = new SettingObserver(
+                secureSettings,
+                mHandler,
+                Secure.CAMERA_AUTOROTATE,
+                currentUser
+        ) {
+            @Override
+            protected void handleValueChanged(int value, boolean observedChange) {
+                // mHandler is the background handler so calling this is OK
+                handleRefreshState(null);
+            }
+        };
+        mBatteryController.observe(getLifecycle(), this);
+    }
+
+    @Override
+    protected void handleInitialize() {
+        mPrivacyManager.addSensorPrivacyListener(CAMERA, mSensorPrivacyChangedListener);
+    }
+
+    @Override
+    public void onPowerSaveChanged(boolean isPowerSave) {
+        refreshState();
     }
 
     @Override
@@ -95,14 +137,46 @@
     protected void handleUpdateState(BooleanState state, Object arg) {
         final boolean rotationLocked = mController.isRotationLocked();
 
+        final boolean powerSave = mBatteryController.isPowerSave();
+        final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(CAMERA);
+        final boolean cameraRotation =
+                !powerSave && !cameraLocked && hasSufficientPermission(mContext)
+                        && mController.isCameraRotationEnabled();
         state.value = !rotationLocked;
         state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
         state.icon = mIcon;
         state.contentDescription = getAccessibilityString(rotationLocked);
+        if (!rotationLocked && cameraRotation) {
+            state.secondaryLabel = mContext.getResources().getString(
+                    R.string.rotation_lock_camera_rotation_on);
+        } else {
+            state.secondaryLabel = "";
+        }
+        state.stateDescription = state.secondaryLabel;
+
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
     }
 
+    @Override
+    protected void handleDestroy() {
+        super.handleDestroy();
+        mSetting.setListening(false);
+        mPrivacyManager.removeSensorPrivacyListener(CAMERA, mSensorPrivacyChangedListener);
+    }
+
+    @Override
+    public void handleSetListening(boolean listening) {
+        super.handleSetListening(listening);
+        mSetting.setListening(listening);
+    }
+
+    @Override
+    protected void handleUserSwitch(int newUserId) {
+        mSetting.setUserId(newUserId);
+        handleRefreshState(null);
+    }
+
     public static boolean isCurrentOrientationLockPortrait(RotationLockController controller,
             Resources resources) {
         int lockOrientation = controller.getRotationLockOrientation();
@@ -140,4 +214,8 @@
             refreshState(rotationLocked);
         }
     };
+
+    private final SensorPrivacyManager.OnSensorPrivacyChangedListener
+            mSensorPrivacyChangedListener =
+            (sensor, enabled) -> refreshState();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 210ee96..3730d12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -379,6 +379,7 @@
         }
     }
 
+    @Nullable
     public String getMediaNotificationKey() {
         return mMediaNotificationKey;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index f500d39..4717b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -20,8 +20,6 @@
 import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.Notification;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -33,6 +31,9 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
@@ -123,8 +124,8 @@
      * filtered out if for instance they are not for the current user
      */
     private final ArrayMap<String, NotificationEntry> mActiveNotifications = new ArrayMap<>();
-    @VisibleForTesting
     /** This is the list of "active notifications for this user in this context" */
+    @VisibleForTesting
     protected final ArrayList<NotificationEntry> mSortedAndFiltered = new ArrayList<>();
     private final List<NotificationEntry> mReadOnlyNotifications =
             Collections.unmodifiableList(mSortedAndFiltered);
@@ -899,7 +900,7 @@
     }
 
     /** Calls to NotificationRankingManager and updates mSortedAndFiltered */
-    private void updateRankingAndSort(@NonNull RankingMap rankingMap, String reason) {
+    private void updateRankingAndSort(RankingMap rankingMap, String reason) {
         if (mNotifPipelineFlags.isNewPipelineEnabled()) {
             mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
             return;
@@ -961,6 +962,7 @@
      * Returns a collections containing ALL notifications we know about, including ones that are
      * hidden or for other users. See {@link CommonNotifCollection#getAllNotifs()}.
      */
+    @NonNull
     @Override
     public Collection<NotificationEntry> getAllNotifs() {
         mNotifPipelineFlags.checkLegacyPipelineEnabled();
@@ -969,7 +971,7 @@
 
     @Nullable
     @Override
-    public NotificationEntry getEntry(String key) {
+    public NotificationEntry getEntry(@NonNull String key) {
         mNotifPipelineFlags.checkLegacyPipelineEnabled();
         return getPendingOrActiveNotif(key);
     }
@@ -989,7 +991,7 @@
     }
 
     @Override
-    public void addCollectionListener(NotifCollectionListener listener) {
+    public void addCollectionListener(@NonNull NotifCollectionListener listener) {
         mNotifCollectionListeners.add(listener);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index f8f1279..b6b9c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -44,7 +44,6 @@
 
 import android.annotation.IntDef;
 import android.annotation.MainThread;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.Notification;
 import android.os.Handler;
@@ -59,6 +58,7 @@
 import android.util.Pair;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dumpable;
@@ -193,7 +193,8 @@
     }
 
     /** @see NotifPipeline#getEntry(String) () */
-    NotificationEntry getEntry(String key) {
+    @Nullable
+    NotificationEntry getEntry(@NonNull String key) {
         return mNotificationSet.get(key);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index e9b7caa5..85c0064 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -82,11 +82,8 @@
     public void attach(NotifPipeline pipeline) {
         mNotifPipeline = pipeline;
         mNotifPipeline.addNotificationDismissInterceptor(mDismissInterceptor);
-        mNotifPipeline.addFinalizeFilter(mNotifFilter);
-        if (mBubblesManagerOptional.isPresent()) {
-            mBubblesManagerOptional.get().addNotifCallback(mNotifCallback);
-        }
-
+        mNotifPipeline.addPreGroupFilter(mNotifFilter);
+        mBubblesManagerOptional.ifPresent(manager -> manager.addNotifCallback(mNotifCallback));
     }
 
     private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index fe1cd7b..33005b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -90,6 +90,7 @@
         readShowSilentNotificationSetting();
 
         setupInvalidateNotifListCallbacks();
+        // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
         pipeline.addFinalizeFilter(mNotifFilter);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 8769969..ecee006 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -49,6 +49,6 @@
 
     @Override
     public void attach(NotifPipeline pipeline) {
-        pipeline.addFinalizeFilter(mMediaFilter);
+        pipeline.addPreGroupFilter(mMediaFilter);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
index 471c357..beaa1ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.notifcollection;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -37,7 +38,7 @@
      * Registers a listener to be informed when notifications are created, added, updated, removed,
      * or deleted.
      */
-    void addCollectionListener(NotifCollectionListener listener);
+    void addCollectionListener(@NonNull NotifCollectionListener listener);
 
     /**
      * Returns the list of all known notifications, i.e. the notifications that are currently posted
@@ -46,11 +47,11 @@
      *
      * The returned collection is read-only, unsorted, unfiltered, and ungrouped.
      */
-    Collection<NotificationEntry> getAllNotifs();
+    @NonNull Collection<NotificationEntry> getAllNotifs();
 
     /**
      * Returns the notification entry for the given notification key;
      * the returned entry (if present) may be in any state.
      */
-    @Nullable NotificationEntry getEntry(String key);
+    @Nullable NotificationEntry getEntry(@NonNull String key);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
index b2e15f4..b61a540 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
@@ -77,11 +77,11 @@
 
     override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
         val previous = currentMediaEntry
-        var newEntry = commonNotifCollection.getEntry(mediaManager.mediaNotificationKey)
-        if (!NotificationMediaManager.isPlayingState(state)) {
-            newEntry = null
-        }
-        currentMediaEntry = newEntry
+        val mediaNotificationKey = mediaManager.mediaNotificationKey
+        currentMediaEntry =
+            if (mediaNotificationKey != null && NotificationMediaManager.isPlayingState(state))
+                commonNotifCollection.getEntry(mediaNotificationKey)
+            else null
         updateAutoHeadsUp(previous)
         updateAutoHeadsUp(currentMediaEntry)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 3a37fb4..9d599cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -41,11 +41,8 @@
 import android.widget.FrameLayout.LayoutParams;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -304,12 +301,9 @@
         } else {
             mMenuContainer = new FrameLayout(mContext);
         }
-        // The setting can win (which is needed for tests) but if not set, then use the flag
         final int showDismissSetting =  Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.SHOW_NEW_NOTIF_DISMISS, -1);
-        final boolean newFlowHideShelf = showDismissSetting == -1
-                ? Dependency.get(FeatureFlags.class).isEnabled(Flags.NOTIFICATION_UPDATES)
-                : showDismissSetting == 1;
+                Settings.Global.SHOW_NEW_NOTIF_DISMISS, /* default = */ 1);
+        final boolean newFlowHideShelf = showDismissSetting == 1;
         if (newFlowHideShelf) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 1cb5e62..464fd06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -21,8 +21,6 @@
 
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -41,7 +39,6 @@
     private final ExpandableView[] mLastInSectionViews;
     private final ExpandableView[] mTmpFirstInSectionViews;
     private final ExpandableView[] mTmpLastInSectionViews;
-    private final FeatureFlags mFeatureFlags;
     private boolean mExpanded;
     private HashSet<ExpandableView> mAnimatedChildren;
     private Runnable mRoundingChangedCallback;
@@ -56,9 +53,7 @@
 
     @Inject
     NotificationRoundnessManager(
-            NotificationSectionsFeatureManager sectionsFeatureManager,
-            FeatureFlags featureFlags) {
-        mFeatureFlags = featureFlags;
+            NotificationSectionsFeatureManager sectionsFeatureManager) {
         int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
         mFirstInSectionViews = new ExpandableView[numberOfSections];
         mLastInSectionViews = new ExpandableView[numberOfSections];
@@ -125,9 +120,6 @@
             ExpandableView viewBefore,
             ExpandableView viewSwiped,
             ExpandableView viewAfter) {
-        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_UPDATES)) {
-            return;
-        }
         final boolean animate = true;
 
         ExpandableView oldViewBefore = mViewBeforeSwipedView;
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 3f16426..3e9ce25 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
@@ -43,7 +43,6 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.AttributeSet;
@@ -80,6 +79,8 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.EmptyShadeView;
@@ -132,14 +133,6 @@
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
 
-    // Usage:
-    // adb shell setprop persist.debug.nssl true && adb reboot
-    private static final boolean DEBUG = SystemProperties.getBoolean("persist.debug.nssl",
-            false /* default */);
-    // TODO(b/187291379) disable again before release
-    private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean(
-            "persist.debug.nssl.dismiss", false /* default */);
-
     // Delay in milli-seconds before shade closes for clear all.
     private final int DELAY_BEFORE_SHADE_CLOSE = 200;
     private boolean mShadeNeedsToClose = false;
@@ -192,7 +185,12 @@
     private float mInitialTouchX;
     private float mInitialTouchY;
 
+    private final boolean mDebugLines;
     private Paint mDebugPaint;
+    /** Used to track the Y positions that were already used to draw debug text labels. */
+    private Set<Integer> mDebugTextUsedYPositions;
+    private final boolean mDebugRemoveAnimation;
+
     private int mContentHeight;
     private int mIntrinsicContentHeight;
     private int mCollapsedSize;
@@ -569,6 +567,9 @@
     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
         super(context, attrs, 0, 0);
         Resources res = getResources();
+        FeatureFlags featureFlags = Dependency.get(FeatureFlags.class);
+        mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
+        mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -591,10 +592,10 @@
                 res.getBoolean(R.bool.config_drawNotificationBackground);
         setOutlineProvider(mOutlineProvider);
 
-        boolean willDraw = mShouldDrawNotificationBackground || DEBUG;
+        boolean willDraw = mShouldDrawNotificationBackground || mDebugLines;
         setWillNotDraw(!willDraw);
         mBackgroundPaint.setAntiAlias(true);
-        if (DEBUG) {
+        if (mDebugLines) {
             mDebugPaint = new Paint();
             mDebugPaint.setColor(0xffff0000);
             mDebugPaint.setStrokeWidth(2);
@@ -729,18 +730,17 @@
             drawHeadsUpBackground(canvas);
         }
 
-        if (DEBUG) {
+        if (mDebugLines) {
             onDrawDebug(canvas);
         }
     }
 
-    /** Used to track the Y positions that were already used to draw debug text labels. */
-    private static final Set<Integer> DEBUG_TEXT_USED_Y_POSITIONS =
-            DEBUG ? new HashSet<>() : Collections.emptySet();
-
     private void onDrawDebug(Canvas canvas) {
-        DEBUG_TEXT_USED_Y_POSITIONS.clear();
-
+        if (mDebugTextUsedYPositions == null) {
+            mDebugTextUsedYPositions = new HashSet<>();
+        } else {
+            mDebugTextUsedYPositions.clear();
+        }
         int y = mTopPadding;
         drawDebugInfo(canvas, y, Color.RED, /* label= */ "mTopPadding");
 
@@ -776,10 +776,10 @@
 
     private int computeDebugYTextPosition(int lineY) {
         int textY = lineY;
-        while (DEBUG_TEXT_USED_Y_POSITIONS.contains(textY)) {
+        while (mDebugTextUsedYPositions.contains(textY)) {
             textY = (int) (textY + mDebugPaint.getTextSize());
         }
-        DEBUG_TEXT_USED_Y_POSITIONS.add(textY);
+        mDebugTextUsedYPositions.add(textY);
         return textY;
     }
 
@@ -2702,14 +2702,14 @@
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     boolean generateRemoveAnimation(ExpandableView child) {
         String key = "";
-        if (DEBUG_REMOVE_ANIMATION) {
+        if (mDebugRemoveAnimation) {
             if (child instanceof ExpandableNotificationRow) {
                 key = ((ExpandableNotificationRow) child).getEntry().getKey();
             }
             Log.d(TAG, "generateRemoveAnimation " + key);
         }
         if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
-            if (DEBUG_REMOVE_ANIMATION) {
+            if (mDebugRemoveAnimation) {
                 Log.d(TAG, "removedBecauseOfHeadsUp " + key);
             }
             mAddedHeadsUpChildren.remove(child);
@@ -2720,7 +2720,7 @@
             mClearTransientViewsWhenFinished.add(child);
             return true;
         }
-        if (DEBUG_REMOVE_ANIMATION) {
+        if (mDebugRemoveAnimation) {
             Log.d(TAG, "generateRemove " + key
                     + "\nmIsExpanded " + mIsExpanded
                     + "\nmAnimationsEnabled " + mAnimationsEnabled
@@ -2728,7 +2728,7 @@
         }
         if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
             if (!mChildrenToAddAnimated.contains(child)) {
-                if (DEBUG_REMOVE_ANIMATION) {
+                if (mDebugRemoveAnimation) {
                     Log.d(TAG, "needsAnimation = true " + key);
                 }
                 // Generate Animations
@@ -3227,7 +3227,7 @@
                     ignoreChildren);
             mAnimationEvents.add(event);
             mSwipedOutViews.remove(child);
-            if (DEBUG_REMOVE_ANIMATION) {
+            if (mDebugRemoveAnimation) {
                 String key = "";
                 if (child instanceof ExpandableNotificationRow) {
                     key = ((ExpandableNotificationRow) child).getEntry().getKey();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index ad1c232..98b5dcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -645,7 +645,7 @@
         if (biometricSourceType.equals(BiometricSourceType.FINGERPRINT)
                 && mUpdateMonitor.isUdfpsSupported()
                 && mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
-            mKeyguardViewController.showBouncer(true);
+            startWakeAndUnlock(MODE_SHOW_BOUNCER);
             UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
             mNumConsecutiveFpFailures = 0;
         }
@@ -668,7 +668,8 @@
                 && mUpdateMonitor.isUdfpsSupported()
                 && (mStatusBarStateController.getState() == StatusBarState.SHADE
                     || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) {
-            mKeyguardViewController.showBouncer(true);
+            startWakeAndUnlock(MODE_SHOW_BOUNCER);
+            UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
         }
         cleanup();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 7ca8652..732e5f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -57,8 +57,7 @@
     private int mUserSwitchPreferredY;
 
     /**
-     * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher
-     * avatar.
+     * Minimum top margin to avoid overlap with status bar or multi-user switcher avatar.
      */
     private int mMinTopMargin;
 
@@ -203,7 +202,7 @@
         if (mBypassEnabled) {
             return mUnlockedStackScrollerPadding;
         } else if (mIsSplitShade) {
-            return getClockY(1.0f, mDarkAmount);
+            return getClockY(1.0f, mDarkAmount) + mUserSwitchHeight;
         } else {
             return getClockY(1.0f, mDarkAmount) + mKeyguardStatusHeight;
         }
@@ -213,7 +212,7 @@
         if (mBypassEnabled) {
             return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount);
         } else if (mIsSplitShade) {
-            return Math.max(0, clockYPosition - mSplitShadeTopNotificationsMargin);
+            return clockYPosition - mSplitShadeTopNotificationsMargin + mUserSwitchHeight;
         } else {
             return clockYPosition + mKeyguardStatusHeight;
         }
@@ -223,7 +222,7 @@
         if (mBypassEnabled) {
             return mUnlockedStackScrollerPadding;
         } else if (mIsSplitShade) {
-            return Math.max(mSplitShadeTargetTopMargin, mMinTopMargin);
+            return mSplitShadeTargetTopMargin + mUserSwitchHeight;
         } else {
             return mMinTopMargin + mKeyguardStatusHeight;
         }
@@ -231,7 +230,7 @@
 
     private int getExpandedPreferredClockY() {
         if (mIsSplitShade) {
-            return Math.max(mSplitShadeTargetTopMargin, mMinTopMargin);
+            return mSplitShadeTargetTopMargin;
         } else {
             return mMinTopMargin;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 434671c..9ca904b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1440,8 +1440,11 @@
             mKeyguardStatusViewController.displayClock(LARGE, shouldAnimateClockChange);
         }
         updateKeyguardStatusViewAlignment(true /* animate */);
-        int userIconHeight = mKeyguardQsUserSwitchController != null
+        int userSwitcherHeight = mKeyguardQsUserSwitchController != null
                 ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0;
+        if (mKeyguardUserSwitcherController != null) {
+            userSwitcherHeight = mKeyguardUserSwitcherController.getHeight();
+        }
         float expandedFraction =
                 mScreenOffAnimationController.shouldExpandNotifications()
                         ? 1.0f : getExpandedFraction();
@@ -1461,7 +1464,7 @@
                 mStatusBarHeaderHeightKeyguard,
                 expandedFraction,
                 mKeyguardStatusViewController.getLockscreenHeight(),
-                userIconHeight,
+                userSwitcherHeight,
                 userSwitcherPreferredY,
                 darkamount, mOverStretchAmount,
                 bypassEnabled, getUnlockedStackScrollerPadding(),
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 ffa7963..04a6a11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -248,6 +248,10 @@
         return mUserSwitcherController.isSimpleUserSwitcher();
     }
 
+    public int getHeight() {
+        return mListView.getHeight();
+    }
+
     /**
      * @param animate if the transition should be animated
      * @return true if the switcher state changed
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
index cd8894c..850a4b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -97,9 +97,12 @@
             } else {
                 // Update clickable state immediately so that the menu feels more responsive
                 userItemViews[i].setClickable(open);
-                // Before running the animation, ensure visibility is set correctly
-                userItemViews[i].updateVisibilities(animate || open /* showItem */,
-                        true /* showTextName */, false /* animate */);
+                // when opening we need to make views visible beforehand so they can be animated
+                if (open) {
+                    userItemViews[i].updateVisibilities(true /* showItem */,
+                            true /* showTextName */, false /* animate */);
+                }
+
             }
         }
 
@@ -117,6 +120,13 @@
                         setClipChildren(true);
                         setClipToPadding(true);
                         mAnimating = false;
+                        if (!open) {
+                            // after closing we hide children so that height of this view is correct
+                            for (int i = 1; i < userItemViews.length; i++) {
+                                userItemViews[i].updateVisibilities(false /* showItem */,
+                                        true /* showTextName */, false /* animate */);
+                            }
+                        }
                     });
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
index f258fb1..1158324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
@@ -23,6 +23,7 @@
     int getRotationLockOrientation();
     boolean isRotationLockAffordanceVisible();
     boolean isRotationLocked();
+    boolean isCameraRotationEnabled();
     void setRotationLocked(boolean locked);
     void setRotationLockedAtAngle(boolean locked, int rotation);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 67f5364..3143a47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -18,6 +18,9 @@
 
 import static com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule.DEVICE_STATE_ROTATION_LOCK_DEFAULTS;
 
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
@@ -34,6 +37,7 @@
 /** Platform implementation of the rotation lock controller. **/
 @SysUISingleton
 public final class RotationLockControllerImpl implements RotationLockController {
+
     private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks =
             new CopyOnWriteArrayList<>();
 
@@ -86,11 +90,15 @@
         return mRotationPolicy.isRotationLocked();
     }
 
+    public boolean isCameraRotationEnabled() {
+        return mRotationPolicy.isCameraRotationEnabled();
+    }
+
     public void setRotationLocked(boolean locked) {
         mRotationPolicy.setRotationLock(locked);
     }
 
-    public void setRotationLockedAtAngle(boolean locked, int rotation){
+    public void setRotationLockedAtAngle(boolean locked, int rotation) {
         mRotationPolicy.setRotationLockAtAngle(locked, rotation);
     }
 
@@ -121,4 +129,11 @@
         callback.onRotationLockStateChanged(mRotationPolicy.isRotationLocked(),
                 mRotationPolicy.isRotationLockToggleVisible());
     }
+
+    public static boolean hasSufficientPermission(Context context) {
+        final PackageManager packageManager = context.getPackageManager();
+        final String rotationPackage = packageManager.getRotationResolverPackageName();
+        return rotationPackage != null && packageManager.checkPermission(
+                Manifest.permission.CAMERA, rotationPackage) == PackageManager.PERMISSION_GRANTED;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
index 2a0cc7d..b64d7be 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.util.wrapper
 
 import android.content.Context
+import android.provider.Settings.Secure.CAMERA_AUTOROTATE
 import com.android.internal.view.RotationPolicy
 import com.android.internal.view.RotationPolicy.RotationPolicyListener
+import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
 
 /**
@@ -30,12 +32,16 @@
     fun getRotationLockOrientation(): Int
     fun isRotationLockToggleVisible(): Boolean
     fun isRotationLocked(): Boolean
+    fun isCameraRotationEnabled(): Boolean
     fun registerRotationPolicyListener(listener: RotationPolicyListener, userHandle: Int)
     fun unregisterRotationPolicyListener(listener: RotationPolicyListener)
 }
 
-class RotationPolicyWrapperImpl @Inject constructor(private val context: Context) :
-    RotationPolicyWrapper {
+class RotationPolicyWrapperImpl @Inject constructor(
+    private val context: Context,
+    private val secureSettings: SecureSettings
+) :
+        RotationPolicyWrapper {
 
     override fun setRotationLock(enabled: Boolean) {
         RotationPolicy.setRotationLock(context, enabled)
@@ -54,6 +60,9 @@
     override fun isRotationLocked(): Boolean =
         RotationPolicy.isRotationLocked(context)
 
+    override fun isCameraRotationEnabled(): Boolean =
+            secureSettings.getInt(CAMERA_AUTOROTATE, 0) == 1
+
     override fun registerRotationPolicyListener(
         listener: RotationPolicyListener,
         userHandle: Int
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index b74ba26..927ca7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -19,14 +19,18 @@
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
+import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
+import android.widget.ImageView
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
@@ -50,24 +54,24 @@
 
     @Test
     fun displayChip_chipAdded() {
-        controllerCommon.displayChip(MediaTttChipState(appIconDrawable))
+        controllerCommon.displayChip(getState())
 
         verify(windowManager).addView(any(), any())
     }
 
     @Test
     fun displayChip_twice_chipNotAddedTwice() {
-        controllerCommon.displayChip(MediaTttChipState(appIconDrawable))
+        controllerCommon.displayChip(getState())
         reset(windowManager)
 
-        controllerCommon.displayChip(MediaTttChipState(appIconDrawable))
+        controllerCommon.displayChip(getState())
         verify(windowManager, never()).addView(any(), any())
     }
 
     @Test
     fun removeChip_chipRemoved() {
         // First, add the chip
-        controllerCommon.displayChip(MediaTttChipState(appIconDrawable))
+        controllerCommon.displayChip(getState())
 
         // Then, remove it
         controllerCommon.removeChip()
@@ -82,6 +86,29 @@
         verify(windowManager, never()).removeView(any())
     }
 
+    @Test
+    fun setIcon_viewHasIconAndContentDescription() {
+        controllerCommon.displayChip(getState())
+        val chipView = getChipView()
+        val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
+        val contentDescription = "test description"
+
+        controllerCommon.setIcon(MediaTttChipState(drawable, contentDescription), chipView)
+
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(drawable)
+        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(contentDescription)
+    }
+
+    private fun getState() = MediaTttChipState(appIconDrawable, APP_ICON_CONTENT_DESCRIPTION)
+
+    private fun getChipView(): ViewGroup {
+        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+        verify(windowManager).addView(viewCaptor.capture(), any())
+        return viewCaptor.value as ViewGroup
+    }
+
+    private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
+
     inner class TestControllerCommon(
         context: Context,
         windowManager: WindowManager
@@ -92,3 +119,5 @@
         }
     }
 }
+
+private const val APP_ICON_CONTENT_DESCRIPTION = "Content description"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 2ff472f..afaab80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.media.taptotransfer.receiver
 
-import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.view.View
 import android.view.ViewGroup
@@ -50,10 +49,12 @@
     @Test
     fun displayChip_chipContainsIcon() {
         val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
+        val contentDescription = "Test description"
 
-        controllerReceiver.displayChip(ChipStateReceiver(drawable))
+        controllerReceiver.displayChip(ChipStateReceiver(drawable, contentDescription))
 
-        assertThat(getChipView().getAppIconDrawable()).isEqualTo(drawable)
+        assertThat(getChipView().getAppIconView().drawable).isEqualTo(drawable)
+        assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(contentDescription)
     }
 
     private fun getChipView(): ViewGroup {
@@ -62,6 +63,5 @@
         return viewCaptor.value as ViewGroup
     }
 
-    private fun ViewGroup.getAppIconDrawable(): Drawable =
-        (this.requireViewById<ImageView>(R.id.app_icon)).drawable
+    private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 028ec55..caef5b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -70,7 +70,8 @@
         controllerSender.displayChip(moveCloserToTransfer())
 
         val chipView = getChipView()
-        assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable)
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
+        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
         assertThat(chipView.getChipText()).contains(DEVICE_NAME)
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
         assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -85,7 +86,8 @@
 
         // Assert we're still in the loading state
         val chipView = getChipView()
-        assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable)
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
+        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
         assertThat(chipView.getChipText()).contains(DEVICE_NAME)
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
         assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -155,7 +157,8 @@
         controllerSender.displayChip(transferSucceeded())
 
         val chipView = getChipView()
-        assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable)
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
+        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
         assertThat(chipView.getChipText()).contains(DEVICE_NAME)
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
     }
@@ -220,8 +223,7 @@
         assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
     }
 
-    private fun LinearLayout.getAppIconDrawable(): Drawable =
-        (this.requireViewById<ImageView>(R.id.app_icon)).drawable
+    private fun LinearLayout.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
 
     private fun LinearLayout.getChipText(): String =
         (this.requireViewById<TextView>(R.id.text)).text as String
@@ -238,20 +240,22 @@
     }
 
     /** Helper method providing default parameters to not clutter up the tests. */
-    private fun moveCloserToTransfer() = MoveCloserToTransfer(appIconDrawable, DEVICE_NAME)
+    private fun moveCloserToTransfer() =
+        MoveCloserToTransfer(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
 
     /** Helper method providing default parameters to not clutter up the tests. */
     private fun transferInitiated(
         future: Future<Runnable?> = TEST_FUTURE
-    ) = TransferInitiated(appIconDrawable, DEVICE_NAME, future)
+    ) = TransferInitiated(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, future)
 
     /** Helper method providing default parameters to not clutter up the tests. */
     private fun transferSucceeded(
         undoRunnable: Runnable? = null
-    ) = TransferSucceeded(appIconDrawable, DEVICE_NAME, undoRunnable)
+    ) = TransferSucceeded(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoRunnable)
 }
 
 private const val DEVICE_NAME = "My Tablet"
+private const val APP_ICON_CONTENT_DESC = "Content description"
 // Use a settable future that hasn't yet been set so that we don't immediately switch to the success
 // state.
 private val TEST_FUTURE: SettableFuture<Runnable?> = SettableFuture.create()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
new file mode 100644
index 0000000..55c51b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import static junit.framework.TestCase.assertEquals;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.hardware.SensorPrivacyManager;
+import android.os.Handler;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.wrapper.RotationPolicyWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class RotationLockTileTest extends SysuiTestCase {
+
+    private static final String PACKAGE_NAME = "package_name";
+    private static final String[] DEFAULT_SETTINGS = new String[]{
+            "0:0",
+            "1:2"
+    };
+
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private ActivityStarter mActivityStarter;
+    @Mock
+    private QSTileHost mHost;
+    @Mock
+    private MetricsLogger mMetricsLogger;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private QSLogger mQSLogger;
+    @Mock
+    private SensorPrivacyManager mPrivacyManager;
+    @Mock
+    private BatteryController mBatteryController;
+    @Mock
+    DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
+    @Mock
+    RotationPolicyWrapper mRotationPolicyWrapper;
+
+    private RotationLockController mController;
+    private TestableLooper mTestableLooper;
+    private RotationLockTile mLockTile;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mTestableLooper = TestableLooper.get(this);
+
+        when(mHost.getContext()).thenReturn(mContext);
+        when(mHost.getUserContext()).thenReturn(mContext);
+
+        mController = new RotationLockControllerImpl(mRotationPolicyWrapper,
+                mDeviceStateRotationLockSettingController, DEFAULT_SETTINGS);
+
+        mLockTile = new RotationLockTile(
+                mHost,
+                mTestableLooper.getLooper(),
+                new Handler(mTestableLooper.getLooper()),
+                new FalsingManagerFake(),
+                mMetricsLogger,
+                mStatusBarStateController,
+                mActivityStarter,
+                mQSLogger,
+                mController,
+                mPrivacyManager,
+                mBatteryController,
+                new FakeSettings()
+        );
+
+        mLockTile.initialize();
+
+        // We are not setting the mocks to listening, so we trigger a first refresh state to
+        // set the initial state
+        mLockTile.refreshState();
+
+        mTestableLooper.processAllMessages();
+
+        mContext.setMockPackageManager(mPackageManager);
+        doReturn(PACKAGE_NAME).when(mPackageManager).getRotationResolverPackageName();
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
+                Manifest.permission.CAMERA, PACKAGE_NAME);
+
+        when(mBatteryController.isPowerSave()).thenReturn(false);
+        when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(false);
+        enableAutoRotation();
+        enableCameraBasedRotation();
+
+        mLockTile.refreshState();
+        mTestableLooper.processAllMessages();
+    }
+
+    @Test
+    public void testSecondaryString_cameraRotateOn_returnsFaceBased() {
+        assertEquals(mContext.getString(R.string.rotation_lock_camera_rotation_on),
+                mLockTile.getState().secondaryLabel.toString());
+    }
+
+    @Test
+    public void testSecondaryString_rotateOff_isEmpty() {
+        disableAutoRotation();
+
+        mLockTile.refreshState();
+        mTestableLooper.processAllMessages();
+
+        assertEquals("", mLockTile.getState().secondaryLabel.toString());
+    }
+
+    @Test
+    public void testSecondaryString_cameraRotateOff_isEmpty() {
+        disableCameraBasedRotation();
+
+        mLockTile.refreshState();
+        mTestableLooper.processAllMessages();
+
+        assertEquals("", mLockTile.getState().secondaryLabel.toString());
+    }
+
+    @Test
+    public void testSecondaryString_powerSaveEnabled_isEmpty() {
+        when(mBatteryController.isPowerSave()).thenReturn(true);
+
+        mLockTile.refreshState();
+        mTestableLooper.processAllMessages();
+
+        assertEquals("", mLockTile.getState().secondaryLabel.toString());
+    }
+
+    @Test
+    public void testSecondaryString_cameraDisabled_isEmpty() {
+        when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(true);
+
+        mLockTile.refreshState();
+        mTestableLooper.processAllMessages();
+
+        assertEquals("", mLockTile.getState().secondaryLabel.toString());
+    }
+
+    @Test
+    public void testSecondaryString_noCameraPermission_isEmpty() {
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+                Manifest.permission.CAMERA, PACKAGE_NAME);
+
+        mLockTile.refreshState();
+        mTestableLooper.processAllMessages();
+
+        assertEquals("", mLockTile.getState().secondaryLabel.toString());
+    }
+
+    private void enableAutoRotation() {
+        when(mRotationPolicyWrapper.isRotationLocked()).thenReturn(false);
+    }
+
+    private void disableAutoRotation() {
+        when(mRotationPolicyWrapper.isRotationLocked()).thenReturn(true);
+    }
+
+    private void enableCameraBasedRotation() {
+        when(mRotationPolicyWrapper.isCameraRotationEnabled()).thenReturn(true);
+    }
+
+    private void disableCameraBasedRotation() {
+        when(mRotationPolicyWrapper.isCameraRotationEnabled()).thenReturn(false);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
index c5dc2b4..3ddff49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
@@ -120,7 +120,7 @@
     private NotifFilter captureFilter(MediaCoordinator coordinator) {
         ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
         coordinator.attach(mNotifPipeline);
-        verify(mNotifPipeline).addFinalizeFilter(filterCaptor.capture());
+        verify(mNotifPipeline).addPreGroupFilter(filterCaptor.capture());
         return filterCaptor.getValue();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 3c84c01..d3c1dc9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -32,7 +32,6 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -44,7 +43,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.HashSet;
@@ -59,8 +57,6 @@
     private Runnable mRoundnessCallback = mock(Runnable.class);
     private ExpandableNotificationRow mFirst;
     private ExpandableNotificationRow mSecond;
-    @Mock
-    private FeatureFlags mFeatureFlags;
     private float mSmallRadiusRatio;
 
     @Before
@@ -70,8 +66,7 @@
         mSmallRadiusRatio = resources.getDimension(R.dimen.notification_corner_radius_small)
                 / resources.getDimension(R.dimen.notification_corner_radius);
         mRoundnessManager = new NotificationRoundnessManager(
-                new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext),
-                mFeatureFlags);
+                new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
         allowTestableLooperAsMainThread();
         NotificationTestHelper testHelper = new NotificationTestHelper(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 1182695..1827c7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -62,6 +62,7 @@
     private float mPanelExpansion;
     private int mKeyguardStatusBarHeaderHeight;
     private int mKeyguardStatusHeight;
+    private int mUserSwitchHeight;
     private float mDark;
     private float mQsExpansion;
     private int mCutoutTopInset = 0;
@@ -264,8 +265,7 @@
 
     @Test
     public void clockPositionedDependingOnMarginInSplitShade() {
-        when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
-                .thenReturn(400);
+        setSplitShadeTopMargin(400);
         mClockPositionAlgorithm.loadDimens(mResources);
         givenLockScreen();
         mIsSplitShade = true;
@@ -291,6 +291,32 @@
     }
 
     @Test
+    public void notifPaddingAccountsForMultiUserSwitcherInSplitShade() {
+        setSplitShadeTopMargin(100);
+        mUserSwitchHeight = 150;
+        mClockPositionAlgorithm.loadDimens(mResources);
+        givenLockScreen();
+        mIsSplitShade = true;
+        // WHEN the position algorithm is run
+        positionClock();
+        // THEN the notif padding is split shade top margin + user switch height
+        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(250);
+    }
+
+    @Test
+    public void clockDoesntAccountForMultiUserSwitcherInSplitShade() {
+        setSplitShadeTopMargin(100);
+        mUserSwitchHeight = 150;
+        mClockPositionAlgorithm.loadDimens(mResources);
+        givenLockScreen();
+        mIsSplitShade = true;
+        // WHEN the position algorithm is run
+        positionClock();
+        // THEN clockY = split shade top margin
+        assertThat(mClockPosition.clockY).isEqualTo(100);
+    }
+
+    @Test
     public void notifPaddingExpandedAlignedWithClockInSplitShadeMode() {
         givenLockScreen();
         mIsSplitShade = true;
@@ -495,6 +521,11 @@
         assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
     }
 
+    private void setSplitShadeTopMargin(int value) {
+        when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
+                .thenReturn(value);
+    }
+
     private void givenHighestBurnInOffset() {
         when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).then(returnsFirstArg());
     }
@@ -529,7 +560,7 @@
                 mKeyguardStatusBarHeaderHeight,
                 mPanelExpansion,
                 mKeyguardStatusHeight,
-                0 /* userSwitchHeight */,
+                mUserSwitchHeight,
                 0 /* userSwitchPreferredY */,
                 mDark,
                 ZERO_DRAG,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index 30717f4..db7b2f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -225,6 +225,11 @@
         }
 
         @Override
+        public boolean isCameraRotationEnabled() {
+            throw new AssertionError("Not implemented");
+        }
+
+        @Override
         public void registerRotationPolicyListener(RotationPolicy.RotationPolicyListener listener,
                 int userHandle) {
             throw new AssertionError("Not implemented");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
index be11024..4f9cb35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -51,6 +51,11 @@
     }
 
     @Override
+    public boolean isCameraRotationEnabled() {
+        return false;
+    }
+
+    @Override
     public void setRotationLockedAtAngle(boolean locked, int rotation) {
 
     }
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 637994f..1914164 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -22,7 +22,6 @@
 import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME;
 import static android.content.ComponentName.createRelative;
 
-import static com.android.internal.util.CollectionUtils.filter;
 import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
 import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
 import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
@@ -57,6 +56,7 @@
 
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -124,14 +124,17 @@
     private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5;
     private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;
 
-    private final Context mContext;
-    private final CompanionDeviceManagerService mService;
-    private final PackageManagerInternal mPackageManager;
+    private final @NonNull Context mContext;
+    private final @NonNull CompanionDeviceManagerService mService;
+    private final @NonNull PackageManagerInternal mPackageManager;
+    private final @NonNull AssociationStore mAssociationStore;
 
-    AssociationRequestsProcessor(CompanionDeviceManagerService service) {
+    AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
+            @NonNull AssociationStore associationStore) {
         mContext = service.getContext();
         mService = service;
         mPackageManager = service.mPackageManagerInternal;
+        mAssociationStore = associationStore;
     }
 
     /**
@@ -330,18 +333,24 @@
         }
 
         // Throttle frequent associations
-        long now = System.currentTimeMillis();
-        Set<AssociationInfo> recentAssociations = filter(
-                mService.getAssociations(userId, packageName),
-                a -> now - a.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS);
-
-        if (recentAssociations.size() >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) {
-            Slog.w(TAG, "Too many associations. " + packageName
-                    + " already associated " + recentAssociations.size()
-                    + " devices within the last " + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS
-                    + "ms: " + recentAssociations);
-            return false;
+        final long now = System.currentTimeMillis();
+        final List<AssociationInfo> associationForPackage =
+                mAssociationStore.getAssociationsForPackage(userId, packageName);
+        // Number of "recent" associations.
+        int recent = 0;
+        for (AssociationInfo association : associationForPackage) {
+            final boolean isRecent =
+                    now - association.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS;
+            if (isRecent) {
+                if (++recent >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) {
+                    Slog.w(TAG, "Too many associations: " + packageName + " already "
+                            + "associated " + recent + " devices within the last "
+                            + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS + "ms");
+                    return false;
+                }
+            }
         }
+
         String[] sameOemCerts = mContext.getResources()
                 .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
 
diff --git a/services/companion/java/com/android/server/companion/AssociationStore.java b/services/companion/java/com/android/server/companion/AssociationStore.java
new file mode 100644
index 0000000..58fc8f7
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/AssociationStore.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Interface for a store of {@link AssociationInfo}-s.
+ */
+public interface AssociationStore {
+
+    @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
+            CHANGE_TYPE_ADDED,
+            CHANGE_TYPE_REMOVED,
+            CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
+            CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ChangeType {}
+
+    int CHANGE_TYPE_ADDED = 0;
+    int CHANGE_TYPE_REMOVED = 1;
+    int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
+    int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
+
+    /**  Listener for any changes to {@link AssociationInfo}-s. */
+    interface OnChangeListener {
+        default void onAssociationChanged(
+                @ChangeType int changeType, AssociationInfo association) {}
+
+        default void onAssociationAdded(AssociationInfo association) {}
+
+        default void onAssociationRemoved(AssociationInfo association) {}
+
+        default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
+    }
+
+    /**
+     * @return all CDM associations.
+     */
+    @NonNull
+    Collection<AssociationInfo> getAssociations();
+
+    /**
+     * @return a {@link List} of associations that belong to the user.
+     */
+    @NonNull
+    List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId);
+
+    /**
+     * @return a {@link List} of association that belong to the package.
+     */
+    @NonNull
+    List<AssociationInfo> getAssociationsForPackage(
+            @UserIdInt int userId, @NonNull String packageName);
+
+    /**
+     * @return an association with the given address that belong to the given package if such an
+     * association exists, otherwise {@code null}.
+     */
+    @Nullable
+    AssociationInfo getAssociationsForPackageWithAddress(
+            @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress);
+
+    /**
+     * @return an association with the given id if such an association exists, otherwise
+     * {@code null}.
+     */
+    @Nullable
+    AssociationInfo getAssociationById(int id);
+
+    /**
+     * @return all associations with the given MAc address.
+     */
+    @NonNull
+    List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress);
+
+    /** Register a {@link OnChangeListener} */
+    void registerListener(@NonNull OnChangeListener listener);
+
+    /** Un-register a previously registered {@link OnChangeListener} */
+    void unregisterListener(@NonNull OnChangeListener listener);
+
+    /** @hide */
+    static String changeTypeToString(@ChangeType int changeType) {
+        switch (changeType) {
+            case CHANGE_TYPE_ADDED:
+                return "ASSOCIATION_ADDED";
+
+            case CHANGE_TYPE_REMOVED:
+                return "ASSOCIATION_REMOVED";
+
+            case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
+                return "ASSOCIATION_UPDATED";
+
+            case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
+                return "ASSOCIATION_UPDATED_ADDRESS_UNCHANGED";
+
+            default:
+                return "Unknown (" + changeType + ")";
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
new file mode 100644
index 0000000..3f0200e
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
+import android.net.MacAddress;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Implementation of the {@link AssociationStore}, with addition of the methods for modification.
+ * <ul>
+ * <li> {@link #addAssociation(AssociationInfo)}
+ * <li> {@link #removeAssociation(int)}
+ * <li> {@link #updateAssociation(AssociationInfo)}
+ * </ul>
+ *
+ * The class has package-private access level, and instances of the class should only be created by
+ * the {@link CompanionDeviceManagerService}.
+ * Other system component (both inside and outside if the com.android.server.companion package)
+ * should use public {@link AssociationStore} interface.
+ */
+class AssociationStoreImpl implements AssociationStore {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "AssociationStore";
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final Map<Integer, AssociationInfo> mIdMap;
+    @GuardedBy("mLock")
+    private final Map<MacAddress, Set<Integer>> mAddressMap;
+    @GuardedBy("mLock")
+    private final SparseArray<List<AssociationInfo>> mCachedPerUser = new SparseArray<>();
+
+    @GuardedBy("mListeners")
+    private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();
+
+    AssociationStoreImpl(Collection<AssociationInfo> associations) {
+        synchronized (mLock) {
+            final int size = associations.size();
+            mIdMap = new HashMap<>(size);
+            mAddressMap = new HashMap<>(size);
+
+            for (AssociationInfo association : associations) {
+                final int id = association.getId();
+                mIdMap.put(id, association);
+
+                final MacAddress address = association.getDeviceMacAddress();
+                if (address != null) {
+                    mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
+                }
+            }
+        }
+    }
+
+    void addAssociation(@NonNull AssociationInfo association) {
+        final int id = association.getId();
+
+        if (DEBUG) {
+            Log.i(TAG, "addAssociation() " + association.toShortString());
+            Log.d(TAG, "  association=" + association);
+        }
+
+        synchronized (mLock) {
+            if (mIdMap.containsKey(id)) {
+                if (DEBUG) Log.w(TAG, "Association already stored.");
+                return;
+            }
+            mIdMap.put(id, association);
+
+            final MacAddress address = association.getDeviceMacAddress();
+            if (address != null) {
+                mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
+            }
+
+            invalidateCacheForUserLocked(association.getUserId());
+        }
+
+        broadcastChange(CHANGE_TYPE_ADDED, association);
+    }
+
+    void updateAssociation(@NonNull AssociationInfo updated) {
+        final int id = updated.getId();
+
+        if (DEBUG) {
+            Log.i(TAG, "updateAssociation() " + updated.toShortString());
+            Log.d(TAG, "  updated=" + updated);
+        }
+
+        final AssociationInfo current;
+        final boolean macAddressChanged;
+        synchronized (mLock) {
+            current = mIdMap.get(id);
+            if (current == null) {
+                if (DEBUG) Log.w(TAG, "Association with id " + id + " does not exist.");
+                return;
+            }
+            if (DEBUG) Log.d(TAG, "  current=" + current);
+
+            if (current.equals(updated)) {
+                if (DEBUG) Log.w(TAG, "  No changes.");
+                return;
+            }
+
+            // Update the ID-to-Association map.
+            mIdMap.put(id, updated);
+
+            // Update the MacAddress-to-List<Association> map if needed.
+            final MacAddress updatedAddress = updated.getDeviceMacAddress();
+            final MacAddress currentAddress = current.getDeviceMacAddress();
+            macAddressChanged = Objects.equals(
+                    current.getDeviceMacAddress(), updated.getDeviceMacAddress());
+            if (macAddressChanged) {
+                if (currentAddress != null) {
+                    mAddressMap.get(currentAddress).remove(id);
+                }
+                if (updatedAddress != null) {
+                    mAddressMap.computeIfAbsent(updatedAddress, it -> new HashSet<>()).add(id);
+                }
+            }
+        }
+
+        final int changeType = macAddressChanged ? CHANGE_TYPE_UPDATED_ADDRESS_CHANGED
+                : CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
+        broadcastChange(changeType, updated);
+    }
+
+    void removeAssociation(int id) {
+        if (DEBUG) Log.i(TAG, "removeAssociation() id=" + id);
+
+        final AssociationInfo association;
+        synchronized (mLock) {
+            association = mIdMap.remove(id);
+
+            if (association == null) {
+                if (DEBUG) Log.w(TAG, "Association with id " + id + " is not stored.");
+                return;
+            } else {
+                if (DEBUG) {
+                    Log.i(TAG, "removed " + association.toShortString());
+                    Log.d(TAG, "  association=" + association);
+                }
+            }
+
+            final MacAddress macAddress = association.getDeviceMacAddress();
+            if (macAddress != null) {
+                mAddressMap.get(macAddress).remove(id);
+            }
+
+            invalidateCacheForUserLocked(association.getUserId());
+        }
+
+        broadcastChange(CHANGE_TYPE_REMOVED, association);
+    }
+
+    public @NonNull Collection<AssociationInfo> getAssociations() {
+        final Collection<AssociationInfo> allAssociations;
+        synchronized (mLock) {
+            allAssociations = mIdMap.values();
+        }
+        return Collections.unmodifiableCollection(allAssociations);
+    }
+
+    public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) {
+        synchronized (mLock) {
+            return getAssociationsForUserLocked(userId);
+        }
+    }
+
+    public @NonNull List<AssociationInfo> getAssociationsForPackage(
+            @UserIdInt int userId, @NonNull String packageName) {
+        final List<AssociationInfo> associationsForUser = getAssociationsForUser(userId);
+        final List<AssociationInfo> associationsForPackage =
+                CollectionUtils.filter(associationsForUser,
+                        it -> it.getPackageName().equals(packageName));
+        return Collections.unmodifiableList(associationsForPackage);
+    }
+
+    public @Nullable AssociationInfo getAssociationsForPackageWithAddress(
+            @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
+        final List<AssociationInfo> associations = getAssociationsByAddress(macAddress);
+        return CollectionUtils.find(associations,
+                it -> it.belongsToPackage(userId, packageName));
+    }
+
+    public @Nullable AssociationInfo getAssociationById(int id) {
+        synchronized (mLock) {
+            return mIdMap.get(id);
+        }
+    }
+
+    public @NonNull List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
+        final MacAddress address = MacAddress.fromString(macAddress);
+
+        synchronized (mLock) {
+            final Set<Integer> ids = mAddressMap.get(address);
+            if (ids == null) return Collections.emptyList();
+
+            final List<AssociationInfo> associations = new ArrayList<>();
+            for (AssociationInfo association : mIdMap.values()) {
+                if (address.equals(association.getDeviceMacAddress())) {
+                    associations.add(association);
+                }
+            }
+
+            return Collections.unmodifiableList(associations);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private @NonNull List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
+        final List<AssociationInfo> cached = mCachedPerUser.get(userId);
+        if (cached != null) {
+            return cached;
+        }
+
+        final List<AssociationInfo> associationsForUser = new ArrayList<>();
+        for (AssociationInfo association : mIdMap.values()) {
+            if (association.getUserId() == userId) {
+                associationsForUser.add(association);
+            }
+        }
+        final List<AssociationInfo> set = Collections.unmodifiableList(associationsForUser);
+        mCachedPerUser.set(userId, set);
+        return set;
+    }
+
+    @GuardedBy("mLock")
+    private void invalidateCacheForUserLocked(@UserIdInt int userId) {
+        mCachedPerUser.delete(userId);
+    }
+
+    public void registerListener(@NonNull OnChangeListener listener) {
+        synchronized (mListeners) {
+            mListeners.add(listener);
+        }
+    }
+
+    public void unregisterListener(@NonNull OnChangeListener listener) {
+        synchronized (mListeners) {
+            mListeners.remove(listener);
+        }
+    }
+
+    private void broadcastChange(@ChangeType int changeType, AssociationInfo association) {
+        synchronized (mListeners) {
+            for (OnChangeListener listener : mListeners) {
+                listener.onAssociationChanged(changeType, association);
+
+                switch (changeType) {
+                    case CHANGE_TYPE_ADDED:
+                        listener.onAssociationAdded(association);
+                        break;
+
+                    case CHANGE_TYPE_REMOVED:
+                        listener.onAssociationRemoved(association);
+                        break;
+
+                    case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
+                        listener.onAssociationUpdated(association, true);
+                        break;
+
+                    case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
+                        listener.onAssociationUpdated(association, false);
+                        break;
+                }
+            }
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a06672b..5aa1c93 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -27,15 +27,12 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.UserHandle.getCallingUserId;
 
-import static com.android.internal.util.CollectionUtils.add;
 import static com.android.internal.util.CollectionUtils.any;
-import static com.android.internal.util.CollectionUtils.filter;
 import static com.android.internal.util.CollectionUtils.find;
-import static com.android.internal.util.CollectionUtils.forEach;
-import static com.android.internal.util.CollectionUtils.map;
 import static com.android.internal.util.Preconditions.checkState;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
+import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
 import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
 import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
 import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId;
@@ -45,8 +42,6 @@
 import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
 import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
 
-import static java.util.Collections.emptySet;
-import static java.util.Collections.unmodifiableSet;
 import static java.util.Objects.requireNonNull;
 import static java.util.concurrent.TimeUnit.MINUTES;
 
@@ -82,7 +77,6 @@
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.UserInfo;
 import android.net.MacAddress;
 import android.net.NetworkPolicyManager;
 import android.os.Binder;
@@ -112,9 +106,7 @@
 import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
-import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -127,6 +119,7 @@
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -135,12 +128,11 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.TimeZone;
-import java.util.function.Function;
-import java.util.function.Predicate;
 
 /** @hide */
 @SuppressLint("LongLogTag")
-public class CompanionDeviceManagerService extends SystemService {
+public class CompanionDeviceManagerService extends SystemService
+        implements AssociationStore.OnChangeListener {
     static final String LOG_TAG = "CompanionDeviceManagerService";
     static final boolean DEBUG = false;
 
@@ -162,10 +154,11 @@
         sDateFormat.setTimeZone(TimeZone.getDefault());
     }
 
-    private final CompanionDeviceManagerImpl mImpl;
     // Persistent data store for all Associations.
-    private final PersistentDataStore mPersistentDataStore;
-    private final AssociationRequestsProcessor mAssociationRequestsProcessor;
+    private PersistentDataStore mPersistentStore;
+    private AssociationStoreImpl mAssociationStore;
+    private AssociationRequestsProcessor mAssociationRequestsProcessor;
+
     private PowerWhitelistManager mPowerWhitelistManager;
     private IAppOpsService mAppOpsManager;
     private BluetoothAdapter mBluetoothAdapter;
@@ -187,20 +180,16 @@
     private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
             new RemoteCallbackList<>();
 
-    final Object mLock = new Object();
     final Handler mMainHandler = Handler.getMain();
     private CompanionDevicePresenceController mCompanionDevicePresenceController;
 
-    /** Maps a {@link UserIdInt} to a set of associations for the user. */
-    @GuardedBy("mLock")
-    private final SparseArray<Set<AssociationInfo>> mCachedAssociations = new SparseArray<>();
     /**
      * A structure that consist of two nested maps, and effectively maps (userId + packageName) to
      * a list of IDs that have been previously assigned to associations for that package.
      * We maintain this structure so that we never re-use association IDs for the same package
      * (until it's uninstalled).
      */
-    @GuardedBy("mLock")
+    @GuardedBy("mPreviouslyUsedIds")
     private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>();
 
     ActivityTaskManagerInternal mAtmInternal;
@@ -209,8 +198,6 @@
 
     public CompanionDeviceManagerService(Context context) {
         super(context);
-        mImpl = new CompanionDeviceManagerImpl();
-        mPersistentDataStore = new PersistentDataStore();
 
         mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
         mAppOpsManager = IAppOpsService.Stub.asInterface(
@@ -221,49 +208,36 @@
         mPermissionControllerManager = requireNonNull(
                 context.getSystemService(PermissionControllerManager.class));
         mUserManager = context.getSystemService(UserManager.class);
-        mCompanionDevicePresenceController = new CompanionDevicePresenceController(this);
-        mAssociationRequestsProcessor = new AssociationRequestsProcessor(this);
-
-        registerPackageMonitor();
-    }
-
-    private void registerPackageMonitor() {
-        new PackageMonitor() {
-            @Override
-            public void onPackageRemoved(String packageName, int uid) {
-                final int userId = getChangingUserId();
-                Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName);
-
-                clearAssociationForPackage(userId, packageName);
-            }
-
-            @Override
-            public void onPackageDataCleared(String packageName, int uid) {
-                final int userId = getChangingUserId();
-                Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName);
-
-                clearAssociationForPackage(userId, packageName);
-            }
-
-            @Override
-            public void onPackageModified(String packageName) {
-                final int userId = getChangingUserId();
-                Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName);
-
-                forEach(getAssociations(userId, packageName), association ->
-                        updateSpecialAccessPermissionForAssociatedPackage(association));
-            }
-        }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
     }
 
     @Override
     public void onStart() {
-        publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
+        mPersistentStore = new PersistentDataStore();
+        final Set<AssociationInfo> allAssociations = new ArraySet<>();
+
+        synchronized (mPreviouslyUsedIds) {
+            // The data is stored in DE directories, so we can read the data for all users now
+            // (which would not be possible if the data was stored to CE directories).
+            mPersistentStore.readStateForUsers(
+                    mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds);
+        }
+
+        mAssociationStore = new AssociationStoreImpl(allAssociations);
+        mAssociationStore.registerListener(this);
+
+        mCompanionDevicePresenceController = new CompanionDevicePresenceController(this);
+        mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mAssociationStore);
+
+        // Publish "binder service"
+        final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl();
+        publishBinderService(Context.COMPANION_DEVICE_SERVICE, impl);
     }
 
     @Override
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            registerPackageMonitor();
+
             // Init Bluetooth
             mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
             if (mBluetoothAdapter != null) {
@@ -282,7 +256,7 @@
     @Override
     public void onUserUnlocking(@NonNull TargetUser user) {
         final int userId = user.getUserIdentifier();
-        final Set<AssociationInfo> associations = getAllAssociationsForUser(userId);
+        final List<AssociationInfo> associations = mAssociationStore.getAssociationsForUser(userId);
 
         if (associations.isEmpty()) return;
 
@@ -293,42 +267,18 @@
                 MINUTES.toMillis(10));
     }
 
-    @NonNull
-    Set<AssociationInfo> getAllAssociationsForUser(@UserIdInt int userId) {
-        synchronized (mLock) {
-            readPersistedStateForUserIfNeededLocked(userId);
-            // This returns non-null, because the readAssociationsInfoForUserIfNeededLocked() method
-            // we just called adds an empty set, if there was no previously saved data.
-            return mCachedAssociations.get(userId);
-        }
-    }
-
-    @NonNull
-    Set<AssociationInfo> getAssociations(@UserIdInt int userId, @NonNull String packageName) {
-        return filter(getAllAssociationsForUser(userId),
-                a -> a.belongsToPackage(userId, packageName));
-    }
-
-    @Nullable
-    private AssociationInfo getAssociation(int associationId) {
-        return find(getAllAssociations(), association -> association.getId() == associationId);
-    }
-
-    @Nullable
-    AssociationInfo getAssociation(
-            @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
-        return find(getAssociations(userId, packageName), a -> a.isLinkedTo(macAddress));
-    }
-
     @Nullable
     AssociationInfo getAssociationWithCallerChecks(
             @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
-        return sanitizeWithCallerChecks(getAssociation(userId, packageName, macAddress));
+        final AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
+                userId, packageName, macAddress);
+        return sanitizeWithCallerChecks(association);
     }
 
     @Nullable
     AssociationInfo getAssociationWithCallerChecks(int associationId) {
-        return sanitizeWithCallerChecks(getAssociation(associationId));
+        final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+        return sanitizeWithCallerChecks(association);
     }
 
     @Nullable
@@ -344,19 +294,6 @@
         return association;
     }
 
-    private Set<AssociationInfo> getAllAssociations() {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final Set<AssociationInfo> result = new ArraySet<>();
-            for (UserInfo user : mUserManager.getAliveUsers()) {
-                result.addAll(getAllAssociationsForUser(user.id));
-            }
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
     void maybeGrantAutoRevokeExemptions() {
         Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()");
         PackageManager pm = getContext().getPackageManager();
@@ -369,10 +306,8 @@
             }
 
             try {
-                Set<AssociationInfo> associations = getAllAssociationsForUser(userId);
-                if (associations == null) {
-                    continue;
-                }
+                final List<AssociationInfo> associations =
+                        mAssociationStore.getAssociationsForUser(userId);
                 for (AssociationInfo a : associations) {
                     try {
                         int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
@@ -387,6 +322,61 @@
         }
     }
 
+    @Override
+    public void onAssociationChanged(
+            @AssociationStore.ChangeType int changeType, AssociationInfo association) {
+        final int id = association.getId();
+        final int userId = association.getUserId();
+        final String packageName = association.getPackageName();
+
+        if (changeType == AssociationStore.CHANGE_TYPE_REMOVED) {
+            markIdAsPreviouslyUsedForPackage(id, userId, packageName);
+        }
+
+        final List<AssociationInfo> updatedAssociations =
+                mAssociationStore.getAssociationsForUser(userId);
+        final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
+        BackgroundThread.getHandler().post(() ->
+                mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser));
+
+        // Notify listeners if ADDED, REMOVED or UPDATED_ADDRESS_CHANGED.
+        // Do NOT notify when UPDATED_ADDRESS_UNCHANGED, which means a minor tweak in association's
+        // configs, which "listeners" won't (and shouldn't) be able to see.
+        if (changeType != CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED) {
+            notifyListeners(userId, updatedAssociations);
+        }
+        updateAtm(userId, updatedAssociations);
+
+        restartBleScan();
+    }
+
+    private void notifyListeners(
+            @UserIdInt int userId, @NonNull List<AssociationInfo> associations) {
+        mListeners.broadcast((listener, callbackUserId) -> {
+            if ((int) callbackUserId == userId) {
+                try {
+                    listener.onAssociationsChanged(associations);
+                } catch (RemoteException ignored) {
+                }
+            }
+        });
+    }
+
+    private void markIdAsPreviouslyUsedForPackage(
+            int associationId, @UserIdInt int userId, @NonNull String packageName) {
+        synchronized (mPreviouslyUsedIds) {
+            Map<String, Set<Integer>> usedIdsForUser = mPreviouslyUsedIds.get(userId);
+            if (usedIdsForUser == null) {
+                usedIdsForUser = new HashMap<>();
+                mPreviouslyUsedIds.put(userId, usedIdsForUser);
+            }
+
+            final Set<Integer> usedIdsForPackage =
+                    usedIdsForUser.computeIfAbsent(packageName, it -> new HashSet<>());
+            usedIdsForPackage.add(associationId);
+        }
+    }
+
     class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
 
         @Override
@@ -424,8 +414,7 @@
                 checkUsesFeature(packageName, getCallingUserId());
             }
 
-            return new ArrayList<>(
-                    CompanionDeviceManagerService.this.getAssociations(userId, packageName));
+            return mAssociationStore.getAssociationsForPackage(userId, packageName);
         }
 
         @Override
@@ -433,8 +422,7 @@
             enforceCallerCanInteractWithUserId(getContext(), userId);
             enforceCallerCanManageCompanionDevice(getContext(), "getAllAssociationsForUser");
 
-            return new ArrayList<>(
-                    CompanionDeviceManagerService.this.getAllAssociationsForUser(userId));
+            return mAssociationStore.getAssociationsForUser(userId);
         }
 
         @Override
@@ -470,7 +458,7 @@
                         + "(ie. it belongs to a different package or a different user).");
             }
 
-            disassociateInternal(userId, association.getId());
+            disassociateInternal(association.getId());
         }
 
         @Override
@@ -483,7 +471,7 @@
                         + "or belongs to a different user");
             }
 
-            disassociateInternal(association.getUserId(), associationId);
+            disassociateInternal(associationId);
         }
 
         @Override
@@ -540,7 +528,7 @@
                 return true;
             }
 
-            return any(CompanionDeviceManagerService.this.getAssociations(userId, packageName),
+            return any(mAssociationStore.getAssociationsForPackage(userId, packageName),
                     a -> a.isLinkedTo(macAddress));
         }
 
@@ -623,25 +611,18 @@
             final int userId = getCallingUserId();
             enforceCallerIsSystemOr(userId, packageName);
 
-            Set<AssociationInfo> deviceAssociations = filter(
-                    CompanionDeviceManagerService.this.getAssociations(userId, packageName),
-                    a -> a.isLinkedTo(deviceAddress));
+            final AssociationInfo association =
+                    mAssociationStore.getAssociationsForPackageWithAddress(
+                            userId, packageName, deviceAddress);
 
-            if (deviceAssociations.isEmpty()) {
+            if (association == null) {
                 throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
                         + " is not associated with device " + deviceAddress
                         + " for user " + userId));
             }
 
-            updateAssociations(associations -> map(associations, association -> {
-                if (association.belongsToPackage(userId, packageName)
-                        && association.isLinkedTo(deviceAddress)) {
-                    association.setNotifyOnDeviceNearby(active);
-                }
-                return association;
-            }), userId);
-
-            restartBleScan();
+            association.setNotifyOnDeviceNearby(active);
+            mAssociationStore.updateAssociation(association);
         }
 
         @Override
@@ -664,14 +645,16 @@
             enforceCallerIsSystemOr(userId, callingPackage);
 
             checkState(!ArrayUtils.isEmpty(
-                    CompanionDeviceManagerService.this.getAssociations(userId, callingPackage)),
+                    mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
                     "App must have an association before calling this API");
             checkUsesFeature(callingPackage, userId);
         }
 
         @Override
         public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) {
-            final AssociationInfo association = getAssociation(userId, packageName, macAddress);
+            final AssociationInfo association =
+                    mAssociationStore.getAssociationsForPackageWithAddress(
+                            userId, packageName, macAddress);
             if (association == null) {
                 return false;
             }
@@ -684,7 +667,8 @@
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                 throws RemoteException {
             enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand");
-            new CompanionDeviceShellCommand(CompanionDeviceManagerService.this)
+            new CompanionDeviceShellCommand(
+                    CompanionDeviceManagerService.this, mAssociationStore)
                     .exec(this, in, out, err, args, callback, resultReceiver);
         }
 
@@ -697,13 +681,8 @@
             }
 
             fout.append("Companion Device Associations:").append('\n');
-            synchronized (mLock) {
-                for (UserInfo user : getAllUsers()) {
-                    forEach(mCachedAssociations.get(user.id), a -> {
-                        fout.append("  ").append(a.toString()).append('\n');
-                    });
-                }
-
+            for (AssociationInfo a : mAssociationStore.getAssociations()) {
+                fout.append("  ").append(a.toString()).append('\n');
             }
 
             fout.append("Currently Connected Devices:").append('\n');
@@ -754,31 +733,56 @@
             @Nullable String deviceProfile, boolean selfManaged) {
         final int id = getNewAssociationIdForPackage(userId, packageName);
         final long timestamp = System.currentTimeMillis();
+
         final AssociationInfo association = new AssociationInfo(id, userId, packageName,
                 macAddress, displayName, deviceProfile, selfManaged, false, timestamp);
+        Slog.i(LOG_TAG, "New CDM association created=" + association);
+        mAssociationStore.addAssociation(association);
 
         updateSpecialAccessPermissionForAssociatedPackage(association);
-        recordAssociation(association, userId);
 
         return association;
     }
 
-    @GuardedBy("mLock")
+    @NonNull
+    private Map<String, Set<Integer>> getPreviouslyUsedIdsForUser(@UserIdInt int userId) {
+        synchronized (mPreviouslyUsedIds) {
+            return getPreviouslyUsedIdsForUserLocked(userId);
+        }
+    }
+
+    @GuardedBy("mPreviouslyUsedIds")
+    @NonNull
+    private Map<String, Set<Integer>> getPreviouslyUsedIdsForUserLocked(@UserIdInt int userId) {
+        final Map<String, Set<Integer>> usedIdsForUser = mPreviouslyUsedIds.get(userId);
+        if (usedIdsForUser == null) {
+            return Collections.emptyMap();
+        }
+        return deepUnmodifiableCopy(usedIdsForUser);
+    }
+
+    @GuardedBy("mPreviouslyUsedIds")
     @NonNull
     private Set<Integer> getPreviouslyUsedIdsForPackageLocked(
             @UserIdInt int userId, @NonNull String packageName) {
-        final Set<Integer> previouslyUsedIds = mPreviouslyUsedIds.get(userId).get(packageName);
-        if (previouslyUsedIds != null) return previouslyUsedIds;
-        return emptySet();
+        // "Deeply unmodifiable" map: the map itself and the Set<Integer> values it contains are all
+        // unmodifiable.
+        final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUserLocked(userId);
+        final Set<Integer> usedIdsForPackage = usedIdsForUser.get(packageName);
+
+        if (usedIdsForPackage == null) {
+            return Collections.emptySet();
+        }
+
+        //The set is already unmodifiable.
+        return usedIdsForPackage;
     }
 
     private int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
-        synchronized (mLock) {
-            readPersistedStateForUserIfNeededLocked(userId);
-
+        synchronized (mPreviouslyUsedIds) {
             // First: collect all IDs currently in use for this user's Associations.
             final SparseBooleanArray usedIds = new SparseBooleanArray();
-            for (AssociationInfo it : getAllAssociationsForUser(userId)) {
+            for (AssociationInfo it : mAssociationStore.getAssociationsForUser(userId)) {
                 usedIds.put(it.getId(), true);
             }
 
@@ -804,41 +808,14 @@
         }
     }
 
-    //TODO also revoke notification access
-    void disassociateInternal(@UserIdInt int userId, int associationId) {
-        updateAssociations(associations ->
-                filterOut(associations, it -> {
-                    if (it.getId() != associationId) return false;
-
-                    onAssociationPreRemove(it);
-                    markIdAsPreviouslyUsedForPackage(
-                            it.getId(), it.getUserId(), it.getPackageName());
-                    return true;
-                }), userId);
-
-        restartBleScan();
+    //TODO: also revoke notification access
+    void disassociateInternal(int associationId) {
+        onAssociationPreRemove(associationId);
+        mAssociationStore.removeAssociation(associationId);
     }
 
-    void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) {
-        if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName);
-
-        mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId);
-        updateAssociations(set -> filterOut(set, it -> it.belongsToPackage(userId, packageName)),
-                userId);
-    }
-
-    private void markIdAsPreviouslyUsedForPackage(
-            int associationId, @UserIdInt int userId, @NonNull String packageName) {
-        synchronized (mLock) {
-            // Mark as previously used.
-            readPersistedStateForUserIfNeededLocked(userId);
-            mPreviouslyUsedIds.get(userId)
-                    .computeIfAbsent(packageName, it -> new HashSet<>())
-                    .add(associationId);
-        }
-    }
-
-    void onAssociationPreRemove(AssociationInfo association) {
+    void onAssociationPreRemove(int associationId) {
+        final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
         if (association.isNotifyOnDeviceNearby()
                 || (association.isSelfManaged()
                 && mPresentSelfManagedDevices.contains(association.getId()))) {
@@ -849,7 +826,7 @@
         String deviceProfile = association.getDeviceProfile();
         if (deviceProfile != null) {
             AssociationInfo otherAssociationWithDeviceProfile = find(
-                    getAllAssociationsForUser(association.getUserId()),
+                    mAssociationStore.getAssociationsForUser(association.getUserId()),
                     a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile()));
             if (otherAssociationWithDeviceProfile != null) {
                 Slog.i(LOG_TAG, "Not revoking " + deviceProfile
@@ -941,49 +918,7 @@
                         .getPackageInfoAsUser(packageName, flags , userId));
     }
 
-    private void recordAssociation(AssociationInfo association, int userId) {
-        Slog.i(LOG_TAG, "recordAssociation(" + association + ")");
-        updateAssociations(associations -> add(associations, association), userId);
-    }
-
-    private void updateAssociations(Function<Set<AssociationInfo>, Set<AssociationInfo>> update,
-            int userId) {
-        final List<AssociationInfo> associationList;
-        synchronized (mLock) {
-            if (DEBUG) Slog.d(LOG_TAG, "Updating Associations set...");
-
-            final Set<AssociationInfo> prevAssociations = getAllAssociationsForUser(userId);
-            if (DEBUG) Slog.d(LOG_TAG, "  > Before : " + prevAssociations + "...");
-
-            final Set<AssociationInfo> updatedAssociations = update.apply(
-                    new ArraySet<>(prevAssociations));
-            if (DEBUG) Slog.d(LOG_TAG, "  > After: " + updatedAssociations);
-
-            associationList = new ArrayList<>(updatedAssociations);
-
-            mCachedAssociations.put(userId, unmodifiableSet(updatedAssociations));
-
-            BackgroundThread.getHandler().sendMessage(
-                    PooledLambda.obtainMessage(
-                            (associations, usedIds) ->
-                                    mPersistentDataStore
-                                            .persistStateForUser(userId, associations, usedIds),
-                            updatedAssociations, deepCopy(mPreviouslyUsedIds.get(userId))));
-
-            updateAtm(userId, updatedAssociations);
-        }
-
-        mListeners.broadcast((listener, callbackUserId) -> {
-            if ((int) callbackUserId == userId) {
-                try {
-                    listener.onAssociationsChanged(associationList);
-                } catch (RemoteException ignored) {
-                }
-            }
-        });
-    }
-
-    private void updateAtm(int userId, Set<AssociationInfo> associations) {
+    private void updateAtm(int userId, List<AssociationInfo> associations) {
         final Set<Integer> companionAppUids = new ArraySet<>();
         for (AssociationInfo association : associations) {
             final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(),
@@ -998,50 +933,18 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void readPersistedStateForUserIfNeededLocked(@UserIdInt int userId) {
-        if (mCachedAssociations.get(userId) != null) return;
-
-        Slog.i(LOG_TAG, "Reading state for user " + userId + "  from the disk");
-
-        final Set<AssociationInfo> associations = new ArraySet<>();
-        final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
-        mPersistentDataStore.readStateForUser(userId, associations, previouslyUsedIds);
-
-        if (DEBUG) {
-            Slog.d(LOG_TAG, "  > associations=" + associations + "\n"
-                    + "  > previouslyUsedIds=" + previouslyUsedIds);
-        }
-
-        mCachedAssociations.put(userId, unmodifiableSet(associations));
-        mPreviouslyUsedIds.append(userId, previouslyUsedIds);
-    }
-
-    private List<UserInfo> getAllUsers() {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return mUserManager.getUsers();
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
     void onDeviceConnected(String address) {
         Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
 
         mCurrentlyConnectedDevices.add(address);
 
-        for (UserInfo user : getAllUsers()) {
-            for (AssociationInfo association : getAllAssociationsForUser(user.id)) {
-                if (association.isLinkedTo(address)) {
-                    if (association.getDeviceProfile() != null) {
-                        Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
-                                + " to " + association.getPackageName()
-                                + " due to device connected: " + association.getDeviceMacAddress());
+        for (AssociationInfo association : mAssociationStore.getAssociationsByAddress(address)) {
+            if (association.getDeviceProfile() != null) {
+                Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
+                        + " to " + association.getPackageName()
+                        + " due to device connected: " + association.getDeviceMacAddress());
 
-                        addRoleHolderForAssociation(getContext(), association);
-                    }
-                }
+                addRoleHolderForAssociation(getContext(), association);
             }
         }
 
@@ -1134,7 +1037,9 @@
                 Date lastNearby = mDevicesLastNearby.valueAt(i);
 
                 if (isDeviceDisappeared(lastNearby)) {
-                    for (AssociationInfo association : getAllAssociations(address)) {
+                    final List<AssociationInfo> associations =
+                            mAssociationStore.getAssociationsByAddress(address);
+                    for (AssociationInfo association : associations) {
                         if (association.isNotifyOnDeviceNearby()) {
                             mCompanionDevicePresenceController.unbindDevicePresenceListener(
                                     association.getPackageName(), association.getUserId());
@@ -1176,20 +1081,6 @@
         }
     }
 
-    private Set<AssociationInfo> getAllAssociations(String deviceAddress) {
-        List<UserInfo> aliveUsers = mUserManager.getAliveUsers();
-        Set<AssociationInfo> result = new ArraySet<>();
-        for (int i = 0, size = aliveUsers.size(); i < size; i++) {
-            UserInfo user = aliveUsers.get(i);
-            for (AssociationInfo association : getAllAssociationsForUser(user.id)) {
-                if (association.isLinkedTo(deviceAddress)) {
-                    result.add(association);
-                }
-            }
-        }
-        return result;
-    }
-
     private void onDeviceNearby(String address) {
         Date timestamp = new Date();
         Date oldTimestamp = mDevicesLastNearby.put(address, timestamp);
@@ -1205,7 +1096,9 @@
                 || timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS;
         if (justAppeared) {
             Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")");
-            for (AssociationInfo association : getAllAssociations(address)) {
+            final List<AssociationInfo> associations =
+                    mAssociationStore.getAssociationsByAddress(address);
+            for (AssociationInfo association : associations) {
                 if (association.isNotifyOnDeviceNearby()) {
                     mCompanionDevicePresenceController.onDeviceNotifyAppeared(association,
                             getContext(), mMainHandler);
@@ -1218,7 +1111,9 @@
         Slog.i(LOG_TAG, "onDeviceDisappeared(address = " + address + ")");
 
         boolean hasDeviceListeners = false;
-        for (AssociationInfo association : getAllAssociations(address)) {
+        final List<AssociationInfo> associations =
+                mAssociationStore.getAssociationsByAddress(address);
+        for (AssociationInfo association : associations) {
             if (association.isNotifyOnDeviceNearby()) {
                 mCompanionDevicePresenceController.onDeviceNotifyDisappeared(
                         association, getContext(), mMainHandler);
@@ -1291,7 +1186,7 @@
     private List<ScanFilter> getBleScanFilters() {
         ArrayList<ScanFilter> result = new ArrayList<>();
         ArraySet<String> addressesSeen = new ArraySet<>();
-        for (AssociationInfo association : getAllAssociations()) {
+        for (AssociationInfo association : mAssociationStore.getAssociations()) {
             if (association.isSelfManaged()) {
                 continue;
             }
@@ -1331,17 +1226,6 @@
         }
     }
 
-    private static @NonNull <T> Set<T> filterOut(
-            @NonNull Set<T> set, @NonNull Predicate<? super T> predicate) {
-        return CollectionUtils.filter(set, predicate.negate());
-    }
-
-    private Map<String, Set<Integer>> deepCopy(Map<String, Set<Integer>> orig) {
-        final Map<String, Set<Integer>> copy = new HashMap<>(orig.size(), 1f);
-        forEach(orig, (key, value) -> copy.put(key, new ArraySet<>(value)));
-        return copy;
-    }
-
     void checkUsesFeature(@NonNull String pkg, @UserIdInt int userId) {
         if (getCallingUid() == SYSTEM_UID) return;
 
@@ -1356,4 +1240,61 @@
                 + FEATURE_COMPANION_DEVICE_SETUP
                 + " in manifest to use this API");
     }
+
+    private void registerPackageMonitor() {
+        new PackageMonitor() {
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                final int userId = getChangingUserId();
+                Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName);
+
+                clearAssociationForPackage(userId, packageName);
+            }
+
+            @Override
+            public void onPackageDataCleared(String packageName, int uid) {
+                final int userId = getChangingUserId();
+                Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName);
+
+                clearAssociationForPackage(userId, packageName);
+            }
+
+            @Override
+            public void onPackageModified(String packageName) {
+                final int userId = getChangingUserId();
+                Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName);
+
+                final List<AssociationInfo> associationsForPackage =
+                        mAssociationStore.getAssociationsForPackage(userId, packageName);
+                for (AssociationInfo association : associationsForPackage) {
+                    updateSpecialAccessPermissionForAssociatedPackage(association);
+                }
+            }
+        }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
+    }
+
+    private void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) {
+        if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName);
+
+        // First, unbind CompanionService if needed.
+        mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId);
+
+        // Clear associations.
+        final List<AssociationInfo> associationsForPackage =
+                mAssociationStore.getAssociationsForPackage(userId, packageName);
+        for (AssociationInfo association : associationsForPackage) {
+            mAssociationStore.removeAssociation(association.getId());
+        }
+    }
+
+    private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) {
+        final Map<String, Set<Integer>> copy = new HashMap<>();
+
+        for (Map.Entry<String, Set<Integer>> entry : orig.entrySet()) {
+            final Set<Integer> valueCopy = new HashSet<>(entry.getValue());
+            copy.put(entry.getKey(), Collections.unmodifiableSet(valueCopy));
+        }
+
+        return Collections.unmodifiableMap(copy);
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 5cb3079..5c0571d 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -16,7 +16,6 @@
 
 package com.android.server.companion;
 
-import static com.android.internal.util.CollectionUtils.forEach;
 import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
 
 import android.companion.AssociationInfo;
@@ -24,24 +23,33 @@
 import android.util.Slog;
 
 import java.io.PrintWriter;
+import java.util.List;
 
 class CompanionDeviceShellCommand extends android.os.ShellCommand {
     private final CompanionDeviceManagerService mService;
+    private final AssociationStore mAssociationStore;
 
-    CompanionDeviceShellCommand(CompanionDeviceManagerService service) {
+    CompanionDeviceShellCommand(CompanionDeviceManagerService service,
+            AssociationStore associationStore) {
         mService = service;
+        mAssociationStore = associationStore;
     }
 
     @Override
     public int onCommand(String cmd) {
+        final PrintWriter out = getOutPrintWriter();
         try {
             switch (cmd) {
                 case "list": {
-                    forEach(
-                            mService.getAllAssociationsForUser(getNextArgInt()),
-                            a -> getOutPrintWriter()
-                                    .println(a.getPackageName() + " "
-                                            + a.getDeviceMacAddress()));
+                    final int userId = getNextArgInt();
+                    final List<AssociationInfo> associationsForUser =
+                            mAssociationStore.getAssociationsForUser(userId);
+                    for (AssociationInfo association : associationsForUser) {
+                        // TODO(b/212535524): use AssociationInfo.toShortString(), once it's not
+                        //  longer referenced in tests.
+                        out.println(association.getPackageName() + " "
+                                + association.getDeviceMacAddress());
+                    }
                 }
                 break;
 
@@ -60,7 +68,7 @@
                     final AssociationInfo association =
                             mService.getAssociationWithCallerChecks(userId, packageName, address);
                     if (association != null) {
-                        mService.disassociateInternal(userId, association.getId());
+                        mService.disassociateInternal(association.getId());
                     }
                 }
                 break;
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index 87558df..97ec3bb 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -33,15 +33,18 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.companion.AssociationInfo;
+import android.content.pm.UserInfo;
 import android.net.MacAddress;
 import android.os.Environment;
+import android.util.ArrayMap;
 import android.util.AtomicFile;
-import android.util.ExceptionUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
 
+import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -50,8 +53,11 @@
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -68,8 +74,8 @@
  *
  * Before Android T the data was stored using the v0 schema.
  *
- * @see #readAssociationsV0(TypedXmlPullParser, int, Set)
- * @see #readAssociationV0(TypedXmlPullParser, int, int, Set)
+ * @see #readAssociationsV0(TypedXmlPullParser, int, Collection)
+ * @see #readAssociationV0(TypedXmlPullParser, int, int, Collection)
  *
  * The following snippet is a sample of a the file that is using v0 schema.
  * <pre>{@code
@@ -100,8 +106,8 @@
  * optional.
  *
  * @see #CURRENT_PERSISTENCE_VERSION
- * @see #readAssociationsV1(TypedXmlPullParser, int, Set)
- * @see #readAssociationV1(TypedXmlPullParser, int, Set)
+ * @see #readAssociationsV1(TypedXmlPullParser, int, Collection)
+ * @see #readAssociationV1(TypedXmlPullParser, int, Collection)
  * @see #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map)
  *
  * The following snippet is a sample of a the file that is using v0 schema.
@@ -168,6 +174,23 @@
     private final @NonNull ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
             new ConcurrentHashMap<>();
 
+    void readStateForUsers(@NonNull List<UserInfo> users,
+            @NonNull Set<AssociationInfo> allAssociationsOut,
+            @NonNull SparseArray<Map<String, Set<Integer>>> previouslyUsedIdsPerUserOut) {
+        for (UserInfo user : users) {
+            final int userId = user.id;
+            // Previously used IDs are stored in the "out" collection per-user.
+            final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
+
+            // Associations for all users are stored in a single "flat" set: so we read directly
+            // into it.
+            readStateForUser(userId, allAssociationsOut, previouslyUsedIds);
+
+            // Save previously used IDs for this user into the "out" structure.
+            previouslyUsedIdsPerUserOut.append(userId, previouslyUsedIds);
+        }
+    }
+
     /**
      * Reads previously persisted data for the given user "into" the provided containers.
      *
@@ -176,7 +199,7 @@
      * @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
      */
     void readStateForUser(@UserIdInt int userId,
-            @NonNull Set<AssociationInfo> associationsOut,
+            @NonNull Collection<AssociationInfo> associationsOut,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
         Slog.i(LOG_TAG, "Reading associations for user " + userId + " from disk");
         final AtomicFile file = getStorageFileForUser(userId);
@@ -237,7 +260,8 @@
      * @param associations a set of user's associations.
      * @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
      */
-    void persistStateForUser(@UserIdInt int userId, @NonNull Set<AssociationInfo> associations,
+    void persistStateForUser(@UserIdInt int userId,
+            @NonNull Collection<AssociationInfo> associations,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
         Slog.i(LOG_TAG, "Writing associations for user " + userId + " to disk");
         if (DEBUG) Slog.d(LOG_TAG, "  > " + associations);
@@ -250,7 +274,7 @@
     }
 
     private int readStateFromFileLocked(@UserIdInt int userId, @NonNull AtomicFile file,
-            @NonNull String rootTag, @Nullable Set<AssociationInfo> associationsOut,
+            @NonNull String rootTag, @Nullable Collection<AssociationInfo> associationsOut,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
         try (FileInputStream in = file.openRead()) {
             final TypedXmlPullParser parser = Xml.resolvePullParser(in);
@@ -282,28 +306,25 @@
     }
 
     private void persistStateToFileLocked(@NonNull AtomicFile file,
-            @Nullable Set<AssociationInfo> associations,
+            @Nullable Collection<AssociationInfo> associations,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
-        file.write(out -> {
-            try {
-                final TypedXmlSerializer serializer = Xml.resolveSerializer(out);
-                serializer.setFeature(
-                        "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        // Writing to file could fail, for example, if the user has been recently removed and so was
+        // their DE (/data/system_de/<user-id>/) directory.
+        writeToFileSafely(file, out -> {
+            final TypedXmlSerializer serializer = Xml.resolveSerializer(out);
+            serializer.setFeature(
+                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
-                serializer.startDocument(null, true);
-                serializer.startTag(null, XML_TAG_STATE);
-                writeIntAttribute(serializer,
-                        XML_ATTR_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION);
+            serializer.startDocument(null, true);
+            serializer.startTag(null, XML_TAG_STATE);
+            writeIntAttribute(serializer,
+                    XML_ATTR_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION);
 
-                writeAssociations(serializer, associations);
-                writePreviouslyUsedIds(serializer, previouslyUsedIdsPerPackage);
+            writeAssociations(serializer, associations);
+            writePreviouslyUsedIds(serializer, previouslyUsedIdsPerPackage);
 
-                serializer.endTag(null, XML_TAG_STATE);
-                serializer.endDocument();
-            } catch (Exception e) {
-                Slog.e(LOG_TAG, "Error while writing associations file", e);
-                throw ExceptionUtils.propagate(e);
-            }
+            serializer.endTag(null, XML_TAG_STATE);
+            serializer.endDocument();
         });
     }
 
@@ -321,7 +342,7 @@
     }
 
     private static void readAssociationsV0(@NonNull TypedXmlPullParser parser,
-            @UserIdInt int userId, @NonNull Set<AssociationInfo> out)
+            @UserIdInt int userId, @NonNull Collection<AssociationInfo> out)
             throws XmlPullParserException, IOException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
 
@@ -342,7 +363,8 @@
     }
 
     private static void readAssociationV0(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
-            int associationId, @NonNull Set<AssociationInfo> out) throws XmlPullParserException {
+            int associationId, @NonNull Collection<AssociationInfo> out)
+            throws XmlPullParserException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATION);
 
         final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
@@ -360,7 +382,7 @@
     }
 
     private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
-            @UserIdInt int userId, @NonNull Set<AssociationInfo> out)
+            @UserIdInt int userId, @NonNull Collection<AssociationInfo> out)
             throws XmlPullParserException, IOException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
 
@@ -374,7 +396,7 @@
     }
 
     private static void readAssociationV1(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
-            @NonNull Set<AssociationInfo> out) throws XmlPullParserException, IOException {
+            @NonNull Collection<AssociationInfo> out) throws XmlPullParserException, IOException {
         requireStartOfTag(parser, XML_TAG_ASSOCIATION);
 
         final int associationId = readIntAttribute(parser, XML_ATTR_ID);
@@ -421,9 +443,11 @@
     }
 
     private static void writeAssociations(@NonNull XmlSerializer parent,
-            @Nullable Set<AssociationInfo> associations) throws IOException {
+            @Nullable Collection<AssociationInfo> associations) throws IOException {
         final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS);
-        forEach(associations, it -> writeAssociation(serializer, it));
+        for (AssociationInfo association : associations) {
+            writeAssociation(serializer, association);
+        }
         serializer.endTag(null, XML_TAG_ASSOCIATIONS);
     }
 
@@ -498,4 +522,13 @@
         }
         return associationInfo;
     }
+
+    private static void writeToFileSafely(@NonNull AtomicFile file,
+            @NonNull ThrowingConsumer<FileOutputStream> consumer) {
+        try {
+            file.write(consumer);
+        } catch (Exception e) {
+            Slog.e(LOG_TAG, "Error while writing to file " + file, e);
+        }
+    }
 }
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 6fe2806..9b2948f 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -50,6 +50,7 @@
 import com.android.server.pm.pkg.AndroidPackageApi;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.mutate.PackageStateMutator;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -885,17 +886,6 @@
     public abstract boolean userNeedsBadging(int userId);
 
     /**
-     * Perform the given action for each package.
-     * Note that packages lock will be held while performing the actions.
-     *
-     * If the caller does not need all packages, prefer the potentially non-locking
-     * {@link #withPackageSettingsSnapshot(Consumer)}.
-     *
-     * @param actionLocked action to be performed
-     */
-    public abstract void forEachPackage(Consumer<AndroidPackage> actionLocked);
-
-    /**
      * Perform the given action for each {@link PackageSetting}.
      * Note that packages lock will be held while performing the actions.
      *
@@ -918,12 +908,24 @@
     public abstract void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action);
 
     /**
+     * {@link #forEachPackageState(boolean, Consumer)} but filtered to only states with packages
+     * on device where {@link PackageStateInternal#getPkg()} is not null.
+     */
+    public abstract void forEachPackage(Consumer<AndroidPackage> action);
+
+    /**
      * Perform the given action for each installed package for a user.
      * Note that packages lock will be held while performing the actions.
      */
     public abstract void forEachInstalledPackage(
             @NonNull Consumer<AndroidPackage> actionLocked, @UserIdInt int userId);
 
+    /**
+     * Perform the given action for each installed package for a user.
+     */
+    public abstract void forEachInstalledPackage(boolean locked,
+            @NonNull Consumer<AndroidPackage> action, @UserIdInt int userId);
+
     /** Returns the list of enabled components */
     public abstract ArraySet<String> getEnabledComponents(String packageName, int userId);
 
@@ -1265,4 +1267,62 @@
      */
     public abstract void reconcileAppsData(int userId, @StorageManager.StorageFlags int flags,
             boolean migrateAppsData);
+
+    /**
+     * Initiates a package state mutation request, returning the current state as known by
+     * PackageManager. This allows the later commit request to compare the initial values and
+     * determine if any state was changed or any packages were updated since the whole request
+     * was initiated.
+     *
+     * As a concrete example, consider the following steps:
+     * <ol>
+     *     <li>Read a package state without taking a lock</li>
+     *     <li>Check some values in that state, determine that a mutation needs to occur</li>
+     *     <li>Call to commit the change with the new value, takes lock</li>
+     * </ol>
+     *
+     * Between steps 1 and 3, because the lock was not taken for the entire flow, it's possible
+     * a package state was changed by another consumer or a package was updated/installed.
+     *
+     * If anything has changed,
+     * {@link #commitPackageStateMutation(PackageStateMutator.InitialState, Consumer)} will return
+     * a {@link PackageStateMutator.Result} indicating so. If the caller has not indicated it can
+     * ignore changes, it can opt to re-run the commit logic from the top with a true write lock
+     * around all of its read-logic-commit loop.
+     *
+     * Note that if the caller does not care about potential race conditions or package/state
+     * changes between steps 1 and 3, it can simply opt to not call this method and pass in null
+     * for the initial state. This is useful to avoid long running data structure locks when the
+     * caller is changing a value as part of a one-off request. Perhaps from an app side API which
+     * mutates only a single package, where it doesn't care what the state of that package is or
+     * any other packages on the devices.
+     *
+     * Important to note is that if no locking is enforced, callers themselves will not be
+     * synchronized with themselves. The caller may be relying on the PackageManager lock to
+     * enforce ordering within their own code path, and that has to be adjusted if migrated off
+     * the lock.
+     */
+    @NonNull
+    public abstract PackageStateMutator.InitialState recordInitialState();
+
+    /**
+     * Some questions to ask when designing a mutation:
+     * <ol>
+     *     <li>What external system state is required and is it synchronized properly?</li>
+     *     <li>Are there any package/state changes that could happen to the target (or another)
+     *     package that could result in the commit being invalid?</li>
+     *     <li>Is the caller synchronized with itself and can handle multiple mutations being
+     *     requested from different threads?</li>
+     *     <li>What should be done in case of a conflict and the commit can't be finished?</li>
+     * </ol>
+     *
+     * @param state See {@link #recordInitialState()}. If null, no result is returned.
+     * @param consumer Lean wrapper around just the logic that changes state values
+     * @return result if anything changed since initial state, or null if nothing changed and
+     * commit was successful
+     */
+    @Nullable
+    public abstract PackageStateMutator.Result commitPackageStateMutation(
+            @Nullable PackageStateMutator.InitialState state,
+            @NonNull Consumer<PackageStateMutator> consumer);
 }
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 6e058412..bc8da84 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -109,10 +109,6 @@
     private static final String BLUETOOTH_PRIVILEGED =
             android.Manifest.permission.BLUETOOTH_PRIVILEGED;
 
-    private static final String SECURE_SETTINGS_BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid";
-    private static final String SECURE_SETTINGS_BLUETOOTH_ADDRESS = "bluetooth_address";
-    private static final String SECURE_SETTINGS_BLUETOOTH_NAME = "bluetooth_name";
-
     private static final int ACTIVE_LOG_MAX_SIZE = 20;
     private static final int CRASH_LOG_MAX_SIZE = 100;
 
@@ -640,7 +636,7 @@
         if (mContext.getResources()
                 .getBoolean(com.android.internal.R.bool.config_bluetooth_address_validation)
                 && Settings.Secure.getIntForUser(mContentResolver,
-                SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 0, mUserId)
+                Settings.Secure.BLUETOOTH_NAME, 0, mUserId)
                 == 0) {
             // if the valid flag is not set, don't load the address and name
             if (DBG) {
@@ -649,9 +645,9 @@
             return;
         }
         mName = Settings.Secure.getStringForUser(
-                mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, mUserId);
+                mContentResolver, Settings.Secure.BLUETOOTH_NAME, mUserId);
         mAddress = Settings.Secure.getStringForUser(
-                mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS, mUserId);
+                mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, mUserId);
         if (DBG) {
             Slog.d(TAG, "Stored bluetooth Name=" + mName + ",Address=" + mAddress);
         }
@@ -665,30 +661,30 @@
      */
     private void storeNameAndAddress(String name, String address) {
         if (name != null) {
-            Settings.Secure.putStringForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, name,
+            Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_NAME, name,
                     mUserId);
             mName = name;
             if (DBG) {
                 Slog.d(TAG, "Stored Bluetooth name: " + Settings.Secure.getStringForUser(
-                        mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME,
+                        mContentResolver, Settings.Secure.BLUETOOTH_NAME,
                         mUserId));
             }
         }
 
         if (address != null) {
-            Settings.Secure.putStringForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS,
+            Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS,
                     address, mUserId);
             mAddress = address;
             if (DBG) {
                 Slog.d(TAG,
                         "Stored Bluetoothaddress: " + Settings.Secure.getStringForUser(
-                                mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS,
+                                mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS,
                                 mUserId));
             }
         }
 
         if ((name != null) && (address != null)) {
-            Settings.Secure.putIntForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 1,
+            Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDR_VALID, 1,
                     mUserId);
         }
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8615393..902cdb9 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9771,7 +9771,7 @@
                                      projection)) {
             Slog.w(TAG, "Permission denied to register audio policy for pid "
                     + Binder.getCallingPid() + " / uid " + Binder.getCallingUid()
-                    + ", need MODIFY_AUDIO_ROUTING or MediaProjection that can project audio");
+                    + ", need system permission or a MediaProjection that can project audio");
             return null;
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 2465ec5..6f71768 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -38,8 +38,8 @@
 
     private static final String TAG = "Biometrics/AcquisitionClient";
 
-    private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
-            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+    private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
     private static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
             VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
@@ -196,7 +196,7 @@
                     getContext().getOpPackageName(),
                     SUCCESS_VIBRATION_EFFECT,
                     getClass().getSimpleName() + "::success",
-                    TOUCH_VIBRATION_ATTRIBUTES);
+                    HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
         }
     }
 
@@ -207,7 +207,7 @@
                     getContext().getOpPackageName(),
                     ERROR_VIBRATION_EFFECT,
                     getClass().getSimpleName() + "::error",
-                    TOUCH_VIBRATION_ATTRIBUTES);
+                    HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
         }
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c0a6abf..c4f2b14 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -714,7 +714,6 @@
                         display.getDisplayInfoLocked().shouldConstrainMetricsForLauncher;
                 if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
                     handleLogicalDisplayChangedLocked(display);
-                    scheduleTraversalLocked(false);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index c5dc23e..47e0e69 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -72,16 +72,16 @@
     @NonNull private final WindowManagerInternal mWindowManagerInternal;
     @NonNull private final Resources mRes;
 
-    private long mLastBindTime;
-    private boolean mHasConnection;
-    @Nullable private String mCurId;
-    @Nullable private String mSelectedMethodId;
-    @Nullable private Intent mCurIntent;
-    @Nullable private IInputMethod mCurMethod;
-    private int mCurMethodUid = Process.INVALID_UID;
-    private IBinder mCurToken;
-    private int mCurSeq;
-    private boolean mVisibleBound;
+    @GuardedBy("mMethodMap") private long mLastBindTime;
+    @GuardedBy("mMethodMap") private boolean mHasConnection;
+    @GuardedBy("mMethodMap") @Nullable private String mCurId;
+    @GuardedBy("mMethodMap") @Nullable private String mSelectedMethodId;
+    @GuardedBy("mMethodMap") @Nullable private Intent mCurIntent;
+    @GuardedBy("mMethodMap") @Nullable private IInputMethod mCurMethod;
+    @GuardedBy("mMethodMap") private int mCurMethodUid = Process.INVALID_UID;
+    @GuardedBy("mMethodMap") private IBinder mCurToken;
+    @GuardedBy("mMethodMap") private int mCurSeq;
+    @GuardedBy("mMethodMap") private boolean mVisibleBound;
     private boolean mSupportsStylusHw;
 
     /**
@@ -146,6 +146,7 @@
      * Time that we last initiated a bind to the input method, to determine
      * if we should try to disconnect and reconnect to it.
      */
+    @GuardedBy("mMethodMap")
     long getLastBindTime() {
         return mLastBindTime;
     }
@@ -154,6 +155,7 @@
      * Set to true if our ServiceConnection is currently actively bound to
      * a service (whether or not we have gotten its IBinder back yet).
      */
+    @GuardedBy("mMethodMap")
     boolean hasConnection() {
         return mHasConnection;
     }
@@ -166,6 +168,7 @@
      *
      * @see #getSelectedMethodId()
      */
+    @GuardedBy("mMethodMap")
     @Nullable
     String getCurId() {
         return mCurId;
@@ -184,11 +187,13 @@
      *
      * @see #getCurId()
      */
+    @GuardedBy("mMethodMap")
     @Nullable
     String getSelectedMethodId() {
         return mSelectedMethodId;
     }
 
+    @GuardedBy("mMethodMap")
     void setSelectedMethodId(@Nullable String selectedMethodId) {
         mSelectedMethodId = selectedMethodId;
     }
@@ -197,6 +202,7 @@
      * The token we have made for the currently active input method, to
      * identify it in the future.
      */
+    @GuardedBy("mMethodMap")
     IBinder getCurToken() {
         return mCurToken;
     }
@@ -204,6 +210,7 @@
     /**
      * The Intent used to connect to the current input method.
      */
+    @GuardedBy("mMethodMap")
     @Nullable
     Intent getCurIntent() {
         return mCurIntent;
@@ -213,6 +220,7 @@
      * The current binding sequence number, incremented every time there is
      * a new bind performed.
      */
+    @GuardedBy("mMethodMap")
     int getSequenceNumber() {
         return mCurSeq;
     }
@@ -221,6 +229,7 @@
      * Increase the current binding sequence number by one.
      * Reset to 1 on overflow.
      */
+    @GuardedBy("mMethodMap")
     void advanceSequenceNumber() {
         mCurSeq += 1;
         if (mCurSeq <= 0) {
@@ -232,6 +241,7 @@
      * If non-null, this is the input method service we are currently connected
      * to.
      */
+    @GuardedBy("mMethodMap")
     @Nullable
     IInputMethod getCurMethod() {
         return mCurMethod;
@@ -240,6 +250,7 @@
     /**
      * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntent()}.
      */
+    @GuardedBy("mMethodMap")
     int getCurMethodUid() {
         return mCurMethodUid;
     }
@@ -247,6 +258,7 @@
     /**
      * Indicates whether {@link #mVisibleConnection} is currently in use.
      */
+    @GuardedBy("mMethodMap")
     boolean isVisibleBound() {
         return mVisibleBound;
     }
@@ -254,11 +266,12 @@
     /**
      * Used to bring IME service up to visible adjustment while it is being shown.
      */
+    @GuardedBy("mMethodMap")
     private final ServiceConnection mVisibleConnection = new ServiceConnection() {
         @Override public void onBindingDied(ComponentName name) {
             synchronized (mMethodMap) {
                 if (mVisibleBound) {
-                    unbindVisibleConnectionLocked();
+                    unbindVisibleConnection();
                 }
             }
         }
@@ -273,6 +286,7 @@
     /**
      * Used to bind the IME while it is not currently being shown.
      */
+    @GuardedBy("mMethodMap")
     private final ServiceConnection mMainConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
@@ -280,10 +294,10 @@
             synchronized (mMethodMap) {
                 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
                     mCurMethod = IInputMethod.Stub.asInterface(service);
-                    updateCurrentMethodUidLocked();
+                    updateCurrentMethodUid();
                     if (mCurToken == null) {
                         Slog.w(TAG, "Service connected without a token!");
-                        unbindCurrentMethodLocked();
+                        unbindCurrentMethod();
                         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                         return;
                     }
@@ -305,7 +319,7 @@
         }
 
         @GuardedBy("mMethodMap")
-        private void updateCurrentMethodUidLocked() {
+        private void updateCurrentMethodUid() {
             final String curMethodPackage = mCurIntent.getComponent().getPackageName();
             final int curMethodUid = mPackageManagerInternal.getPackageUid(
                     curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId());
@@ -339,7 +353,7 @@
                     // We consider this to be a new bind attempt, since the system
                     // should now try to restart the service for us.
                     mLastBindTime = SystemClock.uptimeMillis();
-                    clearCurMethodAndSessionsLocked();
+                    clearCurMethodAndSessions();
                     mService.clearInputShowRequestLocked();
                     mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);
                 }
@@ -348,34 +362,34 @@
     };
 
     @GuardedBy("mMethodMap")
-    void unbindCurrentMethodLocked() {
+    void unbindCurrentMethod() {
         if (mVisibleBound) {
-            unbindVisibleConnectionLocked();
+            unbindVisibleConnection();
         }
 
         if (mHasConnection) {
-            unbindMainConnectionLocked();
+            unbindMainConnection();
         }
 
         if (mCurToken != null) {
-            removeCurrentTokenLocked();
+            removeCurrentToken();
             mService.resetSystemUiLocked();
         }
 
         mCurId = null;
-        clearCurMethodAndSessionsLocked();
+        clearCurMethodAndSessions();
     }
 
     @GuardedBy("mMethodMap")
-    private void clearCurMethodAndSessionsLocked() {
+    private void clearCurMethodAndSessions() {
         mService.clearClientSessionsLocked();
         mCurMethod = null;
         mCurMethodUid = Process.INVALID_UID;
     }
 
     @GuardedBy("mMethodMap")
-    private void removeCurrentTokenLocked() {
-        int curTokenDisplayId = mService.getCurTokenDisplayId();
+    private void removeCurrentToken() {
+        int curTokenDisplayId = mService.getCurTokenDisplayIdLocked();
 
         if (DEBUG) {
             Slog.v(TAG,
@@ -388,7 +402,7 @@
 
     @GuardedBy("mMethodMap")
     @NonNull
-    InputBindResult bindCurrentMethodLocked() {
+    InputBindResult bindCurrentMethod() {
         InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
         if (info == null) {
             throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
@@ -396,11 +410,11 @@
 
         mCurIntent = createImeBindingIntent(info.getComponent());
 
-        if (bindCurrentInputMethodServiceMainConnectionLocked()) {
+        if (bindCurrentInputMethodServiceMainConnection()) {
             mCurId = info.getId();
             mLastBindTime = SystemClock.uptimeMillis();
 
-            addFreshWindowTokenLocked();
+            addFreshWindowToken();
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
                     null, null, mCurId, mCurSeq, false);
@@ -425,11 +439,11 @@
     }
 
     @GuardedBy("mMethodMap")
-    private void addFreshWindowTokenLocked() {
-        int displayIdToShowIme = mService.getDisplayIdToShowIme();
+    private void addFreshWindowToken() {
+        int displayIdToShowIme = mService.getDisplayIdToShowImeLocked();
         mCurToken = new Binder();
 
-        mService.setCurTokenDisplayId(displayIdToShowIme);
+        mService.setCurTokenDisplayIdLocked(displayIdToShowIme);
 
         try {
             if (DEBUG) {
@@ -445,19 +459,19 @@
     }
 
     @GuardedBy("mMethodMap")
-    private void unbindMainConnectionLocked() {
+    private void unbindMainConnection() {
         mContext.unbindService(mMainConnection);
         mHasConnection = false;
     }
 
     @GuardedBy("mMethodMap")
-    void unbindVisibleConnectionLocked() {
+    void unbindVisibleConnection() {
         mContext.unbindService(mVisibleConnection);
         mVisibleBound = false;
     }
 
     @GuardedBy("mMethodMap")
-    private boolean bindCurrentInputMethodServiceLocked(ServiceConnection conn, int flags) {
+    private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
         if (mCurIntent == null || conn == null) {
             Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
             return false;
@@ -467,15 +481,15 @@
     }
 
     @GuardedBy("mMethodMap")
-    private boolean bindCurrentInputMethodServiceVisibleConnectionLocked() {
-        mVisibleBound = bindCurrentInputMethodServiceLocked(mVisibleConnection,
+    private boolean bindCurrentInputMethodServiceVisibleConnection() {
+        mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
                 IME_VISIBLE_BIND_FLAGS);
         return mVisibleBound;
     }
 
     @GuardedBy("mMethodMap")
-    private boolean bindCurrentInputMethodServiceMainConnectionLocked() {
-        mHasConnection = bindCurrentInputMethodServiceLocked(mMainConnection,
+    private boolean bindCurrentInputMethodServiceMainConnection() {
+        mHasConnection = bindCurrentInputMethodService(mMainConnection,
                 mImeConnectionBindFlags);
         return mHasConnection;
     }
@@ -487,26 +501,35 @@
      * Performs a rebind if no binding is achieved in {@link #TIME_TO_RECONNECT} milliseconds.
      */
     @GuardedBy("mMethodMap")
-    void setCurrentMethodVisibleLocked() {
+    void setCurrentMethodVisible() {
         if (mCurMethod != null) {
-            if (DEBUG) Slog.d(TAG, "setCurrentMethodVisibleLocked: mCurToken=" + mCurToken);
+            if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken);
             if (mHasConnection && !mVisibleBound) {
-                bindCurrentInputMethodServiceVisibleConnectionLocked();
+                bindCurrentInputMethodServiceVisibleConnection();
             }
             return;
         }
 
+        // No IME is currently connected. Reestablish the main connection.
+        if (!mHasConnection) {
+            if (DEBUG) {
+                Slog.d(TAG, "Cannot show input: no IME bound. Rebinding.");
+            }
+            bindCurrentMethod();
+            return;
+        }
+
         long bindingDuration = SystemClock.uptimeMillis() - mLastBindTime;
-        if (mHasConnection && bindingDuration >= TIME_TO_RECONNECT) {
+        if (bindingDuration >= TIME_TO_RECONNECT) {
             // The client has asked to have the input method shown, but
             // we have been sitting here too long with a connection to the
             // service and no interface received, so let's disconnect/connect
             // to try to prod things along.
             EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(),
                     bindingDuration, 1);
-            Slog.w(TAG, "Force disconnect/connect to the IME in setCurrentMethodVisibleLocked()");
-            unbindMainConnectionLocked();
-            bindCurrentInputMethodServiceMainConnectionLocked();
+            Slog.w(TAG, "Force disconnect/connect to the IME in setCurrentMethodVisible()");
+            unbindMainConnection();
+            bindCurrentInputMethodServiceMainConnection();
         } else {
             if (DEBUG) {
                 Slog.d(TAG, "Can't show input: connection = " + mHasConnection + ", time = "
@@ -519,9 +542,9 @@
      * Remove the binding needed for the IME to be shown.
      */
     @GuardedBy("mMethodMap")
-    void setCurrentMethodNotVisibleLocked() {
+    void setCurrentMethodNotVisible() {
         if (mVisibleBound) {
-            unbindVisibleConnectionLocked();
+            unbindVisibleConnection();
         }
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 262ab93..41d8332 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -262,6 +262,16 @@
     private static final String ACTION_SHOW_INPUT_METHOD_PICKER =
             "com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER";
 
+    /**
+     * When set, {@link #startInputUncheckedLocked} will return
+     * {@link InputBindResult#NO_EDITOR} instead of starting an IME connection
+     * unless {@link StartInputFlags#IS_TEXT_EDITOR} is set. This behavior overrides
+     * {@link LayoutParams#SOFT_INPUT_STATE_VISIBLE SOFT_INPUT_STATE_VISIBLE} and
+     * {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_VISIBLE SOFT_INPUT_STATE_ALWAYS_VISIBLE}
+     * starting from {@link android.os.Build.VERSION_CODES#P}.
+     */
+    private final boolean mPreventImeStartupUnlessTextEditor;
+
     @UserIdInt
     private int mLastSwitchUserId;
 
@@ -310,7 +320,7 @@
      * The display id for which the latest startInput was called.
      */
     @GuardedBy("mMethodMap")
-    int getDisplayIdToShowIme() {
+    int getDisplayIdToShowImeLocked() {
         return mDisplayIdToShowIme;
     }
 
@@ -416,17 +426,19 @@
      * <p>This can be transiently {@code null} when the system is re-initializing input method
      * settings, e.g., the system locale is just changed.</p>
      *
-     * <p>Note that {@link InputMethodBindingController#getCurId()} is used to track which IME is
-     * being connected to {@link InputMethodManagerService}.</p>
+     * <p>Note that {@link InputMethodBindingController#getCurId()} is used to track which IME
+     * is being connected to {@link InputMethodManagerService}.</p>
      *
      * @see InputMethodBindingController#getCurId()
      */
+    @GuardedBy("mMethodMap")
     @Nullable
-    private String getSelectedMethodId() {
+    private String getSelectedMethodIdLocked() {
         return mBindingController.getSelectedMethodId();
     }
 
-    private void setSelectedMethodId(@Nullable String selectedMethodId) {
+    @GuardedBy("mMethodMap")
+    private void setSelectedMethodIdLocked(@Nullable String selectedMethodId) {
         mBindingController.setSelectedMethodId(selectedMethodId);
     }
 
@@ -434,7 +446,8 @@
      * The current binding sequence number, incremented every time there is
      * a new bind performed.
      */
-    private int getSequenceNumber() {
+    @GuardedBy("mMethodMap")
+    private int getSequenceNumberLocked() {
         return mBindingController.getSequenceNumber();
     }
 
@@ -442,7 +455,8 @@
      * Increase the current binding sequence number by one.
      * Reset to 1 on overflow.
      */
-    private void advanceSequenceNumber() {
+    @GuardedBy("mMethodMap")
+    private void advanceSequenceNumberLocked() {
         mBindingController.advanceSequenceNumber();
     }
 
@@ -501,10 +515,11 @@
      *
      * <p>This can be {@code null} when no input method is connected.</p>
      *
-     * @see #getSelectedMethodId()
+     * @see #getSelectedMethodIdLocked()
      */
+    @GuardedBy("mMethodMap")
     @Nullable
-    private String getCurId() {
+    private String getCurIdLocked() {
         return mBindingController.getCurId();
     }
 
@@ -522,7 +537,8 @@
      * Set to true if our ServiceConnection is currently actively bound to
      * a service (whether or not we have gotten its IBinder back yet).
      */
-    private boolean hasConnection() {
+    @GuardedBy("mMethodMap")
+    private boolean hasConnectionLocked() {
         return mBindingController.hasConnection();
     }
 
@@ -554,8 +570,9 @@
     /**
      * The Intent used to connect to the current input method.
      */
+    @GuardedBy("mMethodMap")
     @Nullable
-    private Intent getCurIntent() {
+    private Intent getCurIntentLocked() {
         return mBindingController.getCurIntent();
     }
 
@@ -563,22 +580,26 @@
      * The token we have made for the currently active input method, to
      * identify it in the future.
      */
-    private IBinder getCurToken() {
+    @GuardedBy("mMethodMap")
+    private IBinder getCurTokenLocked() {
         return mBindingController.getCurToken();
     }
 
     /**
      * The displayId of current active input method.
      */
-    int getCurTokenDisplayId() {
+    @GuardedBy("mMethodMap")
+    int getCurTokenDisplayIdLocked() {
         return mCurTokenDisplayId;
     }
 
-    void setCurTokenDisplayId(int curTokenDisplayId) {
+    @GuardedBy("mMethodMap")
+    void setCurTokenDisplayIdLocked(int curTokenDisplayId) {
         mCurTokenDisplayId = curTokenDisplayId;
     }
 
-    int mCurTokenDisplayId = INVALID_DISPLAY;
+    @GuardedBy("mMethodMap")
+    private int mCurTokenDisplayId = INVALID_DISPLAY;
 
     /**
      * The host input token of the current active input method.
@@ -599,15 +620,17 @@
      * If non-null, this is the input method service we are currently connected
      * to.
      */
+    @GuardedBy("mMethodMap")
     @Nullable
-    private IInputMethod getCurMethod() {
+    private IInputMethod getCurMethodLocked() {
         return mBindingController.getCurMethod();
     }
 
     /**
-     * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntent()}.
+     * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntentLocked()}.
      */
-    private int getCurMethodUid() {
+    @GuardedBy("mMethodMap")
+    private int getCurMethodUidLocked() {
         return mBindingController.getCurMethodUid();
     }
 
@@ -615,7 +638,8 @@
      * Time that we last initiated a bind to the input method, to determine
      * if we should try to disconnect and reconnect to it.
      */
-    private long getLastBindTime() {
+    @GuardedBy("mMethodMap")
+    private long getLastBindTimeLocked() {
         return mBindingController.getLastBindTime();
     }
 
@@ -658,7 +682,7 @@
      * </dd>
      * </dl>
      * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
-     * {@link InputMethodBindingController#unbindCurrentMethodLocked()}.</em>
+     * {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
      */
     int mImeWindowVis;
 
@@ -1649,12 +1673,14 @@
                 mSettings, context);
         mMenuController = new InputMethodMenuController(this);
         mBindingController = new InputMethodBindingController(this);
+        mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
+                com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
     }
 
     @GuardedBy("mMethodMap")
     private void resetDefaultImeLocked(Context context) {
         // Do not reset the default (current) IME when it is a 3rd-party IME
-        String selectedMethodId = getSelectedMethodId();
+        String selectedMethodId = getSelectedMethodIdLocked();
         if (selectedMethodId != null && !mMethodMap.get(selectedMethodId).isSystem()) {
             return;
         }
@@ -1862,7 +1888,7 @@
         if (token == null) {
             throw new InvalidParameterException("token must not be null.");
         }
-        if (token != getCurToken()) {
+        if (token != getCurTokenLocked()) {
             Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
                     + " uid:" + Binder.getCallingUid() + " token:" + token);
             return false;
@@ -1957,15 +1983,16 @@
     @GuardedBy("mMethodMap")
     private void onCreateInlineSuggestionsRequestLocked(@UserIdInt int userId,
             InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) {
-        final InputMethodInfo imi = mMethodMap.get(getSelectedMethodId());
+        final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
         try {
-            IInputMethod curMethod = getCurMethod();
+            IInputMethod curMethod = getCurMethodLocked();
             if (userId == mSettings.getCurrentUserId() && imi != null
                     && imi.isInlineSuggestionsEnabled() && curMethod != null) {
                 executeOrSendMessage(curMethod,
                         mCaller.obtainMessageOOO(MSG_INLINE_SUGGESTIONS_REQUEST, curMethod,
                                 requestInfo, new InlineSuggestionsRequestCallbackDecorator(callback,
-                                        imi.getPackageName(), mCurTokenDisplayId, getCurToken(),
+                                        imi.getPackageName(), mCurTokenDisplayId,
+                                        getCurTokenLocked(),
                                         this)));
             } else {
                 callback.onInlineSuggestionsUnsupported();
@@ -2101,7 +2128,7 @@
             boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) {
         if (userId == mSettings.getCurrentUserId()) {
             final InputMethodInfo imi;
-            String selectedMethodId = getSelectedMethodId();
+            String selectedMethodId = getSelectedMethodIdLocked();
             if (imiId == null && selectedMethodId != null) {
                 imi = mMethodMap.get(selectedMethodId);
             } else {
@@ -2194,7 +2221,7 @@
                             mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
                     if (mBoundToMethod) {
                         mBoundToMethod = false;
-                        IInputMethod curMethod = getCurMethod();
+                        IInputMethod curMethod = getCurMethodLocked();
                         if (curMethod != null) {
                             executeOrSendMessage(curMethod, mCaller.obtainMessageO(
                                     MSG_UNBIND_INPUT, curMethod));
@@ -2225,7 +2252,7 @@
                     + mCurClient.client.asBinder());
             if (mBoundToMethod) {
                 mBoundToMethod = false;
-                IInputMethod curMethod = getCurMethod();
+                IInputMethod curMethod = getCurMethodLocked();
                 if (curMethod != null) {
                     executeOrSendMessage(curMethod, mCaller.obtainMessageO(
                             MSG_UNBIND_INPUT, curMethod));
@@ -2235,7 +2262,8 @@
             scheduleSetActiveToClient(mCurClient, false /* active */, false /* fullscreen */,
                     false /* reportToImeController */);
             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
-                    MSG_UNBIND_CLIENT, getSequenceNumber(), unbindClientReason, mCurClient.client));
+                    MSG_UNBIND_CLIENT, getSequenceNumberLocked(), unbindClientReason,
+                    mCurClient.client));
             mCurClient.sessionRequested = false;
             mCurClient = null;
 
@@ -2276,18 +2304,19 @@
     @NonNull
     InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
         if (!mBoundToMethod) {
-            IInputMethod curMethod = getCurMethod();
+            IInputMethod curMethod = getCurMethodLocked();
             executeOrSendMessage(curMethod, mCaller.obtainMessageOO(
                     MSG_BIND_INPUT, curMethod, mCurClient.binding));
             mBoundToMethod = true;
         }
 
         final Binder startInputToken = new Binder();
-        final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(), getCurToken(),
-                mCurTokenDisplayId, getCurId(), startInputReason, !initial,
+        final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(),
+                getCurTokenLocked(),
+                mCurTokenDisplayId, getCurIdLocked(), startInputReason, !initial,
                 UserHandle.getUserId(mCurClient.uid), mCurClient.selfReportedDisplayId,
                 mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,
-                getSequenceNumber());
+                getSequenceNumberLocked());
         mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow);
         mStartInputHistory.addEntry(info);
 
@@ -2298,7 +2327,7 @@
         // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
         if (mSettings.getCurrentUserId() == UserHandle.getUserId(mCurClient.uid)) {
             mPackageManagerInternal.grantImplicitAccess(mSettings.getCurrentUserId(),
-                    null /* intent */, UserHandle.getAppId(getCurMethodUid()), mCurClient.uid,
+                    null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()), mCurClient.uid,
                     true /* direct */);
         }
 
@@ -2312,22 +2341,22 @@
                     SoftInputShowHideReason.ATTACH_NEW_INPUT);
         }
 
-        String curId = getCurId();
+        String curId = getCurIdLocked();
         final InputMethodInfo curInputMethodInfo = mMethodMap.get(curId);
         final boolean suppressesSpellChecker =
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
         return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                 session.session, (session.channel != null ? session.channel.dup() : null),
-                curId, getSequenceNumber(), suppressesSpellChecker);
+                curId, getSequenceNumberLocked(), suppressesSpellChecker);
     }
 
     @GuardedBy("mMethodMap")
     @NonNull
     InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
             @NonNull EditorInfo attribute, @StartInputFlags int startInputFlags,
-            @StartInputReason int startInputReason) {
+            @StartInputReason int startInputReason, int unverifiedTargetSdkVersion) {
         // If no method is currently selected, do nothing.
-        String selectedMethodId = getSelectedMethodId();
+        String selectedMethodId = getSelectedMethodIdLocked();
         if (selectedMethodId == null) {
             return InputBindResult.NO_IME;
         }
@@ -2337,7 +2366,7 @@
             // party code.
             return new InputBindResult(
                     InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
-                    null, null, selectedMethodId, getSequenceNumber(), false);
+                    null, null, selectedMethodId, getSequenceNumberLocked(), false);
         }
 
         if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
@@ -2369,15 +2398,27 @@
         }
 
         // Bump up the sequence for this client and attach it.
-        advanceSequenceNumber();
+        advanceSequenceNumberLocked();
         mCurClient = cs;
         mCurInputContext = inputContext;
         mCurAttribute = attribute;
 
+        // If configured, we want to avoid starting up the IME if it is not supposed to be showing
+        if (mPreventImeStartupUnlessTextEditor
+                && !InputMethodUtils.isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion,
+                startInputFlags)
+                && !mShowRequested) {
+            if (DEBUG) {
+                Slog.d(TAG, "Avoiding IME startup and unbinding current input method.");
+            }
+            mBindingController.unbindCurrentMethod();
+            return InputBindResult.NO_EDITOR;
+        }
+
         // Check if the input method is changing.
         // We expect the caller has already verified that the client is allowed to access this
         // display ID.
-        if (isSelectedMethodBound()) {
+        if (isSelectedMethodBoundLocked()) {
             if (cs.curSession != null) {
                 // Fast case: if we are already connected to the input method,
                 // then just return it.
@@ -2391,14 +2432,15 @@
             }
         }
 
-        mBindingController.unbindCurrentMethodLocked();
+        mBindingController.unbindCurrentMethod();
 
-        return mBindingController.bindCurrentMethodLocked();
+        return mBindingController.bindCurrentMethod();
     }
 
-    private boolean isSelectedMethodBound() {
-        String curId = getCurId();
-        return curId != null && curId.equals(getSelectedMethodId())
+    @GuardedBy("mMethodMap")
+    private boolean isSelectedMethodBoundLocked() {
+        String curId = getCurIdLocked();
+        return curId != null && curId.equals(getSelectedMethodIdLocked())
                 && mDisplayIdToShowIme == mCurTokenDisplayId;
     }
 
@@ -2417,16 +2459,16 @@
     @GuardedBy("mMethodMap")
     @Nullable
     private InputBindResult tryReuseConnectionLocked(@NonNull ClientState cs) {
-        if (hasConnection()) {
-            if (getCurMethod() != null) {
+        if (hasConnectionLocked()) {
+            if (getCurMethodLocked() != null) {
                 // Return to client, and we will get back with it when
                 // we have had a session made for it.
                 requestClientSessionLocked(cs);
                 return new InputBindResult(
                         InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
-                        null, null, getCurId(), getSequenceNumber(), false);
+                        null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
             } else {
-                long bindingDuration = SystemClock.uptimeMillis() - getLastBindTime();
+                long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked();
                 if (bindingDuration < TIME_TO_RECONNECT) {
                     // In this case we have connected to the service, but
                     // don't yet have its interface.  If it hasn't been too
@@ -2437,10 +2479,10 @@
                     // to see if we can get back in touch with the service.
                     return new InputBindResult(
                             InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
-                            null, null, getCurId(), getSequenceNumber(), false);
+                            null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
                 } else {
                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
-                            getSelectedMethodId(), bindingDuration, 0);
+                            getSelectedMethodIdLocked(), bindingDuration, 0);
                 }
             }
         }
@@ -2493,7 +2535,7 @@
                 channel.dispose();
                 return;
             }
-            IInputMethod curMethod = getCurMethod();
+            IInputMethod curMethod = getCurMethodLocked();
             if (curMethod != null && method != null
                     && curMethod.asBinder() == method.asBinder()) {
                 if (mCurClient != null) {
@@ -2527,8 +2569,8 @@
 
     @GuardedBy("mMethodMap")
     void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
-        setSelectedMethodId(null);
-        mBindingController.unbindCurrentMethodLocked();
+        setSelectedMethodIdLocked(null);
+        mBindingController.unbindCurrentMethod();
         unbindCurrentClientLocked(unbindClientReason);
     }
 
@@ -2546,7 +2588,7 @@
             if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
             InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
             cs.sessionRequested = true;
-            IInputMethod curMethod = getCurMethod();
+            IInputMethod curMethod = getCurMethodLocked();
             executeOrSendMessage(curMethod, mCaller.obtainMessageOOO(
                     MSG_CREATE_SESSION, curMethod, channels[1],
                     new MethodCallback(this, curMethod, channels[0])));
@@ -2581,7 +2623,7 @@
 
     @GuardedBy("mMethodMap")
     void clearClientSessionsLocked() {
-        if (getCurMethod() != null) {
+        if (getCurMethodLocked() != null) {
             final int numClients = mClients.size();
             for (int i = 0; i < numClients; ++i) {
                 clearClientSessionLocked(mClients.valueAt(i));
@@ -2773,7 +2815,7 @@
     // Caution! This method is called in this class. Handle multi-user carefully
     @GuardedBy("mMethodMap")
     private void updateSystemUiLocked(int vis, int backDisposition) {
-        if (getCurToken() == null) {
+        if (getCurTokenLocked() == null) {
             return;
         }
         if (DEBUG) {
@@ -2798,10 +2840,10 @@
             // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
             final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
             if (mStatusBar != null) {
-                mStatusBar.setImeWindowStatus(mCurTokenDisplayId, getCurToken(), vis,
+                mStatusBar.setImeWindowStatus(mCurTokenDisplayId, getCurTokenLocked(), vis,
                         backDisposition, needsToShowImeSwitcher);
             }
-            final InputMethodInfo imi = mMethodMap.get(getSelectedMethodId());
+            final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
             if (imi != null && needsToShowImeSwitcher) {
                 // Used to load label
                 final CharSequence title = mRes.getText(
@@ -2909,7 +2951,7 @@
         }
 
         // See if we need to notify a subtype change within the same IME.
-        if (id.equals(getSelectedMethodId())) {
+        if (id.equals(getSelectedMethodIdLocked())) {
             final int subtypeCount = info.getSubtypeCount();
             if (subtypeCount <= 0) {
                 return;
@@ -2930,7 +2972,7 @@
             }
             if (newSubtype != oldSubtype) {
                 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
-                IInputMethod curMethod = getCurMethod();
+                IInputMethod curMethod = getCurMethodLocked();
                 if (curMethod != null) {
                     try {
                         updateSystemUiLocked(mImeWindowVis, mBackDisposition);
@@ -2952,7 +2994,7 @@
             // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
             // because mCurMethodId is stored as a history in
             // setSelectedInputMethodAndSubtypeLocked().
-            setSelectedMethodId(id);
+            setSelectedMethodIdLocked(id);
 
             if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) {
                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -3045,12 +3087,12 @@
             return false;
         }
 
-        mBindingController.setCurrentMethodVisibleLocked();
-        if (getCurMethod() != null) {
+        mBindingController.setCurrentMethodVisible();
+        if (getCurMethodLocked() != null) {
             // create a placeholder token for IMS so that IMS cannot inject windows into client app.
             Binder showInputToken = new Binder();
             mShowRequestWindowMap.put(showInputToken, windowToken);
-            IInputMethod curMethod = getCurMethod();
+            IInputMethod curMethod = getCurMethodLocked();
             executeOrSendMessage(curMethod, mCaller.obtainMessageIIOOO(MSG_SHOW_SOFT_INPUT,
                     getImeShowFlagsLocked(), reason, curMethod, resultReceiver,
                     showInputToken));
@@ -3125,7 +3167,7 @@
         // since Android Eclair.  That's why we need to accept IMM#hideSoftInput() even when only
         // IMMS#InputShown indicates that the software keyboard is shown.
         // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
-        IInputMethod curMethod = getCurMethod();
+        IInputMethod curMethod = getCurMethodLocked();
         final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown
                 || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
         boolean res;
@@ -3142,7 +3184,7 @@
         } else {
             res = false;
         }
-        mBindingController.setCurrentMethodNotVisibleLocked();
+        mBindingController.setCurrentMethodNotVisible();
         mInputShown = false;
         mShowRequested = false;
         mShowExplicitlyRequested = false;
@@ -3316,7 +3358,7 @@
             }
             if (attribute != null) {
                 return startInputUncheckedLocked(cs, inputContext, attribute, startInputFlags,
-                        startInputReason);
+                        startInputReason, unverifiedTargetSdkVersion);
             }
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
@@ -3357,7 +3399,7 @@
         if (isTextEditor && attribute != null
                 && shouldRestoreImeVisibility(windowToken, softInputMode)) {
             res = startInputUncheckedLocked(cs, inputContext, attribute, startInputFlags,
-                    startInputReason);
+                    startInputReason, unverifiedTargetSdkVersion);
             showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
                     SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
             return res;
@@ -3381,7 +3423,7 @@
                         // Note that we can trust client's display ID as long as it matches
                         // to the display ID obtained from the window.
                         if (cs.selfReportedDisplayId != mCurTokenDisplayId) {
-                            mBindingController.unbindCurrentMethodLocked();
+                            mBindingController.unbindCurrentMethod();
                         }
                     }
                 } else if (isTextEditor && doAutoShow
@@ -3396,7 +3438,7 @@
                     if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
                     if (attribute != null) {
                         res = startInputUncheckedLocked(cs, inputContext, attribute,
-                                startInputFlags, startInputReason);
+                                startInputFlags, startInputReason, unverifiedTargetSdkVersion);
                         didStart = true;
                     }
                     showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
@@ -3427,7 +3469,7 @@
                             unverifiedTargetSdkVersion, startInputFlags)) {
                         if (attribute != null) {
                             res = startInputUncheckedLocked(cs, inputContext, attribute,
-                                    startInputFlags, startInputReason);
+                                    startInputFlags, startInputReason, unverifiedTargetSdkVersion);
                             didStart = true;
                         }
                         showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
@@ -3446,7 +3488,7 @@
                     if (!sameWindowFocused) {
                         if (attribute != null) {
                             res = startInputUncheckedLocked(cs, inputContext, attribute,
-                                    startInputFlags, startInputReason);
+                                    startInputFlags, startInputReason, unverifiedTargetSdkVersion);
                             didStart = true;
                         }
                         showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
@@ -3475,7 +3517,7 @@
                     }
                 }
                 res = startInputUncheckedLocked(cs, inputContext, attribute, startInputFlags,
-                        startInputReason);
+                        startInputReason, unverifiedTargetSdkVersion);
             } else {
                 res = InputBindResult.NULL_EDITOR_INFO;
             }
@@ -3503,10 +3545,10 @@
         if (mCurFocusedWindowClient != null && client != null
                 && mCurFocusedWindowClient.client.asBinder() == client.asBinder()) {
             return true;
-        } else if (getCurIntent() != null && InputMethodUtils.checkIfPackageBelongsToUid(
+        } else if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
                 mAppOpsManager,
                 uid,
-                getCurIntent().getComponent().getPackageName())) {
+                getCurIntentLocked().getComponent().getPackageName())) {
             return true;
         }
         return false;
@@ -3592,7 +3634,7 @@
             if (!calledFromValidUserLocked()) {
                 return;
             }
-            executeOrSendMessage(getCurMethod(), mCaller.obtainMessageO(
+            executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageO(
                     MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
         }
     }
@@ -3613,7 +3655,7 @@
             String targetLastImiId = null;
             int subtypeId = NOT_A_SUBTYPE_ID;
             if (lastIme != null && lastImi != null) {
-                final boolean imiIdIsSame = lastImi.getId().equals(getSelectedMethodId());
+                final boolean imiIdIsSame = lastImi.getId().equals(getSelectedMethodIdLocked());
                 final int lastSubtypeHash = Integer.parseInt(lastIme.second);
                 final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
                         : mCurrentSubtype.hashCode();
@@ -3659,7 +3701,7 @@
             if (!TextUtils.isEmpty(targetLastImiId)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
-                            + ", from: " + getSelectedMethodId() + ", " + subtypeId);
+                            + ", from: " + getSelectedMethodIdLocked() + ", " + subtypeId);
                 }
                 setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
                 return true;
@@ -3676,7 +3718,7 @@
                 return false;
             }
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    onlyCurrentIme, mMethodMap.get(getSelectedMethodId()), mCurrentSubtype);
+                    onlyCurrentIme, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype);
             if (nextSubtype == null) {
                 return false;
             }
@@ -3693,7 +3735,7 @@
                 return false;
             }
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    false /* onlyCurrentIme */, mMethodMap.get(getSelectedMethodId()),
+                    false /* onlyCurrentIme */, mMethodMap.get(getSelectedMethodIdLocked()),
                     mCurrentSubtype);
             if (nextSubtype == null) {
                 return false;
@@ -3908,8 +3950,8 @@
     private void dumpDebug(ProtoOutputStream proto, long fieldId) {
         synchronized (mMethodMap) {
             final long token = proto.start(fieldId);
-            proto.write(CUR_METHOD_ID, getSelectedMethodId());
-            proto.write(CUR_SEQ, getSequenceNumber());
+            proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
+            proto.write(CUR_SEQ, getSequenceNumberLocked());
             proto.write(CUR_CLIENT, Objects.toString(mCurClient));
             proto.write(CUR_FOCUSED_WINDOW_NAME,
                     mWindowManagerInternal.getWindowName(mCurFocusedWindow));
@@ -3920,17 +3962,17 @@
             if (mCurAttribute != null) {
                 mCurAttribute.dumpDebug(proto, CUR_ATTRIBUTE);
             }
-            proto.write(CUR_ID, getCurId());
+            proto.write(CUR_ID, getCurIdLocked());
             proto.write(SHOW_REQUESTED, mShowRequested);
             proto.write(SHOW_EXPLICITLY_REQUESTED, mShowExplicitlyRequested);
             proto.write(SHOW_FORCED, mShowForced);
             proto.write(INPUT_SHOWN, mInputShown);
             proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode);
-            proto.write(CUR_TOKEN, Objects.toString(getCurToken()));
+            proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
             proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
             proto.write(SYSTEM_READY, mSystemReady);
             proto.write(LAST_SWITCH_USER_ID, mLastSwitchUserId);
-            proto.write(HAVE_CONNECTION, hasConnection());
+            proto.write(HAVE_CONNECTION, hasConnectionLocked());
             proto.write(BOUND_TO_METHOD, mBoundToMethod);
             proto.write(IS_INTERACTIVE, mIsInteractive);
             proto.write(BACK_DISPOSITION, mBackDisposition);
@@ -3948,14 +3990,14 @@
             Slog.d(TAG, "Got the notification of a user action.");
         }
         synchronized (mMethodMap) {
-            if (getCurToken() != token) {
+            if (getCurTokenLocked() != token) {
                 if (DEBUG) {
                     Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer"
                             + " active.");
                 }
                 return;
             }
-            final InputMethodInfo imi = mMethodMap.get(getSelectedMethodId());
+            final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
             if (imi != null) {
                 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
             }
@@ -3999,7 +4041,7 @@
                         "Using null token requires permission "
                         + android.Manifest.permission.WRITE_SECURE_SETTINGS);
             }
-        } else if (getCurToken() != token) {
+        } else if (getCurTokenLocked() != token) {
             Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
                     + " token: " + token);
             return;
@@ -4371,7 +4413,7 @@
                 boolean reportToImeController = false;
                 try {
                     reportToImeController = mPlatformCompat.isChangeEnabledByUid(
-                            FINISH_INPUT_NO_FALLBACK_CONNECTION, getCurMethodUid());
+                            FINISH_INPUT_NO_FALLBACK_CONNECTION, getCurMethodUidLocked());
                 } catch (RemoteException e) {
                 }
                 scheduleSetActiveToClient(mCurClient, mIsInteractive, mInFullscreenMode,
@@ -4675,7 +4717,8 @@
     @GuardedBy("mMethodMap")
     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
             boolean setSubtypeOnly) {
-        mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodId(), mCurrentSubtype);
+        mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
+                mCurrentSubtype);
 
         // Set Subtype here
         if (imi == null || subtypeId < 0) {
@@ -4734,7 +4777,7 @@
 
     @GuardedBy("mMethodMap")
     InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
-        String selectedMethodId = getSelectedMethodId();
+        String selectedMethodId = getSelectedMethodIdLocked();
         if (selectedMethodId == null) {
             return null;
         }
@@ -4775,7 +4818,7 @@
 
     @Nullable
     String getCurrentMethodId() {
-        return getSelectedMethodId();
+        return getSelectedMethodIdLocked();
     }
 
     private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
@@ -4988,11 +5031,11 @@
 
         synchronized (mMethodMap) {
             final int uid = Binder.getCallingUid();
-            if (getSelectedMethodId() == null) {
+            if (getSelectedMethodIdLocked() == null) {
                 return null;
             }
-            if (getCurToken() != token) {
-                Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + getCurToken()
+            if (getCurTokenLocked() != token) {
+                Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + getCurTokenLocked()
                         + " token=" + token);
                 return null;
             }
@@ -5127,24 +5170,24 @@
                 p.println("    sessionRequested=" + ci.sessionRequested);
                 p.println("    curSession=" + ci.curSession);
             }
-            p.println("  mCurMethodId=" + getSelectedMethodId());
+            p.println("  mCurMethodId=" + getSelectedMethodIdLocked());
             client = mCurClient;
-            p.println("  mCurClient=" + client + " mCurSeq=" + getSequenceNumber());
+            p.println("  mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
             p.println("  mCurPerceptible=" + mCurPerceptible);
             p.println("  mCurFocusedWindow=" + mCurFocusedWindow
                     + " softInputMode=" +
                     InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode)
                     + " client=" + mCurFocusedWindowClient);
             focusedWindowClient = mCurFocusedWindowClient;
-            p.println("  mCurId=" + getCurId() + " mHaveConnection=" + hasConnection()
+            p.println("  mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
                     + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
                     + mBindingController.isVisibleBound());
-            p.println("  mCurToken=" + getCurToken());
+            p.println("  mCurToken=" + getCurTokenLocked());
             p.println("  mCurTokenDisplayId=" + mCurTokenDisplayId);
             p.println("  mCurHostInputToken=" + mCurHostInputToken);
-            p.println("  mCurIntent=" + getCurIntent());
-            method = getCurMethod();
-            p.println("  mCurMethod=" + getCurMethod());
+            p.println("  mCurIntent=" + getCurIntentLocked());
+            method = getCurMethodLocked();
+            p.println("  mCurMethod=" + getCurMethodLocked());
             p.println("  mEnabledSession=" + mEnabledSession);
             p.println("  mShowRequested=" + mShowRequested
                     + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
@@ -5644,7 +5687,7 @@
                 if (userId == mSettings.getCurrentUserId()) {
                     hideCurrentInputLocked(mCurFocusedWindow, 0, null,
                             SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
-                    mBindingController.unbindCurrentMethodLocked();
+                    mBindingController.unbindCurrentMethod();
                     // Reset the current IME
                     resetSelectedInputMethodAndSubtypeLocked(null);
                     // Also reset the settings of the current IME
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 0fd7cc1..e40d86a 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -331,15 +331,7 @@
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
 
         startMonitoringOpChanges();
-
-        HostEndpointInfo info = new HostEndpointInfo();
-        info.hostEndpointId = (char) mHostEndPointId;
-        info.packageName = mPackage;
-        info.attributionTag = mAttributionTag;
-        info.type = (mUid == Process.SYSTEM_UID)
-             ? HostEndpointInfo.Type.TYPE_FRAMEWORK
-             : HostEndpointInfo.Type.TYPE_APP;
-        mContextHubProxy.onHostEndpointConnected(info);
+        sendHostEndpointConnectedEvent();
     }
 
     /* package */ ContextHubClientBroker(
@@ -556,6 +548,9 @@
     /* package */ void onHubReset() {
         invokeCallback(callback -> callback.onHubReset());
         sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_HUB_RESET));
+
+        // Re-send the host endpoint connected event as the Context Hub restarted.
+        sendHostEndpointConnectedEvent();
     }
 
     /**
@@ -895,6 +890,17 @@
         }
     }
 
+    private void sendHostEndpointConnectedEvent() {
+        HostEndpointInfo info = new HostEndpointInfo();
+        info.hostEndpointId = (char) mHostEndPointId;
+        info.packageName = mPackage;
+        info.attributionTag = mAttributionTag;
+        info.type = (mUid == Process.SYSTEM_UID)
+             ? HostEndpointInfo.Type.TYPE_FRAMEWORK
+             : HostEndpointInfo.Type.TYPE_APP;
+        mContextHubProxy.onHostEndpointConnected(info);
+    }
+
     /**
      * Dump debugging info as ClientBrokerProto
      *
diff --git a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
index 47146c1..d08e5dc 100644
--- a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
@@ -21,26 +21,23 @@
 import android.annotation.Nullable;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import java.lang.reflect.Array;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.ConcurrentModificationException;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 
 /**
  * An in-memory event log to support historical event information. The log is of a constant size,
  * and new events will overwrite old events as the log fills up.
- *
- * @param <T> log event type
  */
 public class LocalEventLog<T> {
 
-    /**
-     * Consumer of log events for iterating over the log.
-     *
-     * @param <T> log event type
-     */
+    /** Consumer of log events for iterating over the log. */
     public interface LogConsumer<T> {
         /** Invoked with a time and a logEvent. */
         void acceptLog(long time, T logEvent);
@@ -48,12 +45,13 @@
 
     // masks for the entries field. 1 bit is used to indicate whether this is a filler event or not,
     // and 31 bits to store the time delta.
-    private static final int IS_FILLER_MASK = 0b10000000000000000000000000000000;
+    private static final int IS_FILLER_MASK  = 0b10000000000000000000000000000000;
     private static final int TIME_DELTA_MASK = 0b01111111111111111111111111111111;
 
     private static final int IS_FILLER_OFFSET = countTrailingZeros(IS_FILLER_MASK);
     private static final int TIME_DELTA_OFFSET = countTrailingZeros(TIME_DELTA_MASK);
 
+    @VisibleForTesting
     static final int MAX_TIME_DELTA = (1 << bitCount(TIME_DELTA_MASK)) - 1;
 
     private static int countTrailingZeros(int i) {
@@ -79,7 +77,7 @@
         return (entry & IS_FILLER_MASK) != 0;
     }
 
-    // circular buffer of log entries and events. each entry corrosponds to the log event at the
+    // circular buffer of log entries and events. each entry corresponds to the log event at the
     // same index. the log entry holds the filler status and time delta according to the bit masks
     // above, and the log event is the log event.
 
@@ -103,6 +101,9 @@
     @GuardedBy("this")
     long mLastLogTime;
 
+    @GuardedBy("this")
+    long mModificationCount;
+
     @SuppressWarnings("unchecked")
     public LocalEventLog(int size, Class<T> clazz) {
         Preconditions.checkArgument(size > 0);
@@ -143,6 +144,7 @@
         if (isEmpty()) {
             mStartTime = time;
             mLastLogTime = mStartTime;
+            mModificationCount++;
         }
 
         addLogEventInternal(false, (int) delta, logEvent);
@@ -156,6 +158,7 @@
         if (mLogSize == mEntries.length) {
             // if log is full, size will remain the same, but update the start time
             mStartTime += getTimeDelta(mEntries[startIndex()]);
+            mModificationCount++;
         } else {
             // otherwise add an item
             mLogSize++;
@@ -170,11 +173,12 @@
 
     /** Clears the log of all entries. */
     public synchronized void clear() {
-        // clear entries to allow gc
+        // clear entries to aid gc
         Arrays.fill(mLogEvents, null);
 
         mLogEndIndex = 0;
         mLogSize = 0;
+        mModificationCount++;
 
         mStartTime = -1;
         mLastLogTime = -1;
@@ -186,7 +190,10 @@
         return mLogSize == 0;
     }
 
-    /** Iterates over the event log, passing each log string to the given consumer. */
+    /**
+     * Iterates over the event log, passing each log event to the given consumer. Locks the log
+     * while executing so that {@link ConcurrentModificationException}s cannot occur.
+     */
     public synchronized void iterate(LogConsumer<? super T> consumer) {
         LogIterator it = new LogIterator();
         while (it.hasNext()) {
@@ -195,15 +202,53 @@
         }
     }
 
+    /**
+     * Iterates over all the given event logs in time order, passing each log event to the given
+     * consumer. It is the caller's responsibility to ensure that {@link
+     * ConcurrentModificationException}s cannot occur, whether through locking or other means.
+     */
+    @SafeVarargs
+    public static <T> void iterate(LogConsumer<? super T> consumer, LocalEventLog<T>... logs) {
+        ArrayList<LocalEventLog<T>.LogIterator> its = new ArrayList<>(logs.length);
+        for (LocalEventLog<T> log : logs) {
+            LocalEventLog<T>.LogIterator it = log.new LogIterator();
+            if (it.hasNext()) {
+                its.add(it);
+                it.next();
+            }
+        }
+
+        while (true) {
+            LocalEventLog<T>.LogIterator next = null;
+            for (LocalEventLog<T>.LogIterator it : its) {
+                if (it != null && (next == null || it.getTime() < next.getTime())) {
+                    next = it;
+                }
+            }
+
+            if (next == null) {
+                return;
+            }
+
+            consumer.acceptLog(next.getTime(), next.getLog());
+
+            if (next.hasNext()) {
+                next.next();
+            } else {
+                its.remove(next);
+            }
+        }
+    }
+
     // returns the index of the first element
     @GuardedBy("this")
-    private int startIndex() {
+    int startIndex() {
         return wrapIndex(mLogEndIndex - mLogSize);
     }
 
     // returns the index after this one
     @GuardedBy("this")
-    private int incrementIndex(int index) {
+    int incrementIndex(int index) {
         if (index == -1) {
             return startIndex();
         } else if (index >= 0) {
@@ -215,12 +260,15 @@
 
     // rolls over the given index if necessary
     @GuardedBy("this")
-    private int wrapIndex(int index) {
+    int wrapIndex(int index) {
         // java modulo will keep negative sign, we need to rollover
         return (index % mEntries.length + mEntries.length) % mEntries.length;
     }
 
-    private class LogIterator {
+    /** Iterator over log times and events. */
+    protected final class LogIterator {
+
+        private final long mModificationCount;
 
         private long mLogTime;
         private int mIndex;
@@ -229,8 +277,10 @@
         private long mCurrentTime;
         private T mCurrentLogEvent;
 
-        LogIterator() {
+        public LogIterator() {
             synchronized (LocalEventLog.this) {
+                mModificationCount = LocalEventLog.this.mModificationCount;
+
                 mLogTime = mStartTime;
                 mIndex = -1;
                 mCount = -1;
@@ -241,6 +291,7 @@
 
         public boolean hasNext() {
             synchronized (LocalEventLog.this) {
+                checkModifications();
                 return mCount < mLogSize;
             }
         }
@@ -277,5 +328,12 @@
                 }
             } while (mCount < mLogSize && isFiller(mEntries[mIndex]));
         }
+
+        @GuardedBy("LocalEventLog.this")
+        private void checkModifications() {
+            if (mModificationCount != LocalEventLog.this.mModificationCount) {
+                throw new ConcurrentModificationException();
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 94953e0..45436e7 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -52,16 +52,28 @@
         if (D) {
             return 600;
         } else {
+            return 300;
+        }
+    }
+
+    private static int getLocationsLogSize() {
+        if (D) {
             return 200;
+        } else {
+            return 100;
         }
     }
 
     @GuardedBy("mAggregateStats")
     private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats;
 
-    public LocationEventLog() {
+    @GuardedBy("this")
+    private final LocationsEventLog mLocationsLog;
+
+    private LocationEventLog() {
         super(getLogSize(), Object.class);
         mAggregateStats = new ArrayMap<>(4);
+        mLocationsLog = new LocationsEventLog(getLocationsLogSize());
     }
 
     /** Copies out all aggregated stats. */
@@ -95,39 +107,39 @@
 
     /** Logs a user switched event. */
     public void logUserSwitched(int userIdFrom, int userIdTo) {
-        addLogEvent(new UserSwitchedEvent(userIdFrom, userIdTo));
+        addLog(new UserSwitchedEvent(userIdFrom, userIdTo));
     }
 
     /** Logs a location enabled/disabled event. */
     public void logLocationEnabled(int userId, boolean enabled) {
-        addLogEvent(new LocationEnabledEvent(userId, enabled));
+        addLog(new LocationEnabledEvent(userId, enabled));
     }
 
     /** Logs a location enabled/disabled event. */
     public void logAdasLocationEnabled(int userId, boolean enabled) {
-        addLogEvent(new LocationAdasEnabledEvent(userId, enabled));
+        addLog(new LocationAdasEnabledEvent(userId, enabled));
     }
 
     /** Logs a location provider enabled/disabled event. */
     public void logProviderEnabled(String provider, int userId, boolean enabled) {
-        addLogEvent(new ProviderEnabledEvent(provider, userId, enabled));
+        addLog(new ProviderEnabledEvent(provider, userId, enabled));
     }
 
     /** Logs a location provider being replaced/unreplaced by a mock provider. */
     public void logProviderMocked(String provider, boolean mocked) {
-        addLogEvent(new ProviderMockedEvent(provider, mocked));
+        addLog(new ProviderMockedEvent(provider, mocked));
     }
 
     /** Logs a new client registration for a location provider. */
     public void logProviderClientRegistered(String provider, CallerIdentity identity,
             LocationRequest request) {
-        addLogEvent(new ProviderClientRegisterEvent(provider, true, identity, request));
+        addLog(new ProviderClientRegisterEvent(provider, true, identity, request));
         getAggregateStats(provider, identity).markRequestAdded(request.getIntervalMillis());
     }
 
     /** Logs a client unregistration for a location provider. */
     public void logProviderClientUnregistered(String provider, CallerIdentity identity) {
-        addLogEvent(new ProviderClientRegisterEvent(provider, false, identity, null));
+        addLog(new ProviderClientRegisterEvent(provider, false, identity, null));
         getAggregateStats(provider, identity).markRequestRemoved();
     }
 
@@ -144,7 +156,7 @@
     /** Logs a client for a location provider entering the foreground state. */
     public void logProviderClientForeground(String provider, CallerIdentity identity) {
         if (D) {
-            addLogEvent(new ProviderClientForegroundEvent(provider, true, identity));
+            addLog(new ProviderClientForegroundEvent(provider, true, identity));
         }
         getAggregateStats(provider, identity).markRequestForeground();
     }
@@ -152,7 +164,7 @@
     /** Logs a client for a location provider leaving the foreground state. */
     public void logProviderClientBackground(String provider, CallerIdentity identity) {
         if (D) {
-            addLogEvent(new ProviderClientForegroundEvent(provider, false, identity));
+            addLog(new ProviderClientForegroundEvent(provider, false, identity));
         }
         getAggregateStats(provider, identity).markRequestBackground();
     }
@@ -160,32 +172,34 @@
     /** Logs a client for a location provider entering the permitted state. */
     public void logProviderClientPermitted(String provider, CallerIdentity identity) {
         if (D) {
-            addLogEvent(new ProviderClientPermittedEvent(provider, true, identity));
+            addLog(new ProviderClientPermittedEvent(provider, true, identity));
         }
     }
 
     /** Logs a client for a location provider leaving the permitted state. */
     public void logProviderClientUnpermitted(String provider, CallerIdentity identity) {
         if (D) {
-            addLogEvent(new ProviderClientPermittedEvent(provider, false, identity));
+            addLog(new ProviderClientPermittedEvent(provider, false, identity));
         }
     }
 
     /** Logs a change to the provider request for a location provider. */
     public void logProviderUpdateRequest(String provider, ProviderRequest request) {
-        addLogEvent(new ProviderUpdateEvent(provider, request));
+        addLog(new ProviderUpdateEvent(provider, request));
     }
 
     /** Logs a new incoming location for a location provider. */
     public void logProviderReceivedLocations(String provider, int numLocations) {
-        addLogEvent(new ProviderReceiveLocationEvent(provider, numLocations));
+        synchronized (this) {
+            mLocationsLog.logProviderReceivedLocations(provider, numLocations);
+        }
     }
 
     /** Logs a location deliver for a client of a location provider. */
     public void logProviderDeliveredLocations(String provider, int numLocations,
             CallerIdentity identity) {
-        if (D) {
-            addLogEvent(new ProviderDeliverLocationEvent(provider, numLocations, identity));
+        synchronized (this) {
+            mLocationsLog.logProviderDeliveredLocations(provider, numLocations, identity);
         }
         getAggregateStats(provider, identity).markLocationDelivered();
     }
@@ -193,19 +207,24 @@
     /** Logs that a provider has entered or exited stationary throttling. */
     public void logProviderStationaryThrottled(String provider, boolean throttled,
             ProviderRequest request) {
-        addLogEvent(new ProviderStationaryThrottledEvent(provider, throttled, request));
+        addLog(new ProviderStationaryThrottledEvent(provider, throttled, request));
     }
 
     /** Logs that the location power save mode has changed. */
     public void logLocationPowerSaveMode(
             @LocationPowerSaveMode int locationPowerSaveMode) {
-        addLogEvent(new LocationPowerSaveModeEvent(locationPowerSaveMode));
+        addLog(new LocationPowerSaveModeEvent(locationPowerSaveMode));
     }
 
-    private void addLogEvent(Object logEvent) {
+    private void addLog(Object logEvent) {
         addLog(SystemClock.elapsedRealtime(), logEvent);
     }
 
+    @Override
+    public synchronized void iterate(LogConsumer<? super Object> consumer) {
+        iterate(consumer, this, mLocationsLog);
+    }
+
     public void iterate(Consumer<String> consumer) {
         iterate(consumer, null);
     }
@@ -488,6 +507,26 @@
         }
     }
 
+    private static final class LocationsEventLog extends LocalEventLog<Object> {
+
+        LocationsEventLog(int size) {
+            super(size, Object.class);
+        }
+
+        public void logProviderReceivedLocations(String provider, int numLocations) {
+            addLog(new ProviderReceiveLocationEvent(provider, numLocations));
+        }
+
+        public void logProviderDeliveredLocations(String provider, int numLocations,
+                CallerIdentity identity) {
+            addLog(new ProviderDeliverLocationEvent(provider, numLocations, identity));
+        }
+
+        private void addLog(Object logEvent) {
+            this.addLog(SystemClock.elapsedRealtime(), logEvent);
+        }
+    }
+
     /**
      * Aggregate statistics for a single package under a single provider.
      */
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 0a47cbb..65c5b88 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -2005,7 +2005,7 @@
         for (final NetworkStateSnapshot snapshot : snapshots) {
             mNetIdToSubId.put(snapshot.getNetwork().getNetId(), parseSubId(snapshot));
 
-            // Policies matched by NPMS only match by subscriber ID or by ssid. Thus subtype
+            // Policies matched by NPMS only match by subscriber ID or by network ID. Thus subtype
             // in the object created here is never used and its value doesn't matter, so use
             // NETWORK_TYPE_UNKNOWN.
             final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot,
@@ -2435,7 +2435,6 @@
                         }
                         final NetworkTemplate.Builder builder =
                                 new NetworkTemplate.Builder(templateType)
-                                        .setWifiNetworkKey(networkId)
                                         .setMeteredness(templateMeteredness);
                         if (subscriberIdMatchRule
                                 == NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT) {
@@ -2443,6 +2442,9 @@
                             ids.add(subscriberId);
                             builder.setSubscriberIds(ids);
                         }
+                        if (networkId != null) {
+                            builder.setWifiNetworkKeys(Set.of(networkId));
+                        }
                         final NetworkTemplate template = builder.build();
                         if (NetworkPolicy.isTemplatePersistable(template)) {
                             mNetworkPolicy.put(template, new NetworkPolicy(template, cycleRule,
@@ -2593,35 +2595,39 @@
      * into {@link WifiConfiguration}.
      */
     private void upgradeWifiMeteredOverride() {
-        final ArrayMap<String, Boolean> wifiNetworkIds = new ArrayMap<>();
+        final ArrayMap<String, Boolean> wifiNetworkKeys = new ArrayMap<>();
         synchronized (mNetworkPoliciesSecondLock) {
             for (int i = 0; i < mNetworkPolicy.size();) {
                 final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
                 if (policy.template.getMatchRule() == NetworkTemplate.MATCH_WIFI
                         && !policy.inferred) {
                     mNetworkPolicy.removeAt(i);
-                    wifiNetworkIds.put(policy.template.getNetworkId(), policy.metered);
+                    final Set<String> keys = policy.template.getWifiNetworkKeys();
+                    wifiNetworkKeys.put(keys.isEmpty() ? null : keys.iterator().next(),
+                            policy.metered);
                 } else {
                     i++;
                 }
             }
         }
 
-        if (wifiNetworkIds.isEmpty()) {
+        if (wifiNetworkKeys.isEmpty()) {
             return;
         }
         final WifiManager wm = mContext.getSystemService(WifiManager.class);
         final List<WifiConfiguration> configs = wm.getConfiguredNetworks();
         for (int i = 0; i < configs.size(); ++i) {
             final WifiConfiguration config = configs.get(i);
-            final String networkId = resolveNetworkId(config);
-            final Boolean metered = wifiNetworkIds.get(networkId);
-            if (metered != null) {
-                Slog.d(TAG, "Found network " + networkId + "; upgrading metered hint");
-                config.meteredOverride = metered
-                        ? WifiConfiguration.METERED_OVERRIDE_METERED
-                        : WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
-                wm.updateNetwork(config);
+            for (String key : config.getAllPersistableNetworkKeys()) {
+                final Boolean metered = wifiNetworkKeys.get(key);
+                if (metered != null) {
+                    Slog.d(TAG, "Found network " + key + "; upgrading metered hint");
+                    config.meteredOverride = metered
+                            ? WifiConfiguration.METERED_OVERRIDE_METERED
+                            : WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
+                    wm.updateNetwork(config);
+                    break;
+                }
             }
         }
 
@@ -2663,9 +2669,9 @@
                         ? NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
                         : NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT;
                 writeIntAttribute(out, ATTR_SUBSCRIBER_ID_MATCH_RULE, subscriberIdMatchRule);
-                final String networkId = template.getNetworkId();
-                if (networkId != null) {
-                    out.attribute(null, ATTR_NETWORK_ID, networkId);
+                if (!template.getWifiNetworkKeys().isEmpty()) {
+                    out.attribute(null, ATTR_NETWORK_ID,
+                            template.getWifiNetworkKeys().iterator().next());
                 }
                 writeIntAttribute(out, ATTR_TEMPLATE_METERED,
                         template.getMeteredness());
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index e84d990..c26f3b3 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -547,7 +547,6 @@
                     true /*notLaunched*/,
                     false /*hidden*/,
                     0 /*distractionFlags*/,
-                    false /*suspended*/,
                     null /*suspendParams*/,
                     false /*instantApp*/,
                     false /*virtualPreload*/,
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 339a951..c595b97 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -857,7 +857,7 @@
         mPm.notifyInstallObserver(request.mInstallResult, request.mArgs.mObserver);
     }
 
-    @GuardedBy("mInstallLock")
+    @GuardedBy("mPm.mInstallLock")
     private void installPackagesTracedLI(List<InstallRequest> requests) {
         try {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
@@ -886,7 +886,7 @@
      *
      * Failure at any phase will result in a full failure to install all packages.
      */
-    @GuardedBy("mInstallLock")
+    @GuardedBy("mPm.mInstallLock")
     private void installPackagesLI(List<InstallRequest> requests) {
         final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size());
         final Map<String, InstallArgs> installArgs = new ArrayMap<>(requests.size());
@@ -1038,7 +1038,7 @@
         }
     }
 
-    @GuardedBy("mInstallLock")
+    @GuardedBy("mPm.mInstallLock")
     private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
             throws PrepareFailure {
         final int installFlags = args.mInstallFlags;
@@ -1882,7 +1882,7 @@
         }
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mPm.mLock")
     private void commitPackagesLocked(final CommitRequest request) {
         // TODO: remove any expected failures from this method; this should only be able to fail due
         //       to unavoidable errors (I/O, etc.)
@@ -1999,7 +1999,7 @@
         ApplicationPackageManager.invalidateGetPackagesForUidCache();
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mPm.mLock")
     private boolean disableSystemPackageLPw(AndroidPackage oldPkg) {
         return mPm.mSettings.disableSystemPackageLPw(oldPkg.getPackageName(), true);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b2cd642..248944e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -239,6 +239,9 @@
 import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.SuspendParams;
+import com.android.server.pm.pkg.mutate.PackageStateMutator;
+import com.android.server.pm.pkg.mutate.PackageStateWrite;
+import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 import com.android.server.pm.verify.domain.DomainVerificationService;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
@@ -594,6 +597,16 @@
     // the suffix "Locked". Some methods may use the legacy suffix "LP"
     final PackageManagerTracedLock mLock;
 
+    // Lock alias for doing package state mutation
+    private final PackageManagerTracedLock mPackageStateWriteLock;
+
+    // Lock alias to track syncing a consistent Computer
+    private final PackageManagerTracedLock mLiveComputerSyncLock;
+
+    private final PackageStateMutator mPackageStateMutator = new PackageStateMutator(
+            this::getPackageSettingForMutation,
+            this::getDisabledPackageSettingForMutation);
+
     // Keys are String (package name), values are Package.
     @Watched
     @GuardedBy("mLock")
@@ -1608,6 +1621,8 @@
         mInstaller = injector.getInstaller();
         mInstallLock = injector.getInstallLock();
         mLock = injector.getLock();
+        mPackageStateWriteLock = mLock;
+        mLiveComputerSyncLock = mLock;
         mPermissionManager = injector.getPermissionManagerServiceInternal();
         mSettings = injector.getSettings();
         mUserManager = injector.getUserManagerService();
@@ -1713,6 +1728,8 @@
 
         mInjector.bootstrap(this);
         mLock = injector.getLock();
+        mPackageStateWriteLock = mLock;
+        mLiveComputerSyncLock = mLock;
         mInstallLock = injector.getInstallLock();
         LockGuard.installLock(mLock, LockGuard.INDEX_PACKAGES);
         EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
@@ -4553,8 +4570,8 @@
         if (pus.isSuspended()) {
             for (int i = 0; i < pus.getSuspendParams().size(); i++) {
                 final SuspendParams params = pus.getSuspendParams().valueAt(i);
-                if (params != null && params.appExtras != null) {
-                    allExtras.putAll(params.appExtras);
+                if (params != null && params.getAppExtras() != null) {
+                    allExtras.putAll(params.getAppExtras());
                 }
             }
         }
@@ -4627,9 +4644,9 @@
         synchronized (mLock) {
             for (String packageName : packagesToChange) {
                 final PackageSetting ps = mSettings.getPackageLPr(packageName);
-                if (ps != null && ps.getSuspended(userId)) {
+                if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) {
                     ps.removeSuspension(suspendingPackagePredicate, userId);
-                    if (!ps.getSuspended(userId)) {
+                    if (!ps.getUserStateOrDefault(userId).isSuspended()) {
                         unsuspendedPackages.add(ps.getPackageName());
                         unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId()));
                     }
@@ -7413,8 +7430,8 @@
             if (userState.isSuspended()) {
                 for (int i = 0; i < userState.getSuspendParams().size(); i++) {
                     final SuspendParams params = userState.getSuspendParams().valueAt(i);
-                    if (params != null && params.launcherExtras != null) {
-                        allExtras.putAll(params.launcherExtras);
+                    if (params != null && params.getLauncherExtras() != null) {
+                        allExtras.putAll(params.getLauncherExtras());
                     }
                 }
             }
@@ -7503,7 +7520,7 @@
             }
 
             final SuspendParams suspendParams = suspendParamsMap.get(suspendingPackage);
-            return (suspendParams != null) ? suspendParams.dialogInfo : null;
+            return (suspendParams != null) ? suspendParams.getDialogInfo() : null;
         }
 
         @Override
@@ -7949,7 +7966,13 @@
         @Override
         public void forEachInstalledPackage(@NonNull Consumer<AndroidPackage> actionLocked,
                 @UserIdInt int userId) {
-            PackageManagerService.this.forEachInstalledPackage(actionLocked, userId);
+            forEachInstalledPackage(true, actionLocked, userId);
+        }
+
+        @Override
+        public void forEachInstalledPackage(boolean locked,
+                @NonNull Consumer<AndroidPackage> action, int userId) {
+            PackageManagerService.this.forEachInstalledPackage(locked, action, userId);
         }
 
         @Override
@@ -8222,51 +8245,24 @@
         @Override
         public void withPackageSettingsSnapshot(
                 @NonNull Consumer<Function<String, PackageStateInternal>> block) {
-            final Computer snapshot = snapshotComputer();
-
-            // This method needs to either lock or not lock consistently throughout the method,
-            // so if the live computer is returned, force a wrapping sync block.
-            if (snapshot == mLiveComputer) {
-                synchronized (mLock) {
-                    block.accept(snapshot::getPackageStateInternal);
-                }
-            } else {
-                block.accept(snapshot::getPackageStateInternal);
-            }
+            executeWithConsistentComputer(computer ->
+                    block.accept(computer::getPackageStateInternal));
         }
 
         @Override
         public <Output> Output withPackageSettingsSnapshotReturning(
                 @NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageStateInternal>,
                         Output> block) {
-            final Computer snapshot = snapshotComputer();
-
-            // This method needs to either lock or not lock consistently throughout the method,
-            // so if the live computer is returned, force a wrapping sync block.
-            if (snapshot == mLiveComputer) {
-                synchronized (mLock) {
-                    return block.apply(snapshot::getPackageStateInternal);
-                }
-            } else {
-                return block.apply(snapshot::getPackageStateInternal);
-            }
+            return executeWithConsistentComputerReturning(computer ->
+                    block.apply(computer::getPackageStateInternal));
         }
 
         @Override
         public <ExceptionType extends Exception> void withPackageSettingsSnapshotThrowing(
                 @NonNull FunctionalUtils.ThrowingCheckedConsumer<Function<String,
                         PackageStateInternal>, ExceptionType> block) throws ExceptionType {
-            final Computer snapshot = snapshotComputer();
-
-            // This method needs to either lock or not lock consistently throughout the method,
-            // so if the live computer is returned, force a wrapping sync block.
-            if (snapshot == mLiveComputer) {
-                synchronized (mLock) {
-                    block.accept(snapshot::getPackageStateInternal);
-                }
-            } else {
-                block.accept(snapshot::getPackageStateInternal);
-            }
+            executeWithConsistentComputerThrowing(computer ->
+                    block.accept(computer::getPackageStateInternal));
         }
 
         @Override
@@ -8276,17 +8272,9 @@
                                 Function<String, PackageStateInternal>, ExceptionOne,
                                 ExceptionTwo> block)
                 throws ExceptionOne, ExceptionTwo {
-            final Computer snapshot = snapshotComputer();
-
-            // This method needs to either lock or not lock consistently throughout the method,
-            // so if the live computer is returned, force a wrapping sync block.
-            if (snapshot == mLiveComputer) {
-                synchronized (mLock) {
-                    block.accept(snapshot::getPackageStateInternal);
-                }
-            } else {
-                block.accept(snapshot::getPackageStateInternal);
-            }
+            executeWithConsistentComputerThrowing2(
+                    (FunctionalUtils.ThrowingChecked2Consumer<Computer, ExceptionOne,
+                            ExceptionTwo>) computer -> block.accept(computer::getPackageStateInternal));
         }
 
         @Override
@@ -8296,17 +8284,8 @@
                                 Function<String, PackageStateInternal>, Output,
                                 ExceptionType> block)
                 throws ExceptionType {
-            final Computer snapshot = snapshotComputer();
-
-            // This method needs to either lock or not lock consistently throughout the method,
-            // so if the live computer is returned, force a wrapping sync block.
-            if (snapshot == mLiveComputer) {
-                synchronized (mLock) {
-                    return block.apply(snapshot::getPackageStateInternal);
-                }
-            } else {
-                return block.apply(snapshot::getPackageStateInternal);
-            }
+            return executeWithConsistentComputerReturningThrowing(computer ->
+                    block.apply(computer::getPackageStateInternal));
         }
 
         @Override
@@ -8315,6 +8294,20 @@
             PackageManagerService.this.mAppDataHelper.reconcileAppsData(userId, flags,
                     migrateAppsData);
         }
+
+        @NonNull
+        @Override
+        public PackageStateMutator.InitialState recordInitialState() {
+            return PackageManagerService.this.recordInitialState();
+        }
+
+        @Nullable
+        @Override
+        public PackageStateMutator.Result commitPackageStateMutation(
+                @Nullable PackageStateMutator.InitialState state,
+                @NonNull Consumer<PackageStateMutator> consumer) {
+            return PackageManagerService.this.commitPackageStateMutation(state, consumer);
+        }
     }
 
     @Override
@@ -8357,7 +8350,15 @@
     @Nullable
     @GuardedBy("mLock")
     PackageSetting getPackageSettingForMutation(String packageName) {
-        return (PackageSetting) mComputer.getPackageStateInternal(packageName);
+        return mSettings.getPackageLPr(packageName);
+    }
+
+    // TODO: Remove
+    @Deprecated
+    @Nullable
+    @GuardedBy("mLock")
+    PackageSetting getDisabledPackageSettingForMutation(String packageName) {
+        return mSettings.getDisabledSystemPkgLPr(packageName);
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -8365,7 +8366,7 @@
     PackageStateInternal getPackageStateInternal(String packageName) {
         Computer computer = snapshotComputer();
         if (computer == mLiveComputer) {
-            synchronized (mLock) {
+            synchronized (mLiveComputerSyncLock) {
                 PackageSetting pkgSetting =
                         (PackageSetting) computer.getPackageStateInternal(packageName);
                 if (pkgSetting == null) {
@@ -8374,15 +8375,16 @@
 
                 return new PackageSetting(pkgSetting);
             }
+        } else {
+            return computer.getPackageStateInternal(packageName);
         }
-        return computer.getPackageStateInternal(packageName);
     }
 
     @Nullable
     PackageStateInternal getPackageStateInternal(String packageName, int callingUid) {
         Computer computer = snapshotComputer();
         if (computer == mLiveComputer) {
-            synchronized (mLock) {
+            synchronized (mLiveComputerSyncLock) {
                 PackageSetting pkgSetting =
                         (PackageSetting) computer.getPackageStateInternal(packageName, callingUid);
                 if (pkgSetting == null) {
@@ -8391,8 +8393,9 @@
 
                 return new PackageSetting(pkgSetting);
             }
+        } else {
+            return computer.getPackageStateInternal(packageName, callingUid);
         }
-        return computer.getPackageStateInternal(packageName, callingUid);
     }
 
     @Nullable
@@ -8400,7 +8403,7 @@
             int callingUid, @UserIdInt int userId) {
         Computer computer = snapshotComputer();
         if (computer == mLiveComputer) {
-            synchronized (mLock) {
+            synchronized (mLiveComputerSyncLock) {
                 PackageSetting pkgSetting =
                         (PackageSetting) filterPackageStateForInstalledAndFiltered(computer,
                                 packageName, callingUid, userId);
@@ -8409,9 +8412,10 @@
                 }
                 return new PackageSetting(pkgSetting);
             }
+        } else {
+            return filterPackageStateForInstalledAndFiltered(computer, packageName, callingUid,
+                    userId);
         }
-
-        return filterPackageStateForInstalledAndFiltered(computer, packageName, callingUid, userId);
     }
 
     @Nullable
@@ -8439,16 +8443,8 @@
         Computer computer = snapshotComputer();
         if (computer == mLiveComputer) {
             return new ArrayMap<>(computer.getPackageStates());
-        }
-        return computer.getPackageStates();
-    }
-
-    void forEachPackage(Consumer<AndroidPackage> actionLocked) {
-        synchronized (mLock) {
-            int numPackages = mPackages.size();
-            for (int i = 0; i < numPackages; i++) {
-                actionLocked.accept(mPackages.valueAt(i));
-            }
+        } else {
+            return computer.getPackageStates();
         }
     }
 
@@ -8461,17 +8457,31 @@
         }
     }
 
-    private void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action) {
+    void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action) {
         if (locked) {
-            forEachPackageSetting(action::accept);
+            synchronized (mLiveComputerSyncLock) {
+                forEachPackageState(mComputer.getPackageStates(), action);
+            }
         } else {
             Computer computer = snapshotComputer();
             if (computer == mLiveComputer) {
-                synchronized (mLock) {
+                synchronized (mLiveComputerSyncLock) {
                     forEachPackageState(computer.getPackageStates(), action);
-                };
+                }
+            } else {
+                forEachPackageState(computer.getPackageStates(), action);
             }
-            forEachPackageState(computer.getPackageStates(), action);
+        }
+    }
+
+    void forEachPackage(Consumer<AndroidPackage> action) {
+        Computer computer = snapshotComputer();
+        if (computer == mLiveComputer) {
+            synchronized (mLiveComputerSyncLock) {
+                forEachPackage(computer.getPackageStates(), action);
+            }
+        } else {
+            forEachPackage(computer.getPackageStates(), action);
         }
     }
 
@@ -8485,21 +8495,106 @@
         }
     }
 
-    void forEachInstalledPackage(@NonNull Consumer<AndroidPackage> actionLocked,
-            @UserIdInt int userId) {
-        synchronized (mLock) {
-            int numPackages = mPackages.size();
-            for (int i = 0; i < numPackages; i++) {
-                AndroidPackage pkg = mPackages.valueAt(i);
-                PackageSetting setting = mSettings.getPackageLPr(pkg.getPackageName());
-                if (setting == null || !setting.getInstalled(userId)) {
-                    continue;
-                }
-                actionLocked.accept(pkg);
+    private void forEachPackage(
+            @NonNull ArrayMap<String, ? extends PackageStateInternal> packageStates,
+            @NonNull Consumer<AndroidPackage> consumer) {
+        int size = packageStates.size();
+        for (int index = 0; index < size; index++) {
+            PackageStateInternal packageState = packageStates.valueAt(index);
+            if (packageState.getPkg() != null) {
+                consumer.accept(packageState.getPkg());
             }
         }
     }
 
+    void forEachInstalledPackage(boolean locked, @NonNull Consumer<AndroidPackage> action,
+            @UserIdInt int userId) {
+        Consumer<PackageStateInternal> actionWrapped = packageState -> {
+            if (packageState.getPkg() != null
+                    && packageState.getUserStateOrDefault(userId).isInstalled()) {
+                action.accept(packageState.getPkg());
+            }
+        };
+        if (locked) {
+            synchronized (mLiveComputerSyncLock) {
+                forEachPackageState(mComputer.getPackageStates(), actionWrapped);
+            }
+        } else {
+            Computer computer = snapshotComputer();
+            if (computer == mLiveComputer) {
+                synchronized (mLiveComputerSyncLock) {
+                    forEachPackageState(computer.getPackageStates(), actionWrapped);
+                }
+            } else {
+                forEachPackageState(computer.getPackageStates(), actionWrapped);
+            }
+        }
+    }
+
+    private void executeWithConsistentComputer(
+            @NonNull FunctionalUtils.ThrowingConsumer<Computer> consumer) {
+        Computer computer = snapshotComputer();
+        if (computer == mLiveComputer) {
+            synchronized (mLiveComputerSyncLock) {
+                consumer.accept(computer);
+            }
+        } else {
+            consumer.accept(computer);
+        }
+    }
+
+    private <T> T executeWithConsistentComputerReturning(
+            @NonNull FunctionalUtils.ThrowingFunction<Computer, T> function) {
+        Computer computer = snapshotComputer();
+        if (computer == mLiveComputer) {
+            synchronized (mLiveComputerSyncLock) {
+                return function.apply(computer);
+            }
+        } else {
+            return function.apply(computer);
+        }
+    }
+
+    private <ExceptionType extends Exception> void executeWithConsistentComputerThrowing(
+            @NonNull FunctionalUtils.ThrowingCheckedConsumer<Computer, ExceptionType> consumer)
+            throws ExceptionType {
+        Computer computer = snapshotComputer();
+        if (computer == mLiveComputer) {
+            synchronized (mLiveComputerSyncLock) {
+                consumer.accept(computer);
+            }
+        } else {
+            consumer.accept(computer);
+        }
+    }
+
+    private <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
+    executeWithConsistentComputerThrowing2(
+            @NonNull FunctionalUtils.ThrowingChecked2Consumer<Computer, ExceptionOne,
+                    ExceptionTwo> consumer) throws ExceptionOne, ExceptionTwo {
+        Computer computer = snapshotComputer();
+        if (computer == mLiveComputer) {
+            synchronized (mLiveComputerSyncLock) {
+                consumer.accept(computer);
+            }
+        } else {
+            consumer.accept(computer);
+        }
+    }
+
+    private <T, ExceptionType extends Exception> T executeWithConsistentComputerReturningThrowing(
+            @NonNull FunctionalUtils.ThrowingCheckedFunction<Computer, T, ExceptionType> function)
+            throws ExceptionType {
+        Computer computer = snapshotComputer();
+        if (computer == mLiveComputer) {
+            synchronized (mLiveComputerSyncLock) {
+                return function.apply(computer);
+            }
+        } else {
+            return function.apply(computer);
+        }
+    }
+
     boolean isHistoricalPackageUsageAvailable() {
         return mPackageUsage.isHistoricalPackageUsageAvailable();
     }
@@ -8695,7 +8790,8 @@
         enforceOwnerRights(packageName, Binder.getCallingUid());
         final boolean changed;
         synchronized (mLock) {
-            changed = mSettings.getPackageLPr(packageName).setMimeGroup(mimeGroup, mimeTypes);
+            changed = mSettings.getPackageLPr(packageName).setMimeGroup(mimeGroup,
+                    new ArraySet<>(mimeTypes));
         }
         if (changed) {
             applyMimeGroupChanges(packageName, mimeGroup);
@@ -9198,4 +9294,62 @@
         return new Pair<>(rescanFlags, reparseFlags);
     }
 
+
+    /**
+     * @see PackageManagerInternal#recordInitialState()
+     */
+    @NonNull
+    public PackageStateMutator.InitialState recordInitialState() {
+        return mPackageStateMutator.initialState(mChangedPackagesSequenceNumber);
+    }
+
+    /**
+     * @see PackageManagerInternal#commitPackageStateMutation(PackageStateMutator.InitialState,
+     * Consumer)
+     */
+    @NonNull
+    public PackageStateMutator.Result commitPackageStateMutation(
+            @Nullable PackageStateMutator.InitialState initialState,
+            @NonNull Consumer<PackageStateMutator> consumer) {
+        synchronized (mPackageStateWriteLock) {
+            final PackageStateMutator.Result result = mPackageStateMutator.generateResult(
+                    initialState, mChangedPackagesSequenceNumber);
+            if (result != PackageStateMutator.Result.SUCCESS) {
+                return result;
+            }
+
+            consumer.accept(mPackageStateMutator);
+            onChanged();
+        }
+
+        return PackageStateMutator.Result.SUCCESS;
+    }
+
+    /**
+     * @see PackageManagerInternal#commitPackageStateMutation(PackageStateMutator.InitialState,
+     * Consumer)
+     */
+    @NonNull
+    public PackageStateMutator.Result commitPackageStateMutation(
+            @Nullable PackageStateMutator.InitialState initialState, @NonNull String packageName,
+            @NonNull Consumer<PackageStateWrite> consumer) {
+        synchronized (mPackageStateWriteLock) {
+            final PackageStateMutator.Result result = mPackageStateMutator.generateResult(
+                    initialState, mChangedPackagesSequenceNumber);
+            if (result != PackageStateMutator.Result.SUCCESS) {
+                return result;
+            }
+
+            PackageStateWrite state = mPackageStateMutator.forPackage(packageName);
+            if (state == null) {
+                return PackageStateMutator.Result.SPECIFIC_PACKAGE_NULL;
+            } else {
+                consumer.accept(state);
+            }
+
+            onChanged();
+        }
+
+        return PackageStateMutator.Result.SUCCESS;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 923a133..0da57bc 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -395,14 +395,13 @@
         return this;
     }
 
-    public boolean setMimeGroup(String mimeGroup, List<String> mimeTypes) {
+    public boolean setMimeGroup(String mimeGroup, ArraySet<String> newMimeTypes) {
         Set<String> oldMimeTypes = mimeGroups == null ? null : mimeGroups.get(mimeGroup);
         if (oldMimeTypes == null) {
             throw new IllegalArgumentException("Unknown MIME group " + mimeGroup
                     + " for package " + mName);
         }
 
-        ArraySet<String> newMimeTypes = new ArraySet<>(mimeTypes);
         boolean hasChanges = !newMimeTypes.equals(oldMimeTypes);
         mimeGroups.put(mimeGroup, newMimeTypes);
         if (hasChanges) {
@@ -670,6 +669,15 @@
         return state;
     }
 
+    public PackageUserStateImpl getOrCreateUserState(@UserIdInt int userId) {
+        PackageUserStateImpl state = mUserStates.get(userId);
+        if (state == null) {
+            state = new PackageUserStateImpl();
+            mUserStates.put(userId, state);
+        }
+        return state;
+    }
+
     @NonNull
     public PackageUserStateInternal readUserState(int userId) {
         PackageUserStateInternal state = mUserStates.get(userId);
@@ -832,7 +840,6 @@
         }
         final SuspendParams oldSuspendParams =
                 existingUserState.getSuspendParams().put(suspendingPackage, newSuspendParams);
-        existingUserState.setSuspended(true);
         onChanged();
         return !Objects.equals(oldSuspendParams, newSuspendParams);
     }
@@ -848,7 +855,6 @@
                 existingUserState.setSuspendParams(null);
             }
         }
-        existingUserState.setSuspended((existingUserState.getSuspendParams() != null));
         onChanged();
         return wasModified;
     }
@@ -866,7 +872,6 @@
                 existingUserState.setSuspendParams(null);
             }
         }
-        existingUserState.setSuspended((existingUserState.getSuspendParams() != null));
         onChanged();
     }
 
@@ -889,7 +894,7 @@
     }
 
     void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
-            boolean notLaunched, boolean hidden, int distractionFlags, boolean suspended,
+            boolean notLaunched, boolean hidden, int distractionFlags,
             ArrayMap<String, SuspendParams> suspendParams, boolean instantApp,
             boolean virtualPreload, String lastDisableAppCaller,
             ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
@@ -904,7 +909,6 @@
                 .setNotLaunched(notLaunched)
                 .setHidden(hidden)
                 .setDistractionFlags(distractionFlags)
-                .setSuspended(suspended)
                 .setLastDisableAppCaller(lastDisableAppCaller)
                 .setEnabledComponents(enabledComponents)
                 .setDisabledComponents(disabledComponents)
@@ -919,11 +923,9 @@
 
     void setUserState(int userId, PackageUserStateInternal otherState) {
         setUserState(userId, otherState.getCeDataInode(), otherState.getEnabledState(),
-                otherState.isInstalled(),
-                otherState.isStopped(), otherState.isNotLaunched(), otherState.isHidden(),
-                otherState.getDistractionFlags(), otherState.isSuspended(),
-                otherState.getSuspendParams(),
-                otherState.isInstantApp(),
+                otherState.isInstalled(), otherState.isStopped(), otherState.isNotLaunched(),
+                otherState.isHidden(), otherState.getDistractionFlags(),
+                otherState.getSuspendParams(), otherState.isInstantApp(),
                 otherState.isVirtualPreload(), otherState.getLastDisableAppCaller(),
                 new ArraySet<>(otherState.getEnabledComponentsNoCopy()),
                 new ArraySet<>(otherState.getDisabledComponentsNoCopy()),
@@ -1553,10 +1555,10 @@
     }
 
     @DataClass.Generated(
-            time = 1635870549646L,
+            time = 1636145878985L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int sharedUserId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackage pkg\nprivate @android.annotation.Nullable com.android.server.pm.SharedUserSetting sharedUser\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long firstInstallTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  java.util.List<java.lang.String> getMimeGroup(java.lang.String)\nprivate  java.util.Set<java.lang.String> getMimeGroupInternal(java.lang.String)\npublic  boolean isSharedUser()\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,java.util.List<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.parsing.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  int getSharedUserIdInt()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  java.lang.String getLastDisabledAppCaller(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n  boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getOverlayPathsForLibrary(int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\n  boolean getSuspended(int)\n  boolean addOrUpdateSuspension(java.lang.String,android.content.pm.SuspendDialogInfo,android.os.PersistableBundle,android.os.PersistableBundle,int)\n  boolean removeSuspension(java.lang.String,int)\n  void removeSuspension(java.util.function.Predicate<java.lang.String>,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,boolean,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  android.util.ArraySet<java.lang.String> getEnabledComponents(int)\n  android.util.ArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(android.util.ArraySet<java.lang.String>,int)\n  void setDisabledComponents(android.util.ArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  void setHarmfulAppWarning(int,java.lang.String)\n  java.lang.String getHarmfulAppWarning(int)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic  void setSplashScreenTheme(int,java.lang.String)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackageApi getAndroidPackage()\npublic @android.annotation.Nullable @java.lang.Override java.lang.Integer getSharedUserId()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.SharedLibraryInfo> getUsesLibraryInfos()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setSharedUser(com.android.server.pm.SharedUserSetting)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int sharedUserId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackage pkg\nprivate @android.annotation.Nullable com.android.server.pm.SharedUserSetting sharedUser\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long firstInstallTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  boolean isSharedUser()\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,java.util.List<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.parsing.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  int getSharedUserIdInt()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  java.lang.String getLastDisabledAppCaller(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n  boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getOverlayPathsForLibrary(int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  android.util.ArraySet<java.lang.String> getEnabledComponents(int)\n  android.util.ArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(android.util.ArraySet<java.lang.String>,int)\n  void setDisabledComponents(android.util.ArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  void setHarmfulAppWarning(int,java.lang.String)\n  java.lang.String getHarmfulAppWarning(int)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic  void setSplashScreenTheme(int,java.lang.String)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackageApi getAndroidPackage()\npublic @android.annotation.Nullable @java.lang.Override java.lang.Integer getSharedUserId()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.SharedLibraryInfo> getUsesLibraryInfos()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setSharedUser(com.android.server.pm.SharedUserSetting)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index ed85ff9..4345d51 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -22,6 +22,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.permission.LegacyPermissionState;
+import com.android.server.pm.pkg.mutate.PackageStateMutator;
 import com.android.server.utils.Snappable;
 import com.android.server.utils.Watchable;
 import com.android.server.utils.WatchableImpl;
@@ -88,6 +89,7 @@
      * Notify listeners that this object has changed.
      */
     protected void onChanged() {
+        PackageStateMutator.onPackageStateChanged();
         dispatchChange(this);
     }
 
@@ -122,7 +124,7 @@
         return mLegacyPermissionsState;
     }
 
-    SettingBase setFlags(int pkgFlags) {
+    public SettingBase setFlags(int pkgFlags) {
         this.mPkgFlags = pkgFlags
                 & (ApplicationInfo.FLAG_SYSTEM
                         | ApplicationInfo.FLAG_EXTERNAL_STORAGE
@@ -131,7 +133,7 @@
         return this;
     }
 
-    SettingBase setPrivateFlags(int pkgPrivateFlags) {
+    public SettingBase setPrivateFlags(int pkgPrivateFlags) {
         this.mPkgPrivateFlags = pkgPrivateFlags
                 & (ApplicationInfo.PRIVATE_FLAG_PRIVILEGED
                 | ApplicationInfo.PRIVATE_FLAG_OEM
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b1ce6a2..6c47eb0 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -114,6 +114,7 @@
 import com.android.server.pm.permission.LegacyPermissionSettings;
 import com.android.server.pm.permission.LegacyPermissionState;
 import com.android.server.pm.permission.LegacyPermissionState.PermissionState;
+import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.SuspendParams;
@@ -162,6 +163,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.function.Consumer;
 
 /**
  * Holds information about dynamic settings.
@@ -257,7 +259,7 @@
         public static final int SIGNATURE_MALFORMED_RECOVER = 3;
     }
 
-    private static final boolean DEBUG_STOPPED = false;
+    static final boolean DEBUG_STOPPED = false;
     private static final boolean DEBUG_MU = false;
     private static final boolean DEBUG_KERNEL = false;
     private static final boolean DEBUG_PARSER = false;
@@ -625,7 +627,15 @@
         mOtherAppIds = new WatchedSparseArray<>();
         mPermissions = new LegacyPermissionSettings(lock);
         mRuntimePermissionsPersistence = new RuntimePermissionPersistence(
-                runtimePermissionsPersistence);
+                runtimePermissionsPersistence, new Consumer<Integer>() {
+            @Override
+            public void accept(Integer userId) {
+                synchronized (mLock) {
+                    mRuntimePermissionsPersistence.writeStateForUserSync(userId,
+                            mPermissionDataProvider, mPackages, mSharedUsers);
+                }
+            }
+        });
         mPermissionDataProvider = permissionDataProvider;
 
         mSystemDir = new File(dataDir, "system");
@@ -989,7 +999,6 @@
                                 true /*notLaunched*/,
                                 false /*hidden*/,
                                 0 /*distractionFlags*/,
-                                false /*suspended*/,
                                 null /*suspendParams*/,
                                 instantApp,
                                 virtualPreload,
@@ -1423,7 +1432,7 @@
 
     void writeAllRuntimePermissionsLPr() {
         for (int userId : UserManagerService.getInstance().getUserIds()) {
-            mRuntimePermissionsPersistence.writeStateForUserAsyncLPr(userId);
+            mRuntimePermissionsPersistence.writeStateForUserAsync(userId);
         }
     }
 
@@ -1432,15 +1441,15 @@
     }
 
     void updateRuntimePermissionsFingerprintLPr(@UserIdInt int userId) {
-        mRuntimePermissionsPersistence.updateRuntimePermissionsFingerprintLPr(userId);
+        mRuntimePermissionsPersistence.updateRuntimePermissionsFingerprint(userId);
     }
 
     int getDefaultRuntimePermissionsVersionLPr(int userId) {
-        return mRuntimePermissionsPersistence.getVersionLPr(userId);
+        return mRuntimePermissionsPersistence.getVersion(userId);
     }
 
     void setDefaultRuntimePermissionsVersionLPr(int version, int userId) {
-        mRuntimePermissionsPersistence.setVersionLPr(version, userId);
+        mRuntimePermissionsPersistence.setVersion(version, userId);
     }
 
     void setPermissionControllerVersion(long version) {
@@ -1646,7 +1655,6 @@
                                 false /*notLaunched*/,
                                 false /*hidden*/,
                                 0 /*distractionFlags*/,
-                                false /*suspended*/,
                                 null /*suspendParams*/,
                                 false /*instantApp*/,
                                 false /*virtualPreload*/,
@@ -1816,10 +1824,9 @@
                         setBlockUninstallLPw(userId, name, true);
                     }
                     ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
-                            hidden, distractionFlags, suspended, suspendParamsMap,
-                            instantApp, virtualPreload, enabledCaller, enabledComponents,
-                            disabledComponents, installReason, uninstallReason, harmfulAppWarning,
-                            splashScreenTheme);
+                            hidden, distractionFlags, suspendParamsMap, instantApp, virtualPreload,
+                            enabledCaller, enabledComponents, disabledComponents, installReason,
+                            uninstallReason, harmfulAppWarning, splashScreenTheme);
 
                     mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
                 } else if (tagName.equals("preferred-activities")) {
@@ -3100,7 +3107,8 @@
         }
 
         for (UserInfo user : users) {
-            mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
+            mRuntimePermissionsPersistence.readStateForUserSync(user.id, getInternalVersion(),
+                    mPackages, mSharedUsers, getUserRuntimePermissionsFile(user.id));
         }
 
         /*
@@ -3124,7 +3132,8 @@
     }
 
     void readPermissionStateForUserSyncLPr(@UserIdInt int userId) {
-        mRuntimePermissionsPersistence.readStateForUserSyncLPr(userId);
+        mRuntimePermissionsPersistence.readStateForUserSync(userId, getInternalVersion(),
+                mPackages, mSharedUsers, getUserRuntimePermissionsFile(userId));
     }
 
     void applyDefaultPreferredAppsLPw(int userId) {
@@ -4120,7 +4129,7 @@
         file.delete();
         removeCrossProfileIntentFiltersLPw(userId);
 
-        mRuntimePermissionsPersistence.onUserRemovedLPw(userId);
+        mRuntimePermissionsPersistence.onUserRemoved(userId);
         mDomainVerificationManager.clearUser(userId);
 
         writePackageListLPr();
@@ -4485,24 +4494,25 @@
                 }
             }
             for (UserInfo user : users) {
+                final PackageUserStateInternal userState = ps.getUserStateOrDefault(user.id);
                 pw.print(checkinTag);
                 pw.print("-");
                 pw.print("usr");
                 pw.print(",");
                 pw.print(user.id);
                 pw.print(",");
-                pw.print(ps.getInstalled(user.id) ? "I" : "i");
-                pw.print(ps.getHidden(user.id) ? "B" : "b");
-                pw.print(ps.getSuspended(user.id) ? "SU" : "su");
-                pw.print(ps.getStopped(user.id) ? "S" : "s");
-                pw.print(ps.getNotLaunched(user.id) ? "l" : "L");
-                pw.print(ps.getInstantApp(user.id) ? "IA" : "ia");
-                pw.print(ps.getVirtualPreload(user.id) ? "VPI" : "vpi");
-                String harmfulAppWarning = ps.getHarmfulAppWarning(user.id);
+                pw.print(userState.isInstalled() ? "I" : "i");
+                pw.print(userState.isHidden() ? "B" : "b");
+                pw.print(userState.isSuspended() ? "SU" : "su");
+                pw.print(userState.isStopped() ? "S" : "s");
+                pw.print(userState.isNotLaunched() ? "l" : "L");
+                pw.print(userState.isInstantApp() ? "IA" : "ia");
+                pw.print(userState.isVirtualPreload() ? "VPI" : "vpi");
+                String harmfulAppWarning = userState.getHarmfulAppWarning();
                 pw.print(harmfulAppWarning != null ? "HA" : "ha");
                 pw.print(",");
-                pw.print(ps.getEnabled(user.id));
-                String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
+                pw.print(userState.getEnabledState());
+                String lastDisabledAppCaller = userState.getLastDisableAppCaller();
                 pw.print(",");
                 pw.print(lastDisabledAppCaller != null ? lastDisabledAppCaller : "?");
                 pw.print(",");
@@ -4819,48 +4829,48 @@
         }
 
         for (UserInfo user : users) {
+            final PackageUserStateInternal userState = ps.getUserStateOrDefault(user.id);
             pw.print(prefix); pw.print("  User "); pw.print(user.id); pw.print(": ");
             pw.print("ceDataInode=");
-            pw.print(ps.getCeDataInode(user.id));
+            pw.print(userState.getCeDataInode());
             pw.print(" installed=");
-            pw.print(ps.getInstalled(user.id));
+            pw.print(userState.isInstalled());
             pw.print(" hidden=");
-            pw.print(ps.getHidden(user.id));
+            pw.print(userState.isHidden());
             pw.print(" suspended=");
-            pw.print(ps.getSuspended(user.id));
+            pw.print(userState.isSuspended());
             pw.print(" distractionFlags=");
-            pw.print(ps.getDistractionFlags(user.id));
+            pw.print(userState.getDistractionFlags());
             pw.print(" stopped=");
-            pw.print(ps.getStopped(user.id));
+            pw.print(userState.isStopped());
             pw.print(" notLaunched=");
-            pw.print(ps.getNotLaunched(user.id));
+            pw.print(userState.isNotLaunched());
             pw.print(" enabled=");
-            pw.print(ps.getEnabled(user.id));
+            pw.print(userState.getEnabledState());
             pw.print(" instant=");
-            pw.print(ps.getInstantApp(user.id));
+            pw.print(userState.isInstantApp());
             pw.print(" virtual=");
-            pw.print(ps.getVirtualPreload(user.id));
+            pw.println(userState.isVirtualPreload());
             pw.print(" installReason=");
-            pw.println(ps.getInstallReason(user.id));
+            pw.println(userState.getInstallReason());
 
-            if (ps.getSuspended(user.id)) {
+            if (userState.isSuspended()) {
                 pw.print(prefix);
                 pw.println("  Suspend params:");
-                final PackageUserStateInternal pus = ps.readUserState(user.id);
-                for (int i = 0; i < pus.getSuspendParams().size(); i++) {
+                for (int i = 0; i < userState.getSuspendParams().size(); i++) {
                     pw.print(prefix);
                     pw.print("    suspendingPackage=");
-                    pw.print(pus.getSuspendParams().keyAt(i));
-                    final SuspendParams params = pus.getSuspendParams().valueAt(i);
+                    pw.print(userState.getSuspendParams().keyAt(i));
+                    final SuspendParams params = userState.getSuspendParams().valueAt(i);
                     if (params != null) {
                         pw.print(" dialogInfo=");
-                        pw.print(params.dialogInfo);
+                        pw.print(params.getDialogInfo());
                     }
                     pw.println();
                 }
             }
 
-            final OverlayPaths overlayPaths = ps.getOverlayPaths(user.id);
+            final OverlayPaths overlayPaths = userState.getOverlayPaths();
             if (overlayPaths != null) {
                 if (!overlayPaths.getOverlayPaths().isEmpty()) {
                     pw.print(prefix);
@@ -4883,7 +4893,7 @@
             }
 
             final Map<String, OverlayPaths> sharedLibraryOverlayPaths =
-                    ps.getOverlayPathsForLibrary(user.id);
+                    userState.getSharedLibraryOverlayPaths();
             if (sharedLibraryOverlayPaths != null) {
                 for (Map.Entry<String, OverlayPaths> libOverlayPaths :
                         sharedLibraryOverlayPaths.entrySet()) {
@@ -4916,7 +4926,7 @@
                 }
             }
 
-            String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
+            String lastDisabledAppCaller = userState.getLastDisableAppCaller();
             if (lastDisabledAppCaller != null) {
                 pw.print(prefix); pw.print("    lastDisabledCaller: ");
                         pw.println(lastDisabledAppCaller);
@@ -4929,21 +4939,21 @@
                         .getPermissionStates(user.id), dumpAll);
             }
 
-            String harmfulAppWarning = ps.getHarmfulAppWarning(user.id);
+            String harmfulAppWarning = userState.getHarmfulAppWarning();
             if (harmfulAppWarning != null) {
                 pw.print(prefix); pw.print("      harmfulAppWarning: ");
                 pw.println(harmfulAppWarning);
             }
 
             if (permissionNames == null) {
-                ArraySet<String> cmp = ps.getDisabledComponents(user.id);
+                Set<String> cmp = userState.getDisabledComponents();
                 if (cmp != null && cmp.size() > 0) {
                     pw.print(prefix); pw.println("    disabledComponents:");
                     for (String s : cmp) {
                         pw.print(prefix); pw.print("      "); pw.println(s);
                     }
                 }
-                cmp = ps.getEnabledComponents(user.id);
+                cmp = userState.getEnabledComponents();
                 if (cmp != null && cmp.size() > 0) {
                     pw.print(prefix); pw.println("    enabledComponents:");
                     for (String s : cmp) {
@@ -5291,9 +5301,10 @@
 
     public void writePermissionStateForUserLPr(int userId, boolean sync) {
         if (sync) {
-            mRuntimePermissionsPersistence.writeStateForUserSyncLPr(userId);
+            mRuntimePermissionsPersistence.writeStateForUserSync(userId, mPermissionDataProvider,
+                    mPackages, mSharedUsers);
         } else {
-            mRuntimePermissionsPersistence.writeStateForUserAsyncLPr(userId);
+            mRuntimePermissionsPersistence.writeStateForUserAsync(userId);
         }
     }
 
@@ -5368,7 +5379,7 @@
         }
     }
 
-    private final class RuntimePermissionPersistence {
+    private static final class RuntimePermissionPersistence {
         private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 200;
         private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000;
 
@@ -5381,6 +5392,8 @@
 
         private final Handler mHandler = new MyHandler();
 
+        private final Object mLock = new Object();
+
         @GuardedBy("mLock")
         private final SparseBooleanArray mWriteScheduled = new SparseBooleanArray();
 
@@ -5400,45 +5413,58 @@
         // The mapping keys are user ids.
         private final SparseBooleanArray mPermissionUpgradeNeeded = new SparseBooleanArray();
 
-        public RuntimePermissionPersistence(RuntimePermissionsPersistence persistence) {
+        // This is a hack to allow this class to invoke a write using Settings's data structures,
+        // to facilitate moving to a finer scoped lock without a significant refactor.
+        private final Consumer<Integer> mInvokeWriteUserStateAsyncCallback;
+
+        public RuntimePermissionPersistence(RuntimePermissionsPersistence persistence,
+                Consumer<Integer> invokeWriteUserStateAsyncCallback) {
             mPersistence = persistence;
+            mInvokeWriteUserStateAsyncCallback = invokeWriteUserStateAsyncCallback;
         }
 
-        @GuardedBy("Settings.this.mLock")
-        int getVersionLPr(int userId) {
-            return mVersions.get(userId, INITIAL_VERSION);
-        }
-
-        @GuardedBy("Settings.this.mLock")
-        void setVersionLPr(int version, int userId) {
-            mVersions.put(userId, version);
-            writeStateForUserAsyncLPr(userId);
-        }
-
-        @GuardedBy("Settings.this.mLock")
-        public boolean isPermissionUpgradeNeeded(int userId) {
-            return mPermissionUpgradeNeeded.get(userId, true);
-        }
-
-        @GuardedBy("Settings.this.mLock")
-        public void updateRuntimePermissionsFingerprintLPr(@UserIdInt int userId) {
-            if (mExtendedFingerprint == null) {
-                throw new RuntimeException("The version of the permission controller hasn't been "
-                        + "set before trying to update the fingerprint.");
+        int getVersion(int userId) {
+            synchronized (mLock) {
+                return mVersions.get(userId, INITIAL_VERSION);
             }
-            mFingerprints.put(userId, mExtendedFingerprint);
-            writeStateForUserAsyncLPr(userId);
+        }
+
+        void setVersion(int version, int userId) {
+            synchronized (mLock) {
+                mVersions.put(userId, version);
+                writeStateForUserAsync(userId);
+            }
+        }
+
+        public boolean isPermissionUpgradeNeeded(int userId) {
+            synchronized (mLock) {
+                return mPermissionUpgradeNeeded.get(userId, true);
+            }
+        }
+
+        public void updateRuntimePermissionsFingerprint(@UserIdInt int userId) {
+            synchronized (mLock) {
+                if (mExtendedFingerprint == null) {
+                    throw new RuntimeException(
+                            "The version of the permission controller hasn't been "
+                                    + "set before trying to update the fingerprint.");
+                }
+                mFingerprints.put(userId, mExtendedFingerprint);
+                writeStateForUserAsync(userId);
+            }
         }
 
         public void setPermissionControllerVersion(long version) {
-            int numUser = mFingerprints.size();
-            mExtendedFingerprint = getExtendedFingerprint(version);
+            synchronized (mLock) {
+                int numUser = mFingerprints.size();
+                mExtendedFingerprint = getExtendedFingerprint(version);
 
-            for (int i = 0;  i < numUser; i++) {
-                int userId = mFingerprints.keyAt(i);
-                String fingerprint = mFingerprints.valueAt(i);
-                mPermissionUpgradeNeeded.put(userId,
-                        !TextUtils.equals(mExtendedFingerprint, fingerprint));
+                for (int i = 0; i < numUser; i++) {
+                    int userId = mFingerprints.keyAt(i);
+                    String fingerprint = mFingerprints.valueAt(i);
+                    mPermissionUpgradeNeeded.put(userId,
+                            !TextUtils.equals(mExtendedFingerprint, fingerprint));
+                }
             }
         }
 
@@ -5446,84 +5472,92 @@
             return PackagePartitions.FINGERPRINT + "?pc_version=" + version;
         }
 
-        public void writeStateForUserAsyncLPr(int userId) {
-            final long currentTimeMillis = SystemClock.uptimeMillis();
+        public void writeStateForUserAsync(int userId) {
+            synchronized (mLock) {
+                final long currentTimeMillis = SystemClock.uptimeMillis();
 
-            if (mWriteScheduled.get(userId)) {
-                mHandler.removeMessages(userId);
+                if (mWriteScheduled.get(userId)) {
+                    mHandler.removeMessages(userId);
 
-                // If enough time passed, write without holding off anymore.
-                final long lastNotWrittenMutationTimeMillis = mLastNotWrittenMutationTimesMillis
-                        .get(userId);
-                final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis
-                        - lastNotWrittenMutationTimeMillis;
-                if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_PERMISSIONS_DELAY_MILLIS) {
-                    mHandler.obtainMessage(userId).sendToTarget();
-                    return;
+                    // If enough time passed, write without holding off anymore.
+                    final long lastNotWrittenMutationTimeMillis = mLastNotWrittenMutationTimesMillis
+                            .get(userId);
+                    final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis
+                            - lastNotWrittenMutationTimeMillis;
+                    if (timeSinceLastNotWrittenMutationMillis
+                            >= MAX_WRITE_PERMISSIONS_DELAY_MILLIS) {
+                        mHandler.obtainMessage(userId).sendToTarget();
+                        return;
+                    }
+
+                    // Hold off a bit more as settings are frequently changing.
+                    final long maxDelayMillis = Math.max(lastNotWrittenMutationTimeMillis
+                            + MAX_WRITE_PERMISSIONS_DELAY_MILLIS - currentTimeMillis, 0);
+                    final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS,
+                            maxDelayMillis);
+
+                    Message message = mHandler.obtainMessage(userId);
+                    mHandler.sendMessageDelayed(message, writeDelayMillis);
+                } else {
+                    mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis);
+                    Message message = mHandler.obtainMessage(userId);
+                    mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS);
+                    mWriteScheduled.put(userId, true);
                 }
-
-                // Hold off a bit more as settings are frequently changing.
-                final long maxDelayMillis = Math.max(lastNotWrittenMutationTimeMillis
-                        + MAX_WRITE_PERMISSIONS_DELAY_MILLIS - currentTimeMillis, 0);
-                final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS,
-                        maxDelayMillis);
-
-                Message message = mHandler.obtainMessage(userId);
-                mHandler.sendMessageDelayed(message, writeDelayMillis);
-            } else {
-                mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis);
-                Message message = mHandler.obtainMessage(userId);
-                mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS);
-                mWriteScheduled.put(userId, true);
             }
         }
 
-        public void writeStateForUserSyncLPr(int userId) {
-            mHandler.removeMessages(userId);
-            mWriteScheduled.delete(userId);
+        public void writeStateForUserSync(int userId, @NonNull LegacyPermissionDataProvider
+                legacyPermissionDataProvider,
+                @NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates,
+                @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers) {
+            synchronized (mLock) {
+                mHandler.removeMessages(userId);
+                mWriteScheduled.delete(userId);
 
-            mPermissionDataProvider.writeLegacyPermissionStateTEMP();
+                legacyPermissionDataProvider.writeLegacyPermissionStateTEMP();
 
-            int version = mVersions.get(userId, INITIAL_VERSION);
+                int version = mVersions.get(userId, INITIAL_VERSION);
 
-            String fingerprint = mFingerprints.get(userId);
+                String fingerprint = mFingerprints.get(userId);
 
-            Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
-                    new ArrayMap<>();
-            int packagesSize = mPackages.size();
-            for (int i = 0; i < packagesSize; i++) {
-                String packageName = mPackages.keyAt(i);
-                PackageSetting packageSetting = mPackages.valueAt(i);
-                if (packageSetting.getSharedUser() == null) {
+                Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
+                        new ArrayMap<>();
+                int packagesSize = packageStates.size();
+                for (int i = 0; i < packagesSize; i++) {
+                    String packageName = packageStates.keyAt(i);
+                    PackageStateInternal packageState = packageStates.valueAt(i);
+                    if (packageState.getSharedUser() == null) {
+                        List<RuntimePermissionsState.PermissionState> permissions =
+                                getPermissionsFromPermissionsState(
+                                        packageState.getLegacyPermissionState(), userId);
+                        if (permissions.isEmpty() && !packageState.isInstallPermissionsFixed()) {
+                            // Storing an empty state means the package is known to the system and
+                            // its install permissions have been granted and fixed. If this is not
+                            // the case, we should not store anything.
+                            continue;
+                        }
+                        packagePermissions.put(packageName, permissions);
+                    }
+                }
+
+                Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
+                        new ArrayMap<>();
+                final int sharedUsersSize = sharedUsers.size();
+                for (int i = 0; i < sharedUsersSize; i++) {
+                    String sharedUserName = sharedUsers.keyAt(i);
+                    SharedUserSetting sharedUserSetting = sharedUsers.valueAt(i);
                     List<RuntimePermissionsState.PermissionState> permissions =
                             getPermissionsFromPermissionsState(
-                                    packageSetting.getLegacyPermissionState(), userId);
-                    if (permissions.isEmpty() && !packageSetting.isInstallPermissionsFixed()) {
-                        // Storing an empty state means the package is known to the system and its
-                        // install permissions have been granted and fixed. If this is not the case,
-                        // we should not store anything.
-                        continue;
-                    }
-                    packagePermissions.put(packageName, permissions);
+                                    sharedUserSetting.getLegacyPermissionState(), userId);
+                    sharedUserPermissions.put(sharedUserName, permissions);
                 }
+
+                RuntimePermissionsState runtimePermissions = new RuntimePermissionsState(version,
+                        fingerprint, packagePermissions, sharedUserPermissions);
+
+                mPersistence.writeForUser(runtimePermissions, UserHandle.of(userId));
             }
-
-            Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
-                    new ArrayMap<>();
-            final int sharedUsersSize = mSharedUsers.size();
-            for (int i = 0; i < sharedUsersSize; i++) {
-                String sharedUserName = mSharedUsers.keyAt(i);
-                SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i);
-                List<RuntimePermissionsState.PermissionState> permissions =
-                        getPermissionsFromPermissionsState(
-                                sharedUserSetting.getLegacyPermissionState(), userId);
-                sharedUserPermissions.put(sharedUserName, permissions);
-            }
-
-            RuntimePermissionsState runtimePermissions = new RuntimePermissionsState(version,
-                    fingerprint, packagePermissions, sharedUserPermissions);
-
-            mPersistence.writeForUser(runtimePermissions, UserHandle.of(userId));
         }
 
         @NonNull
@@ -5541,82 +5575,91 @@
             return permissions;
         }
 
-        @GuardedBy("Settings.this.mLock")
-        private void onUserRemovedLPw(int userId) {
-            // Make sure we do not
-            mHandler.removeMessages(userId);
+        private void onUserRemoved(int userId) {
+            synchronized (mLock) {
+                // Make sure we do not
+                mHandler.removeMessages(userId);
 
-            mPermissionUpgradeNeeded.delete(userId);
-            mVersions.delete(userId);
-            mFingerprints.remove(userId);
+                mPermissionUpgradeNeeded.delete(userId);
+                mVersions.delete(userId);
+                mFingerprints.remove(userId);
+            }
         }
 
         public void deleteUserRuntimePermissionsFile(int userId) {
-            mPersistence.deleteForUser(UserHandle.of(userId));
+            synchronized (mLock) {
+                mPersistence.deleteForUser(UserHandle.of(userId));
+            }
         }
 
-        @GuardedBy("Settings.this.mLock")
-        public void readStateForUserSyncLPr(int userId) {
-            RuntimePermissionsState runtimePermissions = mPersistence.readForUser(UserHandle.of(
-                    userId));
-            if (runtimePermissions == null) {
-                readLegacyStateForUserSyncLPr(userId);
-                writeStateForUserAsyncLPr(userId);
-                return;
-            }
-
-            // If the runtime permissions file exists but the version is not set this is
-            // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION.
-            int version = runtimePermissions.getVersion();
-            if (version == RuntimePermissionsState.NO_VERSION) {
-                version = UPGRADE_VERSION;
-            }
-            mVersions.put(userId, version);
-
-            String fingerprint = runtimePermissions.getFingerprint();
-            mFingerprints.put(userId, fingerprint);
-
-            boolean isUpgradeToR = getInternalVersion().sdkVersion < Build.VERSION_CODES.R;
-
-            Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
-                    runtimePermissions.getPackagePermissions();
-            int packagesSize = mPackages.size();
-            for (int i = 0; i < packagesSize; i++) {
-                String packageName = mPackages.keyAt(i);
-                PackageSetting packageSetting = mPackages.valueAt(i);
-
-                List<RuntimePermissionsState.PermissionState> permissions =
-                        packagePermissions.get(packageName);
-                if (permissions != null) {
-                    readPermissionsStateLpr(permissions, packageSetting.getLegacyPermissionState(),
-                            userId);
-                    packageSetting.setInstallPermissionsFixed(true);
-                } else if (packageSetting.getSharedUser() == null && !isUpgradeToR) {
-                    Slog.w(TAG, "Missing permission state for package: " + packageName);
-                    packageSetting.getLegacyPermissionState().setMissing(true, userId);
+        public void readStateForUserSync(int userId, @NonNull VersionInfo internalVersion,
+                @NonNull WatchedArrayMap<String, PackageSetting> packageSettings,
+                @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers,
+                @NonNull File userRuntimePermissionsFile) {
+            synchronized (mLock) {
+                RuntimePermissionsState runtimePermissions = mPersistence.readForUser(UserHandle.of(
+                        userId));
+                if (runtimePermissions == null) {
+                    readLegacyStateForUserSync(userId, userRuntimePermissionsFile, packageSettings,
+                            sharedUsers);
+                    writeStateForUserAsync(userId);
+                    return;
                 }
-            }
 
-            Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
-                    runtimePermissions.getSharedUserPermissions();
-            int sharedUsersSize = mSharedUsers.size();
-            for (int i = 0; i < sharedUsersSize; i++) {
-                String sharedUserName = mSharedUsers.keyAt(i);
-                SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i);
+                // If the runtime permissions file exists but the version is not set this is
+                // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION.
+                int version = runtimePermissions.getVersion();
+                if (version == RuntimePermissionsState.NO_VERSION) {
+                    version = UPGRADE_VERSION;
+                }
+                mVersions.put(userId, version);
 
-                List<RuntimePermissionsState.PermissionState> permissions =
-                        sharedUserPermissions.get(sharedUserName);
-                if (permissions != null) {
-                    readPermissionsStateLpr(permissions,
-                            sharedUserSetting.getLegacyPermissionState(), userId);
-                } else if (!isUpgradeToR) {
-                    Slog.w(TAG, "Missing permission state for shared user: " + sharedUserName);
-                    sharedUserSetting.getLegacyPermissionState().setMissing(true, userId);
+                String fingerprint = runtimePermissions.getFingerprint();
+                mFingerprints.put(userId, fingerprint);
+
+                boolean isUpgradeToR = internalVersion.sdkVersion < Build.VERSION_CODES.R;
+
+                Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
+                        runtimePermissions.getPackagePermissions();
+                int packagesSize = packageSettings.size();
+                for (int i = 0; i < packagesSize; i++) {
+                    String packageName = packageSettings.keyAt(i);
+                    PackageSetting packageSetting = packageSettings.valueAt(i);
+
+                    List<RuntimePermissionsState.PermissionState> permissions =
+                            packagePermissions.get(packageName);
+                    if (permissions != null) {
+                        readPermissionsState(permissions,
+                                packageSetting.getLegacyPermissionState(),
+                                userId);
+                        packageSetting.setInstallPermissionsFixed(true);
+                    } else if (packageSetting.getSharedUser() == null && !isUpgradeToR) {
+                        Slog.w(TAG, "Missing permission state for package: " + packageName);
+                        packageSetting.getLegacyPermissionState().setMissing(true, userId);
+                    }
+                }
+
+                Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
+                        runtimePermissions.getSharedUserPermissions();
+                int sharedUsersSize = sharedUsers.size();
+                for (int i = 0; i < sharedUsersSize; i++) {
+                    String sharedUserName = sharedUsers.keyAt(i);
+                    SharedUserSetting sharedUserSetting = sharedUsers.valueAt(i);
+
+                    List<RuntimePermissionsState.PermissionState> permissions =
+                            sharedUserPermissions.get(sharedUserName);
+                    if (permissions != null) {
+                        readPermissionsState(permissions,
+                                sharedUserSetting.getLegacyPermissionState(), userId);
+                    } else if (!isUpgradeToR) {
+                        Slog.w(TAG, "Missing permission state for shared user: " + sharedUserName);
+                        sharedUserSetting.getLegacyPermissionState().setMissing(true, userId);
+                    }
                 }
             }
         }
 
-        private void readPermissionsStateLpr(
+        private void readPermissionsState(
                 @NonNull List<RuntimePermissionsState.PermissionState> permissions,
                 @NonNull LegacyPermissionState permissionsState, @UserIdInt int userId) {
             int permissionsSize = permissions.size();
@@ -5630,77 +5673,86 @@
             }
         }
 
-        @GuardedBy("Settings.this.mLock")
-        private void readLegacyStateForUserSyncLPr(int userId) {
-            File permissionsFile = getUserRuntimePermissionsFile(userId);
-            if (!permissionsFile.exists()) {
-                return;
-            }
+        private void readLegacyStateForUserSync(int userId, @NonNull File permissionsFile,
+                @NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates,
+                @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers) {
+            synchronized (mLock) {
+                if (!permissionsFile.exists()) {
+                    return;
+                }
 
-            FileInputStream in;
-            try {
-                in = new AtomicFile(permissionsFile).openRead();
-            } catch (FileNotFoundException fnfe) {
-                Slog.i(PackageManagerService.TAG, "No permissions state");
-                return;
-            }
+                FileInputStream in;
+                try {
+                    in = new AtomicFile(permissionsFile).openRead();
+                } catch (FileNotFoundException fnfe) {
+                    Slog.i(PackageManagerService.TAG, "No permissions state");
+                    return;
+                }
 
-            try {
-                final TypedXmlPullParser parser = Xml.resolvePullParser(in);
-                parseLegacyRuntimePermissionsLPr(parser, userId);
+                try {
+                    final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+                    parseLegacyRuntimePermissions(parser, userId, packageStates, sharedUsers);
 
-            } catch (XmlPullParserException | IOException e) {
-                throw new IllegalStateException("Failed parsing permissions file: "
-                        + permissionsFile, e);
-            } finally {
-                IoUtils.closeQuietly(in);
+                } catch (XmlPullParserException | IOException e) {
+                    throw new IllegalStateException("Failed parsing permissions file: "
+                            + permissionsFile, e);
+                } finally {
+                    IoUtils.closeQuietly(in);
+                }
             }
         }
 
-        // Private internals
-
-        @GuardedBy("Settings.this.mLock")
-        private void parseLegacyRuntimePermissionsLPr(TypedXmlPullParser parser, int userId)
+        private void parseLegacyRuntimePermissions(TypedXmlPullParser parser, int userId,
+                @NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates,
+                @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers)
                 throws IOException, XmlPullParserException {
-            final int outerDepth = parser.getDepth();
-            int type;
-            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                    continue;
-                }
+            synchronized (mLock) {
+                final int outerDepth = parser.getDepth();
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                        continue;
+                    }
 
-                switch (parser.getName()) {
-                    case TAG_RUNTIME_PERMISSIONS: {
-                        // If the permisions settings file exists but the version is not set this is
-                        // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION
-                        int version = parser.getAttributeInt(null, ATTR_VERSION, UPGRADE_VERSION);
-                        mVersions.put(userId, version);
-                        String fingerprint = parser.getAttributeValue(null, ATTR_FINGERPRINT);
-                        mFingerprints.put(userId, fingerprint);
-                    } break;
-
-                    case TAG_PACKAGE: {
-                        String name = parser.getAttributeValue(null, ATTR_NAME);
-                        PackageSetting ps = mPackages.get(name);
-                        if (ps == null) {
-                            Slog.w(PackageManagerService.TAG, "Unknown package:" + name);
-                            XmlUtils.skipCurrentTag(parser);
-                            continue;
+                    switch (parser.getName()) {
+                        case TAG_RUNTIME_PERMISSIONS: {
+                            // If the permisions settings file exists but the version is not set this is
+                            // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION
+                            int version = parser.getAttributeInt(null, ATTR_VERSION,
+                                    UPGRADE_VERSION);
+                            mVersions.put(userId, version);
+                            String fingerprint = parser.getAttributeValue(null, ATTR_FINGERPRINT);
+                            mFingerprints.put(userId, fingerprint);
                         }
-                        parseLegacyPermissionsLPr(parser, ps.getLegacyPermissionState(), userId);
-                    } break;
+                        break;
 
-                    case TAG_SHARED_USER: {
-                        String name = parser.getAttributeValue(null, ATTR_NAME);
-                        SharedUserSetting sus = mSharedUsers.get(name);
-                        if (sus == null) {
-                            Slog.w(PackageManagerService.TAG, "Unknown shared user:" + name);
-                            XmlUtils.skipCurrentTag(parser);
-                            continue;
+                        case TAG_PACKAGE: {
+                            String name = parser.getAttributeValue(null, ATTR_NAME);
+                            PackageStateInternal ps = packageStates.get(name);
+                            if (ps == null) {
+                                Slog.w(PackageManagerService.TAG, "Unknown package:" + name);
+                                XmlUtils.skipCurrentTag(parser);
+                                continue;
+                            }
+                            parseLegacyPermissionsLPr(parser, ps.getLegacyPermissionState(),
+                                    userId);
                         }
-                        parseLegacyPermissionsLPr(parser, sus.getLegacyPermissionState(), userId);
-                    } break;
+                        break;
+
+                        case TAG_SHARED_USER: {
+                            String name = parser.getAttributeValue(null, ATTR_NAME);
+                            SharedUserSetting sus = sharedUsers.get(name);
+                            if (sus == null) {
+                                Slog.w(PackageManagerService.TAG, "Unknown shared user:" + name);
+                                XmlUtils.skipCurrentTag(parser);
+                                continue;
+                            }
+                            parseLegacyPermissionsLPr(parser, sus.getLegacyPermissionState(),
+                                    userId);
+                        }
+                        break;
+                    }
                 }
             }
         }
@@ -5708,25 +5760,27 @@
         private void parseLegacyPermissionsLPr(TypedXmlPullParser parser,
                 LegacyPermissionState permissionsState, int userId)
                 throws IOException, XmlPullParserException {
-            final int outerDepth = parser.getDepth();
-            int type;
-            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                    continue;
-                }
-
-                switch (parser.getName()) {
-                    case TAG_ITEM: {
-                        String name = parser.getAttributeValue(null, ATTR_NAME);
-                        final boolean granted =
-                                parser.getAttributeBoolean(null, ATTR_GRANTED, true);
-                        final int flags =
-                                parser.getAttributeIntHex(null, ATTR_FLAGS, 0);
-                        permissionsState.putPermissionState(new PermissionState(name, true,
-                                granted, flags), userId);
+            synchronized (mLock) {
+                final int outerDepth = parser.getDepth();
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                        continue;
                     }
-                    break;
+
+                    switch (parser.getName()) {
+                        case TAG_ITEM: {
+                            String name = parser.getAttributeValue(null, ATTR_NAME);
+                            final boolean granted =
+                                    parser.getAttributeBoolean(null, ATTR_GRANTED, true);
+                            final int flags =
+                                    parser.getAttributeIntHex(null, ATTR_FLAGS, 0);
+                            permissionsState.putPermissionState(new PermissionState(name, true,
+                                    granted, flags), userId);
+                        }
+                        break;
+                    }
                 }
             }
         }
@@ -5740,9 +5794,7 @@
             public void handleMessage(Message message) {
                 final int userId = message.what;
                 Runnable callback = (Runnable) message.obj;
-                synchronized (mLock) {
-                    writeStateForUserSyncLPr(userId);
-                }
+                mInvokeWriteUserStateAsyncCallback.accept(userId);
                 if (callback != null) {
                     callback.run();
                 }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
index 5460afa..56f62ab 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
@@ -23,6 +23,7 @@
 import android.util.SparseArray;
 
 import com.android.server.pm.InstallSource;
+import com.android.server.pm.PackageKeySetData;
 import com.android.server.pm.SharedUserSetting;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.permission.LegacyPermissionState;
@@ -82,4 +83,7 @@
     String getPathString();
 
     float getLoadingProgress();
+
+    @NonNull
+    PackageKeySetData getKeySetData();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index 32a9cf1..6fafb24 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 
 import java.util.Objects;
@@ -49,7 +50,6 @@
     private boolean mNotLaunched;
     private boolean mHidden; // Is the app restricted by owner / admin
     private int mDistractionFlags;
-    private boolean mSuspended;
     private boolean mInstantApp;
     private boolean mVirtualPreload;
     private int mEnabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -101,7 +101,6 @@
         mNotLaunched = other.mNotLaunched;
         mHidden = other.mHidden;
         mDistractionFlags = other.mDistractionFlags;
-        mSuspended = other.mSuspended;
         mInstantApp = other.mInstantApp;
         mVirtualPreload = other.mVirtualPreload;
         mEnabledState = other.mEnabledState;
@@ -256,6 +255,28 @@
         return mComponentLabelIconOverrideMap.get(componentName);
     }
 
+    @Override
+    public boolean isSuspended() {
+        return !CollectionUtils.isEmpty(mSuspendParams);
+    }
+
+    public PackageUserStateImpl putSuspendParams(@NonNull String suspendingPackage,
+            @NonNull SuspendParams suspendParams) {
+        if (mSuspendParams == null) {
+            mSuspendParams = new ArrayMap<>();
+        }
+        mSuspendParams.put(suspendingPackage, suspendParams);
+        return this;
+    }
+
+    public PackageUserStateImpl removeSuspension(@NonNull String suspendingPackage) {
+        if (mSuspendParams != null) {
+            mSuspendParams.remove(suspendingPackage);
+        }
+        return this;
+    }
+
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -312,11 +333,6 @@
     }
 
     @DataClass.Generated.Member
-    public boolean isSuspended() {
-        return mSuspended;
-    }
-
-    @DataClass.Generated.Member
     public boolean isInstantApp() {
         return mInstantApp;
     }
@@ -433,12 +449,6 @@
     }
 
     @DataClass.Generated.Member
-    public @NonNull PackageUserStateImpl setSuspended( boolean value) {
-        mSuspended = value;
-        return this;
-    }
-
-    @DataClass.Generated.Member
     public @NonNull PackageUserStateImpl setInstantApp( boolean value) {
         mInstantApp = value;
         return this;
@@ -532,7 +542,6 @@
                 && mNotLaunched == that.mNotLaunched
                 && mHidden == that.mHidden
                 && mDistractionFlags == that.mDistractionFlags
-                && mSuspended == that.mSuspended
                 && mInstantApp == that.mInstantApp
                 && mVirtualPreload == that.mVirtualPreload
                 && mEnabledState == that.mEnabledState
@@ -563,7 +572,6 @@
         _hash = 31 * _hash + Boolean.hashCode(mNotLaunched);
         _hash = 31 * _hash + Boolean.hashCode(mHidden);
         _hash = 31 * _hash + mDistractionFlags;
-        _hash = 31 * _hash + Boolean.hashCode(mSuspended);
         _hash = 31 * _hash + Boolean.hashCode(mInstantApp);
         _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload);
         _hash = 31 * _hash + mEnabledState;
@@ -581,10 +589,10 @@
     }
 
     @DataClass.Generated(
-            time = 1633983318771L,
+            time = 1636145886996L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
-            inputSignatures = "protected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mDisabledComponents\nprotected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate  long mCeDataInode\nprivate  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mSuspended\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\nprivate  int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprotected @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mCachedOverlayPaths\nprivate @android.annotation.Nullable android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\nclass PackageUserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserStateInternal]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+            inputSignatures = "protected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mDisabledComponents\nprotected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate  long mCeDataInode\nprivate  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\nprivate  int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprotected @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mCachedOverlayPaths\nprivate @android.annotation.Nullable android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\nclass PackageUserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserStateInternal]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
index 6f33312..bd8b3ab 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
@@ -32,6 +32,7 @@
 
     PackageUserStateInternal DEFAULT = new PackageUserStateDefault();
 
+    // TODO: Make non-null with emptyMap()
     @Nullable
     ArrayMap<String, SuspendParams> getSuspendParams();
 
diff --git a/services/core/java/com/android/server/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
index 71512dc..d24ce96 100644
--- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java
+++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
@@ -42,11 +42,15 @@
     private static final String TAG_APP_EXTRAS = "app-extras";
     private static final String TAG_LAUNCHER_EXTRAS = "launcher-extras";
 
-    public SuspendDialogInfo dialogInfo;
-    public PersistableBundle appExtras;
-    public PersistableBundle launcherExtras;
+    private final SuspendDialogInfo dialogInfo;
+    private final PersistableBundle appExtras;
+    private final PersistableBundle launcherExtras;
 
-    private SuspendParams() {
+    private SuspendParams(SuspendDialogInfo dialogInfo, PersistableBundle appExtras,
+            PersistableBundle launcherExtras) {
+        this.dialogInfo = dialogInfo;
+        this.appExtras = appExtras;
+        this.launcherExtras = launcherExtras;
     }
 
     /**
@@ -60,11 +64,7 @@
         if (dialogInfo == null && appExtras == null && launcherExtras == null) {
             return null;
         }
-        final SuspendParams instance = new SuspendParams();
-        instance.dialogInfo = dialogInfo;
-        instance.appExtras = appExtras;
-        instance.launcherExtras = launcherExtras;
-        return instance;
+        return new SuspendParams(dialogInfo, appExtras, launcherExtras);
     }
 
     @Override
@@ -172,4 +172,16 @@
         }
         return getInstanceOrNull(readDialogInfo, readAppExtras, readLauncherExtras);
     }
+
+    public SuspendDialogInfo getDialogInfo() {
+        return dialogInfo;
+    }
+
+    public PersistableBundle getAppExtras() {
+        return appExtras;
+    }
+
+    public PersistableBundle getLauncherExtras() {
+        return launcherExtras;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
new file mode 100644
index 0000000..35d4d9e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg.mutate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.overlay.OverlayPaths;
+import android.util.ArraySet;
+
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.pkg.PackageUserStateImpl;
+import com.android.server.pm.pkg.SuspendParams;
+
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
+
+public class PackageStateMutator {
+
+    private static final AtomicLong sStateChangeSequence = new AtomicLong();
+
+    private final StateWriteWrapper mStateWrite = new StateWriteWrapper();
+
+    private final Function<String, PackageSetting> mActiveStateFunction;
+    private final Function<String, PackageSetting> mDisabledStateFunction;
+
+    public PackageStateMutator(@NonNull Function<String, PackageSetting> activeStateFunction,
+            @NonNull Function<String, PackageSetting> disabledStateFunction) {
+        mActiveStateFunction = activeStateFunction;
+        mDisabledStateFunction = disabledStateFunction;
+    }
+
+    public static void onPackageStateChanged() {
+        sStateChangeSequence.incrementAndGet();
+    }
+
+    @NonNull
+    public PackageStateWrite forPackage(@NonNull String packageName) {
+        return mStateWrite.setState(mActiveStateFunction.apply(packageName));
+    }
+
+    @Nullable
+    public PackageStateWrite forPackageNullable(@NonNull String packageName) {
+        final PackageSetting packageState = mActiveStateFunction.apply(packageName);
+        mStateWrite.setState(packageState);
+        if (packageState == null) {
+            return null;
+        }
+
+        return mStateWrite.setState(packageState);
+    }
+
+    @NonNull
+    public PackageStateWrite forDisabledSystemPackage(@NonNull String packageName) {
+        return mStateWrite.setState(mDisabledStateFunction.apply(packageName));
+    }
+
+    @Nullable
+    public PackageStateWrite forDisabledSystemPackageNullable(@NonNull String packageName) {
+        final PackageSetting packageState = mDisabledStateFunction.apply(packageName);
+        if (packageState == null) {
+            return null;
+        }
+
+        return mStateWrite.setState(packageState);
+    }
+
+    @NonNull
+    public InitialState initialState(int changedPackagesSequenceNumber) {
+        return new InitialState(changedPackagesSequenceNumber, sStateChangeSequence.get());
+    }
+
+    /**
+     * @return null if initial state is null or if nothing has changed, otherwise return result
+     * with what changed
+     */
+    @Nullable
+    public Result generateResult(@Nullable InitialState state, int changedPackagesSequenceNumber) {
+        if (state == null) {
+            return Result.SUCCESS;
+        }
+
+        boolean packagesChanged = changedPackagesSequenceNumber != state.mPackageSequence;
+        boolean stateChanged = sStateChangeSequence.get() != state.mStateSequence;
+        if (packagesChanged && stateChanged) {
+            return Result.PACKAGES_AND_STATE_CHANGED;
+        } else if (packagesChanged) {
+            return Result.PACKAGES_CHANGED;
+        } else if (stateChanged) {
+            return Result.STATE_CHANGED;
+        } else {
+            return Result.SUCCESS;
+        }
+    }
+
+    public static class InitialState {
+
+        private final int mPackageSequence;
+        private final long mStateSequence;
+
+        public InitialState(int packageSequence, long stateSequence) {
+            mPackageSequence = packageSequence;
+            mStateSequence = stateSequence;
+        }
+    }
+
+    public static class Result {
+
+        public static final Result SUCCESS = new Result(true, false, false, false);
+        public static final Result PACKAGES_CHANGED = new Result(false, true, false, false);
+        public static final Result STATE_CHANGED = new Result(false, false, true, false);
+        public static final Result PACKAGES_AND_STATE_CHANGED = new Result(false, true, true, false);
+        public static final Result SPECIFIC_PACKAGE_NULL = new Result(false, false, true, true);
+
+        private final boolean mCommitted;
+        private final boolean mPackagesChanged;
+        private final boolean mStateChanged;
+        private final boolean mSpecificPackageNull;
+
+        public Result(boolean committed, boolean packagesChanged, boolean stateChanged,
+                boolean specificPackageNull) {
+            mCommitted = committed;
+            mPackagesChanged = packagesChanged;
+            mStateChanged = stateChanged;
+            mSpecificPackageNull = specificPackageNull;
+        }
+
+        public boolean isCommitted() {
+            return mCommitted;
+        }
+
+        public boolean isPackagesChanged() {
+            return mPackagesChanged;
+        }
+
+        public boolean isStateChanged() {
+            return mStateChanged;
+        }
+
+        public boolean isSpecificPackageNull() {
+            return mSpecificPackageNull;
+        }
+    }
+
+    private static class StateWriteWrapper implements PackageStateWrite {
+
+        private final UserStateWriteWrapper mUserStateWrite = new UserStateWriteWrapper();
+
+        @NonNull
+        private PackageSetting mState;
+
+        public StateWriteWrapper setState(PackageSetting state) {
+            this.mState = state;
+            return this;
+        }
+
+        @NonNull
+        @Override
+        public PackageUserStateWrite userState(int userId) {
+            return mUserStateWrite.setStates(
+                    mState == null ? null : mState.getOrCreateUserState(userId));
+        }
+
+        @Override
+        public PackageStateWrite setLastPackageUsageTime(int reason, long timeInMillis) {
+            if (mState != null) {
+                mState.getTransientState().setLastPackageUsageTimeInMills(reason, timeInMillis);
+            }
+            return this;
+        }
+
+        @Override
+        public PackageStateWrite setHiddenUntilInstalled(boolean value) {
+            if (mState != null) {
+                mState.getTransientState().setHiddenUntilInstalled(value);
+            }
+            return this;
+        }
+
+        @NonNull
+        @Override
+        public PackageStateWrite setRequiredForSystemUser(boolean requiredForSystemUser) {
+            if (mState != null) {
+                if (requiredForSystemUser) {
+                    mState.setPrivateFlags(mState.getPrivateFlags()
+                            | ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER);
+                } else {
+                    mState.setPrivateFlags(mState.getPrivateFlags()
+                            & ~ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER);
+                }
+            }
+            return this;
+        }
+
+        @NonNull
+        @Override
+        public PackageStateWrite setMimeGroup(@NonNull String mimeGroup,
+                @NonNull ArraySet<String> mimeTypes) {
+            if (mState != null) {
+                mState.setMimeGroup(mimeGroup, mimeTypes);
+            }
+            return this;
+        }
+
+        private static class UserStateWriteWrapper implements PackageUserStateWrite {
+
+            @Nullable
+            private PackageUserStateImpl mUserState;
+
+            public UserStateWriteWrapper setStates(@Nullable PackageUserStateImpl userState) {
+                mUserState = userState;
+                return this;
+            }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setInstalled(boolean installed) {
+                if (mUserState != null) {
+                    mUserState.setInstalled(installed);
+                }
+                return this;
+            }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setUninstallReason(int reason) {
+                if (mUserState != null) {
+                    mUserState.setUninstallReason(reason);
+                }
+                return this;
+            }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setDistractionFlags(
+                    @PackageManager.DistractionRestriction int restrictionFlags) {
+                if (mUserState != null) {
+                    mUserState.setDistractionFlags(restrictionFlags);
+                }
+                return this;
+            }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage,
+                    @Nullable SuspendParams suspendParams) {
+                if (mUserState != null) {
+                    mUserState.putSuspendParams(suspendingPackage, suspendParams);
+                }
+                return this;
+            }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage) {
+                if (mUserState != null) {
+                    mUserState.removeSuspension(suspendingPackage);
+                }
+                return this;
+            }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setHidden(boolean hidden) {
+                if (mUserState != null) {
+                    mUserState.setHidden(hidden);
+                }
+                return this;
+            }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setStopped(boolean stopped) {
+                if (mUserState != null) {
+                    mUserState.setStopped(stopped);
+                }
+                return this;
+            }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setNotLaunched(boolean notLaunched) {
+                if (mUserState != null) {
+                    mUserState.setNotLaunched(notLaunched);
+                }
+                return this;
+            }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setOverlayPaths(@NonNull OverlayPaths overlayPaths) {
+                if (mUserState != null) {
+                    mUserState.setOverlayPaths(overlayPaths);
+                }
+                return this;
+            }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setOverlayPathsForLibrary(@NonNull String libraryName,
+                    @Nullable OverlayPaths overlayPaths) {
+                if (mUserState != null) {
+                    mUserState.setSharedLibraryOverlayPaths(libraryName, overlayPaths);
+                }
+                return this;
+            }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setHarmfulAppWarning(@Nullable String warning) {
+                if (mUserState != null) {
+                    mUserState.setHarmfulAppWarning(warning);
+                }
+                return this;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
new file mode 100644
index 0000000..585bece
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg.mutate;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManager;
+import android.util.ArraySet;
+
+public interface PackageStateWrite {
+
+    @NonNull
+    PackageUserStateWrite userState(@UserIdInt int userId);
+
+    @NonNull
+    PackageStateWrite setLastPackageUsageTime(@PackageManager.NotifyReason int reason,
+            long timeInMillis);
+
+    @NonNull
+    PackageStateWrite setHiddenUntilInstalled(boolean hiddenUntilInstalled);
+
+    @NonNull
+    PackageStateWrite setRequiredForSystemUser(boolean requiredForSystemUser);
+
+    @NonNull
+    PackageStateWrite setMimeGroup(@NonNull String mimeGroup, @NonNull ArraySet<String> mimeTypes);
+}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
new file mode 100644
index 0000000..e23a1b6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg.mutate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageManager;
+import android.content.pm.overlay.OverlayPaths;
+
+import com.android.server.pm.pkg.SuspendParams;
+
+public interface PackageUserStateWrite {
+
+    @NonNull
+    PackageUserStateWrite setInstalled(boolean installed);
+
+    @NonNull
+    PackageUserStateWrite setUninstallReason(@PackageManager.UninstallReason int reason);
+
+    @NonNull
+    PackageUserStateWrite setDistractionFlags(
+            @PackageManager.DistractionRestriction int restrictionFlags);
+
+    @NonNull
+    PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage,
+            @Nullable SuspendParams suspendParams);
+
+    @NonNull
+    PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage);
+
+    @NonNull
+    PackageUserStateWrite setHidden(boolean hidden);
+
+    @NonNull
+    PackageUserStateWrite setStopped(boolean stopped);
+
+    @NonNull
+    PackageUserStateWrite setNotLaunched(boolean notLaunched);
+
+    @NonNull
+    PackageUserStateWrite setOverlayPaths(@Nullable OverlayPaths overlayPaths);
+
+    @NonNull
+    PackageUserStateWrite setOverlayPathsForLibrary(@NonNull String libraryName,
+            @Nullable OverlayPaths overlayPaths);
+
+    @NonNull
+    PackageUserStateWrite setHarmfulAppWarning(@Nullable String warning);
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 298f102..4cced17 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -363,6 +363,12 @@
     public static final int TOAST_WINDOW_TIMEOUT = 3500 + TOAST_WINDOW_ANIM_BUFFER;
 
     /**
+     * Action for launching assistant in retail mode
+     */
+    private static final String ACTION_VOICE_ASSIST_RETAIL =
+            "android.intent.action.VOICE_ASSIST_RETAIL";
+
+    /**
      * Lock protecting internal state.  Must not call out into window
      * manager with lock held.  (This lock will be acquired in places
      * where the window manager is calling in with its own lock held.)
@@ -1090,29 +1096,35 @@
                 mPowerManager.boostScreenBrightness(eventTime);
                 break;
             case MULTI_PRESS_POWER_LAUNCH_TARGET_ACTIVITY:
-                if (DEBUG_INPUT) {
-                    Slog.d(TAG, "Executing the double press power action.");
-                }
+                launchTargetActivityOnMultiPressPower();
+                break;
+        }
+    }
+
+    private void launchTargetActivityOnMultiPressPower() {
+        if (DEBUG_INPUT) {
+            Slog.d(TAG, "Executing the double press power action.");
+        }
+        if (mPowerDoublePressTargetActivity != null) {
+            Intent intent = new Intent();
+            intent.setComponent(mPowerDoublePressTargetActivity);
+            ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(
+                    intent, /* flags= */0);
+            if (resolveInfo != null) {
                 final boolean keyguardActive =
                         mKeyguardDelegate != null && mKeyguardDelegate.isShowing();
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
                 if (!keyguardActive) {
-                    Intent intent = new Intent();
-                    if (mPowerDoublePressTargetActivity != null) {
-                        intent.setComponent(mPowerDoublePressTargetActivity);
-                        ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(
-                                intent, /* flags= */0);
-                        if (resolveInfo != null) {
-                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-                            startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
-                        } else {
-                            Slog.e(TAG, "Could not resolve activity with : "
-                                    + mPowerDoublePressTargetActivity.flattenToString()
-                                    + " name.");
-                        }
-                    }
+                    startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+                } else {
+                    mKeyguardDelegate.dismissKeyguardToLaunch(intent);
                 }
-                break;
+            } else {
+                Slog.e(TAG, "Could not resolve activity with : "
+                        + mPowerDoublePressTargetActivity.flattenToString()
+                        + " name.");
+            }
         }
     }
 
@@ -1242,6 +1254,12 @@
             return LONG_PRESS_POWER_GLOBAL_ACTIONS;
         }
 
+        // If long press to launch assistant is disabled in settings, do nothing.
+        if (mLongPressOnPowerBehavior == LONG_PRESS_POWER_GO_TO_VOICE_ASSIST
+                && !isLongPressToAssistantEnabled(mContext)) {
+            return LONG_PRESS_POWER_NOTHING;
+        }
+
         return mLongPressOnPowerBehavior;
     }
 
@@ -1273,6 +1291,9 @@
                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                             | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
                     startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+                } else {
+                    // If keyguarded then notify the keyguard.
+                    mKeyguardDelegate.onSystemKeyPressed(KeyEvent.KEYCODE_STEM_PRIMARY);
                 }
                 break;
         }
@@ -3218,17 +3239,50 @@
                 .getSystemService(Context.SEARCH_SERVICE)).launchAssist(args);
     }
 
-    /** Launches ACTION_VOICE_ASSIST. Does nothing on keyguard. */
+    /**
+     * Launches ACTION_VOICE_ASSIST_RETAIL if in retail mode, or ACTION_VOICE_ASSIST otherwise
+     * Does nothing on keyguard except for watches. Delegates it to keyguard if present on watch.
+     */
     private void launchVoiceAssist(boolean allowDuringSetup) {
-        final boolean keyguardActive = mKeyguardDelegate == null
-                ? false
-                : mKeyguardDelegate.isShowing();
+        final boolean keyguardActive =
+                mKeyguardDelegate != null && mKeyguardDelegate.isShowing();
         if (!keyguardActive) {
-            Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
-            startActivityAsUser(intent, null, UserHandle.CURRENT_OR_SELF,
-                    allowDuringSetup);
+            if (mHasFeatureWatch && isInRetailMode()) {
+                launchRetailVoiceAssist(allowDuringSetup);
+            } else {
+                startVoiceAssistIntent(allowDuringSetup);
+            }
+        } else {
+            mKeyguardDelegate.dismissKeyguardToLaunch(new Intent(Intent.ACTION_VOICE_ASSIST));
         }
+    }
 
+    private void launchRetailVoiceAssist(boolean allowDuringSetup) {
+        Intent retailIntent = new Intent(ACTION_VOICE_ASSIST_RETAIL);
+        ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(
+                retailIntent, /* flags= */0);
+        if (resolveInfo != null) {
+            retailIntent.setComponent(
+                    new ComponentName(resolveInfo.activityInfo.packageName,
+                            resolveInfo.activityInfo.name));
+            startActivityAsUser(retailIntent, null, UserHandle.CURRENT_OR_SELF,
+                    allowDuringSetup);
+        } else {
+            Slog.w(TAG, "Couldn't find an app to process " + ACTION_VOICE_ASSIST_RETAIL
+                    + ". Fall back to start " + Intent.ACTION_VOICE_ASSIST);
+            startVoiceAssistIntent(allowDuringSetup);
+        }
+    }
+
+    private void startVoiceAssistIntent(boolean allowDuringSetup) {
+        Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+        startActivityAsUser(intent, null, UserHandle.CURRENT_OR_SELF,
+                allowDuringSetup);
+    }
+
+    private boolean isInRetailMode() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_DEMO_MODE, 0) == 1;
     }
 
     private void startActivityAsUser(Intent intent, UserHandle handle) {
@@ -5824,6 +5878,18 @@
         }
     }
 
+    public static boolean isLongPressToAssistantEnabled(Context context) {
+        ContentResolver resolver = context.getContentResolver();
+        int longPressToAssistant = Settings.System.getIntForUser(resolver,
+                Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED,
+                /* def= */ 1,
+                UserHandle.USER_CURRENT);
+        if(Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "longPressToAssistant = " + longPressToAssistant);
+        }
+        return (longPressToAssistant == 1);
+    }
+
     private class HdmiVideoExtconUEventObserver extends ExtconStateObserver<Boolean> {
         private static final String HDMI_EXIST = "HDMI=1";
         private static final String NAME = "hdmi";
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 0080ec6..97a57e0 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -415,6 +415,17 @@
         }
     }
 
+    public void dismissKeyguardToLaunch(Intent intentToLaunch) {
+        if (mKeyguardService != null) {
+            mKeyguardService.dismissKeyguardToLaunch(intentToLaunch);
+        }
+    }
+    public void onSystemKeyPressed(int keycode) {
+        if (mKeyguardService != null) {
+            mKeyguardService.onSystemKeyPressed(keycode);
+        }
+    }
+
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         proto.write(SHOWING, mKeyguardState.showing);
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index 2029f86..774e261 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -17,6 +17,7 @@
 package com.android.server.policy.keyguard;
 
 import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PowerManager;
@@ -254,6 +255,24 @@
         }
     }
 
+    @Override
+    public void dismissKeyguardToLaunch(Intent intentToLaunch) {
+        try {
+            mService.dismissKeyguardToLaunch(intentToLaunch);
+        } catch (RemoteException e) {
+            Slog.w(TAG , "Remote Exception", e);
+        }
+    }
+
+    @Override
+    public void onSystemKeyPressed(int keycode) {
+        try {
+            mService.onSystemKeyPressed(keycode);
+        } catch (RemoteException e) {
+            Slog.w(TAG , "Remote Exception", e);
+        }
+    }
+
     @Override // Binder interface
     public IBinder asBinder() {
         return mService.asBinder();
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java b/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java
index 7f047f8..fba0fb4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java
@@ -20,8 +20,6 @@
 import android.annotation.Nullable;
 
 import java.lang.reflect.Array;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 import java.util.Collection;
 import java.util.Map;
 
@@ -33,67 +31,28 @@
     static public final int kDefaultMaxCollectionLength = 16;
 
     /**
-     * Simple version of {@link #print(Object, boolean, int)} that prints an object, without
-     * recursing into sub-objects.
-     *
-     * @param obj The object to print.
-     * @return A string representing the object.
-     */
-    static String print(@Nullable Object obj) {
-        return print(obj, false, kDefaultMaxCollectionLength);
-    }
-
-    /**
      * Pretty-prints an object.
      *
      * @param obj                 The object to print.
-     * @param deep                Whether to pretty-print sub-objects (if false, just prints them
-     *                            with {@link Object#toString()}).
      * @param maxCollectionLength Whenever encountering collections, maximum number of elements to
      *                            print.
      * @return A string representing the object.
      */
-    static String print(@Nullable Object obj, boolean deep, int maxCollectionLength) {
+    static String print(@Nullable Object obj, int maxCollectionLength) {
         StringBuilder builder = new StringBuilder();
-        print(builder, obj, deep, maxCollectionLength);
+        print(builder, obj, maxCollectionLength);
         return builder.toString();
     }
 
     /**
-     * This version is suitable for use inside a toString() override of an object, e.g.:
-     * <pre><code>
-     *     class MyObject {
-     *         ...
-     *         @Override
-     *         String toString() {
-     *             return ObjectPrinter.printPublicFields(this, ...);
-     *         }
-     *     }
-     * </code></pre>
-     *
-     * @param obj                 The object to print.
-     * @param deep                Whether to pretty-print sub-objects (if false, just prints them
-     *                            with {@link Object#toString()}).
-     * @param maxCollectionLength Whenever encountering collections, maximum number of elements to
-     *                            print.
-     */
-    static String printPublicFields(@Nullable Object obj, boolean deep, int maxCollectionLength) {
-        StringBuilder builder = new StringBuilder();
-        printPublicFields(builder, obj, deep, maxCollectionLength);
-        return builder.toString();
-    }
-
-    /**
-     * A version of {@link #print(Object, boolean, int)} that uses a {@link StringBuilder}.
+     * A version of {@link #print(Object, int)} that uses a {@link StringBuilder}.
      *
      * @param builder             StringBuilder to print into.
      * @param obj                 The object to print.
-     * @param deep                Whether to pretty-print sub-objects (if false, just prints them
-     *                            with {@link Object#toString()}).
      * @param maxCollectionLength Whenever encountering collections, maximum number of elements to
      *                            print.
      */
-    static void print(@NonNull StringBuilder builder, @Nullable Object obj, boolean deep,
+    static void print(@NonNull StringBuilder builder, @Nullable Object obj,
             int maxCollectionLength) {
         try {
             if (obj == null) {
@@ -101,16 +60,16 @@
                 return;
             }
             if (obj instanceof Boolean) {
-                builder.append(obj.toString());
+                builder.append(obj);
                 return;
             }
             if (obj instanceof Number) {
-                builder.append(obj.toString());
+                builder.append(obj);
                 return;
             }
             if (obj instanceof Character) {
                 builder.append('\'');
-                builder.append(obj.toString());
+                builder.append(obj);
                 builder.append('\'');
                 return;
             }
@@ -137,7 +96,7 @@
                         isLong = true;
                         break;
                     }
-                    print(builder, child, deep, maxCollectionLength);
+                    print(builder, child, maxCollectionLength);
                     ++i;
                 }
                 if (isLong) {
@@ -163,9 +122,9 @@
                         isLong = true;
                         break;
                     }
-                    print(builder, child.getKey(), deep, maxCollectionLength);
+                    print(builder, child.getKey(), maxCollectionLength);
                     builder.append(": ");
-                    print(builder, child.getValue(), deep, maxCollectionLength);
+                    print(builder, child.getValue(), maxCollectionLength);
                     ++i;
                 }
                 if (isLong) {
@@ -189,7 +148,7 @@
                         isLong = true;
                         break;
                     }
-                    print(builder, Array.get(obj, i), deep, maxCollectionLength);
+                    print(builder, Array.get(obj, i), maxCollectionLength);
                 }
                 if (isLong) {
                     builder.append("... (+");
@@ -200,48 +159,7 @@
                 return;
             }
 
-            if (!deep) {
-                builder.append(obj.toString());
-                return;
-            }
-            printPublicFields(builder, obj, deep, maxCollectionLength);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * A version of {@link #printPublicFields(Object, boolean, int)} that uses a {@link
-     * StringBuilder}.
-     *
-     * @param obj                 The object to print.
-     * @param deep                Whether to pretty-print sub-objects (if false, just prints them
-     *                            with {@link Object#toString()}).
-     * @param maxCollectionLength Whenever encountering collections, maximum number of elements to
-     *                            print.
-     */
-    static void printPublicFields(@NonNull StringBuilder builder, @Nullable Object obj,
-            boolean deep,
-            int maxCollectionLength) {
-        try {
-            Class cls = obj.getClass();
-            builder.append("{ ");
-
-            boolean first = true;
-            for (Field fld : cls.getDeclaredFields()) {
-                int mod = fld.getModifiers();
-                if ((mod & Modifier.PUBLIC) != 0 && (mod & Modifier.STATIC) == 0) {
-                    if (first) {
-                        first = false;
-                    } else {
-                        builder.append(", ");
-                    }
-                    builder.append(fld.getName());
-                    builder.append(": ");
-                    print(builder, fld.get(obj), deep, maxCollectionLength);
-                }
-            }
-            builder.append(" }");
+            builder.append(obj);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index 559e777..dc4bdaa 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -18,14 +18,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.media.permission.Identity;
+import android.media.permission.IdentityContext;
 import android.media.soundtrigger.ModelParameterRange;
 import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.RecognitionConfig;
 import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
-import android.media.permission.Identity;
-import android.media.permission.IdentityContext;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
@@ -387,7 +387,7 @@
     }
 
     private static void printObject(@NonNull StringBuilder builder, @Nullable Object obj) {
-        ObjectPrinter.print(builder, obj, true, 16);
+        ObjectPrinter.print(builder, obj, 16);
     }
 
     private static String printObject(@Nullable Object obj) {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
index 76927e1..f3d151f 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -21,9 +21,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.PermissionChecker;
+import android.media.permission.Identity;
+import android.media.permission.IdentityContext;
+import android.media.permission.PermissionUtil;
 import android.media.soundtrigger.ModelParameterRange;
 import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
@@ -31,9 +33,6 @@
 import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
-import android.media.permission.Identity;
-import android.media.permission.IdentityContext;
-import android.media.permission.PermissionUtil;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -144,7 +143,7 @@
         if (status != PermissionChecker.PERMISSION_GRANTED) {
             throw new SecurityException(
                     String.format("Failed to obtain permission %s for identity %s", permission,
-                            ObjectPrinter.print(identity, true, 16)));
+                            ObjectPrinter.print(identity, 16)));
         }
     }
 
@@ -168,7 +167,7 @@
             case PermissionChecker.PERMISSION_HARD_DENIED:
                 throw new SecurityException(
                         String.format("Failed to obtain permission %s for identity %s", permission,
-                                ObjectPrinter.print(identity, true, 16)));
+                                ObjectPrinter.print(identity, 16)));
             default:
                 throw new RuntimeException("Unexpected perimission check result.");
         }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 4243fc7..09035cd 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.media.permission.Identity;
+import android.media.permission.IdentityContext;
 import android.media.soundtrigger.ModelParameterRange;
 import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
@@ -27,8 +29,6 @@
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
-import android.media.permission.Identity;
-import android.media.permission.IdentityContext;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -230,7 +230,7 @@
                     final ModuleState module = mModules.get(handle);
                     pw.println("=========================================");
                     pw.printf("Module %d\n%s\n", handle,
-                            ObjectPrinter.print(module.properties, true, 16));
+                            ObjectPrinter.print(module.properties, 16));
                     pw.println("=========================================");
                     for (Session session : module.sessions) {
                         session.dump(pw);
@@ -250,11 +250,11 @@
     /** State of a sound model. */
     static class ModelState {
         ModelState(SoundModel model) {
-            this.description = ObjectPrinter.print(model, true, 16);
+            this.description = ObjectPrinter.print(model, 16);
         }
 
         ModelState(PhraseSoundModel model) {
-            this.description = ObjectPrinter.print(model, true, 16);
+            this.description = ObjectPrinter.print(model, 16);
         }
 
         /** Activity state of a sound model. */
@@ -690,8 +690,8 @@
             if (mState == ModuleStatus.ALIVE) {
                 pw.println("-------------------------------");
                 pw.printf("Session %s, client: %s\n", toString(),
-                        ObjectPrinter.print(mOriginatorIdentity, true, 16));
-                pw.printf("Loaded models (handle, active, description):", toString());
+                        ObjectPrinter.print(mOriginatorIdentity, 16));
+                pw.println("Loaded models (handle, active, description):");
                 pw.println();
                 pw.println("-------------------------------");
                 for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 332fed7..568e4b8 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -44,6 +44,8 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.media.PlaybackParams;
+import android.media.tv.AdRequest;
+import android.media.tv.AdResponse;
 import android.media.tv.AitInfo;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
@@ -2494,6 +2496,28 @@
         }
 
         @Override
+        public void requestAd(IBinder sessionToken, AdRequest request, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+                    userId, "requestAd");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).requestAd(request);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in requestAd", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            };
+        }
+
+        @Override
         public int getClientPid(String sessionId) {
             ensureTunerResourceAccessPermission();
             final long identity = Binder.clearCallingIdentity();
@@ -3557,6 +3581,23 @@
                 }
             }
         }
+
+        @Override
+        public void onAdResponse (AdResponse response) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onAdResponse()");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onAdResponse(response, mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onAdResponse", e);
+                }
+            }
+        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
index ef36f51..a2bf2fe 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -29,6 +29,8 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.graphics.Rect;
+import android.media.tv.AdRequest;
+import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
@@ -1178,6 +1180,28 @@
         }
 
         @Override
+        public void notifyAdResponse(IBinder sessionToken, AdResponse response, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyAdResponse");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyAdResponse(response);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyAdResponse", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void registerCallback(final ITvIAppManagerCallback callback, int userId) {
             int callingPid = Binder.getCallingPid();
             int callingUid = Binder.getCallingUid();
@@ -1908,6 +1932,23 @@
         }
 
         @Override
+        public void onAdRequest(AdRequest request) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onAdRequest (id=" + request.getId() + ")");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onAdRequest(request, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onAdRequest", e);
+                }
+            }
+        }
+
+        @Override
         public void onSessionStateChanged(int state) {
             synchronized (mLock) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fcc2bd6..30465af 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6337,7 +6337,6 @@
                 }
             } else if (w.isDrawn()) {
                 // The starting window for this container is drawn.
-                mTaskSupervisor.getActivityMetricsLogger().notifyStartingWindowDrawn(this);
                 startingDisplayed = true;
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 51c8daf..3cecce2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -641,22 +641,35 @@
             intent.setComponent(new ComponentName(
                     aInfo.applicationInfo.packageName, aInfo.name));
 
-            // Don't debug things in the system process
-            if (!aInfo.processName.equals("system")) {
-                if ((startFlags & (START_FLAG_DEBUG | START_FLAG_NATIVE_DEBUGGING
-                        | START_FLAG_TRACK_ALLOCATION)) != 0 || profilerInfo != null) {
-
+            final boolean requestDebug = (startFlags & (START_FLAG_DEBUG
+                    | START_FLAG_NATIVE_DEBUGGING | START_FLAG_TRACK_ALLOCATION)) != 0;
+            final boolean requestProfile = profilerInfo != null;
+            if (requestDebug || requestProfile) {
+                final boolean debuggable = (Build.IS_DEBUGGABLE
+                        || (aInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0)
+                        && !aInfo.processName.equals("system");
+                if ((requestDebug && !debuggable) || (requestProfile
+                        && (!debuggable && !aInfo.applicationInfo.isProfileableByShell()))) {
+                    Slog.w(TAG, "Ignore debugging for non-debuggable app: " + aInfo.packageName);
+                } else {
                      // Mimic an AMS synchronous call by passing a message to AMS and wait for AMS
                      // to notify us that the task has completed.
                      // TODO(b/80414790) look into further untangling for the situation where the
                      // caller is on the same thread as the handler we are posting to.
                     synchronized (mService.mGlobalLock) {
                         // Post message to AMS.
-                        final Message msg = PooledLambda.obtainMessage(
-                                ActivityManagerInternal::setDebugFlagsForStartingActivity,
-                                mService.mAmInternal, aInfo, startFlags, profilerInfo,
-                                mService.mGlobalLock);
-                        mService.mH.sendMessage(msg);
+                        mService.mH.post(() -> {
+                            try {
+                                mService.mAmInternal.setDebugFlagsForStartingActivity(aInfo,
+                                        startFlags, profilerInfo, mService.mGlobalLock);
+                            } catch (Throwable e) {
+                                // Simply ignore it because the debugging doesn't take effect.
+                                Slog.w(TAG, e);
+                                synchronized (mService.mGlobalLockWithoutBoost) {
+                                    mService.mGlobalLockWithoutBoost.notifyAll();
+                                }
+                            }
+                        });
                         try {
                             mService.mGlobalLock.wait();
                         } catch (InterruptedException ignore) {
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
index bbda577..c85e04d 100644
--- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
@@ -59,8 +59,13 @@
     /** The list to store the drawn tokens before the rotation animation starts. */
     private ArrayList<WindowToken> mPendingShowTokens;
 
-    /** It is used when the display has rotated, but some windows fade out in old rotation. */
-    private SeamlessRotator mRotator;
+    /**
+     * The sync transactions of the target windows. It is used when the display has rotated but
+     * the windows need to fade out in previous rotation. These transactions will be applied with
+     * fade-in animation, so there won't be a flickering such as the windows have redrawn during
+     * fading out.
+     */
+    private ArrayMap<WindowState, SurfaceControl.Transaction> mCapturedDrawTransactions;
 
     private final int mOriginalRotation;
     private final boolean mHasScreenRotationAnimation;
@@ -110,16 +115,36 @@
                 mTargetWindowTokens.put(w.mToken, null);
             }
         }, true /* traverseTopToBottom */);
+
+        // The transition sync group may be finished earlier because it doesn't wait for these
+        // target windows. But the windows still need to use sync transaction to keep the appearance
+        // in previous rotation, so request a no-op sync to keep the state.
+        if (!mIsChangeTransition && transitionType != WindowManager.TRANSIT_NONE) {
+            for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+                final WindowToken token = mTargetWindowTokens.keyAt(i);
+                for (int j = token.getChildCount() - 1; j >= 0; j--) {
+                    token.getChildAt(j).applyWithNextDraw(t -> {});
+                }
+            }
+        }
     }
 
     @Override
     public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
         if (show) {
-            final SurfaceControl leash = mTargetWindowTokens.remove(windowToken);
-            if (leash != null && mRotator != null) {
-                // The leash was unrotated by start transaction of transition. Clear the transform
-                // to reshow the window in current rotation.
-                mRotator.setIdentityMatrix(mDisplayContent.getPendingTransaction(), leash);
+            // The previous animation leash will be dropped when preparing fade-in animation, so
+            // simply remove it without restoring the transformation.
+            mTargetWindowTokens.remove(windowToken);
+            if (mCapturedDrawTransactions != null) {
+                // Unblock the window to draw its latest content with fade-in animation.
+                final SurfaceControl.Transaction t = mDisplayContent.getPendingTransaction();
+                for (int i = windowToken.getChildCount() - 1; i >= 0; i--) {
+                    final SurfaceControl.Transaction drawT =
+                            mCapturedDrawTransactions.remove(windowToken.getChildAt(i));
+                    if (drawT != null) {
+                        t.merge(drawT);
+                    }
+                }
             }
         }
         super.fadeWindowToken(show, windowToken, animationType);
@@ -225,14 +250,14 @@
             // Take OPEN/CLOSE transition type as the example, the non-activity windows need to
             // fade out in previous rotation while display has rotated to the new rotation, so
             // their leashes are unrotated with the start transaction.
-            mRotator = new SeamlessRotator(mOriginalRotation,
+            final SeamlessRotator rotator = new SeamlessRotator(mOriginalRotation,
                     mDisplayContent.getWindowConfiguration().getRotation(),
                     mDisplayContent.getDisplayInfo(),
                     false /* applyFixedTransformationHint */);
             for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
                 final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
                 if (leash != null) {
-                    mRotator.applyTransform(t, leash);
+                    rotator.applyTransform(t, leash);
                 }
             }
             return;
@@ -280,6 +305,25 @@
         }
     }
 
+    /** Captures the post draw transaction if the window should update with fade-in animation. */
+    boolean handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction) {
+        if (mIsChangeTransition || !isTargetToken(w.mToken)) return false;
+        if (postDrawTransaction != null && w.mTransitionController.inTransition()) {
+            if (mCapturedDrawTransactions == null) {
+                mCapturedDrawTransactions = new ArrayMap<>();
+            }
+            final SurfaceControl.Transaction t = mCapturedDrawTransactions.get(w);
+            if (t == null) {
+                mCapturedDrawTransactions.put(w, postDrawTransaction);
+            } else {
+                t.merge(postDrawTransaction);
+            }
+            return true;
+        }
+        mDisplayContent.finishFadeRotationAnimation(w.mToken);
+        return false;
+    }
+
     @Override
     public Animation getFadeInAnimation() {
         if (mHasScreenRotationAnimation) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 0826904d..535bbb7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1037,13 +1037,7 @@
                 }
             }
 
-            final boolean curDisplayInTransitNotAnimate =
-                    // legacy transition
-                    (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppTransitioning())
-                    // shell transition
-                    || (curDisplay.mTransitionController.isShellTransitionsEnabled()
-                            && !curDisplay.mTransitionController.isPlaying());
-            if (curDisplayInTransitNotAnimate) {
+            if (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppTransitioning()) {
                 // We have finished the animation of an app transition. To do this, we have
                 // delayed a lot of operations like showing and hiding apps, moving apps in
                 // Z-order, etc.
diff --git a/services/core/java/com/android/server/wm/SplashScreenExceptionList.java b/services/core/java/com/android/server/wm/SplashScreenExceptionList.java
index e815a0e..9ca49fe 100644
--- a/services/core/java/com/android/server/wm/SplashScreenExceptionList.java
+++ b/services/core/java/com/android/server/wm/SplashScreenExceptionList.java
@@ -64,7 +64,8 @@
                 mOnPropertiesChangedListener);
     }
 
-    private void updateDeviceConfig(String values) {
+    @VisibleForTesting
+    void updateDeviceConfig(String values) {
         parseDeviceConfigPackageList(values);
     }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1c40044..a477108 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -497,6 +497,11 @@
         if (fadeRotationController != null) {
             fadeRotationController.onTransitionFinished();
         }
+        // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget),
+        // so re-compute in case the IME target is changed after transition.
+        if (mTransientLaunches != null) {
+            mTargetDisplay.computeImeTarget(true /* updateImeTarget */);
+        }
     }
 
     void abort() {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bef7810..61acb97 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3354,8 +3354,12 @@
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             mChildren.get(i).finishSync(outMergedTransaction, cancel);
         }
-        mSyncState = SYNC_STATE_NONE;
         if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this);
+        clearSyncState();
+    }
+
+    void clearSyncState() {
+        mSyncState = SYNC_STATE_NONE;
         mSyncGroup = null;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fc1fd92..7be128b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2548,10 +2548,8 @@
         if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
             transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
         }
-        if (win.inTransition()) {
-            focusMayChange = true;
-            win.mAnimatingExit = true;
-        } else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
+
+        if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
             focusMayChange = true;
             win.mAnimatingExit = true;
         } else if (win.mDisplayContent.okToAnimate() && win.isAnimating(TRANSITION | PARENTS,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f6729c5..863e3ca 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5427,7 +5427,7 @@
 
     @Override
     void assignLayer(Transaction t, int layer) {
-        if (isStartingWindowAssociatedToTask()) {
+        if (mStartingData != null) {
             // The starting window should cover the task.
             t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
             return;
@@ -5747,29 +5747,36 @@
             Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms");
             mActivityRecord.mRelaunchStartTime = 0;
         }
-
-        executeDrawHandlers(postDrawTransaction);
-
-        final boolean applyPostDrawNow = mClientWasDrawingForSync && postDrawTransaction != null;
-        mClientWasDrawingForSync = false;
-        if (!onSyncFinishedDrawing()) {
-            return mWinAnimator.finishDrawingLocked(postDrawTransaction, applyPostDrawNow);
-        }
-
-        if (mActivityRecord != null
-                && mTransitionController.isShellTransitionsEnabled()
-                && mAttrs.type == TYPE_APPLICATION_STARTING) {
+        if (mActivityRecord != null && mAttrs.type == TYPE_APPLICATION_STARTING) {
             mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger()
                     .notifyStartingWindowDrawn(mActivityRecord);
         }
 
-        if (postDrawTransaction != null) {
+        final boolean hasSyncHandlers = executeDrawHandlers(postDrawTransaction);
+
+        boolean skipLayout = false;
+        // Control the timing to switch the appearance of window with different rotations.
+        final FadeRotationAnimationController fadeRotationController =
+                mDisplayContent.getFadeRotationAnimationController();
+        if (fadeRotationController != null
+                && fadeRotationController.handleFinishDrawing(this, postDrawTransaction)) {
+            // Consume the transaction because the controller will apply it with fade animation.
+            // Layout is not needed because the window will be hidden by the fade leash. Clear
+            // sync state because its sync transaction doesn't need to be merged to sync group.
+            postDrawTransaction = null;
+            skipLayout = true;
+            clearSyncState();
+        } else if (onSyncFinishedDrawing() && postDrawTransaction != null) {
             mSyncTransaction.merge(postDrawTransaction);
+            // Consume the transaction because the sync group will merge it.
+            postDrawTransaction = null;
         }
 
-        mWinAnimator.finishDrawingLocked(null, false /* forceApplyNow */);
+        final boolean layoutNeeded =
+                mWinAnimator.finishDrawingLocked(postDrawTransaction, mClientWasDrawingForSync);
+        mClientWasDrawingForSync = false;
         // We always want to force a traversal after a finish draw for blast sync.
-        return true;
+        return !skipLayout && (hasSyncHandlers || layoutNeeded);
     }
 
     void immediatelyNotifyBlastSync() {
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index eda05bf..b811e28 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -129,6 +129,7 @@
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.wifi.WifiInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.INetworkManagementService;
@@ -226,13 +227,13 @@
 
     private static final long TEST_START = 1194220800000L;
     private static final String TEST_IFACE = "test0";
-    private static final String TEST_SSID = "AndroidAP";
+    private static final String TEST_WIFI_NETWORK_KEY = "TestWifiNetworkKey";
     private static final String TEST_IMSI = "310210";
     private static final int TEST_SUB_ID = 42;
     private static final Network TEST_NETWORK = mock(Network.class, CALLS_REAL_METHODS);
 
 
-    private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_SSID);
+    private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_WIFI_NETWORK_KEY);
     private static NetworkTemplate sTemplateCarrierMetered =
             buildTemplateCarrierMetered(TEST_IMSI);
 
@@ -2096,10 +2097,13 @@
     }
 
     private static NetworkStateSnapshot buildWifi() {
+        WifiInfo mockWifiInfo = mock(WifiInfo.class);
+        when(mockWifiInfo.makeCopy(anyLong())).thenReturn(mockWifiInfo);
+        when(mockWifiInfo.getCurrentNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_IFACE);
         final NetworkCapabilities networkCapabilities = new NetworkCapabilities.Builder()
-                .addTransportType(TRANSPORT_WIFI).setSsid(TEST_SSID).build();
+                .addTransportType(TRANSPORT_WIFI).setTransportInfo(mockWifiInfo).build();
         return new NetworkStateSnapshot(TEST_NETWORK, networkCapabilities, prop,
                 null /*subscriberId*/, TYPE_WIFI);
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 6c9f8fe..1b898fa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -264,17 +264,17 @@
         final SuspendParams params = packageUserState1.getSuspendParams().valueAt(0);
         watcher.verifyNoChangeReported("fetch user state");
         assertThat(params, is(notNullValue()));
-        assertThat(params.appExtras.size(), is(1));
-        assertThat(params.appExtras.getString("app_extra_string"), is("value"));
-        assertThat(params.launcherExtras.size(), is(1));
-        assertThat(params.launcherExtras.getLong("launcher_extra_long"), is(4L));
-        assertThat(params.dialogInfo, is(notNullValue()));
-        assertThat(params.dialogInfo.getDialogMessage(), is("Dialog Message"));
-        assertThat(params.dialogInfo.getTitleResId(), is(ID_NULL));
-        assertThat(params.dialogInfo.getIconResId(), is(TEST_RESOURCE_ID));
-        assertThat(params.dialogInfo.getNeutralButtonTextResId(), is(ID_NULL));
-        assertThat(params.dialogInfo.getNeutralButtonAction(), is(BUTTON_ACTION_MORE_DETAILS));
-        assertThat(params.dialogInfo.getDialogMessageResId(), is(ID_NULL));
+        assertThat(params.getAppExtras().size(), is(1));
+        assertThat(params.getAppExtras().getString("app_extra_string"), is("value"));
+        assertThat(params.getLauncherExtras().size(), is(1));
+        assertThat(params.getLauncherExtras().getLong("launcher_extra_long"), is(4L));
+        assertThat(params.getDialogInfo(), is(notNullValue()));
+        assertThat(params.getDialogInfo().getDialogMessage(), is("Dialog Message"));
+        assertThat(params.getDialogInfo().getTitleResId(), is(ID_NULL));
+        assertThat(params.getDialogInfo().getIconResId(), is(TEST_RESOURCE_ID));
+        assertThat(params.getDialogInfo().getNeutralButtonTextResId(), is(ID_NULL));
+        assertThat(params.getDialogInfo().getNeutralButtonAction(), is(BUTTON_ACTION_MORE_DETAILS));
+        assertThat(params.getDialogInfo().getDialogMessageResId(), is(ID_NULL));
     }
 
     @Test
@@ -351,18 +351,18 @@
         final SuspendParams params11 = readPus1.getSuspendParams().valueAt(0);
         watcher.verifyNoChangeReported("read package param");
         assertThat(params11, is(notNullValue()));
-        assertThat(params11.dialogInfo, is(dialogInfo1));
-        assertThat(BaseBundle.kindofEquals(params11.appExtras, appExtras1), is(true));
-        assertThat(BaseBundle.kindofEquals(params11.launcherExtras, launcherExtras1),
+        assertThat(params11.getDialogInfo(), is(dialogInfo1));
+        assertThat(BaseBundle.kindofEquals(params11.getAppExtras(), appExtras1), is(true));
+        assertThat(BaseBundle.kindofEquals(params11.getLauncherExtras(), launcherExtras1),
                 is(true));
         watcher.verifyNoChangeReported("read package param");
 
         assertThat(readPus1.getSuspendParams().keyAt(1), is("suspendingPackage2"));
         final SuspendParams params12 = readPus1.getSuspendParams().valueAt(1);
         assertThat(params12, is(notNullValue()));
-        assertThat(params12.dialogInfo, is(dialogInfo2));
-        assertThat(BaseBundle.kindofEquals(params12.appExtras, appExtras2), is(true));
-        assertThat(BaseBundle.kindofEquals(params12.launcherExtras, launcherExtras2),
+        assertThat(params12.getDialogInfo(), is(dialogInfo2));
+        assertThat(BaseBundle.kindofEquals(params12.getAppExtras(), appExtras2), is(true));
+        assertThat(BaseBundle.kindofEquals(params12.getLauncherExtras(), launcherExtras2),
                 is(true));
         watcher.verifyNoChangeReported("read package param");
 
@@ -373,9 +373,9 @@
         assertThat(readPus2.getSuspendParams().keyAt(0), is("suspendingPackage3"));
         final SuspendParams params21 = readPus2.getSuspendParams().valueAt(0);
         assertThat(params21, is(notNullValue()));
-        assertThat(params21.dialogInfo, is(nullValue()));
-        assertThat(BaseBundle.kindofEquals(params21.appExtras, appExtras1), is(true));
-        assertThat(params21.launcherExtras, is(nullValue()));
+        assertThat(params21.getDialogInfo(), is(nullValue()));
+        assertThat(BaseBundle.kindofEquals(params21.getAppExtras(), appExtras1), is(true));
+        assertThat(params21.getLauncherExtras(), is(nullValue()));
         watcher.verifyNoChangeReported("read package param");
 
         final PackageUserStateInternal readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3)
@@ -388,7 +388,7 @@
     @Test
     public void testPackageRestrictionsSuspendedDefault() {
         final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
-        assertThat(defaultSetting.getSuspended(0), is(false));
+        assertThat(defaultSetting.getUserStateOrDefault(0).isSuspended(), is(false));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
index 828d419c..1e4134e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
@@ -82,7 +82,8 @@
         assertThat(testUserState.equals(oldUserState), is(false));
 
         oldUserState = new PackageUserStateImpl();
-        oldUserState.setSuspended(true);
+        oldUserState.putSuspendParams("suspendingPackage",
+                SuspendParams.getInstanceOrNull(null, new PersistableBundle(), null));
         assertThat(testUserState.equals(oldUserState), is(false));
 
         oldUserState = new PackageUserStateImpl();
@@ -231,7 +232,6 @@
 
 
         final PackageUserStateImpl testUserState1 = new PackageUserStateImpl();
-        testUserState1.setSuspended(true);
         testUserState1.setSuspendParams(paramsMap1);
 
         PackageUserStateImpl testUserState2 =
diff --git a/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java b/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
index 3714d99..f5d915d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
@@ -39,6 +39,7 @@
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 /**
  * Test for the splash screen exception list
@@ -55,7 +56,16 @@
     private DeviceConfig.Properties mInitialWindowManagerProperties;
     private final HandlerExecutor mExecutor = new HandlerExecutor(
             new Handler(Looper.getMainLooper()));
-    private final SplashScreenExceptionList mList = new SplashScreenExceptionList(mExecutor);
+    private final SplashScreenExceptionList mList = new SplashScreenExceptionList(mExecutor) {
+        @Override
+        void updateDeviceConfig(String rawList) {
+            super.updateDeviceConfig(rawList);
+            if (mOnUpdateDeviceConfig != null) {
+                mOnUpdateDeviceConfig.accept(rawList);
+            }
+        }
+    };
+    private Consumer<String> mOnUpdateDeviceConfig;
 
     @Before
     public void setUp() throws Exception {
@@ -91,13 +101,11 @@
 
     private void setExceptionListAndWaitForCallback(String commaSeparatedList) {
         CountDownLatch latch = new CountDownLatch(1);
-        DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener = (p) -> {
-            if (commaSeparatedList.equals(p.getString(KEY_SPLASH_SCREEN_EXCEPTION_LIST, null))) {
+        mOnUpdateDeviceConfig = rawList -> {
+            if (commaSeparatedList.equals(rawList)) {
                 latch.countDown();
             }
         };
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-                mExecutor, onPropertiesChangedListener);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                 KEY_SPLASH_SCREEN_EXCEPTION_LIST, commaSeparatedList, false);
         try {
@@ -105,8 +113,6 @@
                     latch.await(1, TimeUnit.SECONDS));
         } catch (InterruptedException e) {
             Assert.fail(e.getMessage());
-        } finally {
-            DeviceConfig.removeOnPropertiesChangedListener(onPropertiesChangedListener);
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index a1d0eb8..790b154 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -25,11 +25,10 @@
 import static com.android.server.wm.utils.CommonUtils.runWithShellPermissionIdentity;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
@@ -57,7 +56,6 @@
 import androidx.test.filters.MediumTest;
 
 import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.util.Arrays;
@@ -73,49 +71,42 @@
 @MediumTest
 public class TaskStackChangedListenerTest {
 
-    private static final int VIRTUAL_DISPLAY_WIDTH = 800;
-    private static final int VIRTUAL_DISPLAY_HEIGHT = 600;
-    private static final int VIRTUAL_DISPLAY_DENSITY = 160;
-
     private ITaskStackListener mTaskStackListener;
-    private DisplayManager mDisplayManager;
     private VirtualDisplay mVirtualDisplay;
+    private ImageReader mImageReader;
 
     private static final int WAIT_TIMEOUT_MS = 5000;
     private static final Object sLock = new Object();
 
-    @Before
-    public void setUp() {
-        mDisplayManager = getInstrumentation().getContext().getSystemService(
-                DisplayManager.class);
-        mVirtualDisplay = createVirtualDisplay(
-                getClass().getSimpleName() + "_virtualDisplay",
-                VIRTUAL_DISPLAY_WIDTH, VIRTUAL_DISPLAY_HEIGHT, VIRTUAL_DISPLAY_DENSITY);
-    }
-
     @After
     public void tearDown() throws Exception {
-        ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener);
-        mTaskStackListener = null;
-        mVirtualDisplay.release();
+        if (mTaskStackListener != null) {
+            ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener);
+        }
+        if (mVirtualDisplay != null) {
+            mVirtualDisplay.release();
+            mImageReader.close();
+        }
     }
 
-    private VirtualDisplay createVirtualDisplay(String name, int width, int height, int density) {
-        VirtualDisplay virtualDisplay = null;
-        try (ImageReader reader = ImageReader.newInstance(width, height,
-                /* format= */ PixelFormat.RGBA_8888, /* maxImages= */ 2)) {
-            int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
-                    | VIRTUAL_DISPLAY_FLAG_PUBLIC;
-            virtualDisplay = mDisplayManager.createVirtualDisplay(
-                    name, width, height, density, reader.getSurface(), flags);
-            virtualDisplay.setSurface(reader.getSurface());
-        }
-        assertTrue("display id must be unique",
-                virtualDisplay.getDisplay().getDisplayId() != Display.DEFAULT_DISPLAY);
+    private VirtualDisplay createVirtualDisplay() {
+        final int width = 800;
+        final int height = 600;
+        final int density = 160;
+        final DisplayManager displayManager = getInstrumentation().getContext().getSystemService(
+                DisplayManager.class);
+        mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
+                2 /* maxImages */);
+        final int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+                | VIRTUAL_DISPLAY_FLAG_PUBLIC;
+        final String name = getClass().getSimpleName() + "_VirtualDisplay";
+        mVirtualDisplay = displayManager.createVirtualDisplay(name, width, height, density,
+                mImageReader.getSurface(), flags);
+        mVirtualDisplay.setSurface(mImageReader.getSurface());
         assertNotNull("display must be registered",
-                Arrays.asList(mDisplayManager.getDisplays()).stream().filter(
+                Arrays.stream(displayManager.getDisplays()).filter(
                         d -> d.getName().equals(name)).findAny());
-        return virtualDisplay;
+        return mVirtualDisplay;
     }
 
     @Test
@@ -163,11 +154,10 @@
                 mTaskId = taskId;
             }
             @Override
-            public void onTaskDescriptionChanged(int taskId, TaskDescription td)
-                    throws RemoteException {
-                if (mTaskId == taskId && !TextUtils.isEmpty(td.getLabel())) {
-                    params[0] = taskId;
-                    params[1] = td;
+            public void onTaskDescriptionChanged(RunningTaskInfo info) {
+                if (mTaskId == info.taskId && !TextUtils.isEmpty(info.taskDescription.getLabel())) {
+                    params[0] = info.taskId;
+                    params[1] = info.taskDescription;
                     latch.countDown();
                 }
             }
@@ -211,75 +201,71 @@
     @Test
     @Presubmit
     public void testTaskChangeCallBacks() throws Exception {
-        final Object[] params = new Object[2];
         final CountDownLatch taskCreatedLaunchLatch = new CountDownLatch(1);
         final CountDownLatch taskMovedToFrontLatch = new CountDownLatch(1);
         final CountDownLatch taskRemovedLatch = new CountDownLatch(1);
         final CountDownLatch taskRemovalStartedLatch = new CountDownLatch(1);
-        final CountDownLatch onDetachedFromWindowLatch = new CountDownLatch(1);
+        final int[] expectedTaskId = { -1 };
+        final int[] receivedTaskId = { -1 };
+        final ComponentName expectedName = new ComponentName(getInstrumentation().getContext(),
+                ActivityTaskChangeCallbacks.class);
         registerTaskStackChangedListener(new TaskStackListener() {
             @Override
-            public void onTaskCreated(int taskId, ComponentName componentName)
-                    throws RemoteException {
-                params[0] = taskId;
-                params[1] = componentName;
-                taskCreatedLaunchLatch.countDown();
+            public void onTaskCreated(int taskId, ComponentName componentName) {
+                receivedTaskId[0] = taskId;
+                if (expectedName.equals(componentName)) {
+                    taskCreatedLaunchLatch.countDown();
+                }
             }
 
             @Override
-            public void onTaskMovedToFront(int taskId) throws RemoteException {
-                params[0] = taskId;
+            public void onTaskMovedToFront(RunningTaskInfo info) {
+                receivedTaskId[0] = info.taskId;
                 taskMovedToFrontLatch.countDown();
             }
 
             @Override
-            public void onTaskRemovalStarted(int taskId) {
-                params[0] = taskId;
-                taskRemovalStartedLatch.countDown();
+            public void onTaskRemovalStarted(RunningTaskInfo info) {
+                if (expectedTaskId[0] == info.taskId) {
+                    taskRemovalStartedLatch.countDown();
+                }
             }
 
             @Override
-            public void onTaskRemoved(int taskId) throws RemoteException {
-                if (taskCreatedLaunchLatch.getCount() == 1) {
-                    // The test activity hasn't started. Ignore the noise from previous test.
-                    return;
+            public void onTaskRemoved(int taskId) {
+                if (expectedTaskId[0] == taskId) {
+                    taskRemovedLatch.countDown();
                 }
-                params[0] = taskId;
-                taskRemovedLatch.countDown();
             }
         });
 
         final ActivityTaskChangeCallbacks activity =
                 (ActivityTaskChangeCallbacks) startTestActivity(ActivityTaskChangeCallbacks.class);
-        activity.setDetachedFromWindowLatch(onDetachedFromWindowLatch);
-        final int id = activity.getTaskId();
+        expectedTaskId[0] = activity.getTaskId();
 
         // Test for onTaskCreated and onTaskMovedToFront
         waitForCallback(taskMovedToFrontLatch);
         assertEquals(0, taskCreatedLaunchLatch.getCount());
-        assertEquals(id, params[0]);
-        ComponentName componentName = (ComponentName) params[1];
-        assertEquals(ActivityTaskChangeCallbacks.class.getName(), componentName.getClassName());
+        assertEquals(expectedTaskId[0], receivedTaskId[0]);
 
+        // Ensure that the window is attached before removal so there will be a detached callback.
+        waitForCallback(activity.mOnAttachedToWindowCountDownLatch);
         // Test for onTaskRemovalStarted.
         assertEquals(1, taskRemovalStartedLatch.getCount());
         assertEquals(1, taskRemovedLatch.getCount());
         activity.finishAndRemoveTask();
         waitForCallback(taskRemovalStartedLatch);
         // onTaskRemovalStarted happens before the activity's window is removed.
-        assertFalse(activity.mOnDetachedFromWindowCalled);
-        assertEquals(id, params[0]);
+        assertEquals(1, activity.mOnDetachedFromWindowCountDownLatch.getCount());
 
         // Test for onTaskRemoved.
         waitForCallback(taskRemovedLatch);
-        assertEquals(id, params[0]);
-        waitForCallback(onDetachedFromWindowLatch);
-        assertTrue(activity.mOnDetachedFromWindowCalled);
+        waitForCallback(activity.mOnDetachedFromWindowCountDownLatch);
     }
 
     @Test
     public void testTaskDisplayChanged() throws Exception {
-        int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId();
+        final int virtualDisplayId = createVirtualDisplay().getDisplay().getDisplayId();
 
         // Launch a Activity inside VirtualDisplay
         CountDownLatch displayChangedLatch1 = new CountDownLatch(1);
@@ -498,18 +484,18 @@
     }
 
     public static class ActivityTaskChangeCallbacks extends TestActivity {
-        public boolean mOnDetachedFromWindowCalled = false;
-        private CountDownLatch mOnDetachedFromWindowCountDownLatch;
+        final CountDownLatch mOnAttachedToWindowCountDownLatch = new CountDownLatch(1);
+        final CountDownLatch mOnDetachedFromWindowCountDownLatch = new CountDownLatch(1);
+
+        @Override
+        public void onAttachedToWindow() {
+            mOnAttachedToWindowCountDownLatch.countDown();
+        }
 
         @Override
         public void onDetachedFromWindow() {
-            mOnDetachedFromWindowCalled = true;
             mOnDetachedFromWindowCountDownLatch.countDown();
         }
-
-        void setDetachedFromWindowLatch(CountDownLatch countDownLatch) {
-            mOnDetachedFromWindowCountDownLatch = countDownLatch;
-        }
     }
 
     public static class ActivityInVirtualDisplay extends TestActivity {
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 ec6cd92..ed3888c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -556,8 +556,15 @@
 
         // The redrawn window will be faded in when the transition finishes. And because this test
         // only use one non-activity window, the fade rotation controller should also be cleared.
-        statusBar.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+        statusBar.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
+        final SurfaceControl.Transaction postDrawTransaction =
+                mock(SurfaceControl.Transaction.class);
+        final boolean layoutNeeded = statusBar.finishDrawing(postDrawTransaction);
+        assertFalse(layoutNeeded);
         player.finish();
+        // The controller should capture the draw transaction and merge it when preparing to run
+        // fade-in animation.
+        verify(mDisplayContent.getPendingTransaction()).merge(eq(postDrawTransaction));
         assertNull(mDisplayContent.getFadeRotationAnimationController());
     }
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7d17894..82113f2 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4181,6 +4181,56 @@
             "gba_ua_tls_cipher_suite_int";
 
     /**
+     * The data stall recovery timers array in milliseconds, each element is the delay before
+     * performining next recovery action.
+     *
+     * The default value of timers array are: [180000ms, 180000ms, 180000ms] (3 minutes)
+     * Array[0]: It's the timer between RECOVERY_ACTION GET_DATA_CALL_LIST and CLEANUP, if data
+     * stall symptom still occurred, it will perform next recovery action after 180000ms.
+     * Array[1]: It's the timer between RECOVERY_ACTION CLEANUP and RADIO_RESTART, if data stall
+     * symptom still occurred, it will perform next recovery action after 180000ms.
+     * Array[2]: It's the timer between RECOVERY_ACTION RADIO_RESTART and RESET_MODEM, if data stall
+     * symptom still occurred, it will perform next recovery action after 180000ms.
+     *
+     * See the {@code RECOVERY_ACTION_*} constants in
+     * {@link com.android.internal.telephony.data.DataStallRecoveryManager}
+     * @hide
+     */
+    public static final String KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY =
+            "data_stall_recovery_timers_long_array";
+
+    /**
+     * The data stall recovery action boolean array, we use this array to determine if the
+     * data stall recovery action needs to be skipped.
+     *
+     * For example, if the carrier use the same APN for both of IA and default type,
+     * the data call will not disconnect in modem side (so the RECOVERY_ACTION_CLEANUP
+     * did not effect). In this case, we can config the boolean variable of action
+     * RECOVERY_ACTION_CLEANUP to true, then it can be ignored to speed up the recovery
+     * action procedure.
+     *
+     * The default value of boolean array are: [false, false, false, false]
+     * Array[0]: When performing the recovery action, we can use this boolean value to determine
+     * if we need to perform RECOVERY_ACTION_GET_DATA_CALL_LIST.
+     * Array[1]: If data stall symptom still occurred, we can use this boolean value to determine
+     * if we need to perform RECOVERY_ACTION_CLEANUP. For example, if the carrier use the same APN
+     * for both of IA and default type, the data call will not disconnect in modem side
+     * (so the RECOVERY_ACTION_CLEANUP did not effect). In this case, we can config the boolean
+     * variable of action RECOVERY_ACTION_CLEANUP to true, then it can be ignored to speed up the
+     * recovery action procedure.
+     * Array[2]: If data stall symptom still occurred, we can use this boolean value to determine
+     * if we need to perform RECOVERY_ACTION_RADIO_RESTART.
+     * Array[3]: If data stall symptom still occurred, we can use this boolean value to determine
+     * if we need to perform RECOVERY_ACTION_MODEM_RESET.
+     *
+     * See the {@code RECOVERY_ACTION_*} constants in
+     * {@link com.android.internal.telephony.data.DataStallRecoveryManager}
+     * @hide
+     */
+    public static final String KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY =
+            "data_stall_recovery_should_skip_bool_array";
+
+    /**
      * Configs used by ImsServiceEntitlement.
      */
     public static final class ImsServiceEntitlement {
@@ -6172,6 +6222,11 @@
                         + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"});
         sDefaults.putInt(KEY_CELLULAR_USAGE_SETTING_INT,
                 SubscriptionManager.USAGE_SETTING_UNKNOWN);
+        // Default data stall recovery configurations.
+        sDefaults.putLongArray(KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY,
+                new long[] {180000, 180000, 180000});
+        sDefaults.putBooleanArray(KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY,
+                new boolean[] {false, false, false, false});
     }
 
     /**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index 59e8dc8..8fe0029 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -37,16 +37,21 @@
         .launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
     fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) {
-        val button = device.wait(
-                Until.findObject(By.res(getPackage(), "launch_second_activity")),
-                FIND_TIMEOUT)
+        val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY)
+        val button = device.wait(Until.findObject(launchActivityButton), FIND_TIMEOUT)
 
         require(button != null) {
             "Button not found, this usually happens when the device " +
                     "was left in an unknown state (e.g. in split screen)"
         }
         button.click()
+
+        device.wait(Until.gone(launchActivityButton), FIND_TIMEOUT)
         wmHelper.waitForAppTransitionIdle()
         wmHelper.waitForFullScreenApp(component)
     }
+
+    companion object {
+        private const val LAUNCH_SECOND_ACTIVITY = "launch_second_activity"
+    }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index a7a9fe2..19e2c92 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -86,8 +86,8 @@
                 // [Step1]: Swipe right from imeTestApp to testApp task
                 createTag(TAG_IME_VISIBLE)
                 val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
-                device.swipe(0, displayBounds.bounds.height(),
-                        displayBounds.bounds.width(), displayBounds.bounds.height(), 50)
+                device.swipe(0, displayBounds.bounds.height,
+                        displayBounds.bounds.width, displayBounds.bounds.height, 50)
 
                 wmHelper.waitForFullScreenApp(testApp.component)
                 wmHelper.waitForAppTransitionIdle()
@@ -96,8 +96,8 @@
             transitions {
                 // [Step2]: Swipe left to back to imeTestApp task
                 val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
-                device.swipe(displayBounds.bounds.width(), displayBounds.bounds.height(),
-                        0, displayBounds.bounds.height(), 50)
+                device.swipe(displayBounds.bounds.width, displayBounds.bounds.height,
+                        0, displayBounds.bounds.height, 50)
                 wmHelper.waitForFullScreenApp(imeTestApp.component)
             }
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 769cb1a..882e128 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -148,22 +148,28 @@
         val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
 
         testSpec.assertLayers {
-            this.coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY)
+            this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
+                    it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds)
+                }
                 .isInvisible(bgColorLayer)
                 .then()
                 // Transitioning
                 .isVisible(bgColorLayer)
                 .then()
                 // Fully transitioned to simple SIMPLE_ACTIVITY
-                .coversExactly(displayBounds, SIMPLE_ACTIVITY)
+                .invoke("SIMPLE_ACTIVITY coversExactly displayBounds") {
+                    it.visibleRegion(SIMPLE_ACTIVITY).coversExactly(displayBounds)
+                }
                 .isInvisible(bgColorLayer)
                 .then()
                 // Transitioning back
                 .isVisible(bgColorLayer)
                 .then()
                 // Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY
+                .invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
+                    it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds)
+                }
                 .isInvisible(bgColorLayer)
-                .coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY)
         }
     }
 
diff --git a/tests/HwAccelerationTest/Android.bp b/tests/HwAccelerationTest/Android.bp
index e618ed1..51848f2 100644
--- a/tests/HwAccelerationTest/Android.bp
+++ b/tests/HwAccelerationTest/Android.bp
@@ -32,6 +32,10 @@
         "**/*.java",
         "**/*.kt",
     ],
+    static_libs: [
+        "androidx.cardview_cardview",
+    ],
+
     platform_apis: true,
     certificate: "platform",
 }
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 22fe424..b0ccbd1 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -789,6 +789,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="RenderEffectViewActivity"
+                  android:label="RenderEffect/View"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.android.test.hwui.TEST"/>
+            </intent-filter>
+        </activity>
+
         <activity android:name="StretchShaderActivity"
                   android:label="RenderEffect/Stretch"
                   android:exported="true">
diff --git a/tests/HwAccelerationTest/res/drawable-nodpi/scratches.png b/tests/HwAccelerationTest/res/drawable-nodpi/scratches.png
new file mode 100644
index 0000000..cc8adf1
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable-nodpi/scratches.png
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable-nodpi/weather_2.jpg b/tests/HwAccelerationTest/res/drawable-nodpi/weather_2.jpg
new file mode 100644
index 0000000..b5aff10
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable-nodpi/weather_2.jpg
Binary files differ
diff --git a/tests/HwAccelerationTest/res/layout/view_runtime_shader.xml b/tests/HwAccelerationTest/res/layout/view_runtime_shader.xml
new file mode 100644
index 0000000..b91377d
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/view_runtime_shader.xml
@@ -0,0 +1,205 @@
+<?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.
+  -->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:id="@+id/TopLayout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingBottom="8dp"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:text="Sample Card #1"/>
+
+        <androidx.cardview.widget.CardView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:layout_marginBottom="16dp"
+            android:clickable="true"
+            android:focusable="true"
+            android:minHeight="148dp">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:padding="16dp"
+                    android:paddingBottom="8dp">
+
+                    <ImageView
+                        android:layout_width="50dp"
+                        android:layout_height="50dp"
+                        android:layout_marginEnd="8dp"
+                        android:layout_marginRight="8dp"
+                        android:contentDescription="Logo"
+                        android:src="@drawable/icon"/>
+
+                    <LinearLayout
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1.0"
+                        android:layout_marginStart="8dp"
+                        android:layout_marginLeft="8dp"
+                        android:orientation="vertical">
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:textStyle="bold"
+                            android:text="Image Transition"/>
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dp"
+                            android:ellipsize="end"
+                            android:maxLines="1"
+                            android:text="Touch the image to trigger the animation"/>
+                    </LinearLayout>
+                </LinearLayout>
+
+                <com.android.test.hwui.BitmapTransitionView
+                    android:layout_width="match_parent"
+                    android:layout_height="194dp"
+                    android:padding="8dp"/>
+
+            </LinearLayout>
+
+        </androidx.cardview.widget.CardView>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:text="Sample Card #2"/>
+
+        <androidx.cardview.widget.CardView
+            android:id="@+id/CardView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:layout_marginBottom="16dp"
+            android:clickable="true"
+            android:focusable="true"
+            android:minHeight="148dp">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingTop="16dp"
+                android:orientation="vertical">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:padding="16dp"
+                    android:paddingBottom="8dp">
+
+                    <ImageView
+                        android:layout_width="50dp"
+                        android:layout_height="50dp"
+                        android:layout_marginEnd="8dp"
+                        android:layout_marginRight="8dp"
+                        android:contentDescription="Logo"
+                        android:src="@drawable/icon"/>
+
+                    <LinearLayout
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1.0"
+                        android:layout_marginStart="8dp"
+                        android:layout_marginLeft="8dp"
+                        android:orientation="vertical">
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:textStyle="bold"
+                            android:text="View Group Manipulation"/>
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dp"
+                            android:ellipsize="end"
+                            android:maxLines="1"
+                            android:text="Tap the card to trigger the animation"/>
+                    </LinearLayout>
+                </LinearLayout>
+
+                <ImageView
+                    android:layout_width="match_parent"
+                    android:layout_height="194dp"
+                    android:background="@android:color/transparent"
+                    android:src="@drawable/weather_2"/>
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:padding="16dp"
+                    android:paddingBottom="8dp"
+                    android:orientation="vertical">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content">
+                        <RatingBar
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:contentDescription="Card Rating"
+                            android:isIndicator="true"
+                            android:numStars="5"
+                            android:rating="4.5"
+
+                            android:stepSize="0.5"/>
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center_vertical"
+                            android:text="Category 4.5 Storm"/>
+                    </LinearLayout>
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="8dp"
+                        android:maxLines="3"
+                        android:textIsSelectable="true"
+                        android:text="Lorem ipsum dolor sit amet, nec no nominavi scaevola. Per et
+                        sint sapientem, nobis perpetua salutandi mei te. Quo tamquam probatus
+                        reprehendunt in. Eos esse purto eruditi ea. Enim tation persius ut sea,
+                        eos ad consul populo. Ne eum solet altera. Cibo eligendi et est, electram
+                        theophrastus te vel eu."/>
+
+                </LinearLayout>
+
+            </LinearLayout>
+
+        </androidx.cardview.widget.CardView>
+    </LinearLayout>
+</ScrollView>
diff --git a/tests/HwAccelerationTest/res/values/styles.xml b/tests/HwAccelerationTest/res/values/styles.xml
index fa5437f..55f4dd69 100644
--- a/tests/HwAccelerationTest/res/values/styles.xml
+++ b/tests/HwAccelerationTest/res/values/styles.xml
@@ -41,4 +41,5 @@
         <item name="android:spotShadowAlpha">1</item>
         -->
     </style>
+
 </resources>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapTransitionView.kt b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapTransitionView.kt
new file mode 100644
index 0000000..d3ad9e8
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapTransitionView.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.BitmapShader
+import android.graphics.Canvas
+import android.graphics.ImageDecoder
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.RuntimeShader
+import android.graphics.Shader
+import android.util.AttributeSet
+import android.view.View
+
+class BitmapTransitionView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr) {
+
+    private val mPaint = Paint()
+    private val mImageA = ImageDecoder.decodeBitmap(
+            ImageDecoder.createSource(context.resources, R.drawable.large_photo))
+    private val mImageB = ImageDecoder.decodeBitmap(
+            ImageDecoder.createSource(context.resources, R.drawable.very_large_photo))
+    private val mShaderA = BitmapShader(mImageA, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+    private val mShaderB = BitmapShader(mImageB, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+    private val mShader = RuntimeShader(AGSL, false)
+    private var mCurrentProgress = -1f
+    private var mForwardProgress = true
+    private var mCurrentAnimator = ValueAnimator.ofFloat(-1f, 1f)
+
+    init {
+        isClickable = true
+
+        mCurrentAnimator.duration = 1500
+        mCurrentAnimator.addUpdateListener { animation ->
+            mCurrentProgress = animation.animatedValue as Float
+            postInvalidate()
+        }
+    }
+
+    override fun performClick(): Boolean {
+        if (super.performClick()) return true
+
+        if (mCurrentAnimator.isRunning) {
+            mCurrentAnimator.reverse()
+            return true
+        }
+
+        if (mForwardProgress) {
+            mCurrentAnimator.setFloatValues(-1f, 1f)
+            mForwardProgress = false
+        } else {
+            mCurrentAnimator.setFloatValues(1f, -1f)
+            mForwardProgress = true
+        }
+
+        mCurrentAnimator.start()
+        postInvalidate()
+        return true
+    }
+
+    override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
+        val matrixA = Matrix()
+        val matrixB = Matrix()
+
+        matrixA.postScale(width.toFloat() / mImageA.width, height.toFloat() / mImageA.height)
+        matrixB.postScale(width.toFloat() / mImageB.width, height.toFloat() / mImageB.height)
+
+        mShaderA.setLocalMatrix(matrixA)
+        mShaderB.setLocalMatrix(matrixB)
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+
+        mShader.setInputShader("imageA", mShaderA)
+        mShader.setInputShader("imageB", mShaderB)
+        mShader.setIntUniform("imageDimensions", width, height)
+        mShader.setFloatUniform("progress", mCurrentProgress)
+
+        mPaint.shader = mShader
+        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), mPaint)
+    }
+
+    private companion object {
+        const val AGSL = """
+        uniform shader imageA;
+        uniform shader imageB;
+        uniform ivec2 imageDimensions;
+        uniform float progress;
+
+        const vec2 iSize = vec2(48.0, 48.0);
+        const float iDir = 0.5;
+        const  float iRand = 0.81;
+
+        float hash12(vec2 p) {
+            vec3 p3  = fract(vec3(p.xyx) * .1031);
+            p3 += dot(p3, p3.yzx + 33.33);
+            return fract((p3.x + p3.y) * p3.z);
+        }
+
+        float ramp(float2 p) {
+          return mix(hash12(p),
+                     dot(p/vec2(imageDimensions), float2(iDir, 1 - iDir)),
+                     iRand);
+        }
+
+        half4 main(float2 p) {
+          float2 lowRes = p / iSize;
+          float2 cellCenter = (floor(lowRes) + 0.5) * iSize;
+          float2 posInCell = fract(lowRes) * 2 - 1;
+
+          float v = ramp(cellCenter) + progress;
+          float distToCenter = max(abs(posInCell.x), abs(posInCell.y));
+
+          return distToCenter > v ? imageA.eval(p).rgb1 : imageB.eval(p).rgb1;
+        }
+        """
+    }
+}
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RenderEffectViewActivity.kt b/tests/HwAccelerationTest/src/com/android/test/hwui/RenderEffectViewActivity.kt
new file mode 100644
index 0000000..06280d2
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RenderEffectViewActivity.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui
+
+import android.animation.ValueAnimator
+import android.app.Activity
+import android.graphics.Bitmap
+import android.graphics.BitmapShader
+import android.graphics.ImageDecoder
+import android.graphics.Matrix
+import android.graphics.Shader
+import android.graphics.RenderEffect
+import android.graphics.RuntimeShader
+import android.os.Bundle
+import android.view.View
+
+class RenderEffectViewActivity : Activity() {
+
+    private val mDropsShader = RuntimeShader(dropsAGSL, false)
+    private var mDropsAnimator = ValueAnimator.ofFloat(0f, 1f)
+    private var mStartTime = System.currentTimeMillis()
+    private lateinit var mScratchesImage: Bitmap
+    private lateinit var mScratchesShader: BitmapShader
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.view_runtime_shader)
+
+        val dropsView = findViewById<View>(R.id.CardView)!!
+        dropsView.isClickable = true
+        dropsView.setOnClickListener {
+            if (mDropsAnimator.isRunning) {
+                mDropsAnimator.cancel()
+                dropsView.setRenderEffect(null)
+            } else {
+                mDropsAnimator.start()
+            }
+        }
+
+        val imgSource = ImageDecoder.createSource(resources, R.drawable.scratches)
+        mScratchesImage = ImageDecoder.decodeBitmap(imgSource)
+        mScratchesShader = BitmapShader(mScratchesImage,
+                                        Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+
+        mDropsAnimator.duration = 1000
+        mDropsAnimator.repeatCount = ValueAnimator.INFINITE
+        mDropsAnimator.addUpdateListener { _ ->
+            val viewWidth = dropsView.width.toFloat()
+            val viewHeight = dropsView.height.toFloat()
+            val scratchesMatrix = Matrix()
+            scratchesMatrix.postScale(viewWidth / mScratchesImage.width,
+                    viewHeight / mScratchesImage.height)
+            mScratchesShader.setLocalMatrix(scratchesMatrix)
+
+            mDropsShader.setInputShader("scratches", mScratchesShader)
+            mDropsShader.setFloatUniform("elapsedSeconds",
+                                    (System.currentTimeMillis() - mStartTime) / 1000f)
+            mDropsShader.setFloatUniform("viewDimensions", viewWidth, viewHeight)
+
+            val dropsEffect = RenderEffect.createRuntimeShaderEffect(mDropsShader, "background")
+            val blurEffect = RenderEffect.createBlurEffect(10f, 10f, Shader.TileMode.CLAMP)
+
+            dropsView.setRenderEffect(RenderEffect.createChainEffect(dropsEffect, blurEffect))
+        }
+    }
+
+    private companion object {
+        const val dropsAGSL = """
+            uniform float elapsedSeconds;
+            uniform vec2 viewDimensions;
+            uniform shader background;
+            uniform shader scratches;
+
+            vec2 dropsUV(vec2 fragCoord ) {
+                vec2 uv = fragCoord.xy / viewDimensions.xy; // 0 <> 1
+                vec2 offs = vec2(0.);
+                return (offs + uv).xy;
+            }
+
+            const vec3  iFrostColorRGB = vec3(0.5, 0.5, 0.5);
+            const float iFrostColorAlpha = .3;
+
+            half4 main(float2 fragCoord) {
+                half4 bg = background.eval(dropsUV(fragCoord)*viewDimensions.xy);
+                float2 scratchCoord = fragCoord.xy / viewDimensions.xy;;
+                scratchCoord += 1.5;
+                scratchCoord = mod(scratchCoord, 1);
+                half scratch = scratches.eval(scratchCoord*viewDimensions.xy).r;
+                bg.rgb = mix(bg.rgb, iFrostColorRGB, iFrostColorAlpha);
+                bg.rgb = mix(bg.rgb, half3(1), pow(scratch,3));
+                return bg;
+            }
+        """
+    }
+}
\ No newline at end of file