Merge "Send cached target duration when creating sessions" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 09f50a8..08a09e1 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -15,6 +15,7 @@
 aconfig_srcjars = [
     ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
     ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
+    ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
     ":android.nfc.flags-aconfig-java{.generated_srcjars}",
     ":android.os.flags-aconfig-java{.generated_srcjars}",
     ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
@@ -341,3 +342,16 @@
     aconfig_declarations: "android.app.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Broadcast Radio
+aconfig_declarations {
+    name: "android.hardware.radio.flags-aconfig",
+    package: "android.hardware.radio",
+    srcs: ["core/java/android/hardware/radio/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.hardware.radio.flags-aconfig-java",
+    aconfig_declarations: "android.hardware.radio.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index ed0f872..15bd1dc 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -22,8 +22,10 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.FillRequest;
+import android.text.InputType;
 import android.text.Spanned;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 import android.view.View;
@@ -2452,7 +2454,7 @@
                     + node.getTextStyle());
             Log.i(TAG, prefix + "  Text color fg: #" + Integer.toHexString(node.getTextColor())
                     + ", bg: #" + Integer.toHexString(node.getTextBackgroundColor()));
-            Log.i(TAG, prefix + "  Input type: " + node.getInputType());
+            Log.i(TAG, prefix + "  Input type: " + getInputTypeString(node.getInputType()));
             Log.i(TAG, prefix + "  Resource id: " + node.getTextIdEntry());
         }
         String webDomain = node.getWebDomain();
@@ -2664,4 +2666,33 @@
             return new AssistStructure[size];
         }
     };
+
+    private static final ArrayMap<Integer, String> INPUT_TYPE_VARIATIONS = new ArrayMap<>();
+    static {
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS, "EmailSubject");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS, "PostalAddress");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_PERSON_NAME, "PersonName");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_PASSWORD, "Password");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD, "VisiblePassword");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_URI, "URI");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS, "WebEmailAddress");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD, "WebPassword");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE, "LongMessage");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE, "ShortMessage");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_FLAG_MULTI_LINE, "MultiLine");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE, "ImeMultiLine");
+        INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_FILTER, "Filter");
+    }
+
+    private static String getInputTypeString(int inputType) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(inputType);
+        sb.append("(class=").append(inputType & InputType.TYPE_MASK_CLASS).append(')');
+        for (int variation : INPUT_TYPE_VARIATIONS.keySet()) {
+            if ((variation & inputType) == variation) {
+                sb.append('|').append(INPUT_TYPE_VARIATIONS.get(variation));
+            }
+        }
+        return sb.toString();
+    }
 }
diff --git a/core/java/android/hardware/radio/flags.aconfig b/core/java/android/hardware/radio/flags.aconfig
new file mode 100644
index 0000000..dbc1a4b
--- /dev/null
+++ b/core/java/android/hardware/radio/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.hardware.radio"
+
+flag {
+    name: "hd_radio_improved"
+    namespace: "car_framework"
+    description: "Feature flag for improved HD radio support with less vendor extensions"
+    bug: "280300929"
+}
diff --git a/core/java/android/net/network-policy-restrictions.md b/core/java/android/net/network-policy-restrictions.md
index 04c658c..20f3d74 100644
--- a/core/java/android/net/network-policy-restrictions.md
+++ b/core/java/android/net/network-policy-restrictions.md
@@ -29,8 +29,8 @@
 | **DS**  |  *AL* |  ok  | blk   |  ok  |  ok   |
 | **ON**  | *!AL* | blk  | blk   | blk  | blk   |
 |         |  *DL* | blk  | blk   | blk  | blk   |
-| **DS**  |  *AL* | blk  | blk   |  ok  |  ok   |
-| **OFF** | *!AL* | blk  | blk   |  ok  |  ok   |
+| **DS**  |  *AL* |  ok  | blk   |  ok  |  ok   |
+| **OFF** | *!AL* |  ok  | blk   |  ok  |  ok   |
 |         |  *DL* | blk  | blk   | blk  | blk   |
 
 
diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
index 2dbb5da..dae3202 100644
--- a/core/java/android/security/FileIntegrityManager.java
+++ b/core/java/android/security/FileIntegrityManager.java
@@ -76,28 +76,38 @@
      * Enables fs-verity to the owned file under the calling app's private directory. It always uses
      * the common configuration, i.e. SHA-256 digest algorithm, 4K block size, and without salt.
      *
-     * The operation can only succeed when the file is not opened as writable by any process.
+     * <p>For enabling fs-verity to succeed, the device must support fs-verity, the file must be
+     * writable by the app and not already have fs-verity enabled, and the file must not currently
+     * be open for writing by any process. To check whether the device supports fs-verity, use
+     * {@link #isApkVeritySupported()}.
      *
-     * It takes O(file size) time to build the underlying data structure for continuous
+     * <p>It takes O(file size) time to build the underlying data structure for continuous
      * verification. The operation is atomic, i.e. it's either enabled or not, even in case of
      * power failure during or after the call.
      *
-     * Note for the API users: When the file's authenticity is crucial, the app typical needs to
+     * <p>Note for the API users: When the file's authenticity is crucial, the app typical needs to
      * perform a signature check by itself before using the file. The signature is often delivered
      * as a separate file and stored next to the targeting file in the filesystem. The public key of
      * the signer (normally the same app developer) can be put in the APK, and the app can use the
      * public key to verify the signature to the file's actual fs-verity digest (from {@link
-     * #getFsVerityDigest}) before using the file. The exact format is not prescribed by the
+     * #getFsVerityDigest(File)}) before using the file. The exact format is not prescribed by the
      * framework. App developers may choose to use common practices like JCA for the signing and
      * verification, or their own preferred approach.
      *
-     * @param file The file to enable fs-verity. It should be an absolute path.
+     * @param file The file to enable fs-verity. It must represent an absolute path.
+     * @throws IllegalArgumentException If the provided file is not an absolute path.
+     * @throws IOException If the operation failed.
      *
      * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a>
      */
     @FlaggedApi(Flags.FLAG_FSVERITY_API)
     public void setupFsVerity(@NonNull File file) throws IOException {
         if (!file.isAbsolute()) {
+            // fs-verity is to be enabled by installd, which enforces the validation to the
+            // (untrusted) file path passed from here. To make this less error prone, installd
+            // accepts only absolute path. When a relative path is provided, we fail with an
+            // explicit exception to help developers understand the requirement to use an absolute
+            // path.
             throw new IllegalArgumentException("Expect an absolute path");
         }
         IFsveritySetupAuthToken authToken;
@@ -121,11 +131,12 @@
     }
 
     /**
-     * Returns the fs-verity digest for the owned file under the calling app's
-     * private directory, or null when the file does not have fs-verity enabled.
+     * Returns the fs-verity digest for the owned file under the calling app's private directory, or
+     * null when the file does not have fs-verity enabled (including when fs-verity is not supported
+     * on older devices).
      *
      * @param file The file to measure the fs-verity digest.
-     * @return The fs-verity digeset in byte[], null if none.
+     * @return The fs-verity digest in byte[], null if none.
      * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a>
      */
     @FlaggedApi(Flags.FLAG_FSVERITY_API)
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0ba5d06..f421351 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3858,9 +3858,7 @@
                 mPendingTransitions.clear();
             }
 
-            if (mActiveSurfaceSyncGroup != null) {
-                mActiveSurfaceSyncGroup.markSyncReady();
-            }
+            handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction);
         } else if (cancelAndRedraw) {
             mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
                 ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason()
@@ -3874,8 +3872,8 @@
                 }
                 mPendingTransitions.clear();
             }
-            if (!performDraw(mActiveSurfaceSyncGroup) && mActiveSurfaceSyncGroup != null) {
-                mActiveSurfaceSyncGroup.markSyncReady();
+            if (!performDraw(mActiveSurfaceSyncGroup)) {
+                handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction);
             }
         }
 
@@ -3890,6 +3888,7 @@
             mReportNextDraw = false;
             mLastReportNextDrawReason = null;
             mActiveSurfaceSyncGroup = null;
+            mHasPendingTransactions = false;
             mSyncBuffer = false;
             if (isInWMSRequestedSync()) {
                 mWmsRequestSyncGroup.markSyncReady();
@@ -4688,7 +4687,8 @@
             return false;
         }
 
-        final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null;
+        final boolean fullRedrawNeeded =
+                mFullRedrawNeeded || surfaceSyncGroup != null || mHasPendingTransactions;
         mFullRedrawNeeded = false;
 
         mIsDrawing = true;
@@ -4718,8 +4718,15 @@
             mAttachInfo.mPendingAnimatingRenderNodes.clear();
         }
 
-        if (mReportNextDraw) {
+        final Transaction pendingTransaction;
+        if (!usingAsyncReport && mHasPendingTransactions) {
+            pendingTransaction = new Transaction();
+            pendingTransaction.merge(mPendingTransaction);
+        } else {
+            pendingTransaction = null;
+        }
 
+        if (mReportNextDraw) {
             // if we're using multi-thread renderer, wait for the window frame draws
             if (mWindowDrawCountDown != null) {
                 try {
@@ -4741,9 +4748,7 @@
             if (mSurfaceHolder != null && mSurface.isValid()) {
                 usingAsyncReport = true;
                 SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> {
-                    if (surfaceSyncGroup != null) {
-                        surfaceSyncGroup.markSyncReady();
-                    }
+                    handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction);
                 });
 
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
@@ -4756,15 +4761,27 @@
             }
         }
 
-        if (surfaceSyncGroup != null && !usingAsyncReport) {
-            surfaceSyncGroup.markSyncReady();
+        if (!usingAsyncReport) {
+            handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction);
         }
+
         if (mPerformContentCapture) {
             performContentCaptureInitialReport();
         }
         return true;
     }
 
+    private void handleSyncRequestWhenNoAsyncDraw(SurfaceSyncGroup surfaceSyncGroup,
+            @Nullable Transaction pendingTransaction) {
+        if (surfaceSyncGroup != null) {
+            if (pendingTransaction != null) {
+                surfaceSyncGroup.addTransaction(pendingTransaction);
+            }
+            surfaceSyncGroup.markSyncReady();
+        } else if (pendingTransaction != null) {
+            pendingTransaction.apply();
+        }
+    }
     /**
      * Checks (and caches) if content capture is enabled for this context.
      */
@@ -4850,8 +4867,8 @@
         }
     }
 
-    private boolean draw(boolean fullRedrawNeeded,
-            @Nullable SurfaceSyncGroup activeSyncGroup, boolean syncBuffer) {
+    private boolean draw(boolean fullRedrawNeeded, @Nullable SurfaceSyncGroup activeSyncGroup,
+            boolean syncBuffer) {
         Surface surface = mSurface;
         if (!surface.isValid()) {
             return false;
@@ -4995,12 +5012,11 @@
                         mAttachInfo.mThreadedRenderer.forceDrawNextFrame();
                     }
                 } else if (mHasPendingTransactions) {
-                    // Register a calback if there's no sync involved but there were calls to
+                    // Register a callback if there's no sync involved but there were calls to
                     // applyTransactionOnDraw. If there is a sync involved, the sync callback will
                     // handle merging the pending transaction.
                     registerCallbackForPendingTransactions();
                 }
-                mHasPendingTransactions = false;
 
                 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
             } else {
@@ -8977,13 +8993,7 @@
             mAdded = false;
             AnimationHandler.removeRequestor(this);
         }
-        if (mActiveSurfaceSyncGroup != null) {
-            mActiveSurfaceSyncGroup.markSyncReady();
-            mActiveSurfaceSyncGroup = null;
-        }
-        if (mHasPendingTransactions) {
-            mPendingTransaction.apply();
-        }
+        handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction);
         WindowManagerGlobal.getInstance().doRemoveView(this);
     }
 
@@ -11502,9 +11512,7 @@
             Log.d(mTag, "registerCallbacksForSync syncBuffer=" + syncBuffer);
         }
 
-        Transaction t = new Transaction();
-        t.merge(mPendingTransaction);
-
+        surfaceSyncGroup.addTransaction(mPendingTransaction);
         mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() {
             @Override
             public void onFrameDraw(long frame) {
@@ -11518,7 +11526,6 @@
                                     + frame + ".");
                 }
 
-                mergeWithNextTransaction(t, frame);
                 // If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or
                 // SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up
                 // any blast sync or commit callback, and the code should directly call
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index 9b10a7f..932608a3 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import static android.view.WindowManager.transitTypeToString;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
@@ -88,6 +90,11 @@
         this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags);
     }
 
+    /** @hide */
+    String typeToString() {
+        return transitTypeToString(mType);
+    }
+
     /** Requested change to a display. */
     @DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false)
     public static class DisplayChange implements Parcelable {
@@ -263,7 +270,7 @@
         };
 
         @DataClass.Generated(
-                time = 1693425051905L,
+                time = 1695667226050L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
                 inputSignatures = "private final  int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate  int mStartRotation\nprivate  int mEndRotation\nprivate  boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
@@ -298,11 +305,11 @@
      * @param type
      *   The type of the transition being requested.
      * @param triggerTask
-     *   If non-null, If non-null, the task containing the activity whose lifecycle change (start or
+     *   If non-null, the task containing the activity whose lifecycle change (start or
      *   finish) has caused this transition to occur.
      * @param pipTask
-     *   If non-null, If non-null, the task containing the activity whose lifecycle change (start or
-     *   finish) has caused this transition to occur.
+     *   If non-null, the task containing the pip activity that participates in this
+     *   transition.
      * @param remoteTransition
      *   If non-null, a remote-transition associated with the source of this transition.
      * @param displayChange
@@ -431,7 +438,7 @@
         // String fieldNameToString() { ... }
 
         return "TransitionRequestInfo { " +
-                "type = " + mType + ", " +
+                "type = " + typeToString() + ", " +
                 "triggerTask = " + mTriggerTask + ", " +
                 "pipTask = " + mPipTask + ", " +
                 "remoteTransition = " + mRemoteTransition + ", " +
@@ -506,10 +513,10 @@
     };
 
     @DataClass.Generated(
-            time = 1693425051928L,
+            time = 1695667226088L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
-            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\n  java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 0d1871d..663067c 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -203,14 +203,22 @@
         }
         return false;
     }
-    
+
+    /**
+     * Direct reflection of {@link Intent#ACTION_PACKAGE_CHANGED
+     * Intent.ACTION_PACKAGE_CHANGED} being received, this callback
+     * has extras passed in.
+     */
+    public void onPackageChangedWithExtras(String packageName, Bundle extras) {
+    }
+
     public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
         return false;
     }
 
     public void onHandleUserStop(Intent intent, int userHandle) {
     }
-    
+
     public void onUidRemoved(int uid) {
     }
     
@@ -238,21 +246,34 @@
     }
 
     /**
+     * Called when a package disappears with extras passed in.
+     */
+    public void onPackageDisappearedWithExtras(String packageName, Bundle extras) {
+    }
+
+    /**
      * Called when a package appears for any reason.
      */
     public void onPackageAppeared(String packageName, int reason) {
     }
 
+
+    /**
+     * Called when a package appears with extras passed in.
+     */
+    public void onPackageAppearedWithExtras(String packageName, Bundle extras) {
+    }
+
     /**
      * Called when an existing package is updated or its disabled state changes.
      */
     public void onPackageModified(@NonNull String packageName) {
     }
-    
+
     public boolean didSomePackagesChange() {
         return mSomePackagesChanged;
     }
-    
+
     public int isPackageAppearing(String packageName) {
         if (mAppearingPackages != null) {
             for (int i=mAppearingPackages.length-1; i>=0; i--) {
@@ -381,6 +402,7 @@
                     mChangeType = PACKAGE_PERMANENT_CHANGE;
                     onPackageAdded(pkg, uid);
                 }
+                onPackageAppearedWithExtras(pkg, intent.getExtras());
                 onPackageAppeared(pkg, mChangeType);
             }
         } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
@@ -403,6 +425,7 @@
                         onPackageRemovedAllUsers(pkg, uid);
                     }
                 }
+                onPackageDisappearedWithExtras(pkg, intent.getExtras());
                 onPackageDisappeared(pkg, mChangeType);
             }
         } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
@@ -417,6 +440,7 @@
                 if (onPackageChanged(pkg, uid, mModifiedComponents)) {
                     mSomePackagesChanged = true;
                 }
+                onPackageChangedWithExtras(pkg, intent.getExtras());
                 onPackageModified(pkg);
             }
         } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) {
diff --git a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
index e082c25..c7eddabe 100644
--- a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
+++ b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
@@ -17,7 +17,6 @@
 package com.android.internal.content;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -28,6 +27,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.UserHandle;
 
@@ -36,6 +36,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -45,6 +46,7 @@
 @RunWith(AndroidJUnit4.class)
 public class PackageMonitorTest {
     private static final String FAKE_PACKAGE_NAME = "com.android.internal.content.fakeapp";
+    private static final String FAKE_EXTRA_REASON = "android.intent.extra.fakereason";
     private static final int FAKE_PACKAGE_UID = 123;
     private static final int FAKE_USER_ID = 0;
     private static final int WAIT_CALLBACK_CALLED_IN_MS = 300;
@@ -245,6 +247,7 @@
         intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null));
         intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
         intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        intent.putExtra(Intent.EXTRA_REASON, FAKE_EXTRA_REASON);
         String [] packageList = new String[]{FAKE_PACKAGE_NAME};
         intent.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, packageList);
         spyPackageMonitor.doHandlePackageEvent(intent);
@@ -253,6 +256,20 @@
         verify(spyPackageMonitor, times(1))
                 .onPackageChanged(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID), eq(packageList));
         verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME));
+
+        ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(spyPackageMonitor, times(1)).onPackageChangedWithExtras(eq(FAKE_PACKAGE_NAME),
+                argumentCaptor.capture());
+
+        Bundle capturedExtras = argumentCaptor.getValue();
+        Bundle expectedExtras = intent.getExtras();
+        assertThat(capturedExtras.getInt(Intent.EXTRA_USER_HANDLE))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_USER_HANDLE));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_UID))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_UID));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_REASON))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REASON));
+
         verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
         verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
     }
@@ -272,6 +289,21 @@
         verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
         verify(spyPackageMonitor, times(1))
                 .onPackageUpdateStarted(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+
+        ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(spyPackageMonitor, times(1)).onPackageDisappearedWithExtras(eq(FAKE_PACKAGE_NAME),
+                argumentCaptor.capture());
+        Bundle capturedExtras = argumentCaptor.getValue();
+        Bundle expectedExtras = intent.getExtras();
+        assertThat(capturedExtras.getInt(Intent.EXTRA_USER_HANDLE))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_USER_HANDLE));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_UID))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_UID));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_REPLACING))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REPLACING));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_REMOVED_FOR_ALL_USERS))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REMOVED_FOR_ALL_USERS));
+
         verify(spyPackageMonitor, times(1))
                 .onPackageDisappeared(eq(FAKE_PACKAGE_NAME), eq(PackageMonitor.PACKAGE_UPDATING));
         verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
@@ -295,6 +327,21 @@
                 .onPackageRemoved(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
         verify(spyPackageMonitor, times(1))
                 .onPackageRemovedAllUsers(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+
+        ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(spyPackageMonitor, times(1)).onPackageDisappearedWithExtras(eq(FAKE_PACKAGE_NAME),
+                argumentCaptor.capture());
+        Bundle capturedExtras = argumentCaptor.getValue();
+        Bundle expectedExtras = intent.getExtras();
+        assertThat(capturedExtras.getInt(Intent.EXTRA_USER_HANDLE))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_USER_HANDLE));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_UID))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_UID));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_REPLACING))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REPLACING));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_REMOVED_FOR_ALL_USERS))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REMOVED_FOR_ALL_USERS));
+
         verify(spyPackageMonitor, times(1)).onPackageDisappeared(eq(FAKE_PACKAGE_NAME),
                 eq(PackageMonitor.PACKAGE_PERMANENT_CHANGE));
         verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
@@ -316,6 +363,19 @@
         verify(spyPackageMonitor, times(1))
                 .onPackageUpdateFinished(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
         verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME));
+
+        ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(spyPackageMonitor, times(1)).onPackageAppearedWithExtras(eq(FAKE_PACKAGE_NAME),
+                argumentCaptor.capture());
+        Bundle capturedExtras = argumentCaptor.getValue();
+        Bundle expectedExtras = intent.getExtras();
+        assertThat(capturedExtras.getInt(Intent.EXTRA_USER_HANDLE))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_USER_HANDLE));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_UID))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_UID));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_REPLACING))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REPLACING));
+
         verify(spyPackageMonitor, times(1))
                 .onPackageAppeared(eq(FAKE_PACKAGE_NAME), eq(PackageMonitor.PACKAGE_UPDATING));
         verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
@@ -336,6 +396,19 @@
         verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
         verify(spyPackageMonitor, times(1))
                 .onPackageAdded(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+
+        ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(spyPackageMonitor, times(1)).onPackageAppearedWithExtras(eq(FAKE_PACKAGE_NAME),
+                argumentCaptor.capture());
+        Bundle capturedExtras = argumentCaptor.getValue();
+        Bundle expectedExtras = intent.getExtras();
+        assertThat(capturedExtras.getInt(Intent.EXTRA_USER_HANDLE))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_USER_HANDLE));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_UID))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_UID));
+        assertThat(capturedExtras.getInt(Intent.EXTRA_REPLACING))
+                .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REPLACING));
+
         verify(spyPackageMonitor, times(1)).onPackageAppeared(eq(FAKE_PACKAGE_NAME),
                 eq(PackageMonitor.PACKAGE_PERMANENT_CHANGE));
         verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index f8dd208..09ba4f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -37,6 +37,7 @@
 import android.view.WindowManager.TRANSIT_NONE
 import android.view.WindowManager.TRANSIT_OPEN
 import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.RemoteTransition
 import android.window.TransitionInfo
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
@@ -65,7 +66,9 @@
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.sysui.ShellSharedConstants
+import com.android.wm.shell.transition.OneShotRemoteHandler
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionHandler
 import com.android.wm.shell.util.KtProtoLog
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
@@ -139,20 +142,22 @@
     }
 
     /** Show all tasks, that are part of the desktop, on top of launcher */
-    fun showDesktopApps(displayId: Int) {
+    fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) {
         KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
         val wct = WindowContainerTransaction()
-        // TODO(b/278084491): pass in display id
         bringDesktopAppsToFront(displayId, wct)
 
-        // Execute transaction if there are pending operations
-        if (!wct.isEmpty) {
-            if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-                // TODO(b/268662477): add animation for the transition
-                transitions.startTransition(TRANSIT_NONE, wct, null /* handler */)
-            } else {
-                shellTaskOrganizer.applyTransaction(wct)
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            // TODO(b/255649902): ensure remote transition is supplied once state is introduced
+            val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT
+            val handler = remoteTransition?.let {
+                OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
             }
+            transitions.startTransition(transitionType, wct, handler).also { t ->
+                handler?.setTransition(t)
+            }
+        } else {
+            shellTaskOrganizer.applyTransaction(wct)
         }
     }
 
@@ -1093,11 +1098,11 @@
             controller = null
         }
 
-        override fun showDesktopApps(displayId: Int) {
+        override fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition?) {
             ExecutorUtils.executeRemoteCallWithTaskPermission(
                 controller,
                 "showDesktopApps"
-            ) { c -> c.showDesktopApps(displayId) }
+            ) { c -> c.showDesktopApps(displayId, remoteTransition) }
         }
 
         override fun stashDesktopApps(displayId: Int) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 47edfd4..6bdaf1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -17,6 +17,7 @@
 package com.android.wm.shell.desktopmode;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.window.RemoteTransition;
 import com.android.wm.shell.desktopmode.IDesktopTaskListener;
 
 /**
@@ -25,7 +26,7 @@
 interface IDesktopMode {
 
     /** Show apps on the desktop on the given display */
-    void showDesktopApps(int displayId);
+    void showDesktopApps(int displayId, in RemoteTransition remoteTransition);
 
     /** Stash apps on the desktop to allow launching another app from home screen */
     void stashDesktopApps(int displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 0d77a2e..ef8393c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -29,12 +29,16 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewTreeObserver;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.concurrent.Executor;
 
 /**
@@ -74,6 +78,7 @@
     private final TaskViewTaskController mTaskViewTaskController;
     private Region mObscuredTouchRegion;
     private Insets mCaptionInsets;
+    private Handler mHandler;
 
     public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
         super(context, null, 0, 0, true /* disableBackgroundLayer */);
@@ -81,6 +86,7 @@
         // TODO(b/266736992): Think about a better way to set the TaskViewBase on the
         //  TaskViewTaskController and vice-versa
         mTaskViewTaskController.setTaskViewBase(this);
+        mHandler = Handler.getMain();
         getHolder().addCallback(this);
     }
 
@@ -117,14 +123,16 @@
     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
         onLocationChanged();
         if (taskInfo.taskDescription != null) {
-            setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+            final int bgColor = taskInfo.taskDescription.getBackgroundColor();
+            runOnViewThread(() -> setResizeBackgroundColor(bgColor));
         }
     }
 
     @Override
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
         if (taskInfo.taskDescription != null) {
-            setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+            final int bgColor = taskInfo.taskDescription.getBackgroundColor();
+            runOnViewThread(() -> setResizeBackgroundColor(bgColor));
         }
     }
 
@@ -143,7 +151,7 @@
 
     @Override
     public void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
-        setResizeBackgroundColor(t, bgColor);
+        runOnViewThread(() -> setResizeBackgroundColor(t, bgColor));
     }
 
     /**
@@ -272,12 +280,14 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+        mHandler = getHandler();
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+        mHandler = Handler.getMain();
     }
 
     /** Returns the task info for the task in the TaskView. */
@@ -285,4 +295,24 @@
     public ActivityManager.RunningTaskInfo getTaskInfo() {
         return mTaskViewTaskController.getTaskInfo();
     }
+
+    /**
+     * Sets the handler, only for testing.
+     */
+    @VisibleForTesting
+    void setHandler(Handler viewHandler) {
+        mHandler = viewHandler;
+    }
+
+    /**
+     * Ensures that the given runnable runs on the view's thread.
+     */
+    private void runOnViewThread(Runnable r) {
+        if (mHandler.getLooper().isCurrentThread()) {
+            r.run();
+        } else {
+            // If this call is not from the same thread as the view, then post it
+            mHandler.post(r);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRemoteTransition.java
new file mode 100644
index 0000000..0df42b3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRemoteTransition.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.SurfaceControl;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+/**
+ * {@link IRemoteTransition} for testing purposes.
+ * Stores info about
+ * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction,
+ * IRemoteTransitionFinishedCallback)} being called.
+ */
+public class TestRemoteTransition extends IRemoteTransition.Stub {
+    private boolean mCalled = false;
+    final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction();
+
+    @Override
+    public void startAnimation(IBinder transition, TransitionInfo info,
+            SurfaceControl.Transaction startTransaction,
+            IRemoteTransitionFinishedCallback finishCallback)
+            throws RemoteException {
+        mCalled = true;
+        finishCallback.onTransitionFinished(mRemoteFinishWCT, null /* sct */);
+    }
+
+    @Override
+    public void mergeAnimation(IBinder transition, TransitionInfo info,
+            SurfaceControl.Transaction t, IBinder mergeTarget,
+            IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+    }
+
+    /**
+     * Check whether this remote transition
+     * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction,
+     * IRemoteTransitionFinishedCallback)} is called
+     */
+    public boolean isCalled() {
+        return mCalled;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index c6cccc0..664fbb2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -28,10 +28,10 @@
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.WindowManager
 import android.view.WindowManager.TRANSIT_CHANGE
-import android.view.WindowManager.TRANSIT_NONE
 import android.view.WindowManager.TRANSIT_OPEN
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.DisplayAreaInfo
+import android.window.RemoteTransition
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -43,6 +43,7 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRemoteTransition
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.common.DisplayController
@@ -57,8 +58,10 @@
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.OneShotRemoteHandler
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
+import com.android.wm.shell.transition.Transitions.TransitionHandler
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -69,6 +72,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isA
 import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mock
 import org.mockito.Mockito
@@ -174,9 +178,9 @@
         markTaskHidden(task1)
         markTaskHidden(task2)
 
-        controller.showDesktopApps(DEFAULT_DISPLAY)
+        controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
-        val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+        val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java)
         assertThat(wct.hierarchyOps).hasSize(3)
         // Expect order to be from bottom: home, task1, task2
         wct.assertReorderAt(index = 0, homeTask)
@@ -192,9 +196,9 @@
         markTaskVisible(task1)
         markTaskVisible(task2)
 
-        controller.showDesktopApps(DEFAULT_DISPLAY)
+        controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
-        val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+        val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java)
         assertThat(wct.hierarchyOps).hasSize(3)
         // Expect order to be from bottom: home, task1, task2
         wct.assertReorderAt(index = 0, homeTask)
@@ -210,9 +214,9 @@
         markTaskHidden(task1)
         markTaskVisible(task2)
 
-        controller.showDesktopApps(DEFAULT_DISPLAY)
+        controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
-        val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+        val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java)
         assertThat(wct.hierarchyOps).hasSize(3)
         // Expect order to be from bottom: home, task1, task2
         wct.assertReorderAt(index = 0, homeTask)
@@ -224,9 +228,9 @@
     fun showDesktopApps_noActiveTasks_reorderHomeToTop() {
         val homeTask = setUpHomeTask()
 
-        controller.showDesktopApps(DEFAULT_DISPLAY)
+        controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
-        val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+        val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java)
         assertThat(wct.hierarchyOps).hasSize(1)
         wct.assertReorderAt(index = 0, homeTask)
     }
@@ -240,9 +244,9 @@
         markTaskHidden(taskDefaultDisplay)
         markTaskHidden(taskSecondDisplay)
 
-        controller.showDesktopApps(DEFAULT_DISPLAY)
+        controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
-        val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+        val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java)
         assertThat(wct.hierarchyOps).hasSize(2)
         // Expect order to be from bottom: home, task
         wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
@@ -373,7 +377,7 @@
         val task = setUpFreeformTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
         controller.moveToFullscreen(task)
-        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+        val wct = getLatestWct(type = TRANSIT_CHANGE)
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_UNDEFINED)
     }
@@ -383,7 +387,7 @@
         val task = setUpFreeformTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
         controller.moveToFullscreen(task)
-        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+        val wct = getLatestWct(type = TRANSIT_CHANGE)
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
                 .isEqualTo(WINDOWING_MODE_FULLSCREEN)
     }
@@ -401,7 +405,7 @@
 
         controller.moveToFullscreen(taskDefaultDisplay)
 
-        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+        with(getLatestWct(type = TRANSIT_CHANGE)) {
             assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
             assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder())
         }
@@ -414,7 +418,7 @@
 
         controller.moveTaskToFront(task1)
 
-        val wct = getLatestWct(expectTransition = TRANSIT_TO_FRONT)
+        val wct = getLatestWct(type = TRANSIT_TO_FRONT)
         assertThat(wct.hierarchyOps).hasSize(1)
         wct.assertReorderAt(index = 0, task1)
     }
@@ -439,7 +443,7 @@
 
         val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
         controller.moveToNextDisplay(task.taskId)
-        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+        with(getLatestWct(type = TRANSIT_CHANGE)) {
             assertThat(hierarchyOps).hasSize(1)
             assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
             assertThat(hierarchyOps[0].isReparent).isTrue()
@@ -461,7 +465,7 @@
         val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
         controller.moveToNextDisplay(task.taskId)
 
-        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+        with(getLatestWct(type = TRANSIT_CHANGE)) {
             assertThat(hierarchyOps).hasSize(1)
             assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
             assertThat(hierarchyOps[0].isReparent).isTrue()
@@ -747,11 +751,16 @@
     }
 
     private fun getLatestWct(
-        @WindowManager.TransitionType expectTransition: Int = TRANSIT_OPEN
+            @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
+            handlerClass: Class<out TransitionHandler>? = null
     ): WindowContainerTransaction {
         val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         if (ENABLE_SHELL_TRANSITIONS) {
-            verify(transitions).startTransition(eq(expectTransition), arg.capture(), isNull())
+            if (handlerClass == null) {
+                verify(transitions).startTransition(eq(type), arg.capture(), isNull())
+            } else {
+                verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
+            }
         } else {
             verify(shellTaskOrganizer).applyTransaction(arg.capture())
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 5efd9ad..d542139 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -47,11 +47,8 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
 import android.window.RemoteTransition;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
@@ -65,6 +62,7 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRemoteTransition;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.TransitionInfoBuilder;
 import com.android.wm.shell.common.DisplayController;
@@ -205,7 +203,7 @@
 
         // Make sure split-screen is now visible
         assertTrue(mStageCoordinator.isSplitScreenVisible());
-        assertTrue(testRemote.mCalled);
+        assertTrue(testRemote.isCalled());
     }
 
     @Test
@@ -468,24 +466,4 @@
         return out;
     }
 
-    class TestRemoteTransition extends IRemoteTransition.Stub {
-        boolean mCalled = false;
-        final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction();
-
-        @Override
-        public void startAnimation(IBinder transition, TransitionInfo info,
-                SurfaceControl.Transaction startTransaction,
-                IRemoteTransitionFinishedCallback finishCallback)
-                throws RemoteException {
-            mCalled = true;
-            finishCallback.onTransitionFinished(mRemoteFinishWCT, null /* sct */);
-        }
-
-        @Override
-        public void mergeAnimation(IBinder transition, TransitionInfo info,
-                SurfaceControl.Transaction t, IBinder mergeTarget,
-                IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
-        }
-    }
-
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 0088051..4afb29e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -43,6 +43,8 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.SurfaceControl;
@@ -88,6 +90,10 @@
     SyncTransactionQueue mSyncQueue;
     @Mock
     Transitions mTransitions;
+    @Mock
+    Looper mViewLooper;
+    @Mock
+    Handler mViewHandler;
 
     SurfaceSession mSession;
     SurfaceControl mLeash;
@@ -105,6 +111,8 @@
                 .build();
 
         mContext = getContext();
+        doReturn(true).when(mViewLooper).isCurrentThread();
+        doReturn(mViewLooper).when(mViewHandler).getLooper();
 
         mTaskInfo = new ActivityManager.RunningTaskInfo();
         mTaskInfo.token = mToken;
@@ -132,6 +140,7 @@
         mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer,
                 mTaskViewTransitions, mSyncQueue));
         mTaskView = new TaskView(mContext, mTaskViewTaskController);
+        mTaskView.setHandler(mViewHandler);
         mTaskView.setListener(mExecutor, mViewListener);
     }
 
@@ -646,4 +655,17 @@
 
         assertThat(mTaskViewTaskController.getTaskInfo()).isNull();
     }
+
+    @Test
+    public void testOnTaskInfoChangedOnSameUiThread() {
+        mTaskViewTaskController.onTaskInfoChanged(mTaskInfo);
+        verify(mViewHandler, never()).post(any());
+    }
+
+    @Test
+    public void testOnTaskInfoChangedOnDifferentUiThread() {
+        doReturn(false).when(mViewLooper).isCurrentThread();
+        mTaskViewTaskController.onTaskInfoChanged(mTaskInfo);
+        verify(mViewHandler).post(any());
+    }
 }
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 9924fae..09f09b9 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -2409,13 +2409,16 @@
     @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
     @Nullable
     public Descrambler openDescrambler() {
+        acquireTRMSLock("openDescrambler()");
         mDemuxLock.lock();
         try {
-            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+            // no need to unlock mDemuxLock (so pass null instead) as TRMS lock is already acquired
+            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, null)) {
                 return null;
             }
             return requestDescrambler();
         } finally {
+            releaseTRMSLock();
             mDemuxLock.unlock();
         }
     }
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index af6fa86..5c2f979 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -131,5 +131,22 @@
         }
       ]
     }
+  ],
+  // v2/sysui/suite/test-mapping-sysui-screenshot-test
+  "sysui-screenshot-test": [
+    {
+      "name": "SystemUIGoogleScreenshotTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+        }
+      ]
+    }
   ]
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 8e34008..519c0a9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -39,7 +39,9 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.scene.ElementKey
@@ -166,11 +168,18 @@
                 modifier =
                     Modifier.align(Alignment.CenterVertically)
                         // use graphicsLayer instead of Modifier.scale to anchor transform to
-                        // top left corner
+                        // the (start, top) corner
                         .graphicsLayer(
                             scaleX = 2.57f,
                             scaleY = 2.57f,
-                            transformOrigin = TransformOrigin(0f, 0.5f)
+                            transformOrigin =
+                                TransformOrigin(
+                                    when (LocalLayoutDirection.current) {
+                                        LayoutDirection.Ltr -> 0f
+                                        LayoutDirection.Rtl -> 1f
+                                    },
+                                    0.5f
+                                )
                         ),
             )
             Spacer(modifier = Modifier.weight(1f))
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 46d3c8a..79d9c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -1096,8 +1096,19 @@
             // cancel the fingerprint scan.
             mCancelAodFingerUpAction = mFgExecutor.executeDelayed(this::tryAodSendFingerUp,
                     AOD_SEND_FINGER_UP_DELAY_MILLIS);
-            // using a hard-coded value for major and minor until it is available from the sensor
-            onFingerDown(requestId, screenX, screenY, minor, major);
+            // using a hard-coded value for orientation, time and gestureStart until they are
+            // available from the sensor.
+            onFingerDown(
+                    requestId,
+                    MotionEvent.INVALID_POINTER_ID /* pointerId */,
+                    screenX,
+                    screenY,
+                    minor,
+                    major,
+                    0f /* orientation */,
+                    0L /* time */,
+                    0L /* gestureStart */,
+                    true /* isAod */);
         };
 
         if (mScreenOn) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index e489499..ce9d6fd 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -37,7 +37,6 @@
 import com.android.internal.util.UserIcons
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.res.R
 import com.android.systemui.SystemUISecondaryUserService
 import com.android.systemui.animation.Expandable
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -50,6 +49,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.res.R
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.CreateUserActivity
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
@@ -62,6 +62,7 @@
 import com.android.systemui.user.utils.MultiUserActionsEvent
 import com.android.systemui.user.utils.MultiUserActionsEventHelper
 import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.utils.UserRestrictionChecker
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -103,6 +104,7 @@
     private val refreshUsersScheduler: RefreshUsersScheduler,
     private val guestUserInteractor: GuestUserInteractor,
     private val uiEventLogger: UiEventLogger,
+    private val userRestrictionChecker: UserRestrictionChecker,
 ) {
     /**
      * Defines interface for classes that can be notified when the state of users on the device is
@@ -593,6 +595,7 @@
                 ) &&
                     // If the user is auto-created is must not be currently resetting.
                     !(isGuestUserAutoCreated && isGuestUserResetting),
+            userRestrictionChecker = userRestrictionChecker,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
index 93573fa..80139bd 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
@@ -22,10 +22,10 @@
 import android.graphics.Bitmap
 import android.os.UserManager
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
-import com.android.settingslib.RestrictedLockUtilsInternal
 import com.android.systemui.res.R
 import com.android.systemui.user.data.source.UserRecord
 import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.utils.UserRestrictionChecker
 
 /**
  * Defines utility functions for helping with legacy data code for users.
@@ -68,6 +68,7 @@
         actionType: UserActionModel,
         isRestricted: Boolean,
         isSwitchToEnabled: Boolean,
+        userRestrictionChecker: UserRestrictionChecker,
     ): UserRecord {
         return UserRecord(
             isGuest = actionType == UserActionModel.ENTER_GUEST_MODE,
@@ -79,6 +80,7 @@
                 getEnforcedAdmin(
                     context = context,
                     selectedUserId = selectedUserId,
+                    userRestrictionChecker = userRestrictionChecker,
                 ),
             isManageUsers = actionType == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
         )
@@ -103,9 +105,10 @@
     private fun getEnforcedAdmin(
         context: Context,
         selectedUserId: Int,
+        userRestrictionChecker: UserRestrictionChecker
     ): EnforcedAdmin? {
         val admin =
-            RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+            userRestrictionChecker.checkIfRestrictionEnforced(
                 context,
                 UserManager.DISALLOW_ADD_USER,
                 selectedUserId,
@@ -113,7 +116,7 @@
                 ?: return null
 
         return if (
-            !RestrictedLockUtilsInternal.hasBaseUserRestriction(
+            !userRestrictionChecker.hasBaseUserRestriction(
                 context,
                 UserManager.DISALLOW_ADD_USER,
                 selectedUserId,
diff --git a/packages/SystemUI/src/com/android/systemui/utils/UserRestrictionChecker.kt b/packages/SystemUI/src/com/android/systemui/utils/UserRestrictionChecker.kt
new file mode 100644
index 0000000..3f8346b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/utils/UserRestrictionChecker.kt
@@ -0,0 +1,25 @@
+package com.android.systemui.utils
+
+import android.content.Context
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtilsInternal
+import javax.inject.Inject
+
+/** Proxy to call [RestrictedLockUtilsInternal] */
+class UserRestrictionChecker @Inject constructor() {
+    fun checkIfRestrictionEnforced(
+        context: Context,
+        userRestriction: String,
+        userId: Int
+    ): RestrictedLockUtils.EnforcedAdmin? {
+        return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+            context,
+            userRestriction,
+            userId
+        )
+    }
+
+    fun hasBaseUserRestriction(context: Context, userRestriction: String, userId: Int): Boolean {
+        return RestrictedLockUtilsInternal.hasBaseUserRestriction(context, userRestriction, userId)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 8fc63b2..7365460 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
 import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -126,6 +127,7 @@
                 refreshUsersScheduler = refreshUsersScheduler,
                 guestUserInteractor = guestInteractor,
                 uiEventLogger = uiEventLogger,
+                userRestrictionChecker = mock(),
             )
         shadeInteractor =
             ShadeInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index d018cbb..2be1c09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -25,7 +25,6 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
@@ -35,6 +34,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
@@ -54,6 +54,7 @@
 import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
 import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -153,6 +154,7 @@
                 refreshUsersScheduler = refreshUsersScheduler,
                 guestUserInteractor = guestInteractor,
                 uiEventLogger = uiEventLogger,
+                userRestrictionChecker = mock(),
             )
         underTest =
             ShadeInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index bbc49c8..af941d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -34,7 +34,6 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.GuestResetOrExitSessionReceiver
 import com.android.systemui.GuestResumeSessionReceiver
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Text
@@ -45,6 +44,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -1120,6 +1120,7 @@
                     ),
                 uiEventLogger = uiEventLogger,
                 featureFlags = featureFlags,
+                userRestrictionChecker = mock(),
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index 2433e12..a8db368 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -267,6 +267,7 @@
                     refreshUsersScheduler = refreshUsersScheduler,
                     guestUserInteractor = guestUserInteractor,
                     uiEventLogger = uiEventLogger,
+                    userRestrictionChecker = mock(),
                 )
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 8c88f95..6932f5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -176,6 +177,7 @@
                         refreshUsersScheduler = refreshUsersScheduler,
                         guestUserInteractor = guestUserInteractor,
                         uiEventLogger = uiEventLogger,
+                        userRestrictionChecker = mock(),
                     ),
                 guestUserInteractor = guestUserInteractor,
             )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index ceab8e9..945aaed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -50,6 +50,7 @@
 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.utils.UserRestrictionChecker
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.test.TestScope
 import org.mockito.Mockito.mock
@@ -137,6 +138,7 @@
                 refreshUsersScheduler = mock(RefreshUsersScheduler::class.java),
                 guestUserInteractor = mock(GuestUserInteractor::class.java),
                 uiEventLogger = mock(UiEventLogger::class.java),
+                userRestrictionChecker = mock(UserRestrictionChecker::class.java),
             )
         return WithDependencies(
             trustRepository = trustRepository,
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 0480c22..11189cf 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -25,5 +25,12 @@
     name: "send_a11y_events_based_on_state"
     namespace: "accessibility"
     description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
-bug: "295575684"
-}
\ No newline at end of file
+    bug: "295575684"
+}
+
+flag {
+    name: "add_window_token_without_lock"
+    namespace: "accessibility"
+    description: "Calls WMS.addWindowToken without holding A11yManagerService#mLock"
+    bug: "297972548"
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 05b6eb4..fa73cff 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1506,11 +1506,17 @@
         }
     }
 
-    public void onAdded() {
+    /**
+     * Called when the connection is first created. Add a window token for all known displays.
+     * <p>
+     * <strong>Note:</strong> Should not be called while holding the AccessibilityManagerService
+     * lock because this calls out to WindowManagerService.
+     */
+    void addWindowTokensForAllDisplays() {
         final Display[] displays = mDisplayManager.getDisplays();
         for (int i = 0; i < displays.length; i++) {
             final int displayId = displays[i].getDisplayId();
-            onDisplayAdded(displayId);
+            addWindowTokenForDisplay(displayId);
         }
     }
 
@@ -1518,9 +1524,13 @@
      * Called whenever a logical display has been added to the system. Add a window token for adding
      * an accessibility overlay.
      *
+     * <p>
+     * <strong>Note:</strong> Should not be called while holding the AccessibilityManagerService
+     * lock because this calls out to WindowManagerService.
+     *
      * @param displayId The id of the logical display that was added.
      */
-    public void onDisplayAdded(int displayId) {
+    void addWindowTokenForDisplay(int displayId) {
         final long identity = Binder.clearCallingIdentity();
         try {
             final IBinder overlayWindowToken = new Binder();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 93ba362..60d4ee6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -154,6 +154,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IntPair;
+import com.android.internal.util.Preconditions;
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -4500,6 +4501,20 @@
         private int mSystemUiUid = 0;
 
         AccessibilityDisplayListener(Context context, Handler handler) {
+            if (Flags.addWindowTokenWithoutLock()) {
+                // Avoid concerns about one thread adding displays while another thread removes
+                // them by ensuring the looper is the main looper and the DisplayListener
+                // callbacks are always executed on the one main thread.
+                final boolean isMainHandler = handler.getLooper() == Looper.getMainLooper();
+                final String errorMessage =
+                        "AccessibilityDisplayListener must use the main handler";
+                if (Build.IS_USERDEBUG || Build.IS_ENG) {
+                    Preconditions.checkArgument(isMainHandler, errorMessage);
+                } else if (!isMainHandler) {
+                    Slog.e(LOG_TAG, errorMessage);
+                }
+            }
+
             mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
             mDisplayManager.registerDisplayListener(this, handler);
             initializeDisplayList();
@@ -4541,11 +4556,21 @@
 
         @Override
         public void onDisplayAdded(int displayId) {
+            if (Flags.addWindowTokenWithoutLock()) {
+                final boolean isMainThread = Looper.getMainLooper().isCurrentThread();
+                final String errorMessage = "onDisplayAdded must be called from the main thread";
+                if (Build.IS_USERDEBUG || Build.IS_ENG) {
+                    Preconditions.checkArgument(isMainThread, errorMessage);
+                } else if (!isMainThread) {
+                    Slog.e(LOG_TAG, errorMessage);
+                }
+            }
             final Display display = mDisplayManager.getDisplay(displayId);
             if (!isValidDisplay(display)) {
                 return;
             }
 
+            final List<AccessibilityServiceConnection> services;
             synchronized (mLock) {
                 mDisplaysList.add(display);
                 mA11yOverlayLayers.put(
@@ -4554,21 +4579,42 @@
                     mInputFilter.onDisplayAdded(display);
                 }
                 AccessibilityUserState userState = getCurrentUserStateLocked();
-                if (displayId != Display.DEFAULT_DISPLAY) {
-                    final List<AccessibilityServiceConnection> services = userState.mBoundServices;
-                    for (int i = 0; i < services.size(); i++) {
-                        AccessibilityServiceConnection boundClient = services.get(i);
-                        boundClient.onDisplayAdded(displayId);
+                if (Flags.addWindowTokenWithoutLock()) {
+                    services = new ArrayList<>(userState.mBoundServices);
+                } else {
+                    services = userState.mBoundServices;
+                    if (displayId != Display.DEFAULT_DISPLAY) {
+                        for (int i = 0; i < services.size(); i++) {
+                            AccessibilityServiceConnection boundClient = services.get(i);
+                            boundClient.addWindowTokenForDisplay(displayId);
+                        }
                     }
                 }
                 updateMagnificationLocked(userState);
                 updateWindowsForAccessibilityCallbackLocked(userState);
                 notifyClearAccessibilityCacheLocked();
             }
+            if (Flags.addWindowTokenWithoutLock()) {
+                if (displayId != Display.DEFAULT_DISPLAY) {
+                    for (int i = 0; i < services.size(); i++) {
+                        AccessibilityServiceConnection boundClient = services.get(i);
+                        boundClient.addWindowTokenForDisplay(displayId);
+                    }
+                }
+            }
         }
 
         @Override
         public void onDisplayRemoved(int displayId) {
+            if (Flags.addWindowTokenWithoutLock()) {
+                final boolean isMainThread = Looper.getMainLooper().isCurrentThread();
+                final String errorMessage = "onDisplayRemoved must be called from the main thread";
+                if (Build.IS_USERDEBUG || Build.IS_ENG) {
+                    Preconditions.checkArgument(isMainThread, errorMessage);
+                } else if (!isMainThread) {
+                    Slog.e(LOG_TAG, errorMessage);
+                }
+            }
             synchronized (mLock) {
                 if (!removeDisplayFromList(displayId)) {
                     return;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 9e70073..7a2a602 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -44,7 +44,6 @@
 import android.view.Display;
 import android.view.MotionEvent;
 
-
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -169,6 +168,10 @@
 
     @Override
     public void onServiceConnected(ComponentName componentName, IBinder service) {
+        AccessibilityUserState userState = mUserStateWeakReference.get();
+        if (userState != null && Flags.addWindowTokenWithoutLock()) {
+            addWindowTokensForAllDisplays();
+        }
         synchronized (mLock) {
             if (mService != service) {
                 if (mService != null) {
@@ -184,7 +187,6 @@
                 }
             }
             mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
-            AccessibilityUserState userState = mUserStateWeakReference.get();
             if (userState == null) return;
             userState.addServiceLocked(this);
             mSystemSupport.onClientChangeLocked(false);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index ab6cc71..693526a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -224,7 +224,9 @@
 
     void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
         if (!mBoundServices.contains(serviceConnection)) {
-            serviceConnection.onAdded();
+            if (!Flags.addWindowTokenWithoutLock()) {
+                serviceConnection.addWindowTokensForAllDisplays();
+            }
             mBoundServices.add(serviceConnection);
             mComponentNameToServiceMap.put(serviceConnection.getComponentName(), serviceConnection);
             mServiceInfoChangeListener.onServiceInfoChangedLocked(this);
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 208acdf..53c629a 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -25,9 +25,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
+import android.os.Looper;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -35,6 +37,7 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
 import com.android.server.utils.Slogf;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -98,46 +101,47 @@
         accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
         Slogf.i(LOG_TAG, "Registering UiTestAutomationService (id=%s) when called by user %d",
                 accessibilityServiceInfo.getId(), Binder.getCallingUserHandle().getIdentifier());
-        synchronized (mLock) {
-            if (mUiAutomationService != null) {
-                throw new IllegalStateException(
-                        "UiAutomationService " + mUiAutomationService.mServiceInterface
-                                + "already registered!");
-            }
-
-            try {
-                owner.linkToDeath(mUiAutomationServiceOwnerDeathRecipient, 0);
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Couldn't register for the death of a UiTestAutomationService!",
-                        re);
-                return;
-            }
-
-            mUiAutomationFlags = flags;
-            mSystemSupport = systemSupport;
-            // Ignore registering UiAutomation if it is not allowed to use the accessibility
-            // subsystem.
-            if (!useAccessibility()) {
-                return;
-            }
-            mUiAutomationService = new UiAutomationService(context, accessibilityServiceInfo, id,
-                    mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal,
-                    systemActionPerformer, awm);
-            mUiAutomationServiceOwner = owner;
-            mUiAutomationService.mServiceInterface = serviceClient;
-            try {
-                mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService,
-                        0);
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Failed registering death link: " + re);
-                destroyUiAutomationService();
-                return;
-            }
-
-            mUiAutomationService.onAdded();
-
-            mUiAutomationService.connectServiceUnknownThread();
+        if (mUiAutomationService != null) {
+            throw new IllegalStateException(
+                    "UiAutomationService " + mUiAutomationService.mServiceInterface
+                            + "already registered!");
         }
+
+        try {
+            owner.linkToDeath(mUiAutomationServiceOwnerDeathRecipient, 0);
+        } catch (RemoteException re) {
+            Slog.e(LOG_TAG, "Couldn't register for the death of a UiTestAutomationService!",
+                    re);
+            return;
+        }
+
+        mUiAutomationFlags = flags;
+        mSystemSupport = systemSupport;
+        // Ignore registering UiAutomation if it is not allowed to use the accessibility
+        // subsystem.
+        if (!useAccessibility()) {
+            return;
+        }
+        mUiAutomationService = new UiAutomationService(context, accessibilityServiceInfo, id,
+                mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal,
+                systemActionPerformer, awm);
+        mUiAutomationServiceOwner = owner;
+        mUiAutomationService.mServiceInterface = serviceClient;
+        try {
+            mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService,
+                    0);
+        } catch (RemoteException re) {
+            Slog.e(LOG_TAG, "Failed registering death link: " + re);
+            destroyUiAutomationService();
+            return;
+        }
+
+        if (!Flags.addWindowTokenWithoutLock()) {
+            mUiAutomationService.addWindowTokensForAllDisplays();
+        }
+        // UiAutomationService#connectServiceUnknownThread posts to a handler
+        // so this call should return immediately.
+        mUiAutomationService.connectServiceUnknownThread();
     }
 
     void unregisterUiTestAutomationServiceLocked(IAccessibilityServiceClient serviceClient) {
@@ -253,6 +257,13 @@
             super(context, COMPONENT_NAME, accessibilityServiceInfo, id, mainHandler, lock,
                     securityPolicy, systemSupport, trace, windowManagerInternal,
                     systemActionPerformer, awm);
+            final boolean isMainHandler = mainHandler.getLooper() == Looper.getMainLooper();
+            final String errorMessage = "UiAutomationService must use the main handler";
+            if (Build.IS_USERDEBUG || Build.IS_ENG) {
+                Preconditions.checkArgument(isMainHandler, errorMessage);
+            } else if (!isMainHandler) {
+                Slog.e(LOG_TAG, errorMessage);
+            }
             mMainHandler = mainHandler;
             setDisplayTypes(DISPLAY_TYPE_DEFAULT | DISPLAY_TYPE_PROXY);
         }
@@ -274,6 +285,9 @@
                     // If the serviceInterface is null, the UiAutomation has been shut down on
                     // another thread.
                     if (serviceInterface != null) {
+                        if (Flags.addWindowTokenWithoutLock()) {
+                            mUiAutomationService.addWindowTokensForAllDisplays();
+                        }
                         if (mTrace.isA11yTracingEnabledForTypes(
                                 AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
                             mTrace.logTrace("UiAutomationService.connectServiceUnknownThread",
@@ -286,7 +300,7 @@
                                 mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
                     }
                 } catch (RemoteException re) {
-                    Slog.w(LOG_TAG, "Error initialized connection", re);
+                    Slog.w(LOG_TAG, "Error initializing connection", re);
                     destroyUiAutomationService();
                 }
             });
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index e8839a2..720687e 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -562,7 +562,8 @@
     private byte[] constructToken(D2DHandshakeContext.Role role, byte[] authValue)
             throws GeneralSecurityException {
         MessageDigest hash = MessageDigest.getInstance("SHA-256");
-        byte[] roleUtf8 = role.name().getBytes(StandardCharsets.UTF_8);
+        String roleName = role == Role.INITIATOR ? "Initiator" : "Responder";
+        byte[] roleUtf8 = roleName.getBytes(StandardCharsets.UTF_8);
         int tokenLength = roleUtf8.length + authValue.length;
         return hash.digest(ByteBuffer.allocate(tokenLength)
                 .put(roleUtf8)
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 5fa0ffa..6005b64 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1442,7 +1442,6 @@
             uidRec.setFrozen(false);
             postUidFrozenMessage(uidRec.getUid(), false);
         }
-        reportProcessFreezableChangedLocked(app);
 
         opt.setFreezerOverride(false);
         if (pid == 0 || !opt.isFrozen()) {
@@ -1481,6 +1480,7 @@
         if (processKilled) {
             return;
         }
+        reportProcessFreezableChangedLocked(app);
 
         long freezeTime = opt.getFreezeUnfreezeTime();
 
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 3d11c68..0615ecf 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3087,6 +3087,8 @@
             if (old == proc && proc.isPersistent()) {
                 // We are re-adding a persistent process.  Whatevs!  Just leave it there.
                 Slog.w(TAG, "Re-adding persistent process " + proc);
+                // Ensure that the mCrashing flag is cleared, since this is a restart
+                proc.resetCrashingOnRestart();
             } else if (old != null) {
                 if (old.isKilled()) {
                     // The old process has been killed, we probably haven't had
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index cfbb5a5..d8a2695 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -618,6 +618,10 @@
         mPkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.longVersionCode));
     }
 
+    void resetCrashingOnRestart() {
+        mErrorState.setCrashing(false);
+    }
+
     @GuardedBy(anyOf = {"mService", "mProcLock"})
     UidRecord getUidRecord() {
         return mUidRecord;
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 52eef47..b9464d9 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -18,9 +18,6 @@
 
 import static android.app.AppGlobals.getPackageManager;
 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-import static android.content.Intent.ACTION_PACKAGE_ADDED;
-import static android.content.Intent.ACTION_PACKAGE_CHANGED;
-import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 import static android.content.Intent.ACTION_USER_ADDED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_PACKAGE_NAME;
@@ -31,10 +28,10 @@
 import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED;
 import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
 import static android.content.pm.PackageManager.SIGNATURE_MATCH;
+import static android.os.Process.INVALID_UID;
 import static android.os.Trace.TRACE_TAG_RRO;
 import static android.os.Trace.traceBegin;
 import static android.os.Trace.traceEnd;
-
 import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
 
 import android.annotation.NonNull;
@@ -82,6 +79,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.content.om.OverlayConfig;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -261,6 +259,8 @@
 
     private final OverlayActorEnforcer mActorEnforcer;
 
+    private final PackageMonitor mPackageMonitor = new OverlayManagerPackageMonitor();
+
     private int mPrevStartedUserId = -1;
 
     public OverlayManagerService(@NonNull final Context context) {
@@ -277,16 +277,9 @@
                     OverlayConfig.getSystemInstance(), getDefaultOverlayPackages());
             mActorEnforcer = new OverlayActorEnforcer(mPackageManager);
 
-            HandlerThread packageReceiverThread = new HandlerThread(TAG);
-            packageReceiverThread.start();
-
-            final IntentFilter packageFilter = new IntentFilter();
-            packageFilter.addAction(ACTION_PACKAGE_ADDED);
-            packageFilter.addAction(ACTION_PACKAGE_CHANGED);
-            packageFilter.addAction(ACTION_PACKAGE_REMOVED);
-            packageFilter.addDataScheme("package");
-            getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL,
-                    packageFilter, null, packageReceiverThread.getThreadHandler());
+            HandlerThread packageMonitorThread = new HandlerThread(TAG);
+            packageMonitorThread.start();
+            mPackageMonitor.register(context, packageMonitorThread.getLooper(), true);
 
             final IntentFilter userFilter = new IntentFilter();
             userFilter.addAction(ACTION_USER_ADDED);
@@ -372,166 +365,171 @@
         return defaultPackages.toArray(new String[defaultPackages.size()]);
     }
 
-    private final class PackageReceiver extends BroadcastReceiver {
+    private final class OverlayManagerPackageMonitor extends PackageMonitor {
+
         @Override
-        public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
-            final String action = intent.getAction();
-            if (action == null) {
-                Slog.e(TAG, "Cannot handle package broadcast with null action");
-                return;
-            }
-            final Uri data = intent.getData();
-            if (data == null) {
-                Slog.e(TAG, "Cannot handle package broadcast with null data");
-                return;
-            }
-            final String packageName = data.getSchemeSpecificPart();
-
-            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-            final boolean systemUpdateUninstall =
-                    intent.getBooleanExtra(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, false);
-
-            final int[] userIds;
-            final int extraUid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
-            if (extraUid == UserHandle.USER_NULL) {
-                userIds = mUserManager.getUserIds();
-            } else {
-                userIds = new int[] { UserHandle.getUserId(extraUid) };
-            }
-
-            switch (action) {
-                case ACTION_PACKAGE_ADDED:
-                    if (replacing) {
-                        onPackageReplaced(packageName, userIds);
-                    } else {
-                        onPackageAdded(packageName, userIds);
-                    }
-                    break;
-                case ACTION_PACKAGE_CHANGED:
-                    // ignore the intent if it was sent by the package manager as a result of the
-                    // overlay manager having sent ACTION_OVERLAY_CHANGED
-                    if (!ACTION_OVERLAY_CHANGED.equals(intent.getStringExtra(EXTRA_REASON))) {
-                        onPackageChanged(packageName, userIds);
-                    }
-                    break;
-                case ACTION_PACKAGE_REMOVED:
-                    if (replacing) {
-                        onPackageReplacing(packageName, systemUpdateUninstall, userIds);
-                    } else {
-                        onPackageRemoved(packageName, userIds);
-                    }
-                    break;
-                default:
-                    // do nothing
-                    break;
-            }
+        public void onPackageAppearedWithExtras(String packageName, Bundle extras) {
+            handlePackageAdd(packageName, extras);
         }
 
-        private void onPackageAdded(@NonNull final String packageName,
-                @NonNull final int[] userIds) {
-            try {
-                traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName);
-                for (final int userId : userIds) {
-                    synchronized (mLock) {
-                        var packageState = mPackageManager.onPackageAdded(packageName, userId);
-                        if (packageState != null && !mPackageManager.isInstantApp(packageName,
-                                userId)) {
-                            try {
-                                updateTargetPackagesLocked(
-                                        mImpl.onPackageAdded(packageName, userId));
-                            } catch (OperationFailedException e) {
-                                Slog.e(TAG, "onPackageAdded internal error", e);
-                            }
+        @Override
+        public void onPackageChangedWithExtras(String packageName, Bundle extras) {
+            handlePackageChange(packageName, extras);
+        }
+
+        @Override
+        public void onPackageDisappearedWithExtras(String packageName, Bundle extras) {
+            handlePackageRemove(packageName, extras);
+        }
+    }
+
+    private int[] getUserIds(int uid) {
+        final int[] userIds;
+        if (uid == INVALID_UID) {
+            userIds = mUserManager.getUserIds();
+        } else {
+            userIds = new int[] { UserHandle.getUserId(uid) };
+        }
+        return userIds;
+    }
+
+    private void handlePackageAdd(String packageName, Bundle extras) {
+        final boolean replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
+        final int uid = extras.getInt(Intent.EXTRA_UID, 0);
+        final int[] userIds = getUserIds(uid);
+        if (replacing) {
+            onPackageReplaced(packageName, userIds);
+        } else {
+            onPackageAdded(packageName, userIds);
+        }
+    }
+
+    private void handlePackageChange(String packageName, Bundle extras) {
+        final int uid = extras.getInt(Intent.EXTRA_UID, 0);
+        final int[] userIds = getUserIds(uid);
+        if (!ACTION_OVERLAY_CHANGED.equals(extras.getString(EXTRA_REASON))) {
+            onPackageChanged(packageName, userIds);
+        }
+    }
+
+    private void handlePackageRemove(String packageName, Bundle extras) {
+        final boolean replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
+        final boolean systemUpdateUninstall =
+                extras.getBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, false);
+        final int uid = extras.getInt(Intent.EXTRA_UID, 0);
+        final int[] userIds = getUserIds(uid);
+
+        if (replacing) {
+            onPackageReplacing(packageName, systemUpdateUninstall, userIds);
+        } else {
+            onPackageRemoved(packageName, userIds);
+        }
+    }
+
+    private void onPackageAdded(@NonNull final String packageName,
+            @NonNull final int[] userIds) {
+        try {
+            traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName);
+            for (final int userId : userIds) {
+                synchronized (mLock) {
+                    var packageState = mPackageManager.onPackageAdded(packageName, userId);
+                    if (packageState != null && !mPackageManager.isInstantApp(packageName,
+                            userId)) {
+                        try {
+                            updateTargetPackagesLocked(
+                                    mImpl.onPackageAdded(packageName, userId));
+                        } catch (OperationFailedException e) {
+                            Slog.e(TAG, "onPackageAdded internal error", e);
                         }
                     }
                 }
-            } finally {
-                traceEnd(TRACE_TAG_RRO);
             }
+        } finally {
+            traceEnd(TRACE_TAG_RRO);
         }
+    }
 
-        private void onPackageChanged(@NonNull final String packageName,
-                @NonNull final int[] userIds) {
-            try {
-                traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName);
-                for (int userId : userIds) {
-                    synchronized (mLock) {
-                        var packageState = mPackageManager.onPackageUpdated(packageName, userId);
-                        if (packageState != null && !mPackageManager.isInstantApp(packageName,
-                                userId)) {
-                            try {
-                                updateTargetPackagesLocked(
-                                        mImpl.onPackageChanged(packageName, userId));
-                            } catch (OperationFailedException e) {
-                                Slog.e(TAG, "onPackageChanged internal error", e);
-                            }
+    private void onPackageChanged(@NonNull final String packageName,
+            @NonNull final int[] userIds) {
+        try {
+            traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName);
+            for (int userId : userIds) {
+                synchronized (mLock) {
+                    var packageState = mPackageManager.onPackageUpdated(packageName, userId);
+                    if (packageState != null && !mPackageManager.isInstantApp(packageName,
+                            userId)) {
+                        try {
+                            updateTargetPackagesLocked(
+                                    mImpl.onPackageChanged(packageName, userId));
+                        } catch (OperationFailedException e) {
+                            Slog.e(TAG, "onPackageChanged internal error", e);
                         }
                     }
                 }
-            } finally {
-                traceEnd(TRACE_TAG_RRO);
             }
+        } finally {
+            traceEnd(TRACE_TAG_RRO);
         }
+    }
 
-        private void onPackageReplacing(@NonNull final String packageName,
-                boolean systemUpdateUninstall, @NonNull final int[] userIds) {
-            try {
-                traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
-                for (int userId : userIds) {
-                    synchronized (mLock) {
-                        var packageState = mPackageManager.onPackageUpdated(packageName, userId);
-                        if (packageState != null && !mPackageManager.isInstantApp(packageName,
-                                userId)) {
-                            try {
-                                updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName,
-                                        systemUpdateUninstall, userId));
-                            } catch (OperationFailedException e) {
-                                Slog.e(TAG, "onPackageReplacing internal error", e);
-                            }
+    private void onPackageReplacing(@NonNull final String packageName,
+            boolean systemUpdateUninstall, @NonNull final int[] userIds) {
+        try {
+            traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
+            for (int userId : userIds) {
+                synchronized (mLock) {
+                    var packageState = mPackageManager.onPackageUpdated(packageName, userId);
+                    if (packageState != null && !mPackageManager.isInstantApp(packageName,
+                            userId)) {
+                        try {
+                            updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName,
+                                    systemUpdateUninstall, userId));
+                        } catch (OperationFailedException e) {
+                            Slog.e(TAG, "onPackageReplacing internal error", e);
                         }
                     }
                 }
-            } finally {
-                traceEnd(TRACE_TAG_RRO);
             }
+        } finally {
+            traceEnd(TRACE_TAG_RRO);
         }
+    }
 
-        private void onPackageReplaced(@NonNull final String packageName,
-                @NonNull final int[] userIds) {
-            try {
-                traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName);
-                for (int userId : userIds) {
-                    synchronized (mLock) {
-                        var packageState = mPackageManager.onPackageUpdated(packageName, userId);
-                        if (packageState != null && !mPackageManager.isInstantApp(packageName,
-                                userId)) {
-                            try {
-                                updateTargetPackagesLocked(
-                                        mImpl.onPackageReplaced(packageName, userId));
-                            } catch (OperationFailedException e) {
-                                Slog.e(TAG, "onPackageReplaced internal error", e);
-                            }
+    private void onPackageReplaced(@NonNull final String packageName,
+            @NonNull final int[] userIds) {
+        try {
+            traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName);
+            for (int userId : userIds) {
+                synchronized (mLock) {
+                    var packageState = mPackageManager.onPackageUpdated(packageName, userId);
+                    if (packageState != null && !mPackageManager.isInstantApp(packageName,
+                            userId)) {
+                        try {
+                            updateTargetPackagesLocked(
+                                    mImpl.onPackageReplaced(packageName, userId));
+                        } catch (OperationFailedException e) {
+                            Slog.e(TAG, "onPackageReplaced internal error", e);
                         }
                     }
                 }
-            } finally {
-                traceEnd(TRACE_TAG_RRO);
             }
+        } finally {
+            traceEnd(TRACE_TAG_RRO);
         }
+    }
 
-        private void onPackageRemoved(@NonNull final String packageName,
-                @NonNull final int[] userIds) {
-            try {
-                traceBegin(TRACE_TAG_RRO, "OMS#onPackageRemoved " + packageName);
-                for (int userId : userIds) {
-                    synchronized (mLock) {
-                        mPackageManager.onPackageRemoved(packageName, userId);
-                        updateTargetPackagesLocked(mImpl.onPackageRemoved(packageName, userId));
-                    }
+    private void onPackageRemoved(@NonNull final String packageName,
+            @NonNull final int[] userIds) {
+        try {
+            traceBegin(TRACE_TAG_RRO, "OMS#onPackageRemoved " + packageName);
+            for (int userId : userIds) {
+                synchronized (mLock) {
+                    mPackageManager.onPackageRemoved(packageName, userId);
+                    updateTargetPackagesLocked(mImpl.onPackageRemoved(packageName, userId));
                 }
-            } finally {
-                traceEnd(TRACE_TAG_RRO);
             }
+        } finally {
+            traceEnd(TRACE_TAG_RRO);
         }
     }
 
@@ -684,7 +682,7 @@
                     synchronized (mLock) {
                         try {
                             mImpl.setEnabledExclusive(
-                                    overlay, false /* withinCategory */, realUserId)
+                                            overlay, false /* withinCategory */, realUserId)
                                     .ifPresent(
                                             OverlayManagerService.this::updateTargetPackagesLocked);
                             return true;
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 9a69d77..e367609 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -17,9 +17,14 @@
 package com.android.server.pm;
 
 import static android.os.PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED;
+import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.Process.SYSTEM_UID;
 import static android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED;
+
+import static com.android.server.pm.PackageManagerService.DEBUG_BACKUP;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
+import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
 import static com.android.server.pm.PackageManagerService.PACKAGE_SCHEME;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.pm.PackageManagerService.TAG;
@@ -28,6 +33,7 @@
 import android.annotation.AppIdInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.BroadcastOptions;
@@ -38,12 +44,18 @@
 import android.content.Intent;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.PowerExemptionManager;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
 import android.provider.DeviceConfig;
+import android.stats.storage.StorageEnums;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
@@ -51,10 +63,15 @@
 import android.util.SparseArray;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.function.BiFunction;
-import java.util.function.Supplier;
 
 /**
  * Helper class to send broadcasts for various situations.
@@ -70,14 +87,20 @@
     private final UserManagerInternal mUmInternal;
     private final ActivityManagerInternal mAmInternal;
     private final Context mContext;
+    private final Handler mHandler;
+    private final PackageMonitorCallbackHelper mPackageMonitorCallbackHelper;
+    private final AppsFilterSnapshot mAppsFilter;
 
     BroadcastHelper(PackageManagerServiceInjector injector) {
         mUmInternal = injector.getUserManagerInternal();
         mAmInternal = injector.getActivityManagerInternal();
         mContext = injector.getContext();
+        mHandler = injector.getHandler();
+        mPackageMonitorCallbackHelper = injector.getPackageMonitorCallbackHelper();
+        mAppsFilter = injector.getAppsFilter();
     }
 
-    public void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
+    void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
             final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
             final int[] userIds, int[] instantUserIds,
             @Nullable SparseArray<int[]> broadcastAllowList,
@@ -114,9 +137,16 @@
      * the system and applications allowed to see instant applications to receive package
      * lifecycle events for instant applications.
      */
-    public void doSendBroadcast(String action, String pkg, Bundle extras,
-            int flags, String targetPkg, IIntentReceiver finishedReceiver,
-            int[] userIds, boolean isInstantApp, @Nullable SparseArray<int[]> broadcastAllowList,
+    private void doSendBroadcast(
+            @NonNull String action,
+            @Nullable String pkg,
+            @Nullable Bundle extras,
+            int flags,
+            @Nullable String targetPkg,
+            @Nullable IIntentReceiver finishedReceiver,
+            @NonNull int[] userIds,
+            boolean isInstantApp,
+            @Nullable SparseArray<int[]> broadcastAllowList,
             @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
             @Nullable Bundle bOptions) {
         for (int userId : userIds) {
@@ -166,9 +196,11 @@
         }
     }
 
-    public void sendResourcesChangedBroadcast(@NonNull Supplier<Computer> snapshotComputer,
-            boolean mediaStatus, boolean replacing, @NonNull String[] pkgNames,
-            @NonNull int[] uids) {
+    void sendResourcesChangedBroadcast(@NonNull Computer snapshot,
+                                       boolean mediaStatus,
+                                       boolean replacing,
+                                       @NonNull String[] pkgNames,
+                                       @NonNull int[] uids) {
         if (ArrayUtils.isEmpty(pkgNames) || ArrayUtils.isEmpty(uids)) {
             return;
         }
@@ -184,7 +216,7 @@
                 null /* targetPkg */, null /* finishedReceiver */, null /* userIds */,
                 null /* instantUserIds */, null /* broadcastAllowList */,
                 (callingUid, intentExtras) -> filterExtrasChangedPackageList(
-                        snapshotComputer.get(), callingUid, intentExtras),
+                        snapshot, callingUid, intentExtras),
                 null /* bOptions */);
     }
 
@@ -193,8 +225,9 @@
      * automatically without needing an explicit launch.
      * Send it a LOCKED_BOOT_COMPLETED/BOOT_COMPLETED if it would ordinarily have gotten ones.
      */
-    public void sendBootCompletedBroadcastToSystemApp(
-            String packageName, boolean includeStopped, int userId) {
+    private void sendBootCompletedBroadcastToSystemApp(@NonNull String packageName,
+                                                       boolean includeStopped,
+                                                       int userId) {
         // If user is not running, the app didn't miss any broadcast
         if (!mUmInternal.isUserRunning(userId)) {
             return;
@@ -229,7 +262,7 @@
         }
     }
 
-    public @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
+    private @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
             @PowerExemptionManager.ReasonCode int reasonCode) {
         long duration = 10_000;
         if (mAmInternal != null) {
@@ -242,9 +275,14 @@
         return bOptions;
     }
 
-    public void sendPackageChangedBroadcast(String packageName, boolean dontKillApp,
-            ArrayList<String> componentNames, int packageUid, String reason,
-            int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
+    private void sendPackageChangedBroadcast(@NonNull String packageName,
+                                             boolean dontKillApp,
+                                             @NonNull ArrayList<String> componentNames,
+                                             int packageUid,
+                                             @Nullable String reason,
+                                             @Nullable int[] userIds,
+                                             @Nullable int[] instantUserIds,
+                                             @Nullable SparseArray<int[]> broadcastAllowList) {
         if (DEBUG_INSTALL) {
             Log.v(TAG, "Sending package changed: package=" + packageName + " components="
                     + componentNames);
@@ -269,7 +307,7 @@
                 null /* bOptions */);
     }
 
-    public static void sendDeviceCustomizationReadyBroadcast() {
+    static void sendDeviceCustomizationReadyBroadcast() {
         final Intent intent = new Intent(Intent.ACTION_DEVICE_CUSTOMIZATION_READY);
         intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         final IActivityManager am = ActivityManager.getService();
@@ -285,15 +323,23 @@
         }
     }
 
-    public void sendSessionCommitBroadcast(PackageInstaller.SessionInfo sessionInfo, int userId,
-            int launcherUid, @Nullable ComponentName launcherComponent,
-            @Nullable String appPredictionServicePackage) {
+    void sendSessionCommitBroadcast(@NonNull Computer snapshot,
+                                    @NonNull PackageInstaller.SessionInfo sessionInfo,
+                                    int userId,
+                                    @Nullable String appPredictionServicePackage) {
+        UserManagerService ums = UserManagerService.getInstance();
+        if (ums == null || sessionInfo.isStaged()) {
+            return;
+        }
+        final UserInfo parent = ums.getProfileParent(userId);
+        final int launcherUserId = (parent != null) ? parent.id : userId;
+        final ComponentName launcherComponent = snapshot.getDefaultHomeActivity(launcherUserId);
         if (launcherComponent != null) {
             Intent launcherIntent = new Intent(PackageInstaller.ACTION_SESSION_COMMITTED)
                     .putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo)
                     .putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
                     .setPackage(launcherComponent.getPackageName());
-            mContext.sendBroadcastAsUser(launcherIntent, UserHandle.of(launcherUid));
+            mContext.sendBroadcastAsUser(launcherIntent, UserHandle.of(launcherUserId));
         }
         // TODO(b/122900055) Change/Remove this and replace with new permission role.
         if (appPredictionServicePackage != null) {
@@ -301,30 +347,278 @@
                     .putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo)
                     .putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
                     .setPackage(appPredictionServicePackage);
-            mContext.sendBroadcastAsUser(predictorIntent, UserHandle.of(launcherUid));
+            mContext.sendBroadcastAsUser(predictorIntent, UserHandle.of(launcherUserId));
         }
     }
 
-    public void sendPreferredActivityChangedBroadcast(int userId) {
-        final IActivityManager am = ActivityManager.getService();
-        if (am == null) {
+    void sendPreferredActivityChangedBroadcast(int userId) {
+        mHandler.post(() -> {
+            final IActivityManager am = ActivityManager.getService();
+            if (am == null) {
+                return;
+            }
+
+            final Intent intent = new Intent(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED);
+            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            try {
+                am.broadcastIntentWithFeature(null, null, intent, null, null,
+                        0, null, null, null, null, null, android.app.AppOpsManager.OP_NONE,
+                        null, false, false, userId);
+            } catch (RemoteException e) {
+            }
+        });
+    }
+
+    void sendPostInstallBroadcasts(@NonNull Computer snapshot,
+                                   @NonNull InstallRequest request,
+                                   @NonNull String packageName,
+                                   @NonNull String requiredPermissionControllerPackage,
+                                   @NonNull String[] requiredVerifierPackages,
+                                   @NonNull String requiredInstallerPackage,
+                                   @NonNull PackageSender packageSender,
+                                   boolean isLaunchedForRestore,
+                                   boolean isKillApp,
+                                   boolean isUpdate,
+                                   boolean isArchived) {
+        // Send the removed broadcasts
+        if (request.getRemovedInfo() != null) {
+            if (request.getRemovedInfo().mIsExternal) {
+                if (DEBUG_INSTALL) {
+                    Slog.i(TAG, "upgrading pkg " + request.getRemovedInfo().mRemovedPackage
+                            + " is ASEC-hosted -> UNAVAILABLE");
+                }
+                final String[] pkgNames = new String[]{
+                        request.getRemovedInfo().mRemovedPackage};
+                final int[] uids = new int[]{request.getRemovedInfo().mUid};
+                notifyResourcesChanged(
+                        false /* mediaStatus */, true /* replacing */, pkgNames, uids);
+                sendResourcesChangedBroadcast(
+                        snapshot, false /* mediaStatus */, true /* replacing */, pkgNames, uids);
+            }
+            sendPackageRemovedBroadcasts(
+                    request.getRemovedInfo(), packageSender, isKillApp, false /*removedBySystem*/,
+                    false /*isArchived*/);
+        }
+
+        final int[] firstUserIds = request.getFirstTimeBroadcastUserIds();
+        final int[] firstInstantUserIds = request.getFirstTimeBroadcastInstantUserIds();
+        final int[] updateUserIds = request.getUpdateBroadcastUserIds();
+        final int[] instantUserIds = request.getUpdateBroadcastInstantUserIds();
+
+        final String installerPackageName =
+                request.getInstallerPackageName() != null
+                        ? request.getInstallerPackageName()
+                        : request.getRemovedInfo() != null
+                        ? request.getRemovedInfo().mInstallerPackageName
+                        : null;
+
+        Bundle extras = new Bundle();
+        extras.putInt(Intent.EXTRA_UID, request.getAppId());
+        if (isUpdate) {
+            extras.putBoolean(Intent.EXTRA_REPLACING, true);
+        }
+        if (isArchived) {
+            extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
+        }
+        extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, request.getDataLoaderType());
+
+        final String staticSharedLibraryName = request.getPkg().getStaticSharedLibraryName();
+        // If a package is a static shared library, then only the installer of the package
+        // should get the broadcast.
+        if (installerPackageName != null && staticSharedLibraryName != null) {
+            sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName,
+                    extras, 0 /*flags*/,
+                    installerPackageName, null /*finishedReceiver*/,
+                    request.getNewUsers(), null /* instantUserIds*/,
+                    null /* broadcastAllowList */, null);
+        }
+
+        // Send installed broadcasts if the package is not a static shared lib.
+        if (staticSharedLibraryName == null) {
+            // Send PACKAGE_ADDED broadcast for users that see the package for the first time
+            // sendPackageAddedForNewUsers also deals with system apps
+            final int appId = UserHandle.getAppId(request.getAppId());
+            final boolean isSystem = request.isInstallSystem();
+            final boolean isVirtualPreload =
+                    ((request.getInstallFlags() & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
+            sendPackageAddedForNewUsers(snapshot, packageName,
+                    isSystem || isVirtualPreload,
+                    isVirtualPreload /*startReceiver*/, appId,
+                    firstUserIds, firstInstantUserIds, isArchived, request.getDataLoaderType());
+
+            // Send PACKAGE_ADDED broadcast for users that don't see
+            // the package for the first time
+
+            // Send to all running apps.
+            final SparseArray<int[]> newBroadcastAllowList =
+                    mAppsFilter.getVisibilityAllowList(snapshot,
+                            snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
+                            updateUserIds, snapshot.getPackageStates());
+            sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName,
+                    extras, 0 /*flags*/,
+                    null /*targetPackage*/, null /*finishedReceiver*/,
+                    updateUserIds, instantUserIds, newBroadcastAllowList, null);
+            // Send to the installer, even if it's not running.
+            if (installerPackageName != null) {
+                sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName,
+                        extras, 0 /*flags*/,
+                        installerPackageName, null /*finishedReceiver*/,
+                        updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
+            }
+            // Send to PermissionController for all update users, even if it may not be running
+            // for some users
+            if (isPrivacySafetyLabelChangeNotificationsEnabled(mContext)) {
+                sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName,
+                        extras, 0 /*flags*/,
+                        requiredPermissionControllerPackage, null /*finishedReceiver*/,
+                        updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
+            }
+            // Notify required verifier(s) that are not the installer of record for the package.
+            for (String verifierPackageName : requiredVerifierPackages) {
+                if (verifierPackageName != null && !verifierPackageName.equals(
+                        installerPackageName)) {
+                    sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED,
+                            packageName,
+                            extras, 0 /*flags*/,
+                            verifierPackageName, null /*finishedReceiver*/,
+                            updateUserIds, instantUserIds, null /* broadcastAllowList */,
+                            null);
+                }
+            }
+            // If package installer is defined, notify package installer about new
+            // app installed
+            sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName,
+                    extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/,
+                    requiredInstallerPackage, null /*finishedReceiver*/,
+                    firstUserIds, instantUserIds, null /* broadcastAllowList */, null);
+
+            // Send replaced for users that don't see the package for the first time
+            if (isUpdate) {
+                sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED,
+                        packageName, extras, 0 /*flags*/,
+                        null /*targetPackage*/, null /*finishedReceiver*/,
+                        updateUserIds, instantUserIds,
+                        request.getRemovedInfo().mBroadcastAllowList, null);
+                if (installerPackageName != null) {
+                    sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED, packageName,
+                            extras, 0 /*flags*/,
+                            installerPackageName, null /*finishedReceiver*/,
+                            updateUserIds, instantUserIds, null /*broadcastAllowList*/,
+                            null);
+                }
+                for (String verifierPackageName : requiredVerifierPackages) {
+                    if (verifierPackageName != null && !verifierPackageName.equals(
+                            installerPackageName)) {
+                        sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED,
+                                packageName, extras, 0 /*flags*/, verifierPackageName,
+                                null /*finishedReceiver*/, updateUserIds, instantUserIds,
+                                null /*broadcastAllowList*/, null);
+                    }
+                }
+                sendPackageBroadcastAndNotify(Intent.ACTION_MY_PACKAGE_REPLACED,
+                        null /*package*/, null /*extras*/, 0 /*flags*/,
+                        packageName /*targetPackage*/,
+                        null /*finishedReceiver*/, updateUserIds, instantUserIds,
+                        null /*broadcastAllowList*/,
+                        getTemporaryAppAllowlistBroadcastOptions(
+                                REASON_PACKAGE_REPLACED).toBundle());
+            } else if (isLaunchedForRestore && !request.isInstallSystem()) {
+                // First-install and we did a restore, so we're responsible for the
+                // first-launch broadcast.
+                if (DEBUG_BACKUP) {
+                    Slog.i(TAG, "Post-restore of " + packageName
+                            + " sending FIRST_LAUNCH in " + Arrays.toString(firstUserIds));
+                }
+                sendFirstLaunchBroadcast(packageName, installerPackageName,
+                        firstUserIds, firstInstantUserIds);
+            }
+
+            // Send broadcast package appeared if external for all users
+            if (request.getPkg().isExternalStorage()) {
+                if (!isUpdate) {
+                    final StorageManager storage = mContext.getSystemService(StorageManager.class);
+                    VolumeInfo volume =
+                            storage.findVolumeByUuid(
+                                    StorageManager.convert(
+                                            request.getPkg().getVolumeUuid()).toString());
+                    int packageExternalStorageType =
+                            PackageManagerServiceUtils.getPackageExternalStorageType(volume,
+                                    /* isExternalStorage */ true);
+                    // If the package was installed externally, log it.
+                    if (packageExternalStorageType != StorageEnums.UNKNOWN) {
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED,
+                                packageExternalStorageType, packageName);
+                    }
+                }
+                if (DEBUG_INSTALL) {
+                    Slog.i(TAG, "upgrading pkg " + packageName + " is external");
+                }
+                if (!isArchived) {
+                    final String[] pkgNames = new String[]{packageName};
+                    final int[] uids = new int[]{request.getPkg().getUid()};
+                    sendResourcesChangedBroadcast(snapshot,
+                            true /* mediaStatus */, true /* replacing */, pkgNames, uids);
+                    notifyResourcesChanged(true /* mediaStatus */,
+                            true /* replacing */, pkgNames, uids);
+                }
+            }
+        } else { // if static shared lib
+            final ArrayList<AndroidPackage> libraryConsumers = request.getLibraryConsumers();
+            if (!ArrayUtils.isEmpty(libraryConsumers)) {
+                // No need to kill consumers if it's installation of new version static shared lib.
+                final boolean dontKillApp = !isUpdate;
+                for (int i = 0; i < libraryConsumers.size(); i++) {
+                    AndroidPackage pkg = libraryConsumers.get(i);
+                    // send broadcast that all consumers of the static shared library have changed
+                    sendPackageChangedBroadcast(snapshot, pkg.getPackageName(),
+                            dontKillApp,
+                            new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
+                            pkg.getUid(), null);
+                }
+            }
+        }
+    }
+
+    private void sendPackageAddedForNewUsers(@NonNull Computer snapshot,
+                                             @NonNull String packageName,
+                                             boolean sendBootCompleted,
+                                             boolean includeStopped,
+                                             @AppIdInt int appId,
+                                             int[] userIds,
+                                             int[] instantUserIds,
+                                             boolean isArchived,
+                                             int dataLoaderType) {
+        if (ArrayUtils.isEmpty(userIds) && ArrayUtils.isEmpty(instantUserIds)) {
             return;
         }
-
-        final Intent intent = new Intent(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        try {
-            am.broadcastIntentWithFeature(null, null, intent, null, null,
-                    0, null, null, null, null, null, android.app.AppOpsManager.OP_NONE,
-                    null, false, false, userId);
-        } catch (RemoteException e) {
+        SparseArray<int[]> broadcastAllowList = mAppsFilter.getVisibilityAllowList(snapshot,
+                snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
+                userIds, snapshot.getPackageStates());
+        mHandler.post(
+                () -> sendPackageAddedForNewUsers(packageName, appId, userIds,
+                        instantUserIds, isArchived, dataLoaderType, broadcastAllowList));
+        mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(packageName, appId, userIds,
+                instantUserIds, isArchived, dataLoaderType, broadcastAllowList, mHandler);
+        if (sendBootCompleted && !ArrayUtils.isEmpty(userIds)) {
+            mHandler.post(() -> {
+                        for (int userId : userIds) {
+                            sendBootCompletedBroadcastToSystemApp(
+                                    packageName, includeStopped, userId);
+                        }
+                    }
+            );
         }
     }
 
-    public void sendPackageAddedForNewUsers(String packageName, @AppIdInt int appId, int[] userIds,
-            int[] instantUserIds, boolean isArchived, int dataLoaderType,
-            SparseArray<int[]> broadcastAllowlist) {
+    private void sendPackageAddedForNewUsers(@NonNull String packageName,
+                                             @AppIdInt int appId,
+                                             int[] userIds,
+                                             int[] instantUserIds,
+                                             boolean isArchived,
+                                             int dataLoaderType,
+                                             @NonNull SparseArray<int[]> broadcastAllowlist) {
         Bundle extras = new Bundle(1);
         // Set to UID of the first user, EXTRA_UID is automatically updated in sendPackageBroadcast
         final int uid = UserHandle.getUid(
@@ -349,7 +643,30 @@
         }
     }
 
-    public void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
+    void sendPackageAddedForUser(@NonNull Computer snapshot,
+                                 @NonNull String packageName,
+                                 @NonNull PackageStateInternal packageState,
+                                 int userId,
+                                 boolean isArchived,
+                                 int dataLoaderType,
+                                 @Nullable String appPredictionServicePackage) {
+        final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
+        final boolean isSystem = packageState.isSystem();
+        final boolean isInstantApp = userState.isInstantApp();
+        final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
+        final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
+        sendPackageAddedForNewUsers(snapshot, packageName, isSystem /*sendBootCompleted*/,
+                false /*startReceiver*/, packageState.getAppId(), userIds, instantUserIds,
+                isArchived, dataLoaderType);
+
+        // Send a session commit broadcast
+        final PackageInstaller.SessionInfo info = new PackageInstaller.SessionInfo();
+        info.installReason = userState.getInstallReason();
+        info.appPackageName = packageName;
+        sendSessionCommitBroadcast(snapshot, info, userId, appPredictionServicePackage);
+    }
+
+    void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
             int[] userIds, int[] instantUserIds) {
         sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
                 installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */,
@@ -366,7 +683,7 @@
      * access all the packages in the extras.
      */
     @Nullable
-    public static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid,
+    private static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid,
             @NonNull Bundle extras) {
         if (UserHandle.isCore(callingUid)) {
             // see all
@@ -392,7 +709,7 @@
     }
 
     /** Returns whether the Safety Label Change notification, a privacy feature, is enabled. */
-    public static boolean isPrivacySafetyLabelChangeNotificationsEnabled(Context context) {
+    private static boolean isPrivacySafetyLabelChangeNotificationsEnabled(Context context) {
         PackageManager packageManager = context.getPackageManager();
         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
                 SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, true)
@@ -424,4 +741,323 @@
                 pkgList.size() > 0 ? pkgList.toArray(new String[pkgList.size()]) : null,
                 uidList != null && uidList.size() > 0 ? uidList.toArray() : null);
     }
+
+    void sendApplicationHiddenForUser(@NonNull String packageName,
+                                      @NonNull PackageStateInternal packageState,
+                                      int userId,
+                                      @NonNull PackageSender packageSender) {
+        final PackageRemovedInfo info = new PackageRemovedInfo();
+        info.mRemovedPackage = packageName;
+        info.mInstallerPackageName = packageState.getInstallSource().mInstallerPackageName;
+        info.mRemovedUsers = new int[] {userId};
+        info.mBroadcastUsers = new int[] {userId};
+        info.mUid = UserHandle.getUid(userId, packageState.getAppId());
+        info.mRemovedPackageVersionCode = packageState.getVersionCode();
+        sendPackageRemovedBroadcasts(info, packageSender, true /*killApp*/,
+                false /*removedBySystem*/, false /*isArchived*/);
+    }
+
+    void sendPackageChangedBroadcast(@NonNull Computer snapshot,
+                                     @NonNull String packageName,
+                                     boolean dontKillApp,
+                                     @NonNull ArrayList<String> componentNames,
+                                     int packageUid,
+                                     @NonNull String reason) {
+        PackageStateInternal setting = snapshot.getPackageStateInternal(packageName,
+                Process.SYSTEM_UID);
+        if (setting == null) {
+            return;
+        }
+        final int userId = UserHandle.getUserId(packageUid);
+        final boolean isInstantApp =
+                snapshot.isInstantAppInternal(packageName, userId, Process.SYSTEM_UID);
+        final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
+        final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
+        final SparseArray<int[]> broadcastAllowList =
+                isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds);
+        mHandler.post(() -> sendPackageChangedBroadcast(
+                packageName, dontKillApp, componentNames, packageUid, reason, userIds,
+                instantUserIds, broadcastAllowList));
+        mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
+                packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler);
+    }
+
+    private void sendPackageBroadcastAndNotify(@NonNull String action,
+                                               @NonNull  String pkg,
+                                               @NonNull  Bundle extras,
+                                               int flags,
+                                               @Nullable String targetPkg,
+                                               @Nullable IIntentReceiver finishedReceiver,
+                                               @NonNull int[] userIds,
+                                               @NonNull int[] instantUserIds,
+                                               @Nullable SparseArray<int[]> broadcastAllowList,
+                                               @Nullable Bundle bOptions) {
+        mHandler.post(() -> sendPackageBroadcast(action, pkg, extras, flags,
+                targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList,
+                null /* filterExtrasForReceiver */, bOptions));
+        if (targetPkg == null) {
+            // For some broadcast action, e.g. ACTION_PACKAGE_ADDED, this method will be called
+            // many times to different targets, e.g. installer app, permission controller, other
+            // registered apps. We should filter it to avoid calling back many times for the same
+            // action. When the targetPkg is set, it sends the broadcast to specific app, e.g.
+            // installer app or null for registered apps. The callback only need to send back to the
+            // registered apps so we check the null condition here.
+            notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList);
+        }
+    }
+
+    void sendSystemPackageUpdatedBroadcasts(@NonNull PackageRemovedInfo packageRemovedInfo) {
+        if (!packageRemovedInfo.mIsRemovedPackageSystemUpdate) {
+            return;
+        }
+
+        final String removedPackage = packageRemovedInfo.mRemovedPackage;
+        final int removedAppId = packageRemovedInfo.mRemovedAppId;
+        final int uid = packageRemovedInfo.mUid;
+        final String installerPackageName = packageRemovedInfo.mInstallerPackageName;
+        final SparseArray<int[]> broadcastAllowList = packageRemovedInfo.mBroadcastAllowList;
+
+        Bundle extras = new Bundle(2);
+        extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid);
+        extras.putBoolean(Intent.EXTRA_REPLACING, true);
+        sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, removedPackage, extras,
+                0, null /*targetPackage*/, null, null, null, broadcastAllowList, null);
+
+        if (installerPackageName != null) {
+            sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED,
+                    removedPackage, extras, 0 /*flags*/,
+                    installerPackageName, null, null, null, null /* broadcastAllowList */,
+                    null);
+            sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED,
+                    removedPackage, extras, 0 /*flags*/,
+                    installerPackageName, null, null, null, null /* broadcastAllowList */,
+                    null);
+        }
+        sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED, removedPackage,
+                extras, 0, null /*targetPackage*/, null, null, null, broadcastAllowList, null);
+        sendPackageBroadcastAndNotify(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0,
+                removedPackage, null, null, null, null /* broadcastAllowList */,
+                getTemporaryBroadcastOptionsForSystemPackageUpdate(REASON_PACKAGE_REPLACED)
+                        .toBundle());
+    }
+
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private @NonNull BroadcastOptions getTemporaryBroadcastOptionsForSystemPackageUpdate(
+            @PowerExemptionManager.ReasonCode int reasonCode) {
+        long duration = 10_000;
+        if (mAmInternal != null) {
+            duration = mAmInternal.getBootTimeTempAllowListDuration();
+        }
+        final BroadcastOptions bOptions = BroadcastOptions.makeBasic();
+        bOptions.setTemporaryAppAllowlist(duration,
+                TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                reasonCode, "");
+        return bOptions;
+    }
+
+
+    void sendPackageRemovedBroadcasts(
+            @NonNull PackageRemovedInfo packageRemovedInfo,
+            @NonNull PackageSender packageSender,
+            boolean killApp,
+            boolean removedBySystem,
+            boolean isArchived) {
+        final String removedPackage = packageRemovedInfo.mRemovedPackage;
+        final int removedAppId = packageRemovedInfo.mRemovedAppId;
+        final int uid = packageRemovedInfo.mUid;
+        final String installerPackageName = packageRemovedInfo.mInstallerPackageName;
+        final int[] broadcastUserIds = packageRemovedInfo.mBroadcastUsers;
+        final int[] instantUserIds = packageRemovedInfo.mInstantUserIds;
+        final SparseArray<int[]> broadcastAllowList = packageRemovedInfo.mBroadcastAllowList;
+        final boolean dataRemoved = packageRemovedInfo.mDataRemoved;
+        final boolean isUpdate = packageRemovedInfo.mIsUpdate;
+        final boolean isRemovedPackageSystemUpdate =
+                packageRemovedInfo.mIsRemovedPackageSystemUpdate;
+        final boolean isRemovedForAllUsers = packageRemovedInfo.mRemovedForAllUsers;
+        final boolean isStaticSharedLib = packageRemovedInfo.mIsStaticSharedLib;
+
+        Bundle extras = new Bundle();
+        final int removedUid = removedAppId >= 0  ? removedAppId : uid;
+        extras.putInt(Intent.EXTRA_UID, removedUid);
+        extras.putBoolean(Intent.EXTRA_DATA_REMOVED, dataRemoved);
+        extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, isRemovedPackageSystemUpdate);
+        extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
+        extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem);
+        final boolean isReplace = isUpdate || isRemovedPackageSystemUpdate;
+        if (isReplace || isArchived) {
+            extras.putBoolean(Intent.EXTRA_REPLACING, true);
+        }
+        if (isArchived) {
+            extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
+        }
+        extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, isRemovedForAllUsers);
+
+        // Send PACKAGE_REMOVED broadcast to the respective installer.
+        if (removedPackage != null && installerPackageName != null) {
+            sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REMOVED,
+                    removedPackage, extras, 0 /*flags*/,
+                    installerPackageName, null, broadcastUserIds, instantUserIds, null, null);
+        }
+        if (isStaticSharedLib) {
+            // When uninstalling static shared libraries, only the package's installer needs to be
+            // sent a PACKAGE_REMOVED broadcast. There are no other intended recipients.
+            return;
+        }
+        if (removedPackage != null) {
+            sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REMOVED,
+                    removedPackage, extras, 0, null /*targetPackage*/, null,
+                    broadcastUserIds, instantUserIds, broadcastAllowList, null);
+            sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REMOVED_INTERNAL,
+                    removedPackage, extras, 0 /*flags*/, PLATFORM_PACKAGE_NAME,
+                    null /*finishedReceiver*/, broadcastUserIds, instantUserIds,
+                    broadcastAllowList, null /*bOptions*/);
+            if (dataRemoved && !isRemovedPackageSystemUpdate) {
+                sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_FULLY_REMOVED,
+                        removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null,
+                        null, broadcastUserIds, instantUserIds, broadcastAllowList, null);
+                packageSender.notifyPackageRemoved(removedPackage, removedUid);
+            }
+        }
+        if (removedAppId >= 0) {
+            // If a system app's updates are uninstalled the UID is not actually removed. Some
+            // services need to know the package name affected.
+            if (isReplace) {
+                extras.putString(Intent.EXTRA_PACKAGE_NAME, removedPackage);
+            }
+
+            sendPackageBroadcastAndNotify(Intent.ACTION_UID_REMOVED,
+                    null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
+                    null, null, broadcastUserIds, instantUserIds, broadcastAllowList, null);
+        }
+    }
+
+    /**
+     * Send broadcast intents for packages suspension changes.
+     *
+     * @param intent The action name of the suspension intent.
+     * @param pkgList The names of packages which have suspension changes.
+     * @param uidList The uids of packages which have suspension changes.
+     * @param userId The user where packages reside.
+     */
+    void sendPackagesSuspendedOrUnsuspendedForUser(@NonNull Computer snapshot,
+                                                   @NonNull String intent,
+                                                   @NonNull String[] pkgList,
+                                                   @NonNull int[] uidList,
+                                                   boolean quarantined,
+                                                   int userId) {
+        final Bundle extras = new Bundle(3);
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+        if (quarantined) {
+            extras.putBoolean(Intent.EXTRA_QUARANTINED, true);
+        }
+        final int flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND;
+        final Bundle options = new BroadcastOptions()
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+                .toBundle();
+        mHandler.post(() -> sendPackageBroadcast(intent, null /* pkg */,
+                extras, flags, null /* targetPkg */, null /* finishedReceiver */,
+                new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
+                (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+                        snapshot, callingUid, intentExtras),
+                options));
+        notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
+                null /* instantUserIds */, null /* broadcastAllowList */);
+    }
+
+    void sendMyPackageSuspendedOrUnsuspended(@NonNull Computer snapshot,
+                                             @NonNull String[] affectedPackages,
+                                             boolean suspended,
+                                             int userId) {
+        final String action = suspended
+                ? Intent.ACTION_MY_PACKAGE_SUSPENDED
+                : Intent.ACTION_MY_PACKAGE_UNSUSPENDED;
+        mHandler.post(() -> {
+            final IActivityManager am = ActivityManager.getService();
+            if (am == null) {
+                Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ "
+                        + (suspended ? "" : "UN") + "SUSPENDED broadcasts");
+                return;
+            }
+            final int[] targetUserIds = new int[] {userId};
+            for (String packageName : affectedPackages) {
+                final Bundle appExtras = suspended
+                        ? SuspendPackageHelper.getSuspendedPackageAppExtras(
+                                snapshot, packageName, userId, SYSTEM_UID)
+                        : null;
+                final Bundle intentExtras;
+                if (appExtras != null) {
+                    intentExtras = new Bundle(1);
+                    intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras);
+                } else {
+                    intentExtras = null;
+                }
+                doSendBroadcast(action, null, intentExtras,
+                        Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
+                        targetUserIds, false, null, null, null);
+            }
+        });
+    }
+
+    /**
+     * Send broadcast intents for packages distracting changes.
+     *
+     * @param pkgList The names of packages which have suspension changes.
+     * @param uidList The uids of packages which have suspension changes.
+     * @param userId The user where packages reside.
+     */
+    void sendDistractingPackagesChanged(@NonNull Computer snapshot,
+                                        @NonNull String[] pkgList,
+                                        @NonNull int[] uidList,
+                                        int userId,
+                                        int distractionFlags) {
+        final Bundle extras = new Bundle();
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+        extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
+
+        mHandler.post(() -> sendPackageBroadcast(
+                Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */,
+                extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+                null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+                null /* broadcastAllowList */,
+                (callingUid, intentExtras) -> filterExtrasChangedPackageList(
+                        snapshot, callingUid, intentExtras),
+                null /* bOptions */));
+    }
+
+    void sendResourcesChangedBroadcastAndNotify(@NonNull Computer snapshot,
+                                                boolean mediaStatus,
+                                                boolean replacing,
+                                                @NonNull ArrayList<AndroidPackage> packages) {
+        final int size = packages.size();
+        final String[] packageNames = new String[size];
+        final int[] packageUids = new int[size];
+        for (int i = 0; i < size; i++) {
+            final AndroidPackage pkg = packages.get(i);
+            packageNames[i] = pkg.getPackageName();
+            packageUids[i] = pkg.getUid();
+        }
+        sendResourcesChangedBroadcast(snapshot, mediaStatus,
+                replacing, packageNames, packageUids);
+        notifyResourcesChanged(mediaStatus, replacing, packageNames, packageUids);
+    }
+
+    private void notifyPackageMonitor(@NonNull String action,
+                                      @NonNull String pkg,
+                                      @Nullable Bundle extras,
+                                      @NonNull int[] userIds,
+                                      @NonNull int[] instantUserIds,
+                                      @Nullable SparseArray<int[]> broadcastAllowList) {
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds,
+                instantUserIds, broadcastAllowList, mHandler);
+    }
+
+    private void notifyResourcesChanged(boolean mediaStatus,
+                                boolean replacing,
+                                @NonNull String[] pkgNames,
+                                @NonNull int[] uids) {
+        mPackageMonitorCallbackHelper.notifyResourcesChanged(mediaStatus, replacing, pkgNames,
+                uids, mHandler);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 83f90a1..8e767e7 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -63,7 +63,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.ArchiveState;
 import com.android.server.pm.pkg.PackageStateInternal;
@@ -87,19 +86,16 @@
 
     private final PackageManagerService mPm;
     private final UserManagerInternal mUserManagerInternal;
-    private final PermissionManagerServiceInternal mPermissionManager;
     private final RemovePackageHelper mRemovePackageHelper;
+    private final BroadcastHelper mBroadcastHelper;
 
     // TODO(b/198166813): remove PMS dependency
-    DeletePackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper) {
+    DeletePackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper,
+                        BroadcastHelper broadcastHelper) {
         mPm = pm;
         mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
-        mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
         mRemovePackageHelper = removePackageHelper;
-    }
-
-    DeletePackageHelper(PackageManagerService pm) {
-        this(pm, new RemovePackageHelper(pm));
+        mBroadcastHelper = broadcastHelper;
     }
 
     /**
@@ -121,7 +117,7 @@
      */
     public int deletePackageX(String packageName, long versionCode, int userId, int deleteFlags,
             boolean removedBySystem) {
-        final PackageRemovedInfo info = new PackageRemovedInfo(mPm);
+        final PackageRemovedInfo info = new PackageRemovedInfo();
         final boolean res;
 
         final int removeUser = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0
@@ -251,8 +247,9 @@
         if (res) {
             final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
             final boolean isArchived = (deleteFlags & PackageManager.DELETE_ARCHIVE) != 0;
-            info.sendPackageRemovedBroadcasts(killApp, removedBySystem, isArchived);
-            info.sendSystemPackageUpdatedBroadcasts();
+            mBroadcastHelper.sendPackageRemovedBroadcasts(info, mPm, killApp,
+                    removedBySystem, isArchived);
+            mBroadcastHelper.sendSystemPackageUpdatedBroadcasts(info);
             PackageMetrics.onUninstallSucceeded(info, deleteFlags, removeUser);
         }
 
@@ -314,7 +311,7 @@
                             Slog.i(TAG, "Enabling system stub after removal; pkg: "
                                     + stubPkg.getPackageName());
                         }
-                        new InstallPackageHelper(mPm).enableCompressedPackage(stubPkg, stubPs);
+                        mPm.enableCompressedPackage(stubPkg, stubPs);
                     } else if (DEBUG_COMPRESSION) {
                         Slog.i(TAG, "System stub disabled for all users, leaving uncompressed "
                                 + "after removal; pkg: " + stubPkg.getPackageName());
@@ -491,8 +488,7 @@
             // When an updated system application is deleted we delete the existing resources
             // as well and fall back to existing code in system partition
             deleteInstalledSystemPackage(action, allUserHandles, writeSettings);
-            new InstallPackageHelper(mPm).restoreDisabledSystemPackageLIF(
-                    action, allUserHandles, writeSettings);
+            mPm.restoreDisabledSystemPackageLIF(action, allUserHandles, writeSettings);
         } else {
             if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.getPackageName());
             if (ps.isIncremental()) {
diff --git a/services/core/java/com/android/server/pm/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
index 8ebb6ea..c5ec73b 100644
--- a/services/core/java/com/android/server/pm/DistractingPackageHelper.java
+++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
@@ -19,10 +19,7 @@
 import static android.content.pm.PackageManager.RESTRICTION_NONE;
 
 import android.annotation.NonNull;
-import android.content.Intent;
 import android.content.pm.PackageManager.DistractionRestriction;
-import android.os.Bundle;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.IntArray;
@@ -42,17 +39,16 @@
 
     // TODO(b/198166813): remove PMS dependency
     private final PackageManagerService mPm;
-    private final PackageManagerServiceInjector mInjector;
     private final BroadcastHelper mBroadcastHelper;
     private final SuspendPackageHelper mSuspendPackageHelper;
 
     /**
      * Constructor for {@link PackageManagerService}.
      */
-    DistractingPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector,
-            BroadcastHelper broadcastHelper, SuspendPackageHelper suspendPackageHelper) {
+    DistractingPackageHelper(PackageManagerService pm,
+                             BroadcastHelper broadcastHelper,
+                             SuspendPackageHelper suspendPackageHelper) {
         mPm = pm;
-        mInjector = injector;
         mBroadcastHelper = broadcastHelper;
         mSuspendPackageHelper = suspendPackageHelper;
     }
@@ -127,8 +123,8 @@
         if (!changedPackagesList.isEmpty()) {
             final String[] changedPackages = changedPackagesList.toArray(
                     new String[changedPackagesList.size()]);
-            sendDistractingPackagesChanged(changedPackages, changedUids.toArray(), userId,
-                    restrictionFlags);
+            mBroadcastHelper.sendDistractingPackagesChanged(mPm.snapshotComputer(),
+                    changedPackages, changedUids.toArray(), userId, restrictionFlags);
             mPm.scheduleWritePackageRestrictions(userId);
         }
         return unactionedPackages.toArray(new String[0]);
@@ -202,34 +198,9 @@
         if (!changedPackages.isEmpty()) {
             final String[] packageArray = changedPackages.toArray(
                     new String[changedPackages.size()]);
-            sendDistractingPackagesChanged(packageArray, changedUids.toArray(), userId,
-                    RESTRICTION_NONE);
+            mBroadcastHelper.sendDistractingPackagesChanged(mPm.snapshotComputer(),
+                    packageArray, changedUids.toArray(), userId, RESTRICTION_NONE);
             mPm.scheduleWritePackageRestrictions(userId);
         }
     }
-
-    /**
-     * Send broadcast intents for packages distracting changes.
-     *
-     * @param pkgList The names of packages which have suspension changes.
-     * @param uidList The uids of packages which have suspension changes.
-     * @param userId The user where packages reside.
-     */
-    void sendDistractingPackagesChanged(@NonNull String[] pkgList, int[] uidList, int userId,
-            int distractionFlags) {
-        final Bundle extras = new Bundle();
-        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
-        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
-        extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
-
-        final Handler handler = mInjector.getHandler();
-        handler.post(() -> mBroadcastHelper.sendPackageBroadcast(
-                Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */,
-                extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
-                null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
-                null /* broadcastAllowList */,
-                (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
-                        mPm.snapshotComputer(), callingUid, intentExtras),
-                null /* bOptions */));
-    }
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index e1e5e6d..86cdc04 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -38,7 +38,6 @@
 import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
 import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
 import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
-import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.incremental.IncrementalManager.isIncrementalPath;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
@@ -50,7 +49,6 @@
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
 import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
 import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
-import static com.android.server.pm.PackageManagerService.DEBUG_BACKUP;
 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
@@ -130,7 +128,6 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Message;
@@ -143,9 +140,6 @@
 import android.os.UserManager;
 import android.os.incremental.IncrementalManager;
 import android.os.incremental.IncrementalStorage;
-import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
-import android.stats.storage.StorageEnums;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.text.TextUtils;
@@ -163,7 +157,6 @@
 import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.SystemConfig;
@@ -220,6 +213,7 @@
     private final AppDataHelper mAppDataHelper;
     private final BroadcastHelper mBroadcastHelper;
     private final RemovePackageHelper mRemovePackageHelper;
+    private final DeletePackageHelper mDeletePackageHelper;
     private final IncrementalManager mIncrementalManager;
     private final ApexManager mApexManager;
     private final DexManager mDexManager;
@@ -233,12 +227,17 @@
     private final UpdateOwnershipHelper mUpdateOwnershipHelper;
 
     // TODO(b/198166813): remove PMS dependency
-    InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) {
+    InstallPackageHelper(PackageManagerService pm,
+                         AppDataHelper appDataHelper,
+                         RemovePackageHelper removePackageHelper,
+                         DeletePackageHelper deletePackageHelper,
+                         BroadcastHelper broadcastHelper) {
         mPm = pm;
         mInjector = pm.mInjector;
         mAppDataHelper = appDataHelper;
-        mBroadcastHelper = new BroadcastHelper(pm.mInjector);
-        mRemovePackageHelper = new RemovePackageHelper(pm);
+        mBroadcastHelper = broadcastHelper;
+        mRemovePackageHelper = removePackageHelper;
+        mDeletePackageHelper = deletePackageHelper;
         mIncrementalManager = pm.mInjector.getIncrementalManager();
         mApexManager = pm.mInjector.getApexManager();
         mDexManager = pm.mInjector.getDexManager();
@@ -251,10 +250,6 @@
         mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
     }
 
-    InstallPackageHelper(PackageManagerService pm) {
-        this(pm, new AppDataHelper(pm));
-    }
-
     /**
      * Commits the package scan and modifies system state.
      * <p><em>WARNING:</em> The method may throw an exception in the middle
@@ -263,7 +258,7 @@
      * possible and the system is not left in an inconsistent state.
      */
     @GuardedBy("mPm.mLock")
-    public AndroidPackage commitReconciledScanResultLocked(
+    private AndroidPackage commitReconciledScanResultLocked(
             @NonNull ReconciledPackage reconciledPkg, int[] allUsers) {
         final InstallRequest request = reconciledPkg.mInstallRequest;
         // TODO(b/135203078): Move this even further away
@@ -731,8 +726,9 @@
                 }
                 // TODO(b/278553670) Store archive state for the user.
                 boolean isArchived = (pkgSetting.getPkg() == null);
-                mPm.sendPackageAddedForUser(mPm.snapshotComputer(), packageName, pkgSetting, userId,
-                        isArchived, DataLoaderType.NONE);
+                mBroadcastHelper.sendPackageAddedForUser(mPm.snapshotComputer(), packageName,
+                        pkgSetting, userId, isArchived, DataLoaderType.NONE,
+                        mPm.mAppPredictionServicePackage);
                 synchronized (mPm.mLock) {
                     mPm.updateSequenceNumberLP(pkgSetting, new int[]{ userId });
                 }
@@ -1745,7 +1741,7 @@
                 }
 
                 // Update what is removed
-                PackageRemovedInfo removedInfo = new PackageRemovedInfo(mPm);
+                PackageRemovedInfo removedInfo = new PackageRemovedInfo();
                 removedInfo.mUid = ps.getAppId();
                 removedInfo.mRemovedPackage = ps.getPackageName();
                 removedInfo.mInstallerPackageName =
@@ -2074,8 +2070,6 @@
             final InstallRequest installRequest = reconciledPkg.mInstallRequest;
             final ParsedPackage parsedPackage = installRequest.getParsedPackage();
             final String packageName = parsedPackage.getPackageName();
-            final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
-            final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
 
             installRequest.onCommitStarted();
             if (installRequest.isInstallReplace()) {
@@ -2097,7 +2091,7 @@
                                 allUsers, mPm.mSettings.getPackagesLocked());
                 if (installRequest.isInstallSystem()) {
                     // Remove existing system package
-                    removePackageHelper.removePackage(oldPackage, true);
+                    mRemovePackageHelper.removePackage(oldPackage, true);
                     if (!disableSystemPackageLPw(oldPackage)) {
                         // We didn't need to disable the .apk as a current system package,
                         // which means we are replacing another update that is already
@@ -2113,7 +2107,7 @@
                 } else {
                     try {
                         // Settings will be written during the call to updateSettingsLI().
-                        deletePackageHelper.executeDeletePackage(
+                        mDeletePackageHelper.executeDeletePackage(
                                 reconciledPkg.mDeletePackageAction, packageName,
                                 true, allUsers, false);
                     } catch (SystemDeleteException e) {
@@ -2796,24 +2790,21 @@
         final Computer snapshot = mPm.snapshotComputer();
         // Send broadcasts
         for (int i = 0; i < numBroadcasts; i++) {
-            mPm.sendPackageChangedBroadcast(snapshot, packages[i], true /* dontKillApp */,
-                    components[i], uids[i], null /* reason */);
+            mBroadcastHelper.sendPackageChangedBroadcast(snapshot, packages[i],
+                    true /* dontKillApp */, components[i], uids[i], null /* reason */);
         }
     }
 
     void handlePackagePostInstall(InstallRequest request, boolean launchedForRestore) {
         final boolean killApp =
                 (request.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) == 0;
-        final boolean virtualPreload =
-                ((request.getInstallFlags() & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
-        final String installerPackage = request.getInstallerPackageName();
-        final int dataLoaderType = request.getDataLoaderType();
         final boolean succeeded = request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED;
         final boolean update = request.isUpdate();
         final boolean archived = request.isArchived();
         final String packageName = request.getName();
+        final Computer snapshot = mPm.snapshotComputer();
         final PackageStateInternal pkgSetting =
-                succeeded ? mPm.snapshotComputer().getPackageStateInternal(packageName) : null;
+                succeeded ? snapshot.getPackageStateInternal(packageName) : null;
         final boolean removedBeforeUpdate = (pkgSetting == null)
                 || (pkgSetting.isSystem() && !pkgSetting.getPath().getPath().equals(
                 request.getPkg().getPath()));
@@ -2834,208 +2825,22 @@
             // Clear the uid cache after we installed a new package.
             mPm.mPerUidReadTimeoutsCache = null;
 
-            // Send the removed broadcasts
-            if (request.getRemovedInfo() != null) {
-                if (request.getRemovedInfo().mIsExternal) {
-                    if (DEBUG_INSTALL) {
-                        Slog.i(TAG, "upgrading pkg " + request.getRemovedInfo().mRemovedPackage
-                                + " is ASEC-hosted -> UNAVAILABLE");
-                    }
-                    final String[] pkgNames = new String[]{
-                            request.getRemovedInfo().mRemovedPackage};
-                    final int[] uids = new int[]{request.getRemovedInfo().mUid};
-                    mPm.notifyResourcesChanged(false /* mediaStatus */,
-                            true /* replacing */, pkgNames, uids);
-                    mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
-                            false /* mediaStatus */, true /* replacing */, pkgNames, uids);
-                }
-                request.getRemovedInfo().sendPackageRemovedBroadcasts(
-                        killApp, false /*removedBySystem*/, false /*isArchived*/);
-            }
-
-            final String installerPackageName =
-                    request.getInstallerPackageName() != null
-                            ? request.getInstallerPackageName()
-                            : request.getRemovedInfo() != null
-                                    ? request.getRemovedInfo().mInstallerPackageName
-                                    : null;
-
             mPm.notifyInstantAppPackageInstalled(request.getPkg().getPackageName(),
                     request.getNewUsers());
 
             request.populateBroadcastUsers();
             final int[] firstUserIds = request.getFirstTimeBroadcastUserIds();
-            final int[] firstInstantUserIds = request.getFirstTimeBroadcastInstantUserIds();
-            final int[] updateUserIds = request.getUpdateBroadcastUserIds();
-            final int[] instantUserIds = request.getUpdateBroadcastInstantUserIds();
 
-            Bundle extras = new Bundle();
-            extras.putInt(Intent.EXTRA_UID, request.getAppId());
-            if (update) {
-                extras.putBoolean(Intent.EXTRA_REPLACING, true);
-            }
-            if (archived) {
-                extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
-            }
-            extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
-
-            // If a package is a static shared library, then only the installer of the package
-            // should get the broadcast.
-            if (installerPackageName != null
-                    && request.getPkg().getStaticSharedLibraryName() != null) {
-                mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                        extras, 0 /*flags*/,
-                        installerPackageName, null /*finishedReceiver*/,
-                        request.getNewUsers(), null /* instantUserIds*/,
-                        null /* broadcastAllowList */, null);
-            }
-
-            // Send installed broadcasts if the package is not a static shared lib.
             if (request.getPkg().getStaticSharedLibraryName() == null) {
                 mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath());
-
-                // Send PACKAGE_ADDED broadcast for users that see the package for the first time
-                // sendPackageAddedForNewUsers also deals with system apps
-                int appId = UserHandle.getAppId(request.getAppId());
-                boolean isSystem = request.isInstallSystem();
-                mPm.sendPackageAddedForNewUsers(mPm.snapshotComputer(), packageName,
-                        isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId,
-                        firstUserIds, firstInstantUserIds, archived, dataLoaderType);
-
-                // Send PACKAGE_ADDED broadcast for users that don't see
-                // the package for the first time
-
-                // Send to all running apps.
-                final SparseArray<int[]> newBroadcastAllowList;
-                synchronized (mPm.mLock) {
-                    final Computer snapshot = mPm.snapshotComputer();
-                    newBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(snapshot,
-                            snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
-                            updateUserIds, mPm.mSettings.getPackagesLocked());
-                }
-                mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                        extras, 0 /*flags*/,
-                        null /*targetPackage*/, null /*finishedReceiver*/,
-                        updateUserIds, instantUserIds, newBroadcastAllowList, null);
-                // Send to the installer, even if it's not running.
-                if (installerPackageName != null) {
-                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                            extras, 0 /*flags*/,
-                            installerPackageName, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
-                }
-                // Send to PermissionController for all update users, even if it may not be running
-                // for some users
-                if (BroadcastHelper.isPrivacySafetyLabelChangeNotificationsEnabled(mContext)) {
-                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                            extras, 0 /*flags*/,
-                            mPm.mRequiredPermissionControllerPackage, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
-                }
-                // Notify required verifier(s) that are not the installer of record for the package.
-                for (String verifierPackageName : mPm.mRequiredVerifierPackages) {
-                    if (verifierPackageName != null && !verifierPackageName.equals(
-                            installerPackageName)) {
-                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                                extras, 0 /*flags*/,
-                                verifierPackageName, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /* broadcastAllowList */,
-                                null);
-                    }
-                }
-                // If package installer is defined, notify package installer about new
-                // app installed
-                mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                        extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/,
-                        mPm.mRequiredInstallerPackage, null /*finishedReceiver*/,
-                        firstUserIds, instantUserIds, null /* broadcastAllowList */, null);
-
-                // Send replaced for users that don't see the package for the first time
-                if (update) {
-                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                            packageName, extras, 0 /*flags*/,
-                            null /*targetPackage*/, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds,
-                            request.getRemovedInfo().mBroadcastAllowList, null);
-                    if (installerPackageName != null) {
-                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
-                                extras, 0 /*flags*/,
-                                installerPackageName, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /*broadcastAllowList*/,
-                                null);
-                    }
-                    for (String verifierPackageName : mPm.mRequiredVerifierPackages) {
-                        if (verifierPackageName != null && !verifierPackageName.equals(
-                                installerPackageName)) {
-                            mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                                    packageName, extras, 0 /*flags*/, verifierPackageName,
-                                    null /*finishedReceiver*/, updateUserIds, instantUserIds,
-                                    null /*broadcastAllowList*/, null);
-                        }
-                    }
-                    mPm.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
-                            null /*package*/, null /*extras*/, 0 /*flags*/,
-                            packageName /*targetPackage*/,
-                            null /*finishedReceiver*/, updateUserIds, instantUserIds,
-                            null /*broadcastAllowList*/,
-                            mBroadcastHelper.getTemporaryAppAllowlistBroadcastOptions(
-                                    REASON_PACKAGE_REPLACED).toBundle());
-                } else if (launchedForRestore && !request.isInstallSystem()) {
-                    // First-install and we did a restore, so we're responsible for the
-                    // first-launch broadcast.
-                    if (DEBUG_BACKUP) {
-                        Slog.i(TAG, "Post-restore of " + packageName
-                                + " sending FIRST_LAUNCH in " + Arrays.toString(firstUserIds));
-                    }
-                    mBroadcastHelper.sendFirstLaunchBroadcast(packageName, installerPackage,
-                            firstUserIds, firstInstantUserIds);
-                }
-
-                // Send broadcast package appeared if external for all users
-                if (request.getPkg().isExternalStorage()) {
-                    if (!update) {
-                        final StorageManager storageManager =
-                                mInjector.getSystemService(StorageManager.class);
-                        VolumeInfo volume =
-                                storageManager.findVolumeByUuid(
-                                        StorageManager.convert(
-                                                request.getPkg().getVolumeUuid()).toString());
-                        int packageExternalStorageType =
-                                PackageManagerServiceUtils.getPackageExternalStorageType(volume,
-                                        request.getPkg().isExternalStorage());
-                        // If the package was installed externally, log it.
-                        if (packageExternalStorageType != StorageEnums.UNKNOWN) {
-                            FrameworkStatsLog.write(
-                                    FrameworkStatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED,
-                                    packageExternalStorageType, packageName);
-                        }
-                    }
-                    if (DEBUG_INSTALL) {
-                        Slog.i(TAG, "upgrading pkg " + request.getPkg() + " is external");
-                    }
-                    if (!archived) {
-                        final String[] pkgNames = new String[]{packageName};
-                        final int[] uids = new int[]{request.getPkg().getUid()};
-                        mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
-                                true /* mediaStatus */, true /* replacing */, pkgNames, uids);
-                        mPm.notifyResourcesChanged(true /* mediaStatus */, true /* replacing */,
-                                pkgNames, uids);
-                    }
-                }
-            } else if (!ArrayUtils.isEmpty(request.getLibraryConsumers())) { // if static shared lib
-                // No need to kill consumers if it's installation of new version static shared lib.
-                final Computer snapshot = mPm.snapshotComputer();
-                final boolean dontKillApp = !update
-                        && request.getPkg().getStaticSharedLibraryName() != null;
-                for (int i = 0; i < request.getLibraryConsumers().size(); i++) {
-                    AndroidPackage pkg = request.getLibraryConsumers().get(i);
-                    // send broadcast that all consumers of the static shared library have changed
-                    mPm.sendPackageChangedBroadcast(snapshot, pkg.getPackageName(), dontKillApp,
-                            new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
-                            pkg.getUid(), null);
-                }
             }
 
+            mBroadcastHelper.sendPostInstallBroadcasts(mPm.snapshotComputer(), request, packageName,
+                    mPm.mRequiredPermissionControllerPackage, mPm.mRequiredVerifierPackages,
+                    mPm.mRequiredInstallerPackage,
+                    /* packageSender= */ mPm, launchedForRestore, killApp, update, archived);
+
+
             // Work that needs to happen on first install within each user
             if (firstUserIds.length > 0) {
                 for (int userId : firstUserIds) {
@@ -3074,7 +2879,6 @@
             }
 
             if (!archived) {
-                final Computer snapshot = mPm.snapshotComputer();
                 // Notify DexManager that the package was installed for new users.
                 // The updated users should already be indexed and the package code paths
                 // should not change.
@@ -3090,7 +2894,7 @@
                 }
             } else {
                 // Now send PACKAGE_REMOVED + EXTRA_REPLACING broadcast.
-                final PackageRemovedInfo info = new PackageRemovedInfo(mPm);
+                final PackageRemovedInfo info = new PackageRemovedInfo();
                 info.mRemovedPackage = packageName;
                 info.mInstallerPackageName = request.getInstallerPackageName();
                 info.mRemovedUsers = firstUserIds;
@@ -3099,8 +2903,8 @@
                 info.mRemovedPackageVersionCode = request.getPkg().getLongVersionCode();
                 info.mRemovedForAllUsers = true;
 
-                info.sendPackageRemovedBroadcasts(false /*killApp*/,
-                        false /*removedBySystem*/, true /*isArchived*/);
+                mBroadcastHelper.sendPackageRemovedBroadcasts(info, mPm,
+                        false /*killApp*/, false /*removedBySystem*/, true /*isArchived*/);
             }
         }
 
@@ -3291,15 +3095,14 @@
         synchronized (mPm.mLock) {
             mPm.mSettings.disableSystemPackageLPw(stubPkg.getPackageName(), true /*replaced*/);
         }
-        final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
-        removePackageHelper.removePackage(stubPkg, true /*chatty*/);
+        mRemovePackageHelper.removePackage(stubPkg, true /*chatty*/);
         try {
             return initPackageTracedLI(scanFile, parseFlags, scanFlags);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
                     e);
             // Remove the failed install
-            removePackageHelper.removeCodePath(scanFile);
+            mRemovePackageHelper.removeCodePath(scanFile);
             throw e;
         }
     }
@@ -3342,7 +3145,7 @@
             if (!dstCodePath.exists()) {
                 return null;
             }
-            new RemovePackageHelper(mPm).removeCodePath(dstCodePath);
+            mRemovePackageHelper.removeCodePath(dstCodePath);
             return null;
         }
 
@@ -4298,8 +4101,8 @@
                         parsedPackage.getPackageName(), UserHandle.USER_ALL,
                         "scanPackageInternalLI", ApplicationExitInfo.REASON_OTHER,
                         null /* request */)) {
-                    DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
-                    deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true,
+                    mDeletePackageHelper.deletePackageLIF(
+                            parsedPackage.getPackageName(), null, true,
                             mPm.mUserManager.getUserIds(), 0, null, false);
                 }
             } else if (newPkgVersionGreater || newSharedUserSetting) {
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index fe6a8a1..ca8dc29 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -94,8 +94,6 @@
     private final UserHandle mUser;
     @NonNull
     final PackageManagerService mPm;
-    final InstallPackageHelper mInstallPackageHelper;
-    final RemovePackageHelper mRemovePackageHelper;
     final boolean mIsInherit;
     final int mSessionId;
     final int mRequireUserAction;
@@ -108,8 +106,6 @@
             PackageLite packageLite, PackageManagerService pm) {
         mPm = pm;
         mUser = user;
-        mInstallPackageHelper = new InstallPackageHelper(mPm);
-        mRemovePackageHelper = new RemovePackageHelper(mPm);
         mOriginInfo = originInfo;
         mMoveInfo = moveInfo;
         mObserver = observer;
@@ -142,8 +138,6 @@
             PackageLite packageLite, PackageManagerService pm) {
         mPm = pm;
         mUser = user;
-        mInstallPackageHelper = new InstallPackageHelper(mPm);
-        mRemovePackageHelper = new RemovePackageHelper(mPm);
         mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
         mMoveInfo = null;
         mInstallReason = fixUpInstallReason(
@@ -242,7 +236,7 @@
         // state can change within this delay and hence we need to re-verify certain conditions.
         boolean isStaged = (mInstallFlags & INSTALL_STAGED) != 0;
         if (isStaged) {
-            Pair<Integer, String> ret = mInstallPackageHelper.verifyReplacingVersionCode(
+            Pair<Integer, String> ret = mPm.verifyReplacingVersionCode(
                     pkgLite, mRequiredInstalledVersionCode, mInstallFlags);
             mRet = ret.first;
             if (mRet != INSTALL_SUCCEEDED) {
@@ -540,39 +534,39 @@
                 }
             }
         } else {
-            mInstallPackageHelper.installPackagesTraced(installRequests);
+            mPm.installPackagesTraced(installRequests);
 
             for (InstallRequest request : installRequests) {
                 doPostInstall(request);
             }
         }
         for (InstallRequest request : installRequests) {
-            mInstallPackageHelper.restoreAndPostInstall(request);
+            mPm.restoreAndPostInstall(request);
         }
     }
 
     private void doPostInstall(InstallRequest request) {
         if (mMoveInfo != null) {
             if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
-                mRemovePackageHelper.cleanUpForMoveInstall(mMoveInfo.mFromUuid,
+                mPm.cleanUpForMoveInstall(mMoveInfo.mFromUuid,
                         mMoveInfo.mPackageName, mMoveInfo.mFromCodePath);
             } else {
-                mRemovePackageHelper.cleanUpForMoveInstall(mMoveInfo.mToUuid,
+                mPm.cleanUpForMoveInstall(mMoveInfo.mToUuid,
                         mMoveInfo.mPackageName, mMoveInfo.mFromCodePath);
             }
         } else {
             if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
-                mRemovePackageHelper.removeCodePath(request.getCodeFile());
+                mPm.removeCodePath(request.getCodeFile());
             }
         }
     }
 
     private void cleanUpForFailedInstall(InstallRequest request) {
         if (request.isInstallMove()) {
-            mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(),
+            mPm.cleanUpForMoveInstall(request.getMoveToUuid(),
                     request.getMovePackageName(), request.getMoveFromCodePath());
         } else {
-            mRemovePackageHelper.removeCodePath(request.getCodeFile());
+            mPm.removeCodePath(request.getCodeFile());
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index b4ca477..4ecbd15 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -60,14 +60,10 @@
  */
 final class PackageHandler extends Handler {
     private final PackageManagerService mPm;
-    private final InstallPackageHelper mInstallPackageHelper;
-    private final RemovePackageHelper mRemovePackageHelper;
 
     PackageHandler(Looper looper, PackageManagerService pm) {
         super(looper);
         mPm = pm;
-        mInstallPackageHelper = new InstallPackageHelper(mPm);
-        mRemovePackageHelper = new RemovePackageHelper(mPm);
     }
 
     @Override
@@ -82,7 +78,7 @@
     void doHandleMessage(Message msg) {
         switch (msg.what) {
             case SEND_PENDING_BROADCAST: {
-                mInstallPackageHelper.sendPendingBroadcasts();
+                mPm.sendPendingBroadcasts();
                 break;
             }
             case POST_INSTALL: {
@@ -96,7 +92,7 @@
                 request.onInstallCompleted();
                 request.runPostInstallRunnable();
                 if (!request.isInstallExistingForUser()) {
-                    mInstallPackageHelper.handlePackagePostInstall(request, didRestore);
+                    mPm.handlePackagePostInstall(request, didRestore);
                 } else if (DEBUG_INSTALL) {
                     // No post-install when we run restore from installExistingPackageForUser
                     Slog.i(TAG, "Nothing to do for post-install token " + msg.arg1);
@@ -107,7 +103,7 @@
             case DEFERRED_NO_KILL_POST_DELETE: {
                 InstallArgs args = (InstallArgs) msg.obj;
                 if (args != null) {
-                    mRemovePackageHelper.cleanUpResources(args.mCodeFile, args.mInstructionSets);
+                    mPm.cleanUpResources(args.mCodeFile, args.mInstructionSets);
                 }
             } break;
             case DEFERRED_NO_KILL_INSTALL_OBSERVER:
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 95b565d..1bb20b47 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -18,7 +18,9 @@
 
 import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
 import static android.os.Process.INVALID_UID;
+
 import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
+
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
@@ -407,11 +409,10 @@
     }
 
     private void removeStagingDirs(ArraySet<File> stagingDirsToRemove) {
-        final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
         // Clean up orphaned staging directories
         for (File stage : stagingDirsToRemove) {
             Slog.w(TAG, "Deleting orphan stage " + stage);
-            removePackageHelper.removeCodePath(stage);
+            mPm.removeCodePath(stage);
         }
     }
 
@@ -1320,9 +1321,8 @@
     @Override
     public void installExistingPackage(String packageName, int installFlags, int installReason,
             IntentSender statusReceiver, int userId, List<String> allowListedPermissions) {
-        final InstallPackageHelper installPackageHelper = new InstallPackageHelper(mPm);
 
-        var result = installPackageHelper.installExistingPackageAsUser(packageName, userId,
+        var result = mPm.installExistingPackageAsUser(packageName, userId,
                 installFlags, installReason, allowListedPermissions, statusReceiver);
 
         int returnCode = result.first;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 54a2e3ad..d0e5f96 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2449,14 +2449,13 @@
     }
 
     private void onSystemDataLoaderUnrecoverable() {
-        final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
         final String packageName = getPackageName();
         if (TextUtils.isEmpty(packageName)) {
             // The package has not been installed.
             return;
         }
         mHandler.post(() -> {
-            if (deletePackageHelper.deletePackageX(packageName,
+            if (mPm.deletePackageX(packageName,
                     PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
                     PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/)
                     != PackageManager.DELETE_SUCCEEDED) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 33cb85c..ddc8369 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -70,7 +70,6 @@
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
@@ -98,6 +97,7 @@
 import android.content.pm.InstantAppRequest;
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ComponentEnabledSetting;
@@ -115,6 +115,7 @@
 import android.content.pm.UserInfo;
 import android.content.pm.UserPackage;
 import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VerifierInfo;
 import android.content.pm.VersionedPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.content.pm.parsing.PackageLite;
@@ -985,7 +986,7 @@
     private final DeletePackageHelper mDeletePackageHelper;
     private final InitAppsHelper mInitAppsHelper;
     private final AppDataHelper mAppDataHelper;
-    private final InstallPackageHelper mInstallPackageHelper;
+    @NonNull private final InstallPackageHelper mInstallPackageHelper;
     private final PreferredActivityHelper mPreferredActivityHelper;
     private final ResolveIntentHelper mResolveIntentHelper;
     private final DexOptHelper mDexOptHelper;
@@ -1715,7 +1716,8 @@
                 (i, pm) -> new CrossProfileIntentFilterHelper(i.getSettings(),
                         i.getUserManagerService(), i.getLock(), i.getUserManagerInternal(),
                         context),
-                (i, pm) -> new UpdateOwnershipHelper());
+                (i, pm) -> new UpdateOwnershipHelper(),
+                (i, pm) -> new PackageMonitorCallbackHelper());
 
         if (Build.VERSION.SDK_INT <= 0) {
             Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -2067,17 +2069,19 @@
         mDomainVerificationManager.setConnection(mDomainVerificationConnection);
 
         mBroadcastHelper = new BroadcastHelper(mInjector);
-        mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper(mInjector);
+        mPackageMonitorCallbackHelper = injector.getPackageMonitorCallbackHelper();
         mAppDataHelper = new AppDataHelper(this);
-        mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
-        mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
-        mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper);
+        mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper, mBroadcastHelper);
+        mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
+                mBroadcastHelper);
+        mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper, mRemovePackageHelper,
+                mDeletePackageHelper, mBroadcastHelper);
 
         mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
                 mInjector.getUserManagerInternal(), mDeletePackageHelper);
 
         mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
-        mPreferredActivityHelper = new PreferredActivityHelper(this);
+        mPreferredActivityHelper = new PreferredActivityHelper(this, mBroadcastHelper);
         mResolveIntentHelper = new ResolveIntentHelper(mContext, mPreferredActivityHelper,
                 injector.getCompatibility(), mUserManager, mDomainVerificationManager,
                 mUserNeedsBadging, () -> mResolveInfo, () -> mInstantAppInstallerActivity,
@@ -2085,7 +2089,7 @@
         mDexOptHelper = new DexOptHelper(this);
         mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mUserManager,
                 mBroadcastHelper, mProtectedPackages);
-        mDistractingPackageHelper = new DistractingPackageHelper(this, mInjector, mBroadcastHelper,
+        mDistractingPackageHelper = new DistractingPackageHelper(this, mBroadcastHelper,
                 mSuspendPackageHelper);
         mStorageEventHelper = new StorageEventHelper(this, mDeletePackageHelper,
                 mRemovePackageHelper);
@@ -3078,38 +3082,6 @@
     }
 
     @Override
-    public void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
-            final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
-            final int[] userIds, int[] instantUserIds,
-            @Nullable SparseArray<int[]> broadcastAllowList,
-            @Nullable Bundle bOptions) {
-        mHandler.post(() -> mBroadcastHelper.sendPackageBroadcast(action, pkg, extras, flags,
-                targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList,
-                null /* filterExtrasForReceiver */, bOptions));
-        if (targetPkg == null) {
-            // For some broadcast action, e.g. ACTION_PACKAGE_ADDED, this method will be called
-            // many times to different targets, e.g. installer app, permission controller, other
-            // registered apps. We should filter it to avoid calling back many times for the same
-            // action. When the targetPkg is set, it sends the broadcast to specific app, e.g.
-            // installer app or null for registered apps. The callback only need to send back to the
-            // registered apps so we check the null condition here.
-            notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList);
-        }
-    }
-
-    void notifyPackageMonitor(String action, String pkg, Bundle extras, int[] userIds,
-            int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
-        mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds,
-                instantUserIds, broadcastAllowList);
-    }
-
-    void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
-            @NonNull String[] pkgNames, @NonNull int[] uids) {
-        mPackageMonitorCallbackHelper.notifyResourcesChanged(mediaStatus, replacing, pkgNames,
-                uids);
-    }
-
-    @Override
     public void notifyPackageAdded(String packageName, int uid) {
         mPackageObserverHelper.notifyAdded(packageName, uid);
     }
@@ -3125,64 +3097,6 @@
         UserPackage.removeFromCache(UserHandle.getUserId(uid), packageName);
     }
 
-    void sendPackageAddedForUser(@NonNull Computer snapshot, String packageName,
-            @NonNull PackageStateInternal packageState, int userId, boolean isArchived,
-            int dataLoaderType) {
-        final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
-        final boolean isSystem = packageState.isSystem();
-        final boolean isInstantApp = userState.isInstantApp();
-        final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
-        final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
-        sendPackageAddedForNewUsers(snapshot, packageName, isSystem /*sendBootCompleted*/,
-                false /*startReceiver*/, packageState.getAppId(), userIds, instantUserIds,
-                isArchived, dataLoaderType);
-
-        // Send a session commit broadcast
-        final PackageInstaller.SessionInfo info = new PackageInstaller.SessionInfo();
-        info.installReason = userState.getInstallReason();
-        info.appPackageName = packageName;
-        sendSessionCommitBroadcast(info, userId);
-    }
-
-    @Override
-    public void sendPackageAddedForNewUsers(@NonNull Computer snapshot, String packageName,
-            boolean sendBootCompleted, boolean includeStopped, @AppIdInt int appId, int[] userIds,
-            int[] instantUserIds, boolean isArchived, int dataLoaderType) {
-        if (ArrayUtils.isEmpty(userIds) && ArrayUtils.isEmpty(instantUserIds)) {
-            return;
-        }
-        SparseArray<int[]> broadcastAllowList = mAppsFilter.getVisibilityAllowList(snapshot,
-                snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
-                userIds, snapshot.getPackageStates());
-        mHandler.post(
-                () -> mBroadcastHelper.sendPackageAddedForNewUsers(packageName, appId, userIds,
-                        instantUserIds, isArchived, dataLoaderType, broadcastAllowList));
-        mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(packageName, appId, userIds,
-                instantUserIds, isArchived, dataLoaderType, broadcastAllowList);
-        if (sendBootCompleted && !ArrayUtils.isEmpty(userIds)) {
-            mHandler.post(() -> {
-                        for (int userId : userIds) {
-                            mBroadcastHelper.sendBootCompletedBroadcastToSystemApp(
-                                    packageName, includeStopped, userId);
-                        }
-                    }
-            );
-        }
-    }
-
-    private void sendApplicationHiddenForUser(String packageName, PackageStateInternal packageState,
-            int userId) {
-        final PackageRemovedInfo info = new PackageRemovedInfo(this);
-        info.mRemovedPackage = packageName;
-        info.mInstallerPackageName = packageState.getInstallSource().mInstallerPackageName;
-        info.mRemovedUsers = new int[] {userId};
-        info.mBroadcastUsers = new int[] {userId};
-        info.mUid = UserHandle.getUid(userId, packageState.getAppId());
-        info.mRemovedPackageVersionCode = packageState.getVersionCode();
-        info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/,
-                false /*isArchived*/);
-    }
-
     boolean isUserRestricted(int userId, String restrictionKey) {
         Bundle restrictions = mUserManager.getUserRestrictions(userId);
         if (restrictions.getBoolean(restrictionKey, false)) {
@@ -3505,11 +3419,6 @@
         }
     }
 
-    void postPreferredActivityChangedBroadcast(int userId) {
-        mHandler.post(() -> mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId));
-    }
-
-
     /** This method takes a specific user id as well as UserHandle.USER_ALL. */
     @GuardedBy("mLock")
     void clearPackagePreferredActivitiesLPw(String packageName,
@@ -3609,17 +3518,8 @@
     }
 
     public void sendSessionCommitBroadcast(PackageInstaller.SessionInfo sessionInfo, int userId) {
-        UserManagerService ums = UserManagerService.getInstance();
-        if (ums == null || sessionInfo.isStaged()) {
-            return;
-        }
-        final UserInfo parent = ums.getProfileParent(userId);
-        final int launcherUid = (parent != null) ? parent.id : userId;
-        // TODO: Should this snapshot be moved further up?
-        final ComponentName launcherComponent = snapshotComputer()
-                .getDefaultHomeActivity(launcherUid);
-        mBroadcastHelper.sendSessionCommitBroadcast(sessionInfo, userId, launcherUid,
-                launcherComponent, mAppPredictionServicePackage);
+        mBroadcastHelper.sendSessionCommitBroadcast(snapshotComputer(), sessionInfo, userId,
+                mAppPredictionServicePackage);
     }
 
     private @Nullable String getSetupWizardPackageNameImpl(@NonNull Computer computer) {
@@ -3988,7 +3888,7 @@
             if (isSystemStub
                     && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                     || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
-                if (!mInstallPackageHelper.enableCompressedPackage(deletedPkg, pkgSetting)) {
+                if (!enableCompressedPackage(deletedPkg, pkgSetting)) {
                     Slog.w(TAG, "Failed setApplicationEnabledSetting: failed to enable "
                             + "commpressed package " + setting.getPackageName());
                     updateAllowed[i] = false;
@@ -4070,8 +3970,8 @@
                 final ArrayList<String> components = sendNowBroadcasts.valueAt(i);
                 final int packageUid = UserHandle.getUid(
                         userId, pkgSettings.get(packageName).getAppId());
-                sendPackageChangedBroadcast(newSnapshot, packageName, false /* dontKillApp */,
-                        components, packageUid, null /* reason */);
+                mBroadcastHelper.sendPackageChangedBroadcast(newSnapshot, packageName,
+                        false /* dontKillApp */, components, packageUid, null /* reason */);
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
@@ -4147,27 +4047,6 @@
         }
     }
 
-    void sendPackageChangedBroadcast(@NonNull Computer snapshot, String packageName,
-            boolean dontKillApp, ArrayList<String> componentNames, int packageUid, String reason) {
-        PackageStateInternal setting = snapshot.getPackageStateInternal(packageName,
-                Process.SYSTEM_UID);
-        if (setting == null) {
-            return;
-        }
-        final int userId = UserHandle.getUserId(packageUid);
-        final boolean isInstantApp =
-                snapshot.isInstantAppInternal(packageName, userId, Process.SYSTEM_UID);
-        final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
-        final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
-        final SparseArray<int[]> broadcastAllowList =
-                isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds);
-        mHandler.post(() -> mBroadcastHelper.sendPackageChangedBroadcast(
-                packageName, dontKillApp, componentNames, packageUid, reason, userIds,
-                instantUserIds, broadcastAllowList));
-        mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
-                packageUid, reason, userIds, instantUserIds, broadcastAllowList);
-    }
-
     /**
      * Used by SystemServer
      */
@@ -4312,7 +4191,7 @@
                 if (pkg == null) {
                     return;
                 }
-                sendPackageChangedBroadcast(snapshot, pkg.getPackageName(),
+                mBroadcastHelper.sendPackageChangedBroadcast(snapshot, pkg.getPackageName(),
                         true /* dontKillApp */,
                         new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
                         pkg.getUid(),
@@ -5294,7 +5173,7 @@
                 throw new SecurityException("Calling package " + packageName
                         + " does not belong to calling uid " + callingUid);
             }
-            return mSuspendPackageHelper
+            return SuspendPackageHelper
                     .getSuspendedPackageAppExtras(snapshot, packageName, userId, callingUid);
         }
 
@@ -5857,10 +5736,14 @@
                 if (hidden) {
                     killApplication(packageName, newPackageState.getAppId(), userId, "hiding pkg",
                             ApplicationExitInfo.REASON_OTHER);
-                    sendApplicationHiddenForUser(packageName, newPackageState, userId);
+                    mBroadcastHelper.sendApplicationHiddenForUser(
+                            packageName, newPackageState, userId,
+                            /* packageSender= */ PackageManagerService.this);
                 } else {
-                    sendPackageAddedForUser(newSnapshot, packageName, newPackageState, userId,
-                            false /* isArchived */, DataLoaderType.NONE);
+                    mBroadcastHelper.sendPackageAddedForUser(
+                            newSnapshot, packageName, newPackageState, userId,
+                            false /* isArchived */, DataLoaderType.NONE,
+                            mAppPredictionServicePackage);
                 }
 
                 scheduleWritePackageRestrictions(userId);
@@ -7929,4 +7812,75 @@
             }
         }
     }
+
+    void removeCodePath(@Nullable File codePath) {
+        mRemovePackageHelper.removeCodePath(codePath);
+    }
+
+    void cleanUpResources(@Nullable File codeFile, @Nullable String[] instructionSets) {
+        mRemovePackageHelper.cleanUpResources(codeFile, instructionSets);
+    }
+
+    void cleanUpForMoveInstall(String volumeUuid, String packageName, String fromCodePath) {
+        mRemovePackageHelper.cleanUpForMoveInstall(volumeUuid, packageName, fromCodePath);
+    }
+
+    void sendPendingBroadcasts() {
+        mInstallPackageHelper.sendPendingBroadcasts();
+    }
+
+    void handlePackagePostInstall(@NonNull InstallRequest request, boolean launchedForRestore) {
+        mInstallPackageHelper.handlePackagePostInstall(request, launchedForRestore);
+    }
+
+    Pair<Integer, IntentSender> installExistingPackageAsUser(
+            @Nullable String packageName,
+            @UserIdInt int userId, @PackageManager.InstallFlags int installFlags,
+            @PackageManager.InstallReason int installReason,
+            @Nullable List<String> allowlistedRestrictedPermissions,
+            @Nullable IntentSender intentSender) {
+        return mInstallPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
+                installReason, allowlistedRestrictedPermissions, intentSender);
+    }
+    AndroidPackage initPackageTracedLI(File scanFile, final int parseFlags, int scanFlags)
+            throws PackageManagerException {
+        return mInstallPackageHelper.initPackageTracedLI(scanFile, parseFlags, scanFlags);
+    }
+
+    void restoreDisabledSystemPackageLIF(@NonNull DeletePackageAction action,
+                                         @NonNull int[] allUserHandles,
+                                         boolean writeSettings) throws SystemDeleteException {
+        mInstallPackageHelper.restoreDisabledSystemPackageLIF(
+                action, allUserHandles, writeSettings);
+    }
+    boolean enableCompressedPackage(@NonNull AndroidPackage stubPkg,
+                                    @NonNull PackageSetting stubPs) {
+        return mInstallPackageHelper.enableCompressedPackage(stubPkg, stubPs);
+    }
+
+    void installPackagesTraced(List<InstallRequest> requests) {
+        mInstallPackageHelper.installPackagesTraced(requests);
+    }
+
+    void restoreAndPostInstall(InstallRequest request) {
+        mInstallPackageHelper.restoreAndPostInstall(request);
+    }
+
+    Pair<Integer, String> verifyReplacingVersionCode(@NonNull PackageInfoLite pkgLite,
+                                    long requiredInstalledVersionCode,
+                                    int installFlags) {
+        return mInstallPackageHelper.verifyReplacingVersionCode(
+                pkgLite, requiredInstalledVersionCode, installFlags);
+    }
+
+    int getUidForVerifier(VerifierInfo verifierInfo) {
+        return mInstallPackageHelper.getUidForVerifier(verifierInfo);
+    }
+
+    int deletePackageX(String packageName, long versionCode, int userId, int deleteFlags,
+                        boolean removedBySystem) {
+        return mDeletePackageHelper.deletePackageX(packageName,
+                PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
+                PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 0c2e082..5b770aab 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -146,6 +146,7 @@
     private final Singleton<SharedLibrariesImpl> mSharedLibrariesProducer;
     private final Singleton<CrossProfileIntentFilterHelper> mCrossProfileIntentFilterHelperProducer;
     private final Singleton<UpdateOwnershipHelper> mUpdateOwnershipHelperProducer;
+    private final Singleton<PackageMonitorCallbackHelper> mPackageMonitorCallbackHelper;
 
     PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock,
             Installer installer, Object installLock, PackageAbiHelper abiHelper,
@@ -186,7 +187,8 @@
             Producer<IBackupManager> iBackupManager,
             Producer<SharedLibrariesImpl> sharedLibrariesProducer,
             Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer,
-            Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer) {
+            Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer,
+            Producer<PackageMonitorCallbackHelper> packageMonitorCallbackHelper) {
         mContext = context;
         mLock = lock;
         mInstaller = installer;
@@ -242,6 +244,7 @@
         mCrossProfileIntentFilterHelperProducer = new Singleton<>(
                 crossProfileIntentFilterHelperProducer);
         mUpdateOwnershipHelperProducer = new Singleton<>(updateOwnershipHelperProducer);
+        mPackageMonitorCallbackHelper = new Singleton<>(packageMonitorCallbackHelper);
     }
 
     /**
@@ -431,6 +434,10 @@
         return mUpdateOwnershipHelperProducer.get(this, mPackageManager);
     }
 
+    public PackageMonitorCallbackHelper getPackageMonitorCallbackHelper() {
+        return mPackageMonitorCallbackHelper.get(this, mPackageManager);
+    }
+
 
     /** Provides an abstraction to static access to system state. */
     public interface SystemWrapper {
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index bb3bf53..b8c2b86 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -52,12 +52,6 @@
     private final Object mLock = new Object();
     final IActivityManager mActivityManager = ActivityManager.getService();
 
-    final Handler mHandler;
-
-    PackageMonitorCallbackHelper(PackageManagerServiceInjector injector) {
-        mHandler = injector.getHandler();
-    }
-
     @NonNull
     @GuardedBy("mLock")
     private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>();
@@ -100,7 +94,8 @@
 
     public void notifyPackageAddedForNewUsers(String packageName,
             @AppIdInt int appId, @NonNull int[] userIds, @NonNull int[] instantUserIds,
-            boolean isArchived, int dataLoaderType, SparseArray<int[]> broadcastAllowList) {
+            boolean isArchived, int dataLoaderType, SparseArray<int[]> broadcastAllowList,
+            @NonNull Handler handler) {
         Bundle extras = new Bundle(2);
         // Set to UID of the first user, EXTRA_UID is automatically updated in sendPackageBroadcast
         final int uid = UserHandle.getUid(
@@ -111,11 +106,11 @@
         }
         extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
         notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras ,
-                userIds /* userIds */, instantUserIds, broadcastAllowList);
+                userIds /* userIds */, instantUserIds, broadcastAllowList, handler);
     }
 
     public void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
-            @NonNull String[] pkgNames, @NonNull int[] uids) {
+            @NonNull String[] pkgNames, @NonNull int[] uids, @NonNull Handler handler) {
         Bundle extras = new Bundle();
         extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgNames);
         extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids);
@@ -125,12 +120,12 @@
         String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
                 : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
         notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */);
+                null /* instantUserIds */, null /* broadcastAllowList */, handler);
     }
 
     public void notifyPackageChanged(String packageName, boolean dontKillApp,
             ArrayList<String> componentNames, int packageUid, String reason, int[] userIds,
-            int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
+            int[] instantUserIds, SparseArray<int[]> broadcastAllowList, Handler handler) {
         Bundle extras = new Bundle(4);
         extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0));
         String[] nameList = new String[componentNames.size()];
@@ -142,11 +137,12 @@
             extras.putString(Intent.EXTRA_REASON, reason);
         }
         notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds,
-                instantUserIds, broadcastAllowList);
+                instantUserIds, broadcastAllowList, handler);
     }
 
     public void notifyPackageMonitor(String action, String pkg, Bundle extras,
-            int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
+            int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList,
+            Handler handler) {
         if (!isAllowedCallbackAction(action)) {
             return;
         }
@@ -160,9 +156,10 @@
             }
 
             if (ArrayUtils.isEmpty(instantUserIds)) {
-                doNotifyCallbacks(action, pkg, extras, resolvedUserIds, broadcastAllowList);
+                doNotifyCallbacks(
+                        action, pkg, extras, resolvedUserIds, broadcastAllowList, handler);
             } else {
-                doNotifyCallbacks(action, pkg, extras, instantUserIds, broadcastAllowList);
+                doNotifyCallbacks(action, pkg, extras, instantUserIds, broadcastAllowList, handler);
             }
         } catch (RemoteException e) {
             // do nothing
@@ -181,7 +178,7 @@
     }
 
     private void doNotifyCallbacks(String action, String pkg, Bundle extras, int[] userIds,
-            SparseArray<int[]> broadcastAllowList) {
+            SparseArray<int[]> broadcastAllowList, Handler handler) {
         RemoteCallbackList<IRemoteCallback> callbacks;
         synchronized (mLock) {
             callbacks = mCallbacks;
@@ -202,7 +199,7 @@
             final int[] allowUids =
                     broadcastAllowList != null ? broadcastAllowList.get(userId) : new int[]{};
 
-            mHandler.post(() -> callbacks.broadcast((callback, user) -> {
+            handler.post(() -> callbacks.broadcast((callback, user) -> {
                 RegisterUser registerUser = (RegisterUser) user;
                 if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
                         != userId)) {
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 9f02542..7ee1772 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -16,24 +16,12 @@
 
 package com.android.server.pm;
 
-import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED;
-import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-
-import android.annotation.NonNull;
-import android.app.ActivityManagerInternal;
-import android.app.BroadcastOptions;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.PowerExemptionManager;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.util.ArrayUtils;
-import com.android.server.LocalServices;
 
 final class PackageRemovedInfo {
-    final PackageSender mPackageSender;
     String mRemovedPackage;
     String mInstallerPackageName;
     int mUid = -1;
@@ -58,116 +46,6 @@
     InstallArgs mArgs = null;
     private static final int[] EMPTY_INT_ARRAY = new int[0];
 
-    PackageRemovedInfo(PackageSender packageSender) {
-        mPackageSender = packageSender;
-    }
-
-    void sendPackageRemovedBroadcasts(boolean killApp, boolean removedBySystem,
-            boolean isArchived) {
-        sendPackageRemovedBroadcastInternal(killApp, removedBySystem, isArchived);
-    }
-
-    void sendSystemPackageUpdatedBroadcasts() {
-        if (mIsRemovedPackageSystemUpdate) {
-            sendSystemPackageUpdatedBroadcastsInternal();
-        }
-    }
-
-    private void sendSystemPackageUpdatedBroadcastsInternal() {
-        Bundle extras = new Bundle(2);
-        extras.putInt(Intent.EXTRA_UID, mRemovedAppId >= 0 ? mRemovedAppId : mUid);
-        extras.putBoolean(Intent.EXTRA_REPLACING, true);
-        mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, mRemovedPackage, extras,
-                0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null);
-        if (mInstallerPackageName != null) {
-            mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
-                    mRemovedPackage, extras, 0 /*flags*/,
-                    mInstallerPackageName, null, null, null, null /* broadcastAllowList */,
-                    null);
-            mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                    mRemovedPackage, extras, 0 /*flags*/,
-                    mInstallerPackageName, null, null, null, null /* broadcastAllowList */,
-                    null);
-        }
-        mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, mRemovedPackage,
-                extras, 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null);
-        mPackageSender.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0,
-                mRemovedPackage, null, null, null, null /* broadcastAllowList */,
-                getTemporaryAppAllowlistBroadcastOptions(REASON_PACKAGE_REPLACED).toBundle());
-    }
-
-    private static @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
-            @PowerExemptionManager.ReasonCode int reasonCode) {
-        long duration = 10_000;
-        final ActivityManagerInternal amInternal =
-                LocalServices.getService(ActivityManagerInternal.class);
-        if (amInternal != null) {
-            duration = amInternal.getBootTimeTempAllowListDuration();
-        }
-        final BroadcastOptions bOptions = BroadcastOptions.makeBasic();
-        bOptions.setTemporaryAppAllowlist(duration,
-                TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
-                reasonCode, "");
-        return bOptions;
-    }
-
-    private void sendPackageRemovedBroadcastInternal(boolean killApp, boolean removedBySystem,
-            boolean isArchived) {
-        Bundle extras = new Bundle();
-        final int removedUid = mRemovedAppId >= 0  ? mRemovedAppId : mUid;
-        extras.putInt(Intent.EXTRA_UID, removedUid);
-        extras.putBoolean(Intent.EXTRA_DATA_REMOVED, mDataRemoved);
-        extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, mIsRemovedPackageSystemUpdate);
-        extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
-        extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem);
-        final boolean isReplace = mIsUpdate || mIsRemovedPackageSystemUpdate;
-        if (isReplace || isArchived) {
-            extras.putBoolean(Intent.EXTRA_REPLACING, true);
-        }
-        if (isArchived) {
-            extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
-        }
-        extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, mRemovedForAllUsers);
-
-        // Send PACKAGE_REMOVED broadcast to the respective installer.
-        if (mRemovedPackage != null && mInstallerPackageName != null) {
-            mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
-                    mRemovedPackage, extras, 0 /*flags*/,
-                    mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
-        }
-        if (mIsStaticSharedLib) {
-            // When uninstalling static shared libraries, only the package's installer needs to be
-            // sent a PACKAGE_REMOVED broadcast. There are no other intended recipients.
-            return;
-        }
-        if (mRemovedPackage != null) {
-            mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
-                    mRemovedPackage, extras, 0, null /*targetPackage*/, null,
-                    mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null);
-            mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED_INTERNAL,
-                    mRemovedPackage, extras, 0 /*flags*/, PLATFORM_PACKAGE_NAME,
-                    null /*finishedReceiver*/, mBroadcastUsers, mInstantUserIds,
-                    mBroadcastAllowList, null /*bOptions*/);
-            if (mDataRemoved && !mIsRemovedPackageSystemUpdate) {
-                mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED,
-                        mRemovedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null,
-                        null, mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null);
-                mPackageSender.notifyPackageRemoved(mRemovedPackage, removedUid);
-            }
-        }
-        if (mRemovedAppId >= 0) {
-            // If a system app's updates are uninstalled the UID is not actually removed. Some
-            // services need to know the package name affected.
-            if (isReplace) {
-                extras.putString(Intent.EXTRA_PACKAGE_NAME, mRemovedPackage);
-            }
-
-            mPackageSender.sendPackageBroadcast(Intent.ACTION_UID_REMOVED,
-                    null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
-                    null, null, mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null);
-        }
-    }
-
     public void populateBroadcastUsers(PackageSetting deletedPackageSetting) {
         if (mRemovedUsers == null) {
             mBroadcastUsers = null;
diff --git a/services/core/java/com/android/server/pm/PackageSender.java b/services/core/java/com/android/server/pm/PackageSender.java
index 82e1d5f3..db83f59 100644
--- a/services/core/java/com/android/server/pm/PackageSender.java
+++ b/services/core/java/com/android/server/pm/PackageSender.java
@@ -16,24 +16,7 @@
 
 package com.android.server.pm;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.IIntentReceiver;
-import android.os.Bundle;
-import android.util.SparseArray;
-
 interface PackageSender {
-    /**
-     * @param userIds User IDs where the action occurred on a full application
-     * @param instantUserIds User IDs where the action occurred on an instant application
-     */
-    void sendPackageBroadcast(String action, String pkg,
-            Bundle extras, int flags, String targetPkg,
-            IIntentReceiver finishedReceiver, int[] userIds, int[] instantUserIds,
-            @Nullable SparseArray<int[]> broadcastAllowList, @Nullable Bundle bOptions);
-    void sendPackageAddedForNewUsers(@NonNull Computer snapshot, String packageName,
-            boolean sendBootCompleted, boolean includeStopped, int appId, int[] userIds,
-            int[] instantUserIds, boolean isArchived, int dataLoaderType);
     void notifyPackageAdded(String packageName, int uid);
     void notifyPackageChanged(String packageName, int uid);
     void notifyPackageRemoved(String packageName, int uid);
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 571aab4..41d2aeb 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -69,10 +69,12 @@
     private static final String TAG_DEFAULT_APPS = "da";
 
     private final PackageManagerService mPm;
+    private final BroadcastHelper mBroadcastHelper;
 
     // TODO(b/198166813): remove PMS dependency
-    PreferredActivityHelper(PackageManagerService pm) {
+    PreferredActivityHelper(PackageManagerService pm, BroadcastHelper broadcastHelper) {
         mPm = pm;
+        mBroadcastHelper = broadcastHelper;
     }
 
     private ResolveInfo findPreferredActivityNotLocked(@NonNull Computer snapshot, Intent intent,
@@ -120,7 +122,7 @@
         }
         if (changedUsers.size() > 0) {
             updateDefaultHomeNotLocked(mPm.snapshotComputer(), changedUsers);
-            mPm.postPreferredActivityChangedBroadcast(userId);
+            mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
             mPm.scheduleWritePackageRestrictions(userId);
         }
     }
@@ -167,7 +169,7 @@
         return mPm.setActiveLauncherPackage(packageName, userId,
                 successful -> {
                     if (successful) {
-                        mPm.postPreferredActivityChangedBroadcast(userId);
+                        mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
                     }
                 });
     }
@@ -215,7 +217,7 @@
         }
         // Re-snapshot after mLock
         if (!(isHomeFilter(filter) && updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId))) {
-            mPm.postPreferredActivityChangedBroadcast(userId);
+            mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
         }
     }
 
@@ -411,7 +413,7 @@
         if (isHomeFilter(filter)) {
             updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId);
         }
-        mPm.postPreferredActivityChangedBroadcast(userId);
+        mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
     }
 
     public void clearPackagePersistentPreferredActivities(String packageName, int userId) {
@@ -426,7 +428,7 @@
         }
         if (changed) {
             updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId);
-            mPm.postPreferredActivityChangedBroadcast(userId);
+            mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
             mPm.scheduleWritePackageRestrictions(userId);
         }
     }
@@ -443,7 +445,7 @@
         }
         if (changed) {
             updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId);
-            mPm.postPreferredActivityChangedBroadcast(userId);
+            mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
             mPm.scheduleWritePackageRestrictions(userId);
         }
     }
@@ -616,7 +618,7 @@
                 mPm.clearPackagePreferredActivitiesLPw(null, changedUsers, userId);
             }
             if (changedUsers.size() > 0) {
-                mPm.postPreferredActivityChangedBroadcast(userId);
+                mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId);
             }
             synchronized (mPm.mLock) {
                 mPm.mSettings.applyDefaultPreferredAppsLPw(userId);
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index d989c90..3d61f3e 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -22,6 +22,7 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
+
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
@@ -69,9 +70,11 @@
     private final PermissionManagerServiceInternal mPermissionManager;
     private final SharedLibrariesImpl mSharedLibraries;
     private final AppDataHelper mAppDataHelper;
+    private final BroadcastHelper mBroadcastHelper;
 
     // TODO(b/198166813): remove PMS dependency
-    RemovePackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) {
+    RemovePackageHelper(PackageManagerService pm, AppDataHelper appDataHelper,
+                        BroadcastHelper broadcastHelper) {
         mPm = pm;
         mIncrementalManager = mPm.mInjector.getIncrementalManager();
         mInstaller = mPm.mInjector.getInstaller();
@@ -79,10 +82,7 @@
         mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
         mSharedLibraries = mPm.mInjector.getSharedLibrariesImpl();
         mAppDataHelper = appDataHelper;
-    }
-
-    RemovePackageHelper(PackageManagerService pm) {
-        this(pm, new AppDataHelper(pm));
+        mBroadcastHelper = broadcastHelper;
     }
 
     public void removeCodePath(File codePath) {
@@ -265,7 +265,8 @@
 
         final List<AndroidPackage> sharedUserPkgs =
                 sus != null ? sus.getPackages() : Collections.emptyList();
-        final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm);
+        final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm,
+                mBroadcastHelper);
         final int[] userIds = (userId == UserHandle.USER_ALL) ? mUserManagerInternal.getUserIds()
                 : new int[] {userId};
         for (int nextUserId : userIds) {
@@ -395,10 +396,10 @@
             }
             if (changedUsers.size() > 0) {
                 final PreferredActivityHelper preferredActivityHelper =
-                        new PreferredActivityHelper(mPm);
+                        new PreferredActivityHelper(mPm, mBroadcastHelper);
                 preferredActivityHelper.updateDefaultHomeNotLocked(mPm.snapshotComputer(),
                         changedUsers);
-                mPm.postPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
+                mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
             }
         } else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate
                 && outInfo.mRemovedUsers != null) {
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index db5b9b1..c725cdc 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -147,7 +147,6 @@
 
         final Settings.VersionInfo ver;
         final List<? extends PackageStateInternal> packages;
-        final InstallPackageHelper installPackageHelper = new InstallPackageHelper(mPm);
         synchronized (mPm.mLock) {
             ver = mPm.mSettings.findOrCreateVersion(volumeUuid);
             packages = mPm.mSettings.getVolumePackagesLPr(volumeUuid);
@@ -160,7 +159,7 @@
             synchronized (mPm.mInstallLock) {
                 final AndroidPackage pkg;
                 try {
-                    pkg = installPackageHelper.initPackageTracedLI(
+                    pkg = mPm.initPackageTracedLI(
                             ps.getPath(), parseFlags, SCAN_INITIAL);
                     loaded.add(pkg);
 
@@ -228,7 +227,8 @@
         }
 
         if (DEBUG_INSTALL) Slog.d(TAG, "Loaded packages " + loaded);
-        sendResourcesChangedBroadcast(true /* mediaStatus */, false /* replacing */, loaded);
+        mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm.snapshotComputer(),
+                true /* mediaStatus */, false /* replacing */, loaded);
         synchronized (mLoadedVolumes) {
             mLoadedVolumes.add(vol.getId());
         }
@@ -256,7 +256,7 @@
 
                     final AndroidPackage pkg = ps.getPkg();
                     final int deleteFlags = PackageManager.DELETE_KEEP_DATA;
-                    final PackageRemovedInfo outInfo = new PackageRemovedInfo(mPm);
+                    final PackageRemovedInfo outInfo = new PackageRemovedInfo();
 
                     try (PackageFreezer freezer = mPm.freezePackageForDelete(ps.getPackageName(),
                              UserHandle.USER_ALL, deleteFlags,
@@ -280,7 +280,8 @@
         }
 
         if (DEBUG_INSTALL) Slog.d(TAG, "Unloaded packages " + unloaded);
-        sendResourcesChangedBroadcast(false /* mediaStatus */, false /* replacing */, unloaded);
+        mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm.snapshotComputer(),
+                false /* mediaStatus */, false /* replacing */, unloaded);
         synchronized (mLoadedVolumes) {
             mLoadedVolumes.remove(vol.getId());
         }
@@ -295,21 +296,6 @@
         }
     }
 
-    private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
-            ArrayList<AndroidPackage> packages) {
-        final int size = packages.size();
-        final String[] packageNames = new String[size];
-        final int[] packageUids = new int[size];
-        for (int i = 0; i < size; i++) {
-            final AndroidPackage pkg = packages.get(i);
-            packageNames[i] = pkg.getPackageName();
-            packageUids[i] = pkg.getUid();
-        }
-        mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, mediaStatus,
-                replacing, packageNames, packageUids);
-        mPm.notifyResourcesChanged(mediaStatus, replacing, packageNames, packageUids);
-    }
-
     /**
      * Examine all apps present on given mounted volume, and destroy apps that
      * aren't expected, either due to uninstallation or reinstallation on
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index ddb045d..29d99a73 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -26,17 +26,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
 import android.app.AppOpsManager;
-import android.app.BroadcastOptions;
-import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.content.pm.SuspendDialogInfo;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
@@ -47,7 +43,6 @@
 import android.util.IntArray;
 import android.util.Slog;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.server.LocalServices;
@@ -207,19 +202,22 @@
             }
         });
 
+        final Computer newSnapshot = mPm.snapshotComputer();
         if (!notifyPackagesList.isEmpty()) {
             final String[] changedPackages =
                     notifyPackagesList.toArray(new String[0]);
-            sendPackagesSuspendedForUser(
+            mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
                     suspended ? Intent.ACTION_PACKAGES_SUSPENDED
                             : Intent.ACTION_PACKAGES_UNSUSPENDED,
                     changedPackages, notifyUids.toArray(), quarantined, userId);
-            sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
+            mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, changedPackages,
+                    suspended, userId);
             mPm.scheduleWritePackageRestrictions(userId);
         }
         // Send the suspension changed broadcast to ensure suspension state is not stale.
         if (!changedPackagesList.isEmpty()) {
-            sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+            mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
+                    Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
                     changedPackagesList.toArray(new String[0]), changedUids.toArray(), quarantined,
                     userId);
         }
@@ -269,8 +267,10 @@
      * @return The app extras of the suspended package.
      */
     @Nullable
-    Bundle getSuspendedPackageAppExtras(@NonNull Computer snapshot, @NonNull String packageName,
-            int userId, int callingUid) {
+    static Bundle getSuspendedPackageAppExtras(@NonNull Computer snapshot,
+                                               @NonNull String packageName,
+                                               int userId,
+                                               int callingUid) {
         final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName, callingUid);
         if (ps == null) {
             return null;
@@ -299,7 +299,7 @@
      *                                   suspensions will be removed.
      * @param userId The user for which the changes are taking place.
      */
-    void removeSuspensionsBySuspendingPackage(@NonNull Computer computer,
+    void removeSuspensionsBySuspendingPackage(@NonNull Computer snapshot,
             @NonNull String[] packagesToChange,
             @NonNull Predicate<String> suspendingPackagePredicate, int userId) {
         final List<String> unsuspendedPackages = new ArrayList<>();
@@ -307,7 +307,7 @@
         final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>();
         for (String packageName : packagesToChange) {
             final PackageStateInternal packageState =
-                    computer.getPackageStateInternal(packageName);
+                    snapshot.getPackageStateInternal(packageName);
             final PackageUserStateInternal packageUserState = packageState == null
                     ? null : packageState.getUserStateOrDefault(userId);
             if (packageUserState == null || !packageUserState.isSuspended()) {
@@ -350,11 +350,14 @@
         });
 
         mPm.scheduleWritePackageRestrictions(userId);
+        final Computer newSnapshot = mPm.snapshotComputer();
         if (!unsuspendedPackages.isEmpty()) {
             final String[] packageArray = unsuspendedPackages.toArray(
                     new String[unsuspendedPackages.size()]);
-            sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
-            sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
+            mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, packageArray,
+                    false, userId);
+            mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
+                    Intent.ACTION_PACKAGES_UNSUSPENDED,
                     packageArray, unsuspendedUids.toArray(), false, userId);
         }
     }
@@ -610,38 +613,6 @@
     }
 
     /**
-     * Send broadcast intents for packages suspension changes.
-     *
-     * @param intent The action name of the suspension intent.
-     * @param pkgList The names of packages which have suspension changes.
-     * @param uidList The uids of packages which have suspension changes.
-     * @param userId The user where packages reside.
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    void sendPackagesSuspendedForUser(@NonNull String intent, @NonNull String[] pkgList,
-            @NonNull int[] uidList, boolean quarantined, int userId) {
-        final Handler handler = mInjector.getHandler();
-        final Bundle extras = new Bundle(3);
-        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
-        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
-        if (quarantined) {
-            extras.putBoolean(Intent.EXTRA_QUARANTINED, true);
-        }
-        final int flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND;
-        final Bundle options = new BroadcastOptions()
-                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
-                .toBundle();
-        handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
-                extras, flags, null /* targetPkg */, null /* finishedReceiver */,
-                new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
-                (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
-                        mPm.snapshotComputer(), callingUid, intentExtras),
-                options));
-        mPm.notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
-                null /* instantUserIds */, null /* broadcastAllowList */);
-    }
-
-    /**
      * Suspends packages on behalf of an admin.
      *
      * @return array of packages that are unsuspendable, either because admin is not allowed to
@@ -756,37 +727,4 @@
         }
         return false;
     }
-
-    private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended,
-            int userId) {
-        final Handler handler = mInjector.getHandler();
-        final String action = suspended
-                ? Intent.ACTION_MY_PACKAGE_SUSPENDED
-                : Intent.ACTION_MY_PACKAGE_UNSUSPENDED;
-        handler.post(() -> {
-            final IActivityManager am = ActivityManager.getService();
-            if (am == null) {
-                Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ "
-                        + (suspended ? "" : "UN") + "SUSPENDED broadcasts");
-                return;
-            }
-            final int[] targetUserIds = new int[] {userId};
-            final Computer snapshot = mPm.snapshotComputer();
-            for (String packageName : affectedPackages) {
-                final Bundle appExtras = suspended
-                        ? getSuspendedPackageAppExtras(snapshot, packageName, userId, SYSTEM_UID)
-                        : null;
-                final Bundle intentExtras;
-                if (appExtras != null) {
-                    intentExtras = new Bundle(1);
-                    intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras);
-                } else {
-                    intentExtras = null;
-                }
-                mBroadcastHelper.doSendBroadcast(action, null, intentExtras,
-                        Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
-                        targetUserIds, false, null, null, null);
-            }
-        });
-    }
 }
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 8c73ce8..c6435ae 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -139,7 +139,6 @@
     private final UserHandle mUser;
     @NonNull
     private final PackageManagerService mPm;
-    private final InstallPackageHelper mInstallPackageHelper;
 
     VerifyingSession(UserHandle user, File stagedDir, IPackageInstallObserver2 observer,
             PackageInstaller.SessionParams sessionParams, InstallSource installSource,
@@ -147,7 +146,6 @@
             boolean userActionRequired, PackageManagerService pm) {
         mPm = pm;
         mUser = user;
-        mInstallPackageHelper = new InstallPackageHelper(mPm);
         mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
         mObserver = observer;
         mInstallFlags = sessionParams.installFlags;
@@ -181,7 +179,7 @@
         PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext,
                 mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags, mPackageAbiOverride);
 
-        Pair<Integer, String> ret = mInstallPackageHelper.verifyReplacingVersionCode(
+        Pair<Integer, String> ret = mPm.verifyReplacingVersionCode(
                 pkgLite, mRequiredInstalledVersionCode, mInstallFlags);
         setReturnCode(ret.first, ret.second);
         if (mRet != INSTALL_SUCCEEDED) {
@@ -729,7 +727,7 @@
                 continue;
             }
 
-            final int verifierUid = mInstallPackageHelper.getUidForVerifier(verifierInfo);
+            final int verifierUid = mPm.getUidForVerifier(verifierInfo);
             if (verifierUid == -1) {
                 continue;
             }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
index 4a2bf75..5d3eba8 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -17,24 +17,22 @@
 package com.android.server.pm;
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
 import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
+
 import static java.lang.reflect.Modifier.isFinal;
 import static java.lang.reflect.Modifier.isPublic;
 import static java.lang.reflect.Modifier.isStatic;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.AppGlobals;
-import android.content.IIntentReceiver;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Postsubmit;
-import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -87,18 +85,6 @@
     @Test
     public void testPackageRemoval() {
         class PackageSenderImpl implements PackageSender {
-            public void sendPackageBroadcast(final String action, final String pkg,
-                    final Bundle extras, final int flags, final String targetPkg,
-                    final IIntentReceiver finishedReceiver, final int[] userIds,
-                    int[] instantUserIds, SparseArray<int[]> broadcastAllowList,
-                    @Nullable Bundle bOptions) {
-            }
-
-            public void sendPackageAddedForNewUsers(@NonNull Computer snapshot, String packageName,
-                    boolean sendBootComplete, boolean includeStopped, int appId,
-                    int[] userIds, int[] instantUserIds, boolean isArchived, int dataLoaderType) {
-            }
-
             @Override
             public void notifyPackageAdded(String packageName, int uid) {
             }
@@ -113,9 +99,8 @@
             }
         }
 
-        PackageSenderImpl sender = new PackageSenderImpl();
         PackageSetting setting = null;
-        PackageRemovedInfo pri = new PackageRemovedInfo(sender);
+        PackageRemovedInfo pri = new PackageRemovedInfo();
 
         // Initial conditions: nothing there
         Assert.assertNull(pri.mRemovedUsers);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index 52044bf..de8b308 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -117,7 +117,9 @@
                 Build.VERSION_CODES.CUR_DEVELOPMENT,
                 Build.VERSION.INCREMENTAL);
         mMockSystem.system().validateFinalState();
-        mInstallPackageHelper = new InstallPackageHelper(mPmService, mock(AppDataHelper.class));
+        mInstallPackageHelper = new InstallPackageHelper(mPmService, mock(AppDataHelper.class),
+                mock(RemovePackageHelper.class), mock(DeletePackageHelper.class),
+                mock(BroadcastHelper.class));
     }
 
     @NonNull
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
index d6a4d40..931b38d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -34,6 +34,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.mock
 
 @RunWith(JUnit4::class)
 class DeletePackageHelperTest {
@@ -79,7 +80,8 @@
         whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0))
         whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1)
 
-        val dph = DeletePackageHelper(mPms)
+        val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+            mock(BroadcastHelper::class.java))
         val result = dph.deletePackageX("a.data.package", 1L, 1, 0, false)
 
         assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED)
@@ -97,7 +99,8 @@
         whenever(mUserManagerInternal.getUserInfo(parentId)).thenReturn(
             UserInfo(userId, "testparent", 0))
 
-        val dph = DeletePackageHelper(mPms)
+        val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+            mock(BroadcastHelper::class.java))
         val result = dph.deletePackageX("a.data.package", 1L, userId, 0, false)
 
         assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED)
@@ -112,7 +115,8 @@
         whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
             .thenReturn(PERMISSION_DENIED)
 
-        val dph = DeletePackageHelper(mPms)
+        val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+            mock(BroadcastHelper::class.java))
         val result = dph.deletePackageX("a.data.package", 1L, 1,
             PackageManager.DELETE_SYSTEM_APP, false)
 
@@ -133,7 +137,8 @@
         whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
             .thenReturn(PERMISSION_DENIED)
 
-        val dph = DeletePackageHelper(mPms)
+        val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+            mock(BroadcastHelper::class.java))
         val result = dph.deletePackageX("a.data.package", 1L, userId,
             PackageManager.DELETE_SYSTEM_APP, false)
 
@@ -150,7 +155,8 @@
         whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
             .thenReturn(PERMISSION_DENIED)
 
-        val dph = DeletePackageHelper(mPms)
+        val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+            mock(BroadcastHelper::class.java))
         val result = dph.deletePackageX("a.data.package", 1L, 1,
                 PackageManager.DELETE_SYSTEM_APP, false)
 
@@ -164,7 +170,8 @@
         whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
             .thenReturn(PERMISSION_GRANTED)
 
-        val dph = DeletePackageHelper(mPms)
+        val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+            mock(BroadcastHelper::class.java))
         val result = dph.deletePackageX("a.data.package", 1L, 1,
             PackageManager.DELETE_SYSTEM_APP, false)
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
index 9f1cec3..cf81f0a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -39,7 +39,7 @@
     override fun setup() {
         super.setup()
         distractingPackageHelper = DistractingPackageHelper(
-                pms, rule.mocks().injector, broadcastHelper, suspendPackageHelper)
+                pms, broadcastHelper, suspendPackageHelper)
     }
 
     @Test
@@ -50,12 +50,11 @@
         testHandler.flush()
 
         verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
-        verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
-                nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-                nullable(), nullable(), nullable(), nullable())
+        verify(broadcastHelper).sendDistractingPackagesChanged(any(Computer::class.java),
+                pkgListCaptor.capture(), any(), any(), flagsCaptor.capture())
 
-        val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-        val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
+        val modifiedPackages = pkgListCaptor.value
+        val distractionFlags = flagsCaptor.value
         assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
         assertThat(distractionFlags).isEqualTo(PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
         assertThat(unactionedPackages).isEmpty()
@@ -75,10 +74,8 @@
                 PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, deviceOwnerUid)
         testHandler.flush()
         verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
-        verify(broadcastHelper, never()).sendPackageBroadcast(
-                eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
-                nullable())
+        verify(broadcastHelper, never()).sendDistractingPackagesChanged(
+                any(), any(), any(), any(), any())
         assertThat(unactionedPackages).isEmpty()
     }
 
@@ -154,11 +151,11 @@
         testHandler.flush()
 
         verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
-        verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
-                nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-                nullable(), nullable(), nullable(), nullable())
-        val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-        val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
+        verify(broadcastHelper).sendDistractingPackagesChanged(
+                any(Computer::class.java), pkgListCaptor.capture(), any(), eq(TEST_USER_ID),
+                flagsCaptor.capture())
+        val modifiedPackages = pkgListCaptor.value
+        val distractionFlags = flagsCaptor.value
         assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
         assertThat(distractionFlags).isEqualTo(PackageManager.RESTRICTION_NONE)
     }
@@ -170,9 +167,8 @@
         testHandler.flush()
 
         verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
-        verify(broadcastHelper, never()).sendPackageBroadcast(eq(
-                Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
-                nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
+        verify(broadcastHelper, never()).sendDistractingPackagesChanged(
+                any(), any(), any(), any(), any())
     }
 
     @Test
@@ -189,22 +185,21 @@
                 arrayOfNulls(0), TEST_USER_ID)
         testHandler.flush()
         verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
-        verify(broadcastHelper, never()).sendPackageBroadcast(eq(
-                Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
-                nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
+        verify(broadcastHelper, never()).sendDistractingPackagesChanged(
+                any(), any(), any(), any(), any())
     }
 
     @Test
     fun sendDistractingPackagesChanged() {
-        distractingPackageHelper.sendDistractingPackagesChanged(packagesToChange, uidsToChange,
-                TEST_USER_ID, PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
+        broadcastHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
+                packagesToChange, uidsToChange, TEST_USER_ID,
+                PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
         testHandler.flush()
-        verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
-                nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-                nullable(), nullable(), nullable(), nullable())
+        verify(broadcastHelper).sendDistractingPackagesChanged(any(Computer::class.java),
+                pkgListCaptor.capture(), uidsCaptor.capture(), eq(TEST_USER_ID), any())
 
-        var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-        var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+        var changedPackages = pkgListCaptor.value
+        var changedUids = uidsCaptor.value
         assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
         assertThat(changedUids).asList().containsExactly(
                 packageSetting1.appId, packageSetting2.appId)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
index 5fd270e..eb00164 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
@@ -17,7 +17,6 @@
 package com.android.server.pm
 
 import android.os.Build
-import android.os.Bundle
 import android.os.UserHandle
 import android.os.UserManager
 import com.android.server.pm.pkg.PackageStateInternal
@@ -68,7 +67,11 @@
     lateinit var protectedPackages: ProtectedPackages
 
     @Captor
-    lateinit var bundleCaptor: ArgumentCaptor<Bundle>
+    lateinit var pkgListCaptor: ArgumentCaptor<Array<String>>
+    @Captor
+    lateinit var flagsCaptor: ArgumentCaptor<Int>
+    @Captor
+    lateinit var uidsCaptor: ArgumentCaptor<IntArray>
 
     @Rule
     @JvmField
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index c5db5db..6c44fd0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -17,12 +17,12 @@
 package com.android.server.pm;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.content.Intent;
 import android.content.pm.PackageInstaller;
@@ -63,9 +63,7 @@
 
     @Before
     public void setup() {
-        when(mMockSystem.mocks().getInjector().getHandler()).thenReturn(mHandler);
-        mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper(
-                mMockSystem.mocks().getInjector());
+        mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper();
     }
 
 
@@ -80,7 +78,7 @@
 
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -93,7 +91,7 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
-                null /* broadcastAllowList */);
+                null /* broadcastAllowList */, mHandler);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
 
@@ -101,7 +99,7 @@
         mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback);
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -114,7 +112,7 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
 
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -138,7 +136,7 @@
         // Notify for user 10
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -155,7 +153,7 @@
         mPackageMonitorCallbackHelper.notifyPackageChanged(FAKE_PACKAGE_NAME,
                 false /* dontKillApp */, components, FAKE_PACKAGE_UID, null /* reason */,
                 new int[]{0} /* userIds */, null /* instantUserIds */,
-                null /* broadcastAllowList */);
+                null /* broadcastAllowList */, mHandler);
 
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -183,7 +181,8 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(FAKE_PACKAGE_NAME,
                 FAKE_PACKAGE_UID, new int[]{0} /* userIds */, new int[0], false /* isArchived */,
-                PackageInstaller.DATA_LOADER_TYPE_STREAMING, null /* broadcastAllowList */);
+                PackageInstaller.DATA_LOADER_TYPE_STREAMING, null /* broadcastAllowList */,
+                mHandler);
 
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -207,7 +206,7 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyResourcesChanged(true /* mediaStatus */,
                 true /* replacing */, new String[]{FAKE_PACKAGE_NAME},
-                new int[]{FAKE_PACKAGE_UID} /* uids */);
+                new int[]{FAKE_PACKAGE_UID} /* uids */, mHandler);
 
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -240,7 +239,7 @@
         mPackageMonitorCallbackHelper.onUserRemoved(10);
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -256,7 +255,7 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, broadcastAllowList);
+                null /* instantUserIds */, broadcastAllowList, mHandler);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
     }
@@ -272,7 +271,7 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, broadcastAllowList);
+                null /* instantUserIds */, broadcastAllowList, mHandler);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -288,7 +287,7 @@
                 Process.SYSTEM_UID);
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, broadcastAllowList);
+                null /* instantUserIds */, broadcastAllowList, mHandler);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index 6797576..4240373 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -22,12 +22,11 @@
 import android.os.PersistableBundle
 import com.android.server.testutils.any
 import com.android.server.testutils.eq
-import com.android.server.testutils.nullable
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
@@ -44,17 +43,10 @@
         testHandler.flush()
 
         verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
-        verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED),
-            nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-            nullable(), nullable(), nullable(), nullable())
-        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
-            nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(),
-            nullable(), nullable())
-        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
-            nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(),
-            nullable(), nullable())
+        verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java),
+            eq(Intent.ACTION_PACKAGES_SUSPENDED), pkgListCaptor.capture(), any(), any(), any())
 
-        var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+        var modifiedPackages = pkgListCaptor.value
         assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
         assertThat(failedNames).isEmpty()
     }
@@ -146,6 +138,7 @@
                 null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
                 TEST_USER_ID, deviceOwnerUid, false /* forQuietMode */, false /* quarantined */)
         testHandler.flush()
+        Mockito.clearInvocations(broadcastHelper)
         assertThat(failedNames).isEmpty()
         failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, false /* suspended */, null /* appExtras */,
@@ -154,17 +147,13 @@
         testHandler.flush()
 
         verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
-        verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
-            nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-            nullable(), nullable(), nullable(), nullable())
-        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
-            nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
-            nullable(), nullable(), nullable())
-        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
-            nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
-            nullable(), nullable(), nullable())
+        verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java),
+                eq(Intent.ACTION_PACKAGES_UNSUSPENDED), pkgListCaptor.capture(), any(), any(),
+                any())
+        verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(any(Computer::class.java),
+                any(), any(), any())
 
-        var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+        var modifiedPackages = pkgListCaptor.value
         assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
         assertThat(failedNames).isEmpty()
     }
@@ -206,7 +195,7 @@
         testHandler.flush()
         assertThat(failedNames).isEmpty()
 
-        val result = suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
+        val result = SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
             TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)!!
 
         assertThat(result.getString(TEST_PACKAGE_1)).isEqualTo(TEST_PACKAGE_1)
@@ -222,14 +211,15 @@
                 null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
                 false /* forQuietMode */, false /* quarantined */)
         testHandler.flush()
+        Mockito.clearInvocations(broadcastHelper)
         assertThat(failedNames).isEmpty()
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
             TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
             TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
-        assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
+        assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
             TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNotNull()
-        assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
+        assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
             TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull()
 
         suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
@@ -238,23 +228,18 @@
 
         testHandler.flush()
         verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
-        verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
-            nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-            nullable(), nullable(), nullable(), nullable())
-        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
-            nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
-            nullable(), nullable(), nullable())
-        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
-            nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
-            nullable(), nullable(), nullable())
+        verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java),
+                eq(Intent.ACTION_PACKAGES_UNSUSPENDED), any(), any(), any(), any())
+        verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(any(Computer::class.java),
+                any(), any(), any())
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
             TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
             TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull()
-        assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
+        assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
             TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
-        assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
+        assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
             TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull()
     }
 
@@ -319,39 +304,4 @@
 
         assertThat(result.title).isEqualTo(TEST_PACKAGE_1)
     }
-
-    @Test
-    @Throws(Exception::class)
-    fun sendPackagesSuspendedForUser() {
-        suspendPackageHelper.sendPackagesSuspendedForUser(
-            Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, false, TEST_USER_ID)
-        testHandler.flush()
-        verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
-                nullable())
-
-        var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-        var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
-        assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
-        assertThat(changedUids).asList().containsExactly(
-                packageSetting1.appId, packageSetting2.appId)
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun sendPackagesSuspendModifiedForUser() {
-        suspendPackageHelper.sendPackagesSuspendedForUser(
-            Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange, false, TEST_USER_ID)
-        testHandler.flush()
-        verify(broadcastHelper).sendPackageBroadcast(
-                eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
-                nullable())
-
-        var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-        var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
-        assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
-        assertThat(modifiedUids).asList().containsExactly(
-                packageSetting1.appId, packageSetting2.appId)
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 82b7540..2f0257a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -39,11 +39,16 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.hardware.display.DisplayManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.DexmakerShareClassLoaderRule;
 import android.view.Display;
+import android.view.WindowManager;
 
 import com.android.server.accessibility.magnification.MagnificationProcessor;
 import com.android.server.accessibility.test.MessageCapturingHandler;
@@ -76,6 +81,9 @@
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     AccessibilityServiceConnection mConnection;
 
     @Mock AccessibilityUserState mMockUserState;
@@ -113,6 +121,8 @@
 
         when(mMockIBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient);
         when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false);
+        when(mMockContext.getSystemService(Context.DISPLAY_SERVICE))
+                .thenReturn(new DisplayManager(mMockContext));
 
         mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext,
                 COMPONENT_NAME, mMockServiceInfo, SERVICE_ID, mHandler, new Object(),
@@ -168,6 +178,18 @@
         assertFalse(mConnection.getServiceInfo().crashed);
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK)
+    public void onServiceConnected_addsWindowTokens() {
+        setServiceBinding(COMPONENT_NAME);
+        mConnection.bindLocked();
+        mConnection.onServiceConnected(COMPONENT_NAME, mMockIBinder);
+
+        verify(mMockWindowManagerInternal).addWindowToken(
+                any(), eq(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY),
+                anyInt(), eq(null));
+    }
+
     private void setServiceBinding(ComponentName componentName) {
         when(mMockUserState.getBindingServicesLocked())
                 .thenReturn(new HashSet<>(Arrays.asList(componentName)));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index b4558b2..63281b7 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -35,6 +35,7 @@
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -46,6 +47,9 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Color;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
 import android.testing.DexmakerShareClassLoaderRule;
@@ -88,6 +92,9 @@
     @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Mock private AccessibilityServiceInfo mMockServiceInfo;
 
     @Mock private AccessibilityServiceConnection mMockConnection;
@@ -188,7 +195,7 @@
 
         mUserState.addServiceLocked(mMockConnection);
 
-        verify(mMockConnection, never()).onAdded();
+        verify(mMockListener, never()).onServiceInfoChangedLocked(any());
     }
 
     @Test
@@ -197,13 +204,24 @@
 
         mUserState.addServiceLocked(mMockConnection);
 
-        verify(mMockConnection).onAdded();
         assertTrue(mUserState.getBoundServicesLocked().contains(mMockConnection));
         assertEquals(mMockConnection, mUserState.mComponentNameToServiceMap.get(COMPONENT_NAME));
         verify(mMockListener).onServiceInfoChangedLocked(eq(mUserState));
     }
 
     @Test
+    // addServiceLocked only calls addWindowTokensForAllDisplays when
+    // FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK is off, so skip the test if it is on.
+    @RequiresFlagsDisabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK)
+    public void addService_flagDisabled_addsWindowTokens() {
+        when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
+
+        mUserState.addServiceLocked(mMockConnection);
+
+        verify(mMockConnection).addWindowTokensForAllDisplays();
+    }
+
+    @Test
     public void reconcileSoftKeyboardMode_whenStateNotMatchSettings_setBothDefault() {
         // When soft kb show mode is hidden in settings and is auto in state.
         putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 4ce9ba0..3ee5f61 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -21,8 +21,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -36,6 +39,10 @@
 import android.content.pm.ServiceInfo;
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.server.accessibility.test.MessageCapturingHandler;
@@ -43,6 +50,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -58,6 +66,9 @@
 
     MessageCapturingHandler mMessageCapturingHandler;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Mock Context mMockContext;
     @Mock AccessibilityServiceInfo mMockServiceInfo;
     @Mock ResolveInfo mMockResolveInfo;
@@ -197,6 +208,24 @@
         assertEquals(0, mUiAutomationManager.getRequestedEventMaskLocked());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK)
+    public void registerUiAutomationService_callsAddWindowTokenUsingHandler() {
+        register(0);
+        // registerUiTestAutomationServiceLocked() should not directly call addWindowToken.
+        verify(mMockWindowManagerInternal, never()).addWindowToken(
+                any(), anyInt(), anyInt(), any());
+
+        // Advance UiAutomationManager#UiAutomationService's handler.
+        mMessageCapturingHandler.sendAllMessages();
+
+        // After advancing the handler we expect addWindowToken to have been called
+        // by the UiAutomationService instance.
+        verify(mMockWindowManagerInternal).addWindowToken(
+                any(), eq(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY),
+                anyInt(), eq(null));
+    }
+
     private void register(int flags) {
         mUiAutomationManager.registerUiTestAutomationServiceLocked(mMockOwner,
                 mMockAccessibilityServiceClient, mMockContext, mMockServiceInfo, SERVICE_ID,