Merge "Add "Locked" suffix to IMMS methods"
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/core/api/test-current.txt b/core/api/test-current.txt
index fad681f..b37c938 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2086,6 +2086,7 @@
     field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
     field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
     field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
+    field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
   }
 
   public final class Settings {
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/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 22b444e..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;
 
@@ -1340,28 +1339,43 @@
         mInflater = (LayoutInflater)getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow");
-        mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
-                WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
-        mWindow.getWindow().getAttributes().setFitInsetsTypes(statusBars() | navigationBars());
-        mWindow.getWindow().getAttributes().setFitInsetsSides(Side.all() & ~Side.BOTTOM);
-        mWindow.getWindow().getAttributes().receiveInsetsIgnoringZOrder = true;
+        mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
 
-        // 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 c2286d1..66288d6 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -42,23 +42,15 @@
 import java.lang.annotation.Retention;
 
 /**
- * A SoftInputWindow is a Dialog that is intended to be used for a top-level input
- * method window.  It will be displayed along the edge of the screen, moving
- * the application user interface away from it so that the focused item is
- * always visible.
- * @hide
+ * A {@link SoftInputWindow} is a {@link Dialog} that is intended to be used for a top-level input
+ * method window.  It will be displayed along the edge of the screen, moving the application user
+ * interface away from it so that the focused item is always visible.
  */
-public final class SoftInputWindow extends Dialog {
+final class SoftInputWindow extends Dialog {
     private static final boolean DEBUG = false;
     private static final String TAG = "SoftInputWindow";
 
-    private final String mName;
-    private final Callback mCallback;
-    private final KeyEvent.Callback mKeyEventCallback;
     private final KeyEvent.DispatcherState mDispatcherState;
-    private final int mWindowType;
-    private final int mGravity;
-    private final boolean mTakesFocus;
     private final Rect mBounds = new Rect();
 
     @Retention(SOURCE)
@@ -93,22 +85,12 @@
     private int mWindowState = WindowState.TOKEN_PENDING;
 
     /**
-     * Used to provide callbacks.
-     */
-    public interface Callback {
-        /**
-         * Used to be notified when {@link Dialog#onBackPressed()} gets called.
-         */
-        void onBackPressed();
-    }
-
-    /**
      * Set {@link IBinder} window token to the window.
      *
      * <p>This method can be called only once.</p>
      * @param token {@link IBinder} token to be associated with the window.
      */
-    public void setToken(IBinder token) {
+    void setToken(IBinder token) {
         switch (mWindowState) {
             case WindowState.TOKEN_PENDING:
                 // Normal scenario.  Nothing to worry about.
@@ -152,18 +134,9 @@
      *        using styles. This theme is applied on top of the current theme in
      *        <var>context</var>. If 0, the default dialog theme will be used.
      */
-    public SoftInputWindow(Context context, String name, int theme, Callback callback,
-            KeyEvent.Callback keyEventCallback, KeyEvent.DispatcherState dispatcherState,
-            int windowType, int gravity, boolean takesFocus) {
+    SoftInputWindow(Context context, int theme, KeyEvent.DispatcherState dispatcherState) {
         super(context, theme);
-        mName = name;
-        mCallback = callback;
-        mKeyEventCallback = keyEventCallback;
         mDispatcherState = dispatcherState;
-        mWindowType = windowType;
-        mGravity = gravity;
-        mTakesFocus = takesFocus;
-        initDockWindow();
     }
 
     @Override
@@ -188,83 +161,6 @@
         }
     }
 
-    private void updateWidthHeight(WindowManager.LayoutParams lp) {
-        if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
-            lp.width = WindowManager.LayoutParams.MATCH_PARENT;
-            lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
-        } else {
-            lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
-            lp.height = WindowManager.LayoutParams.MATCH_PARENT;
-        }
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (mKeyEventCallback != null && mKeyEventCallback.onKeyDown(keyCode, event)) {
-            return true;
-        }
-        return super.onKeyDown(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
-        if (mKeyEventCallback != null && mKeyEventCallback.onKeyLongPress(keyCode, event)) {
-            return true;
-        }
-        return super.onKeyLongPress(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (mKeyEventCallback != null && mKeyEventCallback.onKeyUp(keyCode, event)) {
-            return true;
-        }
-        return super.onKeyUp(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
-        if (mKeyEventCallback != null && mKeyEventCallback.onKeyMultiple(keyCode, count, event)) {
-            return true;
-        }
-        return super.onKeyMultiple(keyCode, count, event);
-    }
-
-    @Override
-    public void onBackPressed() {
-        if (mCallback != null) {
-            mCallback.onBackPressed();
-        } else {
-            super.onBackPressed();
-        }
-    }
-
-    private void initDockWindow() {
-        WindowManager.LayoutParams lp = getWindow().getAttributes();
-
-        lp.type = mWindowType;
-        lp.setTitle(mName);
-
-        lp.gravity = mGravity;
-        updateWidthHeight(lp);
-
-        getWindow().setAttributes(lp);
-
-        int windowSetFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
-        int windowModFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
-
-        if (!mTakesFocus) {
-            windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        } else {
-            windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-            windowModFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-        }
-
-        getWindow().setFlags(windowSetFlags, windowModFlags);
-    }
-
     @Override
     public void show() {
         switch (mWindowState) {
@@ -372,10 +268,11 @@
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        proto.write(NAME, mName);
-        proto.write(WINDOW_TYPE, mWindowType);
-        proto.write(GRAVITY, mGravity);
-        proto.write(TAKES_FOCUS, mTakesFocus);
+        // TODO(b/192412909): Deprecate the following 4 entries, as they are all constant.
+        proto.write(NAME, "InputMethod");
+        proto.write(WINDOW_TYPE, WindowManager.LayoutParams.TYPE_INPUT_METHOD);
+        proto.write(GRAVITY, Gravity.BOTTOM);
+        proto.write(TAKES_FOCUS, false);
         mBounds.dumpDebug(proto, BOUNDS);
         proto.write(WINDOW_STATE, mWindowState);
         proto.end(token);
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/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 22b9578..6349cde 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -564,6 +564,14 @@
     public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
 
     /**
+     * Definitions for selection toolbar related functions.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
+
+    /**
      * List of namespaces which can be read without READ_DEVICE_CONFIG permission
      *
      * @hide
@@ -571,7 +579,7 @@
     @NonNull
     private static final List<String> PUBLIC_NAMESPACES =
             Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,
-                    NAMESPACE_STATSD_JAVA_BOOT);
+                    NAMESPACE_STATSD_JAVA_BOOT, NAMESPACE_SELECTION_TOOLBAR);
     /**
      * Privacy related properties definitions.
      *
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/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 3b52709..ab33fea 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -515,6 +515,15 @@
     public static final int ENABLE_BACKPRESSURE = 0x00000100;
 
     /**
+     * Buffers from this SurfaceControl should be considered display decorations.
+     *
+     * If the hardware has optimizations for display decorations (e.g. rounded corners, camera
+     * cutouts, etc), it should use them for this layer.
+     * @hide
+     */
+    public static final int DISPLAY_DECORATION = 0x00000200;
+
+    /**
      * Surface creation flag: Creates a surface where color components are interpreted
      * as "non pre-multiplied" by their alpha channel. Of course this flag is
      * meaningless for surfaces without an alpha channel. By default
@@ -3266,6 +3275,21 @@
         }
 
         /**
+         * Sets whether the surface should take advantage of display decoration optimizations.
+         * @hide
+         */
+        public Transaction setDisplayDecoration(SurfaceControl sc, boolean displayDecoration) {
+            checkPreconditions(sc);
+            if (displayDecoration) {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, DISPLAY_DECORATION,
+                        DISPLAY_DECORATION);
+            } else {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, 0, DISPLAY_DECORATION);
+            }
+            return this;
+        }
+
+        /**
          * Indicates whether the surface must be considered opaque, even if its pixel format is
          * set to translucent. This can be useful if an application needs full RGBA 8888 support
          * for instance but will still draw every pixel opaque.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1fdbf0e2..748e551 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -493,6 +493,9 @@
     protected final ViewFrameInfo mViewFrameInfo = new ViewFrameInfo();
     private final InputEventAssigner mInputEventAssigner = new InputEventAssigner();
 
+    // Whether to draw this surface as DISPLAY_DECORATION.
+    boolean mDisplayDecorationCached = false;
+
     /**
      * Update the Choreographer's FrameInfo object with the timing information for the current
      * ViewRootImpl instance. Erase the data in the current ViewFrameInfo to prepare for the next
@@ -1970,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) {
@@ -2853,6 +2859,12 @@
                 if (mSurfaceControl.isValid()) {
                     updateOpacity(mWindowAttributes, dragResizing,
                             surfaceControlChanged /*forceUpdate */);
+                    // 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();
+                    }
                 }
 
                 if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
@@ -7884,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);
@@ -10440,6 +10446,23 @@
     }
 
     /**
+     * @hide
+     */
+    public void setDisplayDecoration(boolean displayDecoration) {
+        if (displayDecoration == mDisplayDecorationCached) return;
+
+        mDisplayDecorationCached = displayDecoration;
+
+        if (mSurfaceControl.isValid()) {
+            updateDisplayDecoration();
+        }
+    }
+
+    private void updateDisplayDecoration() {
+        mTransaction.setDisplayDecoration(mSurfaceControl, mDisplayDecorationCached).apply();
+    }
+
+    /**
      * Sends a list of blur regions to SurfaceFlinger, tagged with a frame.
      *
      * @param regionCopy List of regions
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index aa6cb83..cd9f3eb6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -105,6 +105,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IInputConstants;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -120,6 +121,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
@@ -2755,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;
 
@@ -2765,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;
 
@@ -3325,21 +3327,13 @@
         public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 3;
 
         /**
-         * When this window has focus, disable touch pad pointer gesture processing.
-         * The window will receive raw position updates from the touch pad instead
-         * of pointer movements and synthetic touch events.
-         *
-         * @hide
-         */
-        public static final int INPUT_FEATURE_DISABLE_POINTER_GESTURES = 0x00000001;
-
-        /**
          * Does not construct an input channel for this window.  The channel will therefore
          * be incapable of receiving input.
          *
          * @hide
          */
-        public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002;
+        public static final int INPUT_FEATURE_NO_INPUT_CHANNEL =
+                IInputConstants.InputFeature.NO_INPUT_CHANNEL;
 
         /**
          * When this window has focus, does not call user activity for all input events so
@@ -3352,7 +3346,8 @@
          * @hide
          */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004;
+        public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY =
+                IInputConstants.InputFeature.DISABLE_USER_ACTIVITY;
 
         /**
          * An input spy window. This window will receive all pointer events within its touchable
@@ -3361,7 +3356,25 @@
          * event's coordinates.
          * @hide
          */
-        public static final int INPUT_FEATURE_SPY = 0x00000020;
+        public static final int INPUT_FEATURE_SPY =
+                IInputConstants.InputFeature.SPY;
+
+        /**
+         * When used with the window flag {@link #FLAG_NOT_TOUCHABLE}, this window will continue
+         * to receive events from a stylus device within its touchable region. All other pointer
+         * events, such as from a mouse or touchscreen, will be dispatched to the windows behind it.
+         *
+         * This input feature has no effect when the window flag {@link #FLAG_NOT_TOUCHABLE} is
+         * not set.
+         *
+         * The window must be a trusted overlay to use this input feature.
+         *
+         * @see #FLAG_NOT_TOUCHABLE
+         *
+         * @hide
+         */
+        public static final int INPUT_FEATURE_INTERCEPTS_STYLUS =
+                IInputConstants.InputFeature.INTERCEPTS_STYLUS;
 
         /**
          * An internal annotation for flags that can be specified to {@link #inputFeatures}.
@@ -3370,18 +3383,20 @@
          */
         @Retention(RetentionPolicy.SOURCE)
         @IntDef(flag = true, prefix = { "INPUT_FEATURE_" }, value = {
-            INPUT_FEATURE_DISABLE_POINTER_GESTURES,
             INPUT_FEATURE_NO_INPUT_CHANNEL,
             INPUT_FEATURE_DISABLE_USER_ACTIVITY,
+            INPUT_FEATURE_SPY,
+            INPUT_FEATURE_INTERCEPTS_STYLUS,
         })
         public @interface InputFeatureFlags {}
 
         /**
          * Control special features of the input subsystem.
          *
-         * @see #INPUT_FEATURE_DISABLE_POINTER_GESTURES
          * @see #INPUT_FEATURE_NO_INPUT_CHANNEL
          * @see #INPUT_FEATURE_DISABLE_USER_ACTIVITY
+         * @see #INPUT_FEATURE_SPY
+         * @see #INPUT_FEATURE_INTERCEPTS_STYLUS
          * @hide
          */
         @InputFeatureFlags
@@ -4485,7 +4500,7 @@
                 sb.append(hasSystemUiListeners);
             }
             if (inputFeatures != 0) {
-                sb.append(" if=").append(inputFeatureToString(inputFeatures));
+                sb.append(" if=").append(inputFeaturesToString(inputFeatures));
             }
             if (userActivityTimeout >= 0) {
                 sb.append(" userActivityTimeout=").append(userActivityTimeout);
@@ -4787,17 +4802,28 @@
             }
         }
 
-        private static String inputFeatureToString(int inputFeature) {
-            switch (inputFeature) {
-                case INPUT_FEATURE_DISABLE_POINTER_GESTURES:
-                    return "DISABLE_POINTER_GESTURES";
-                case INPUT_FEATURE_NO_INPUT_CHANNEL:
-                    return "NO_INPUT_CHANNEL";
-                case INPUT_FEATURE_DISABLE_USER_ACTIVITY:
-                    return "DISABLE_USER_ACTIVITY";
-                default:
-                    return Integer.toString(inputFeature);
+        private static String inputFeaturesToString(int inputFeatures) {
+            final List<String> features = new ArrayList<>();
+            if ((inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) != 0) {
+                inputFeatures &= ~INPUT_FEATURE_NO_INPUT_CHANNEL;
+                features.add("INPUT_FEATURE_NO_INPUT_CHANNEL");
             }
+            if ((inputFeatures & INPUT_FEATURE_DISABLE_USER_ACTIVITY) != 0) {
+                inputFeatures &= ~INPUT_FEATURE_DISABLE_USER_ACTIVITY;
+                features.add("INPUT_FEATURE_DISABLE_USER_ACTIVITY");
+            }
+            if ((inputFeatures & INPUT_FEATURE_SPY) != 0) {
+                inputFeatures &= ~INPUT_FEATURE_SPY;
+                features.add("INPUT_FEATURE_SPY");
+            }
+            if ((inputFeatures & INPUT_FEATURE_INTERCEPTS_STYLUS) != 0) {
+                inputFeatures &= ~INPUT_FEATURE_INTERCEPTS_STYLUS;
+                features.add("INPUT_FEATURE_INTERCEPTS_STYLUS");
+            }
+            if (inputFeatures != 0) {
+                features.add(Integer.toHexString(inputFeatures));
+            }
+            return String.join(" | ", features);
         }
 
         /**
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/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/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/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/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/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 2b9560e..a316b8a 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -47,7 +47,6 @@
 import android.os.RemoteException;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.DataUnit;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -119,7 +118,7 @@
      * is reached.
      * @hide
      */
-    public static final long MIN_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(2);
+    public static final long MIN_THRESHOLD_BYTES = 2 * 1_048_576L; // 2MiB
 
     private final Context mContext;
     private final INetworkStatsService mService;
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/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
index fa65061..d8feb88 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
@@ -29,7 +29,6 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.util.DataUnit;
 
 import com.android.server.NetworkManagementSocketTagger;
 
@@ -59,19 +58,19 @@
      */
     public final static int UNSUPPORTED = -1;
 
-    /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+    /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
     @Deprecated
     public static final long KB_IN_BYTES = 1024;
-    /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+    /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
     @Deprecated
     public static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
-    /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+    /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
     @Deprecated
     public static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
-    /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+    /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
     @Deprecated
     public static final long TB_IN_BYTES = GB_IN_BYTES * 1024;
-    /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+    /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
     @Deprecated
     public static final long PB_IN_BYTES = TB_IN_BYTES * 1024;
 
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/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/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/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/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/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/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..e2a814b 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -33,11 +33,14 @@
 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;
@@ -51,7 +54,9 @@
 import java.io.File;
 import java.io.FileInputStream;
 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 +73,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 +105,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 +173,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 +198,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 +259,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 +273,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,7 +305,7 @@
     }
 
     private void persistStateToFileLocked(@NonNull AtomicFile file,
-            @Nullable Set<AssociationInfo> associations,
+            @Nullable Collection<AssociationInfo> associations,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
         file.write(out -> {
             try {
@@ -321,7 +344,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 +365,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 +384,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 +398,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 +445,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);
     }
 
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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 4a3aa25..34ea86b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5725,7 +5725,7 @@
         final PrintWriter pw = shellCommand.getOutPrintWriter();
         switch (cmd) {
             case "start":
-                ImeTracing.getInstance().getInstance().startTrace(pw);
+                ImeTracing.getInstance().startTrace(pw);
                 break;  // proceed to the next step to update the IME client processes.
             case "stop":
                 ImeTracing.getInstance().stopTrace(pw);
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/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 23bc511..f474044 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -135,7 +135,7 @@
     /** Automatically destroy sessions older than this */
     private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
     /** Automatically destroy staged sessions that have not changed state in this time */
-    private static final long MAX_TIME_SINCE_UPDATE_MILLIS = 21 * DateUtils.DAY_IN_MILLIS;
+    private static final long MAX_TIME_SINCE_UPDATE_MILLIS = 7 * DateUtils.DAY_IN_MILLIS;
     /** Upper bound on number of active sessions for a UID that has INSTALL_PACKAGES */
     private static final long MAX_ACTIVE_SESSIONS_WITH_PERMISSION = 1024;
     /** Upper bound on number of active sessions for a UID without INSTALL_PACKAGES */
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/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});
     }
 
     /**