Merge "Allow ProtoLog.init to be called multiple times in the same process" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index baf142a..c7df662 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9875,6 +9875,7 @@
     field public static final int RESULT_DISCOVERY_TIMEOUT = 2; // 0x2
     field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
     field public static final int RESULT_OK = -1; // 0xffffffff
+    field @FlaggedApi("android.companion.association_failure_code") public static final int RESULT_SECURITY_ERROR = 4; // 0x4
     field public static final int RESULT_USER_REJECTED = 1; // 0x1
   }
 
@@ -9884,7 +9885,7 @@
     method public void onAssociationPending(@NonNull android.content.IntentSender);
     method @Deprecated public void onDeviceFound(@NonNull android.content.IntentSender);
     method public abstract void onFailure(@Nullable CharSequence);
-    method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int);
+    method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int, @Nullable CharSequence);
   }
 
   public abstract class CompanionDeviceService extends android.app.Service {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 1529842..1cdf3b1 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -21,7 +21,6 @@
 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
 
-import static java.util.Collections.unmodifiableMap;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
@@ -58,7 +57,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
-import android.util.ArrayMap;
 import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -78,7 +76,6 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.BiConsumer;
@@ -146,12 +143,19 @@
     /**
      * The result code to propagate back to the user activity, indicates the internal error
      * in CompanionDeviceManager.
-     * E.g. Missing necessary permissions or duplicate {@link AssociationRequest}s when create the
-     * {@link AssociationInfo}.
      */
     public static final int RESULT_INTERNAL_ERROR = 3;
 
     /**
+     * The result code to propagate back to the user activity and
+     * {@link Callback#onFailure(int, CharSequence)}, indicates app is not allow to create the
+     * association due to the security issue.
+     * E.g. There are missing necessary permissions when creating association.
+     */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE)
+    public static final int RESULT_SECURITY_ERROR = 4;
+
+    /**
      * Requesting applications will receive the String in {@link Callback#onFailure} if the
      * association dialog is explicitly declined by the users. E.g. press the Don't allow
      * button.
@@ -374,7 +378,6 @@
          */
         public void onAssociationCreated(@NonNull AssociationInfo associationInfo) {}
 
-        //TODO(b/331459560): Add deprecated and remove abstract after API cut for W.
         /**
          * Invoked if the association could not be created.
          *
@@ -385,11 +388,15 @@
         /**
          * Invoked if the association could not be created.
          *
-         * @param resultCode indicate the particular reason why the association
-         *                   could not be created.
+         * Please note that both {@link #onFailure(CharSequence error)} and this
+         * API will be called if the association could not be created.
+         *
+         * @param errorCode indicate the particular error code why the association
+         *                  could not be created.
+         * @param error error message.
          */
         @FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE)
-        public void onFailure(@ResultCode int resultCode) {}
+        public void onFailure(@ResultCode int errorCode, @Nullable CharSequence error) {}
     }
 
     private final ICompanionDeviceManager mService;
@@ -1825,12 +1832,12 @@
         }
 
         @Override
-        public void onFailure(@ResultCode int resultCode) {
+        public void onFailure(@ResultCode int errorCode, @Nullable CharSequence error) {
             if (Flags.associationFailureCode()) {
-                execute(mCallback::onFailure, resultCode);
+                execute(mCallback::onFailure, errorCode, error);
             }
 
-            execute(mCallback::onFailure, RESULT_CODE_TO_REASON.get(resultCode));
+            execute(mCallback::onFailure, error);
         }
 
         private <T> void execute(Consumer<T> callback, T arg) {
@@ -1840,6 +1847,12 @@
                 mHandler.post(() -> callback.accept(arg));
             }
         }
+
+        private <T, U> void execute(BiConsumer<T, U> callback, T arg1, U arg2) {
+            if (mExecutor != null) {
+                mExecutor.execute(() -> callback.accept(arg1, arg2));
+            }
+        }
     }
 
     private static class OnAssociationsChangedListenerProxy
@@ -2014,15 +2027,4 @@
             }
         }
     }
-
-    private static final Map<Integer, String> RESULT_CODE_TO_REASON;
-    static {
-        final Map<Integer, String> map = new ArrayMap<>();
-        map.put(RESULT_CANCELED, REASON_CANCELED);
-        map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED);
-        map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT);
-        map.put(RESULT_INTERNAL_ERROR, REASON_INTERNAL_ERROR);
-
-        RESULT_CODE_TO_REASON = unmodifiableMap(map);
-    }
 }
diff --git a/core/java/android/companion/IAssociationRequestCallback.aidl b/core/java/android/companion/IAssociationRequestCallback.aidl
index b1be30a..a6f86a5 100644
--- a/core/java/android/companion/IAssociationRequestCallback.aidl
+++ b/core/java/android/companion/IAssociationRequestCallback.aidl
@@ -25,5 +25,5 @@
 
     oneway void onAssociationCreated(in AssociationInfo associationInfo);
 
-    oneway void onFailure(in int resultCode);
+    oneway void onFailure(in int errorCode, in CharSequence error);
 }
\ No newline at end of file
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 1922327..eb35817 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -83,7 +83,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.StrictMode;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.system.Os;
 import android.text.TextUtils;
@@ -6247,18 +6246,6 @@
 
     private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent,
             @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
-        try {
-            Trace.beginSection(rv.hasDrawInstructions()
-                    ? "RemoteViews#inflateViewWithDrawInstructions"
-                    : "RemoteViews#inflateView");
-            return inflateViewInternal(context, rv, parent, applyThemeResId, colorResources);
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    private View inflateViewInternal(Context context, RemoteViews rv, @Nullable ViewGroup parent,
-            @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
         // RemoteViews may be built by an application installed in another
         // user. So build a context that loads resources from that user but
         // still returns the current users userId so settings like data / time formats
@@ -6397,7 +6384,7 @@
 
         private View mResult;
         private ViewTree mTree;
-        private List<Action> mActions;
+        private Action[] mActions;
         private Exception mError;
 
         private AsyncApplyTask(
@@ -6424,20 +6411,11 @@
 
                 if (mRV.mActions != null) {
                     int count = mRV.mActions.size();
-                    mActions = new ArrayList<>(count);
-                    try {
-                        Trace.beginSection(hasDrawInstructions()
-                                ? "RemoteViews#initActionAsyncWithDrawInstructions"
-                                : "RemoteViews#initActionAsync");
-                        for (Action action : mRV.mActions) {
-                            if (isCancelled()) {
-                                break;
-                            }
-                            // TODO: check if isCancelled in nested views.
-                            mActions.add(action.initActionAsync(mTree, mParent, mApplyParams));
-                        }
-                    } finally {
-                        Trace.endSection();
+                    mActions = new Action[count];
+                    for (int i = 0; i < count && !isCancelled(); i++) {
+                        // TODO: check if isCancelled in nested views.
+                        mActions[i] = mRV.mActions.get(i)
+                                .initActionAsync(mTree, mParent, mApplyParams);
                     }
                 } else {
                     mActions = null;
@@ -6459,7 +6437,14 @@
 
                 try {
                     if (mActions != null) {
-                        mRV.performApply(viewTree.mRoot, mParent, mApplyParams, mActions);
+
+                        ActionApplyParams applyParams = mApplyParams.clone();
+                        if (applyParams.handler == null) {
+                            applyParams.handler = DEFAULT_INTERACTION_HANDLER;
+                        }
+                        for (Action a : mActions) {
+                            a.apply(viewTree.mRoot, mParent, applyParams);
+                        }
                     }
                     // If the parent of the view is has is a root, resolve the recycling.
                     if (mTopLevel && mResult instanceof ViewGroup) {
@@ -6635,11 +6620,6 @@
     }
 
     private void performApply(View v, ViewGroup parent, ActionApplyParams params) {
-        performApply(v, parent, params, mActions);
-    }
-
-    private void performApply(
-            View v, ViewGroup parent, ActionApplyParams params, List<Action> actions) {
         params = params.clone();
         if (params.handler == null) {
             params.handler = DEFAULT_INTERACTION_HANDLER;
@@ -6650,15 +6630,8 @@
         }
         if (mActions != null) {
             final int count = mActions.size();
-            try {
-                Trace.beginSection(hasDrawInstructions()
-                        ? "RemoteViews#applyActionsWithDrawInstructions"
-                        : "RemoteViews#applyActions");
-                for (int i = 0; i < count; i++) {
-                    mActions.get(i).apply(v, parent, params);
-                }
-            } finally {
-                Trace.endSection();
+            for (int i = 0; i < count; i++) {
+                mActions.get(i).apply(v, parent, params);
             }
         }
     }
diff --git a/core/java/com/android/internal/accessibility/TEST_MAPPING b/core/java/com/android/internal/accessibility/TEST_MAPPING
index 1c67399..b2b3041 100644
--- a/core/java/com/android/internal/accessibility/TEST_MAPPING
+++ b/core/java/com/android/internal/accessibility/TEST_MAPPING
@@ -2,6 +2,9 @@
   "imports": [
     {
       "path": "frameworks/base/services/accessibility/TEST_MAPPING"
+    },
+    {
+      "path": "frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING"
     }
   ]
 }
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 1d9d366..1204ef3 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -698,7 +698,7 @@
             case CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE:
                 return "DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE";
             case CUJ_DESKTOP_MODE_SNAP_RESIZE:
-                return "CUJ_DESKTOP_MODE_SNAP_RESIZE";
+                return "DESKTOP_MODE_SNAP_RESIZE";
         }
         return "UNKNOWN";
     }
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
index 31c9db7..3a3bfb47 100644
--- a/libs/hwui/ColorFilter.h
+++ b/libs/hwui/ColorFilter.h
@@ -106,7 +106,7 @@
 
 private:
     sk_sp<SkColorFilter> createInstance() override {
-        return SkColorFilters::Matrix(mMatrix.data());
+        return SkColorFilters::Matrix(mMatrix.data(), SkColorFilters::Clamp::kNo);
     }
 
 private:
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 6117330..7974a37 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -19,6 +19,7 @@
 import static android.companion.CompanionDeviceManager.RESULT_CANCELED;
 import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
 import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_SECURITY_ERROR;
 import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
@@ -33,6 +34,7 @@
 import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_TITLES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_SELF_MANAGED_PROFILES;
+import static com.android.companiondevicemanager.Utils.RESULT_CODE_TO_REASON;
 import static com.android.companiondevicemanager.Utils.getApplicationLabel;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.getIcon;
@@ -51,6 +53,7 @@
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
+import android.companion.Flags;
 import android.companion.IAssociationRequestCallback;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -231,7 +234,7 @@
         boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
         if (forCancelDialog) {
             Slog.i(TAG, "Cancelling the user confirmation");
-            cancel(RESULT_CANCELED);
+            cancel(RESULT_CANCELED, null);
             return;
         }
 
@@ -243,10 +246,15 @@
         if (appCallback == null) {
             return;
         }
-        Slog.e(TAG, "More than one AssociationRequests are processing.");
 
         try {
-            appCallback.onFailure(RESULT_INTERNAL_ERROR);
+            if (Flags.associationFailureCode()) {
+                appCallback.onFailure(
+                        RESULT_SECURITY_ERROR, "More than one AssociationRequests are processing.");
+            } else {
+                appCallback.onFailure(
+                        RESULT_INTERNAL_ERROR, "More than one AssociationRequests are processing.");
+            }
         } catch (RemoteException ignore) {
         }
     }
@@ -257,7 +265,7 @@
 
         // TODO: handle config changes without cancelling.
         if (!isDone()) {
-            cancel(RESULT_CANCELED); // will finish()
+            cancel(RESULT_CANCELED, null); // will finish()
         }
     }
 
@@ -331,7 +339,7 @@
                 && CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) {
             synchronized (LOCK) {
                 if (sDiscoveryStarted) {
-                    cancel(RESULT_DISCOVERY_TIMEOUT);
+                    cancel(RESULT_DISCOVERY_TIMEOUT, null);
                 }
             }
         }
@@ -371,7 +379,7 @@
         mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
     }
 
-    private void cancel(int failureCode) {
+    private void cancel(int errorCode, @Nullable CharSequence error) {
         if (isDone()) {
             Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
             return;
@@ -385,13 +393,14 @@
 
         // First send callback to the app directly...
         try {
-            Slog.i(TAG, "Sending onFailure to app due to failureCode=" + failureCode);
-            mAppCallback.onFailure(failureCode);
+            CharSequence errorMessage = error != null
+                    ? error : RESULT_CODE_TO_REASON.get(errorCode);
+            mAppCallback.onFailure(errorCode, errorMessage);
         } catch (RemoteException ignore) {
         }
 
         // ... then set result and finish ("sending" onActivityResult()).
-        setResultAndFinish(null, failureCode);
+        setResultAndFinish(null, errorCode);
     }
 
     private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
@@ -436,7 +445,7 @@
             }
         } catch (PackageManager.NameNotFoundException e) {
             Slog.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
-            cancel(RESULT_INTERNAL_ERROR);
+            cancel(RESULT_INTERNAL_ERROR, e.getMessage());
             return;
         }
 
@@ -625,7 +634,7 @@
         // Disable the button, to prevent more clicks.
         v.setEnabled(false);
 
-        cancel(RESULT_USER_REJECTED);
+        cancel(RESULT_USER_REJECTED, null);
     }
 
     private void onShowHelperDialog(View view) {
@@ -755,8 +764,8 @@
             };
 
     @Override
-    public void onShowHelperDialogFailed() {
-        cancel(RESULT_INTERNAL_ERROR);
+    public void onShowHelperDialogFailed(CharSequence errorMessage) {
+        cancel(RESULT_INTERNAL_ERROR, errorMessage);
     }
 
     @Override
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index ec92987..b2d78da 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -54,7 +54,7 @@
     private Button mButton;
 
     interface CompanionVendorHelperDialogListener {
-        void onShowHelperDialogFailed();
+        void onShowHelperDialogFailed(CharSequence error);
         void onHelperDialogDismissed();
     }
 
@@ -110,7 +110,7 @@
             appLabel = getApplicationLabel(getContext(), packageName, userId);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
-            mListener.onShowHelperDialogFailed();
+            mListener.onShowHelperDialogFailed(e.getMessage());
             return;
         }
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
index 8c14f80..2f97132 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -16,6 +16,15 @@
 
 package com.android.companiondevicemanager;
 
+import static android.companion.CompanionDeviceManager.REASON_CANCELED;
+import static android.companion.CompanionDeviceManager.REASON_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.REASON_USER_REJECTED;
+import static android.companion.CompanionDeviceManager.RESULT_CANCELED;
+import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED;
+
+import static java.util.Collections.unmodifiableMap;
+
 import android.annotation.NonNull;
 import android.annotation.StringRes;
 import android.content.Context;
@@ -31,6 +40,9 @@
 import android.os.ResultReceiver;
 import android.text.Html;
 import android.text.Spanned;
+import android.util.ArrayMap;
+
+import java.util.Map;
 
 /**
  * Utilities.
@@ -40,6 +52,17 @@
             "android.companion.vendor_icon";
     private static final String COMPANION_DEVICE_ACTIVITY_VENDOR_NAME =
             "android.companion.vendor_name";
+    // This map solely the common error messages that occur during the Association
+    // creation process.
+    static final Map<Integer, String> RESULT_CODE_TO_REASON;
+    static {
+        final Map<Integer, String> map = new ArrayMap<>();
+        map.put(RESULT_CANCELED, REASON_CANCELED);
+        map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED);
+        map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT);
+
+        RESULT_CODE_TO_REASON = unmodifiableMap(map);
+    }
 
     /**
      * Convert an instance of a "locally-defined" ResultReceiver to an instance of
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7c0db8d..0b364ac 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -541,6 +541,16 @@
 }
 
 flag {
+    name: "clipboard_shared_transitions"
+    namespace: "systemui"
+    description: "Show shared transitions from clipboard"
+    bug: "360843770"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "clipboard_image_timeout"
     namespace: "systemui"
     description: "Wait for clipboard image to load before showing UI"
@@ -1346,3 +1356,14 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "face_message_defer_update"
+    namespace: "systemui"
+    description: "Only analyze the last n frames when determining whether to defer a face auth help message like low light"
+    bug: "351863611"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0bef05dc..9e292d0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -68,7 +68,6 @@
 import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.Dp
@@ -82,7 +81,6 @@
 import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.thenIf
-import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.res.R
@@ -137,14 +135,16 @@
                 .notificationHeadsUpHeight(stackScrollView)
                 .debugBackground(viewModel, DEBUG_HUN_COLOR)
                 .onGloballyPositioned { coordinates: LayoutCoordinates ->
+                    val positionInWindow = coordinates.positionInWindow()
                     val boundsInWindow = coordinates.boundsInWindow()
                     debugLog(viewModel) {
                         "HUNS onGloballyPositioned:" +
                             " size=${coordinates.size}" +
                             " bounds=$boundsInWindow"
                     }
-                    // Note: boundsInWindow doesn't scroll off the screen
-                    stackScrollView.setHeadsUpTop(boundsInWindow.top)
+                    // Note: boundsInWindow doesn't scroll off the screen, so use positionInWindow
+                    // for top bound, which can scroll off screen while snoozing
+                    stackScrollView.setHeadsUpTop(positionInWindow.y)
                     stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
                 }
     )
@@ -159,16 +159,10 @@
     stackScrollView: NotificationScrollView,
     viewModel: NotificationsPlaceholderViewModel,
 ) {
-    val context = LocalContext.current
-    val density = LocalDensity.current
-    val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
-    val headsUpPadding =
-        with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
-
     val isHeadsUp by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false)
 
     var scrollOffset by remember { mutableFloatStateOf(0f) }
-    val minScrollOffset = -(statusBarHeight + headsUpPadding.toFloat())
+    val minScrollOffset = -(stackScrollView.getHeadsUpInset().toFloat())
     val maxScrollOffset = 0f
 
     val scrollableState = rememberScrollableState { delta ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index c5518b0..2ba4bf9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -834,4 +835,86 @@
                     )
                 )
         }
+
+    /**
+     * KTF: LOCKSCREEN -> ALTERNATE_BOUNCER starts but then STL: GLANCEABLE_HUB -> BLANK interrupts.
+     *
+     * Verifies that we correctly cancel the previous KTF state before starting the glanceable hub
+     * transition.
+     */
+    @Test
+    fun transition_to_blank_after_ktf_started_another_transition() =
+        testScope.runTest {
+            // Another transition has already started to the alternate bouncer.
+            keyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    from = LOCKSCREEN,
+                    to = ALTERNATE_BOUNCER,
+                    animator = null,
+                    ownerName = "external",
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                ),
+            )
+
+            val allSteps by collectValues(keyguardTransitionRepository.transitions)
+            // Keep track of existing size to drop any pre-existing steps that we don't
+            // care about.
+            val numToDrop = allSteps.size
+
+            sceneTransitions.value = hubToBlank
+            runCurrent()
+            progress.emit(0.4f)
+            runCurrent()
+            // We land on blank.
+            sceneTransitions.value = Idle(CommunalScenes.Blank)
+
+            // We should cancel the previous ALTERNATE_BOUNCER transition and transition back
+            // to the GLANCEABLE_HUB before we can transition away from it.
+            assertThat(allSteps.drop(numToDrop))
+                .containsExactly(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = ALTERNATE_BOUNCER,
+                        transitionState = CANCELED,
+                        value = 0f,
+                        ownerName = "external",
+                    ),
+                    TransitionStep(
+                        from = ALTERNATE_BOUNCER,
+                        to = GLANCEABLE_HUB,
+                        transitionState = STARTED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = ALTERNATE_BOUNCER,
+                        to = GLANCEABLE_HUB,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    ),
+                )
+                .inOrder()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 1bc2e24..5a6f2be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1968,47 +1968,6 @@
 
     @Test
     @DisableSceneContainer
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun glanceableHubToLockscreen_communalKtfRefactor() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-            runCurrent()
-            clearInvocations(transitionRepository)
-
-            // WHEN a transition away from glanceable hub starts
-            val currentScene = CommunalScenes.Communal
-            val targetScene = CommunalScenes.Blank
-
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = currentScene,
-                        toScene = targetScene,
-                        currentScene = flowOf(targetScene),
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            communalSceneInteractor.setTransitionState(transitionState)
-            progress.value = .1f
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.LOCKSCREEN,
-                    animatorAssertion = { it.isNull() }, // transition should be manually animated
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @DisableSceneContainer
     @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToDozing() =
         testScope.runTest {
@@ -2260,54 +2219,6 @@
         }
 
     @Test
-    @DisableSceneContainer
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun glanceableHubToDreaming_communalKtfRefactor() =
-        testScope.runTest {
-            // GIVEN that we are dreaming and not dozing
-            powerInteractor.setAwakeForTest()
-            keyguardRepository.setDreaming(true)
-            keyguardRepository.setDreamingWithOverlay(true)
-            keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
-            )
-            advanceTimeBy(600L)
-
-            // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-            runCurrent()
-            clearInvocations(transitionRepository)
-
-            // WHEN a transition away from glanceable hub starts
-            val currentScene = CommunalScenes.Communal
-            val targetScene = CommunalScenes.Blank
-
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = currentScene,
-                        toScene = targetScene,
-                        currentScene = flowOf(targetScene),
-                        progress = flowOf(0f, 0.1f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            communalSceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.DREAMING,
-                    animatorAssertion = { it.isNull() }, // transition should be manually animated
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
     @BrokenWithSceneContainer(339465026)
     @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToOccludedDoesNotTriggerWhenDreamStateChanges_communalKtfRefactor() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6dbe94b..a407468 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -360,6 +360,25 @@
         }
 
     @Test
+    fun alpha_transitionBetweenHubAndDream_isZero() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha(viewState))
+
+            // Default value check
+            assertThat(alpha).isEqualTo(1f)
+
+            // Start transitioning between DREAM and HUB but don't finish.
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.DREAMING,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope = testScope,
+                throughTransitionState = TransitionState.STARTED,
+            )
+
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
     @EnableSceneContainer
     fun alpha_transitionToHub_isZero_scene_container() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index c15a4e5..163b9b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -58,6 +58,7 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.shared.logger.sceneLogger
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
@@ -133,6 +134,7 @@
                 sceneInteractor = sceneInteractor,
                 falsingInteractor = kosmos.falsingInteractor,
                 powerInteractor = kosmos.powerInteractor,
+                logger = kosmos.sceneLogger,
                 motionEventHandlerReceiver = {},
             )
             .apply { setTransitionState(transitionState) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 64a13de..8f8d2e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -1682,6 +1682,7 @@
             underTest.start()
 
             // run all pending dismiss succeeded/cancelled calls from setup:
+            runCurrent()
             kosmos.fakeExecutor.runAllReady()
 
             val dismissCallback: IKeyguardDismissCallback = mock()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index b315783..f856c55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.logger.sceneLogger
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.testKosmos
@@ -72,6 +73,7 @@
                 sceneInteractor = sceneInteractor,
                 falsingInteractor = kosmos.falsingInteractor,
                 powerInteractor = kosmos.powerInteractor,
+                logger = kosmos.sceneLogger,
                 motionEventHandlerReceiver = { motionEventHandler ->
                     this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
                 },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index eac86e5..ce9b3be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -23,7 +23,6 @@
 import android.os.Handler
 import android.testing.TestableLooper
 import android.view.View
-import android.widget.FrameLayout
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -46,6 +45,8 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.never
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -67,12 +68,9 @@
 
     @Mock private lateinit var session: SmartspaceSession
 
-    private lateinit var controller: CommunalSmartspaceController
+    private val preconditionListenerCaptor = argumentCaptor<SmartspacePrecondition.Listener>()
 
-    // TODO(b/272811280): Remove usage of real view
-    private val fakeParent by lazy {
-        FrameLayout(context)
-    }
+    private lateinit var controller: CommunalSmartspaceController
 
     /**
      * A class which implements SmartspaceView and extends View. This is mocked to provide the right
@@ -155,6 +153,26 @@
         verify(session).close()
     }
 
+    /** Ensures smartspace session begins when precondition is met if there is any listener. */
+    @Test
+    fun testConnectOnPreconditionMet() {
+        // Precondition not met
+        `when`(precondition.conditionsMet()).thenReturn(false)
+        controller.addListener(listener)
+
+        // Verify session not created because precondition not met
+        verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+        // Precondition met
+        `when`(precondition.conditionsMet()).thenReturn(true)
+        verify(precondition).addListener(preconditionListenerCaptor.capture())
+        val preconditionListener = preconditionListenerCaptor.firstValue
+        preconditionListener.onCriteriaChanged()
+
+        // Verify session created
+        verify(smartspaceManager).createSmartspaceSession(any())
+    }
+
     /**
      * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace
      * view is detached.
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 040af90..aabfbd1 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -20,6 +20,7 @@
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
 import static com.android.systemui.Flags.clipboardImageTimeout;
+import static com.android.systemui.Flags.clipboardSharedTransitions;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -33,7 +34,6 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -207,7 +207,7 @@
         mClipboardUtils = clipboardUtils;
         mBgExecutor = bgExecutor;
 
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             mView.setCallbacks(this);
         } else {
             mView.setCallbacks(mClipboardCallbacks);
@@ -220,7 +220,7 @@
         });
 
         mTimeoutHandler.setOnTimeoutRunnable(() -> {
-            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            if (clipboardSharedTransitions()) {
                 finish(CLIPBOARD_OVERLAY_TIMED_OUT);
             } else {
                 mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
@@ -232,7 +232,7 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
-                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                    if (clipboardSharedTransitions()) {
                         finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
                     } else {
                         mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
@@ -248,7 +248,7 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (SCREENSHOT_ACTION.equals(intent.getAction())) {
-                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                    if (clipboardSharedTransitions()) {
                         finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
                     } else {
                         mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
@@ -481,7 +481,7 @@
                 remoteAction.ifPresent(action -> {
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
                     mView.post(() -> mView.setActionChip(action, () -> {
-                        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                        if (clipboardSharedTransitions()) {
                             finish(CLIPBOARD_OVERLAY_ACTION_TAPPED);
                         } else {
                             mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
@@ -528,7 +528,7 @@
                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
                         if (!mView.isInTouchRegion(
                                 (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
-                            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                            if (clipboardSharedTransitions()) {
                                 finish(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
                             } else {
                                 mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
@@ -575,6 +575,10 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
+                // check again after animation to see if we should still be minimized
+                if (mIsMinimized && !shouldShowMinimized(mWindow.getWindowInsets())) {
+                    animateFromMinimized();
+                }
                 if (mOnUiUpdate != null) {
                     mOnUiUpdate.run();
                 }
@@ -690,14 +694,14 @@
 
     @Override
     public void onDismissButtonTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             finish(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
         }
     }
 
     @Override
     public void onRemoteCopyButtonTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED,
                     IntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext));
         }
@@ -705,7 +709,7 @@
 
     @Override
     public void onShareButtonTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             if (mClipboardModel.getType() != ClipboardModel.Type.OTHER) {
                 finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
                         IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
@@ -715,7 +719,7 @@
 
     @Override
     public void onPreviewTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             switch (mClipboardModel.getType()) {
                 case TEXT:
                     finish(CLIPBOARD_OVERLAY_EDIT_TAPPED,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index 6343752..5bbb46d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -223,19 +223,15 @@
             // KTF for this case and just collect the new progress instead.
             collectProgress(transition)
         } else if (transition.toScene == CommunalScenes.Communal) {
-            if (currentTransitionId != null) {
-                if (currentToState == KeyguardState.GLANCEABLE_HUB) {
-                    transitionKtfTo(transitionInteractor.getStartedFromState())
-                }
+            if (currentToState == KeyguardState.GLANCEABLE_HUB) {
+                transitionKtfTo(transitionInteractor.getStartedFromState())
             }
             startTransitionToGlanceableHub()
             collectProgress(transition)
         } else if (transition.toScene == CommunalScenes.Blank) {
-            if (currentTransitionId != null) {
-                // Another transition started before this one is completed. Transition to the
-                // GLANCEABLE_HUB state so that we can properly transition away from it.
-                transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
-            }
+            // Another transition started before this one is completed. Transition to the
+            // GLANCEABLE_HUB state so that we can properly transition away from it.
+            transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
             startTransitionFromGlanceableHub()
             collectProgress(transition)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index 80db535..012c844 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -65,6 +65,12 @@
     var preconditionListener =
         object : SmartspacePrecondition.Listener {
             override fun onCriteriaChanged() {
+                if (session == null && hasActiveSessionListeners()) {
+                    Log.d(TAG, "Precondition criteria changed. Attempting to connect session.")
+                    connectSession()
+                    return
+                }
+
                 reloadSmartspace()
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e5f3a57..4d75d66 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -337,10 +337,6 @@
     // TODO(b/278714186) Tracking Bug
     @JvmField
     val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag("clipboard_image_timeout", teamfood = true)
-    // TODO(b/279405451): Tracking Bug
-    @JvmField
-    val CLIPBOARD_SHARED_TRANSITIONS =
-            unreleasedFlag("clipboard_shared_transitions", teamfood = true)
 
     // 1900
     @JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 06f77bf..54964d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
@@ -45,6 +47,7 @@
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.kotlin.BooleanFlowOperators.any
 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
@@ -198,29 +201,37 @@
             .distinctUntilChanged()
 
     /**
-     * Keyguard should not show while the communal hub is fully visible. This check is added since
-     * at the moment, closing the notification shade will cause the keyguard alpha to be set back to
-     * 1. Also ensure keyguard is never visible when GONE.
+     * Keyguard states which should fully hide the keyguard.
+     *
+     * Note: [GONE] is not included as it is handled separately.
+     */
+    private val hiddenKeyguardStates = listOf(OCCLUDED, DREAMING, GLANCEABLE_HUB)
+
+    /**
+     * Keyguard should not show if fully transitioned into a hidden keyguard state or if
+     * transitioning between hidden states.
      */
     private val hideKeyguard: Flow<Boolean> =
-        combine(
-                communalInteractor.isIdleOnCommunal,
+        (hiddenKeyguardStates.map { state ->
                 keyguardTransitionInteractor
-                    .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+                    .transitionValue(state)
                     .map { it == 1f }
-                    .onStart { emit(false) },
-                keyguardTransitionInteractor
-                    .transitionValue(OCCLUDED)
-                    .map { it == 1f }
-                    .onStart { emit(false) },
-                keyguardTransitionInteractor
-                    .transitionValue(KeyguardState.DREAMING)
-                    .map { it == 1f }
-                    .onStart { emit(false) },
-            ) { isIdleOnCommunal, isGone, isOccluded, isDreaming ->
-                isIdleOnCommunal || isGone || isOccluded || isDreaming
-            }
-            .distinctUntilChanged()
+                    .onStart { emit(false) }
+            } +
+                listOf(
+                    communalInteractor.isIdleOnCommunal,
+                    keyguardTransitionInteractor
+                        .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+                        .map { it == 1f }
+                        .onStart { emit(false) },
+                    keyguardTransitionInteractor
+                        .isInTransitionWhere(
+                            fromStatePredicate = { hiddenKeyguardStates.contains(it) },
+                            toStatePredicate = { hiddenKeyguardStates.contains(it) },
+                        )
+                        .onStart { emit(false) },
+                ))
+            .any()
 
     /** Last point that the root view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 5885193..75cb017d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -28,8 +28,6 @@
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.util.kotlin.getValue
-import com.android.systemui.util.kotlin.pairwiseBy
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -83,20 +81,7 @@
      * Note that during a transition between scenes, more than one scene might be rendered but only
      * one is considered the committed/current scene.
      */
-    val currentScene: StateFlow<SceneKey> =
-        repository.currentScene
-            .pairwiseBy(initialValue = repository.currentScene.value) { from, to ->
-                logger.logSceneChangeCommitted(
-                    from = from,
-                    to = to,
-                )
-                to
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = repository.currentScene.value,
-            )
+    val currentScene: StateFlow<SceneKey> = repository.currentScene
 
     /**
      * The current state of the transition.
@@ -234,14 +219,15 @@
             return
         }
 
-        logger.logSceneChangeRequested(
+        onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(resolvedScene, sceneState) }
+
+        logger.logSceneChanged(
             from = currentSceneKey,
             to = resolvedScene,
             reason = loggingReason,
             isInstant = false,
         )
 
-        onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(resolvedScene, sceneState) }
         repository.changeScene(resolvedScene, transitionKey)
     }
 
@@ -274,7 +260,7 @@
             return
         }
 
-        logger.logSceneChangeRequested(
+        logger.logSceneChanged(
             from = currentSceneKey,
             to = resolvedScene,
             reason = loggingReason,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index cf1518e..94c94e2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -43,7 +43,7 @@
         )
     }
 
-    fun logSceneChangeRequested(
+    fun logSceneChanged(
         from: SceneKey,
         to: SceneKey,
         reason: String,
@@ -60,7 +60,7 @@
             },
             messagePrinter = {
                 buildString {
-                    append("Scene change requested: $str1 → $str2")
+                    append("Scene changed: $str1 → $str2")
                     if (isInstant) {
                         append(" (instant)")
                     }
@@ -70,21 +70,6 @@
         )
     }
 
-    fun logSceneChangeCommitted(
-        from: SceneKey,
-        to: SceneKey,
-    ) {
-        logBuffer.log(
-            tag = TAG,
-            level = LogLevel.INFO,
-            messageInitializer = {
-                str1 = from.toString()
-                str2 = to.toString()
-            },
-            messagePrinter = { "Scene change committed: $str1 → $str2" },
-        )
-    }
-
     fun logSceneTransition(transitionState: ObservableTransitionState) {
         when (transitionState) {
             is ObservableTransitionState.Transition -> {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 7f7f0f1..f8a9f8c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.Scenes
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -42,6 +43,7 @@
     private val sceneInteractor: SceneInteractor,
     private val falsingInteractor: FalsingInteractor,
     private val powerInteractor: PowerInteractor,
+    private val logger: SceneLogger,
     @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
 ) : SysUiViewModel() {
     /**
@@ -135,16 +137,29 @@
                 else -> null
             }
 
-        return interactionTypeOrNull?.let { interactionType ->
-            // It's important that the falsing system is always queried, even if no enforcement will
-            // occur. This helps build up the right signal in the system.
-            val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
+        val fromScene = currentScene.value
+        val isAllowed =
+            interactionTypeOrNull?.let { interactionType ->
+                // It's important that the falsing system is always queried, even if no enforcement
+                // will occur. This helps build up the right signal in the system.
+                val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
 
-            // Only enforce falsing if moving from the lockscreen scene to a new scene.
-            val fromLockscreenScene = currentScene.value == Scenes.Lockscreen
+                // Only enforce falsing if moving from the lockscreen scene to a new scene.
+                val fromLockscreenScene = fromScene == Scenes.Lockscreen
 
-            !fromLockscreenScene || !isFalseTouch
-        } ?: true
+                !fromLockscreenScene || !isFalseTouch
+            } ?: true
+
+        if (isAllowed) {
+            // A scene change is guaranteed; log it.
+            logger.logSceneChanged(
+                from = fromScene,
+                to = toScene,
+                reason = "user interaction",
+                isInstant = false,
+            )
+        }
+        return isAllowed
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 50ea3bb..448f7c4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -99,6 +99,9 @@
     ) {
         val displays = getDisplaysToScreenshot(screenshotRequest.type)
         val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
+        if (displays.isEmpty()) {
+            Log.wtf(TAG, "No displays found for screenshot.")
+        }
         displays.forEach { display ->
             val displayId = display.displayId
             var screenshotHandler: ScreenshotHandler =
@@ -219,8 +222,7 @@
     }
 
     private fun getScreenshotController(display: Display): InteractiveScreenshotHandler {
-        val controller =
-            screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
+        val controller = screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
         screenshotController = controller
         return controller
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
index ee1944e..ad27da9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
@@ -214,8 +214,7 @@
                 break;
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
-                if (mCurrentDraggingBoundary != CropBoundary.NONE
-                        && mActivePointerId == event.getPointerId(mActivePointerId)) {
+                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                     updateListener(MotionEvent.ACTION_UP, event.getX(0));
                     return true;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 7c3072d..6a2c602 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -139,6 +139,9 @@
     /** Fraction of shade expansion. */
     private float mExpansionFraction;
 
+    /** Fraction of QS expansion. 0 when in shade, 1 when in QS. */
+    private float mQsExpansionFraction;
+
     /** Height of the notifications panel when expansion completes. */
     private float mStackEndHeight;
 
@@ -208,6 +211,14 @@
     }
 
     /**
+     * @param expansionFraction Fraction of QS expansion.
+     */
+    public void setQsExpansionFraction(float expansionFraction) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        mQsExpansionFraction = expansionFraction;
+    }
+
+    /**
      * @param isSwipingUp Whether we are swiping up.
      */
     public void setSwipingUp(boolean isSwipingUp) {
@@ -258,6 +269,14 @@
     }
 
     /**
+     * @return Fraction of QS expansion.
+     */
+    public float getQsExpansionFraction() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+        return mQsExpansionFraction;
+    }
+
+    /**
      * @see #getStackHeight()
      */
     public void setStackHeight(float stackHeight) {
@@ -837,6 +856,7 @@
         pw.println("mAppearFraction=" + mAppearFraction);
         pw.println("mAppearing=" + mAppearing);
         pw.println("mExpansionFraction=" + mExpansionFraction);
+        pw.println("mQsExpansionFraction=" + mQsExpansionFraction);
         pw.println("mExpandingVelocity=" + mExpandingVelocity);
         pw.println("mOverScrollTopAmount=" + mOverScrollTopAmount);
         pw.println("mOverScrollBottomAmount=" + mOverScrollBottomAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 07b0b83..f26f840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1244,6 +1244,7 @@
     @Override
     public void setHeadsUpTop(float headsUpTop) {
         mAmbientState.setHeadsUpTop(headsUpTop);
+        requestChildrenUpdate();
     }
 
     @Override
@@ -1563,6 +1564,12 @@
         }
     }
 
+    @Override
+    public void setQsExpandFraction(float expandFraction) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        mAmbientState.setQsExpansionFraction(expandFraction);
+    }
+
     /**
      * Update the height of the panel.
      *
@@ -2534,6 +2541,11 @@
         return getTopHeadsUpIntrinsicHeight();
     }
 
+    @Override
+    public int getHeadsUpInset() {
+        return mHeadsUpInset;
+    }
+
     /**
      * Calculate the gap height between two different views
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index f35d666..55f0566 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -132,6 +132,7 @@
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpNotificationViewControllerEmptyImpl;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -773,7 +774,7 @@
                     mHeadsUpManager,
                     statusBarService.get(),
                     getHeadsUpCallback(),
-                    new HeadsUpNotificationViewControllerEmptyImpl()
+                    getHeadsUpNotificationViewController()
             );
         }
         mNotificationRoundnessManager = notificationRoundnessManager;
@@ -1851,6 +1852,32 @@
         return mTouchHandler;
     }
 
+    private HeadsUpNotificationViewController getHeadsUpNotificationViewController() {
+        HeadsUpNotificationViewController headsUpViewController;
+        if (SceneContainerFlag.isEnabled()) {
+            headsUpViewController = new HeadsUpNotificationViewController() {
+                @Override
+                public void setHeadsUpDraggingStartingHeight(int startHeight) {
+                    // do nothing
+                }
+
+                @Override
+                public void setTrackedHeadsUp(ExpandableNotificationRow expandableNotificationRow) {
+                    setTrackingHeadsUp(expandableNotificationRow);
+                }
+
+                @Override
+                public void startExpand(float newX, float newY, boolean startTracking,
+                        float expandedHeight) {
+                    // do nothing
+                }
+            };
+        } else {
+            headsUpViewController = new HeadsUpNotificationViewControllerEmptyImpl();
+        }
+        return headsUpViewController;
+    }
+
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("mMaxAlphaFromView=" + mMaxAlphaFromView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index aee1d3e..0c2b5ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import static androidx.core.math.MathUtils.clamp;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -890,7 +888,14 @@
                 continue;
             }
             ExpandableViewState childState = row.getViewState();
-            if (topHeadsUpEntry == null && row.mustStayOnScreen() && !childState.headsUpIsVisible) {
+            boolean shouldSetTopHeadsUpEntry;
+            if (SceneContainerFlag.isEnabled()) {
+                shouldSetTopHeadsUpEntry = row.isHeadsUp();
+            } else {
+                shouldSetTopHeadsUpEntry = row.mustStayOnScreen();
+            }
+            if (topHeadsUpEntry == null && shouldSetTopHeadsUpEntry
+                    && !childState.headsUpIsVisible) {
                 topHeadsUpEntry = row;
                 childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
             }
@@ -898,7 +903,7 @@
             float unmodifiedEndLocation = childState.getYTranslation() + childState.height;
             if (mIsExpanded) {
                 if (SceneContainerFlag.isEnabled()) {
-                    if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
+                    if (shouldHunBeVisibleWhenScrolled(row.isHeadsUp(),
                             childState.headsUpIsVisible, row.showingPulsing(),
                             ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
                         // the height of this child before clamping it to the top
@@ -909,10 +914,19 @@
                                 /* viewState = */ childState
                         );
                         float baseZ = ambientState.getBaseZHeight();
-                        if (headsUpTranslation < ambientState.getStackTop()) {
-                            // HUN displayed above the stack top, it needs a fix shadow
-                            childState.setZTranslation(baseZ + mPinnedZTranslationExtra);
-                        } else {
+                        if (headsUpTranslation > ambientState.getStackTop()
+                                && row.isAboveShelf()) {
+                            // HUN displayed outside of the stack during transition from Gone/LS;
+                            // add a shadow that corresponds to the transition progress.
+                            float fraction = 1 - ambientState.getExpansionFraction();
+                            childState.setZTranslation(baseZ + fraction * mPinnedZTranslationExtra);
+                        } else if (headsUpTranslation < ambientState.getStackTop()
+                                && row.isAboveShelf()) {
+                            // HUN displayed outside of the stack during transition from QS;
+                            // add a shadow that corresponds to the transition progress.
+                            float fraction = ambientState.getQsExpansionFraction();
+                            childState.setZTranslation(baseZ + fraction * mPinnedZTranslationExtra);
+                        } else if (headsUpTranslation > ambientState.getStackTop()) {
                             // HUN displayed within the stack, add a shadow if it overlaps with
                             // other elements.
                             //
@@ -927,6 +941,8 @@
                                     /* baseZ = */ baseZ,
                                     /* viewState = */ childState
                             );
+                        } else {
+                            childState.setZTranslation(baseZ);
                         }
                         if (isTopEntry && row.isAboveShelf()) {
                             clampHunToMaxTranslation(
@@ -1081,7 +1097,7 @@
         if (scrollingContentTopPadding > 0f) {
             // scrollingContentTopPadding makes a gap between the bottom of the HUN and the top
             // of the scrolling content. Use this to animate to the full shadow.
-            shadowFraction = clamp(overlap / scrollingContentTopPadding, 0f, 1f);
+            shadowFraction = Math.clamp(overlap / scrollingContentTopPadding, 0f, 1f);
         }
 
         if (overlap > 0.0f) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 6226fe7..288924d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -80,9 +80,15 @@
     /** sets the current expand fraction */
     fun setExpandFraction(expandFraction: Float)
 
+    /** sets the current QS expand fraction */
+    fun setQsExpandFraction(expandFraction: Float)
+
     /** Sets whether the view is displayed in doze mode. */
     fun setDozing(dozing: Boolean)
 
+    /** Gets the inset for HUNs when they are not visible */
+    fun getHeadsUpInset(): Int
+
     /** Adds a listener to be notified, when the stack height might have changed. */
     fun addStackHeightChangedListener(runnable: Runnable)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 950b14d..40761e07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -85,9 +85,14 @@
             launch {
                 viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) }
             }
+            launch { viewModel.qsExpandFraction.collect { view.setQsExpandFraction(it) } }
             launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
             launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
-            launch { viewModel.shouldResetStackTop.filter { it }.collect { view.setStackTop(0f) } }
+            launch {
+                viewModel.shouldResetStackTop
+                    .filter { it }
+                    .collect { view.setStackTop(-(view.getHeadsUpInset().toFloat())) }
+            }
 
             launchAndDispose {
                 view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index ed69e6f..6489264 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -124,6 +124,9 @@
             .distinctUntilChanged()
             .dumpWhileCollecting("expandFraction")
 
+    val qsExpandFraction: Flow<Float> =
+        shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction")
+
     val shouldResetStackTop: Flow<Boolean> =
         sceneInteractor.transitionState
             .mapNotNull { state ->
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index c425e82..5fc1971 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
+import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -26,7 +27,6 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -47,6 +47,8 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.PersistableBundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.view.WindowInsets;
 import android.view.textclassifier.TextLinks;
 
@@ -130,7 +132,6 @@
                 new ClipData.Item("Test Item"));
 
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, true); // turned off for old tests
     }
 
     /**
@@ -299,8 +300,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() {
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
         initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
@@ -311,6 +312,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onShareTapped() {
         initController();
         mOverlayController.setClipData(mSampleClipData, "");
@@ -324,8 +326,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onDismissTapped_sharedTransitionsOff() {
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
         initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
@@ -336,6 +338,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onDismissTapped() {
         initController();
 
@@ -350,7 +353,6 @@
 
     @Test
     public void test_multipleDismissals_dismissesOnce_sharedTransitionsOff() {
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
         initController();
         mCallbacks.onSwipeDismissInitiated(mAnimator);
         mCallbacks.onDismissButtonTapped();
@@ -362,6 +364,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_multipleDismissals_dismissesOnce() {
         initController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index ad029d7..7e79019 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -296,6 +296,7 @@
             collapsedHeight = 100,
             intrinsicHeight = intrinsicHunHeight,
         )
+        ambientState.qsExpansionFraction = 1.0f
         whenever(notificationRow.isAboveShelf).thenReturn(true)
 
         // When
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index b3a2da4..d56f17b 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -20,6 +20,7 @@
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
 import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_SECURITY_ERROR;
 import static android.content.ComponentName.createRelative;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 
@@ -40,6 +41,7 @@
 import android.companion.AssociatedDevice;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
+import android.companion.Flags;
 import android.companion.IAssociationRequestCallback;
 import android.content.ComponentName;
 import android.content.Context;
@@ -182,7 +184,11 @@
             String errorMessage = "3p apps are not allowed to create associations on watch.";
             Slog.e(TAG, errorMessage);
             try {
-                callback.onFailure(RESULT_INTERNAL_ERROR);
+                if (Flags.associationFailureCode()) {
+                    callback.onFailure(RESULT_SECURITY_ERROR, errorMessage);
+                } else {
+                    callback.onFailure(RESULT_INTERNAL_ERROR, errorMessage);
+                }
             } catch (RemoteException e) {
                 // ignored
             }
@@ -251,9 +257,12 @@
         } catch (SecurityException e) {
             // Since, at this point the caller is our own UI, we need to catch the exception on
             // forward it back to the application via the callback.
-            Slog.e(TAG, e.getMessage());
             try {
-                callback.onFailure(RESULT_INTERNAL_ERROR);
+                if (Flags.associationFailureCode()) {
+                    callback.onFailure(RESULT_SECURITY_ERROR, e.getMessage());
+                } else {
+                    callback.onFailure(RESULT_INTERNAL_ERROR, e.getMessage());
+                }
             } catch (RemoteException ignore) {
             }
             return;
@@ -378,7 +387,7 @@
             // Send the association back via the app's callback
             if (callback != null) {
                 try {
-                    callback.onFailure(RESULT_INTERNAL_ERROR);
+                    callback.onFailure(RESULT_INTERNAL_ERROR, "Association doesn't exist.");
                 } catch (RemoteException ignore) {
                 }
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 756a750..f1bdc05 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -50,7 +50,6 @@
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
@@ -101,15 +100,12 @@
 import static android.os.Process.BLUETOOTH_UID;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.INVALID_UID;
-import static android.os.Process.NETWORK_STACK_UID;
-import static android.os.Process.NFC_UID;
 import static android.os.Process.PHONE_UID;
 import static android.os.Process.PROC_OUT_LONG;
 import static android.os.Process.PROC_SPACE_TERM;
 import static android.os.Process.ROOT_UID;
 import static android.os.Process.SCHED_FIFO;
 import static android.os.Process.SCHED_RESET_ON_FORK;
-import static android.os.Process.SE_UID;
 import static android.os.Process.SHELL_UID;
 import static android.os.Process.SIGNAL_USR1;
 import static android.os.Process.SYSTEM_UID;
@@ -145,8 +141,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
@@ -155,7 +149,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
@@ -265,10 +258,7 @@
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
-import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetManagerInternal;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
@@ -316,9 +306,6 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
-import android.media.audiofx.AudioEffect;
-import android.net.ConnectivityManager;
-import android.net.Proxy;
 import android.net.Uri;
 import android.os.AppZygote;
 import android.os.BatteryStats;
@@ -374,9 +361,7 @@
 import android.server.ServerProtoEnums;
 import android.system.Os;
 import android.system.OsConstants;
-import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -386,7 +371,6 @@
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Pair;
-import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -440,7 +424,6 @@
 import com.android.server.BootReceiver;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.DisplayThread;
-import com.android.server.IntentResolver;
 import com.android.server.IoThread;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
@@ -452,7 +435,6 @@
 import com.android.server.SystemServiceManager;
 import com.android.server.ThreadPriorityBooster;
 import com.android.server.Watchdog;
-import com.android.server.am.ComponentAliasResolver.Resolution;
 import com.android.server.am.LowMemDetector.MemFactor;
 import com.android.server.appop.AppOpsService;
 import com.android.server.compat.PlatformCompat;
@@ -463,14 +445,12 @@
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.net.NetworkManagementInternal;
 import com.android.server.os.NativeTombstoneManager;
-import com.android.server.pm.Computer;
 import com.android.server.pm.Installer;
 import com.android.server.pm.SaferIntentUtils;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.SELinuxUtil;
-import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.sdksandbox.SdkSandboxManagerLocal;
 import com.android.server.stats.pull.StatsPullAtomService;
@@ -513,7 +493,6 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -538,7 +517,6 @@
 
     static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
     static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
-    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
     private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
     private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
@@ -566,9 +544,6 @@
 
     static final String SYSTEM_USER_HOME_NEEDED = "ro.system_user_home_needed";
 
-    // Maximum number of receivers an app can register.
-    private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
-
     // How long we wait for a launched process to attach to the activity manager
     // before we decide it's never going to come up for real.
     static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
@@ -653,15 +628,6 @@
     static final String EXTRA_BUGREPORT_NONCE = "android.intent.extra.BUGREPORT_NONCE";
     static final String EXTRA_EXTRA_ATTACHMENT_URI =
             "android.intent.extra.EXTRA_ATTACHMENT_URI";
-    /**
-     * It is now required for apps to explicitly set either
-     * {@link android.content.Context#RECEIVER_EXPORTED} or
-     * {@link android.content.Context#RECEIVER_NOT_EXPORTED} when registering a receiver for an
-     * unprotected broadcast in code.
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
 
     /**
      * The maximum number of bytes that {@link #setProcessStateSummary} accepts.
@@ -738,11 +704,9 @@
     // so that dispatch of foreground broadcasts gets precedence.
     private BroadcastQueue mBroadcastQueue;
 
-    @GuardedBy("this")
-    BroadcastStats mLastBroadcastStats;
-
-    @GuardedBy("this")
-    BroadcastStats mCurBroadcastStats;
+    // TODO: Add a consistent way of accessing the methods within this class. Currently, some
+    // methods require access while holding a lock, while others do not.
+    BroadcastController mBroadcastController;
 
     TraceErrorLogger mTraceErrorLogger;
 
@@ -862,12 +826,6 @@
     };
 
     /**
-     * Broadcast actions that will always be deliverable to unlaunched/background apps
-     */
-    @GuardedBy("this")
-    private ArraySet<String> mBackgroundLaunchBroadcasts;
-
-    /**
      * When an app has restrictions on the other apps that can have associations with it,
      * it appears here with a set of the allowed apps and also track debuggability of the app.
      */
@@ -1135,97 +1093,6 @@
     private final HashSet<Integer> mAlreadyLoggedViolatedStacks = new HashSet<Integer>();
     private static final int MAX_DUP_SUPPRESSED_STACKS = 5000;
 
-    /**
-     * Keeps track of all IIntentReceivers that have been registered for broadcasts.
-     * Hash keys are the receiver IBinder, hash value is a ReceiverList.
-     */
-    @GuardedBy("this")
-    final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
-
-    /**
-     * Resolver for broadcast intents to registered receivers.
-     * Holds BroadcastFilter (subclass of IntentFilter).
-     */
-    final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver
-            = new IntentResolver<BroadcastFilter, BroadcastFilter>() {
-        @Override
-        protected boolean allowFilterResult(
-                BroadcastFilter filter, List<BroadcastFilter> dest) {
-            IBinder target = filter.receiverList.receiver.asBinder();
-            for (int i = dest.size() - 1; i >= 0; i--) {
-                if (dest.get(i).receiverList.receiver.asBinder() == target) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        @Override
-        protected BroadcastFilter newResult(@NonNull Computer computer, BroadcastFilter filter,
-                int match, int userId, long customFlags) {
-            if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
-                    || userId == filter.owningUserId) {
-                return super.newResult(computer, filter, match, userId, customFlags);
-            }
-            return null;
-        }
-
-        @Override
-        protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
-            return input;
-        }
-
-        @Override
-        protected BroadcastFilter[] newArray(int size) {
-            return new BroadcastFilter[size];
-        }
-
-        @Override
-        protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
-            return packageName.equals(filter.packageName);
-        }
-    };
-
-    /**
-     * State of all active sticky broadcasts per user.  Keys are the action of the
-     * sticky Intent, values are an ArrayList of all broadcasted intents with
-     * that action (which should usually be one).  The SparseArray is keyed
-     * by the user ID the sticky is for, and can include UserHandle.USER_ALL
-     * for stickies that are sent to all users.
-     */
-    @GuardedBy("mStickyBroadcasts")
-    final SparseArray<ArrayMap<String, ArrayList<StickyBroadcast>>> mStickyBroadcasts =
-            new SparseArray<>();
-
-    @VisibleForTesting
-    static final class StickyBroadcast {
-        public Intent intent;
-        public boolean deferUntilActive;
-        public int originalCallingUid;
-        /** The snapshot process state of the app who sent this broadcast */
-        public int originalCallingAppProcessState;
-        public String resolvedDataType;
-
-        public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
-                int originalCallingUid, int originalCallingAppProcessState,
-                String resolvedDataType) {
-            final StickyBroadcast b = new StickyBroadcast();
-            b.intent = intent;
-            b.deferUntilActive = deferUntilActive;
-            b.originalCallingUid = originalCallingUid;
-            b.originalCallingAppProcessState = originalCallingAppProcessState;
-            b.resolvedDataType = resolvedDataType;
-            return b;
-        }
-
-        @Override
-        public String toString() {
-            return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
-                    + originalCallingUid + ", originalCallingAppProcessState="
-                    + originalCallingAppProcessState + ", type=" + resolvedDataType + "}";
-        }
-    }
-
     final ActiveServices mServices;
 
     final static class Association {
@@ -1687,7 +1554,7 @@
     // Encapsulates the global setting "hidden_api_blacklist_exemptions"
     final HiddenApiSettings mHiddenApiBlacklist;
 
-    private final PlatformCompat mPlatformCompat;
+    final PlatformCompat mPlatformCompat;
 
     PackageManagerInternal mPackageManagerInt;
     PermissionManagerServiceInternal mPermissionManagerInt;
@@ -2328,7 +2195,7 @@
                 mService.mBatteryStatsService.systemServicesReady();
                 mService.mServices.systemServicesReady();
             } else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
-                mService.startBroadcastObservers();
+                mService.mBroadcastController.startBroadcastObservers();
             } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
                 if (!refactorCrashrecovery()) {
                     mService.mPackageWatchdog.onPackagesReady();
@@ -2542,6 +2409,7 @@
         mPendingStartActivityUids = new PendingStartActivityUids();
         mUseFifoUiScheduling = false;
         mBroadcastQueue = injector.getBroadcastQueue(this);
+        mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
         mComponentAliasResolver = new ComponentAliasResolver(this);
     }
 
@@ -2584,6 +2452,7 @@
                 : new OomAdjuster(this, mProcessList, activeUids);
 
         mBroadcastQueue = mInjector.getBroadcastQueue(this);
+        mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
 
         mServices = new ActiveServices(this);
         mCpHelper = new ContentProviderHelper(this, true);
@@ -2652,6 +2521,7 @@
 
     void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
         mBroadcastQueue = broadcastQueue;
+        mBroadcastController.setBroadcastQueueForTest(broadcastQueue);
     }
 
     BroadcastQueue getBroadcastQueue() {
@@ -2686,18 +2556,6 @@
         mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
     }
 
-    private ArraySet<String> getBackgroundLaunchBroadcasts() {
-        if (mBackgroundLaunchBroadcasts == null) {
-            mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
-        }
-        return mBackgroundLaunchBroadcasts;
-    }
-
-    private String getWearRemoteIntentAction() {
-        return mContext.getResources().getString(
-                    com.android.internal.R.string.config_wearRemoteIntentAction);
-    }
-
     /**
      * Ensures that the given package name has an explicit set of allowed associations.
      * If it does not, give it an empty set.
@@ -2768,7 +2626,7 @@
     }
 
     /** Updates allowed associations for app info (specifically, based on debuggability).  */
-    private void updateAssociationForApp(ApplicationInfo appInfo) {
+    void updateAssociationForApp(ApplicationInfo appInfo) {
         ensureAllowedAssociations();
         PackageAssociationInfo pai = mAllowedAssociations.get(appInfo.packageName);
         if (pai != null) {
@@ -3894,7 +3752,7 @@
         forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED, null);
     }
 
-    private void forceStopPackage(final String packageName, int userId, int userRunningFlags,
+    void forceStopPackage(final String packageName, int userId, int userRunningFlags,
             String reason) {
         if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -4222,7 +4080,7 @@
         mPackageManagerInt.sendPackageRestartedBroadcast(packageName, uid, flags);
     }
 
-    private void cleanupDisabledPackageComponentsLocked(
+    void cleanupDisabledPackageComponentsLocked(
             String packageName, int userId, String[] changedClasses) {
 
         Set<String> disabledClasses = null;
@@ -4460,9 +4318,7 @@
 
         if (packageName == null) {
             // Remove all sticky broadcasts from this user.
-            synchronized (mStickyBroadcasts) {
-                mStickyBroadcasts.remove(userId);
-            }
+            mBroadcastController.removeStickyBroadcasts(userId);
         }
 
         ArrayList<ContentProviderRecord> providers = new ArrayList<>();
@@ -9335,10 +9191,6 @@
                 Settings.Global.DEVICE_PROVISIONED, 0) != 0;
     }
 
-    private void startBroadcastObservers() {
-        mBroadcastQueue.start(mContext.getContentResolver());
-    }
-
     private void updateForceBackgroundCheck(boolean enabled) {
         synchronized (this) {
             synchronized (mProcLock) {
@@ -10530,14 +10382,15 @@
                 pw.println(
                         "-------------------------------------------------------------------------------");
             }
-            dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+            mBroadcastController.dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
             pw.println();
             if (dumpAll) {
                 pw.println(
                         "-------------------------------------------------------------------------------");
             }
             if (dumpAll || dumpPackage != null) {
-                dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+                mBroadcastController.dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll,
+                        dumpPackage);
                 pw.println();
                 if (dumpAll) {
                     pw.println(
@@ -10788,7 +10641,7 @@
             } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
                 // output proto is ActivityManagerServiceDumpBroadcastsProto
                 synchronized (this) {
-                    writeBroadcastsToProtoLocked(proto);
+                    mBroadcastController.writeBroadcastsToProtoLocked(proto);
                 }
             } else if ("provider".equals(cmd)) {
                 String[] newArgs;
@@ -10852,7 +10705,7 @@
                     proto.end(activityToken);
 
                     long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
-                    writeBroadcastsToProtoLocked(proto);
+                    mBroadcastController.writeBroadcastsToProtoLocked(proto);
                     proto.end(broadcastToken);
 
                     long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
@@ -10912,7 +10765,8 @@
                     opti++;
                 }
                 synchronized (this) {
-                    dumpBroadcastsLocked(fd, pw, args, opti, /* dumpAll= */ true, dumpPackage);
+                    mBroadcastController.dumpBroadcastsLocked(fd, pw, args, opti,
+                            /* dumpAll= */ true, dumpPackage);
                 }
             } else if ("broadcast-stats".equals(cmd)) {
                 if (opti < args.length) {
@@ -10921,10 +10775,11 @@
                 }
                 synchronized (this) {
                     if (dumpCheckinFormat) {
-                        dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin,
-                                dumpPackage);
+                        mBroadcastController.dumpBroadcastStatsCheckinLocked(fd, pw, args, opti,
+                                dumpCheckin, dumpPackage);
                     } else {
-                        dumpBroadcastStatsLocked(fd, pw, args, opti, true, dumpPackage);
+                        mBroadcastController.dumpBroadcastStatsLocked(fd, pw, args, opti, true,
+                                dumpPackage);
                     }
                 }
             } else if ("intents".equals(cmd) || "i".equals(cmd)) {
@@ -11078,7 +10933,8 @@
 
         // No piece of data specified, dump everything.
         if (dumpCheckinFormat) {
-            dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin, dumpPackage);
+            mBroadcastController.dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin,
+                    dumpPackage);
         } else {
             if (dumpClient) {
                 // dumpEverything() will take the lock when needed, and momentarily drop
@@ -11789,42 +11645,6 @@
         }
     }
 
-    void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
-        if (mRegisteredReceivers.size() > 0) {
-            Iterator it = mRegisteredReceivers.values().iterator();
-            while (it.hasNext()) {
-                ReceiverList r = (ReceiverList)it.next();
-                r.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_LIST);
-            }
-        }
-        mReceiverResolver.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
-        mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
-        synchronized (mStickyBroadcasts) {
-            for (int user = 0; user < mStickyBroadcasts.size(); user++) {
-                long token = proto.start(
-                        ActivityManagerServiceDumpBroadcastsProto.STICKY_BROADCASTS);
-                proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
-                for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
-                        : mStickyBroadcasts.valueAt(user).entrySet()) {
-                    long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
-                    proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
-                    for (StickyBroadcast broadcast : ent.getValue()) {
-                        broadcast.intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
-                                false, true, true, false);
-                    }
-                    proto.end(actionToken);
-                }
-                proto.end(token);
-            }
-        }
-
-        long handlerToken = proto.start(ActivityManagerServiceDumpBroadcastsProto.HANDLER);
-        proto.write(ActivityManagerServiceDumpBroadcastsProto.MainHandler.HANDLER, mHandler.toString());
-        mHandler.getLooper().dumpDebug(proto,
-            ActivityManagerServiceDumpBroadcastsProto.MainHandler.LOOPER);
-        proto.end(handlerToken);
-    }
-
     void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         pw.println(
@@ -11860,219 +11680,6 @@
         }
     }
 
-    @NeverCompile
-    void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage) {
-        boolean dumpConstants = true;
-        boolean dumpHistory = true;
-        boolean needSep = false;
-        boolean onlyHistory = false;
-        boolean printedAnything = false;
-        boolean onlyReceivers = false;
-        int filteredUid = Process.INVALID_UID;
-
-        if ("history".equals(dumpPackage)) {
-            if (opti < args.length && "-s".equals(args[opti])) {
-                dumpAll = false;
-            }
-            onlyHistory = true;
-            dumpPackage = null;
-        }
-        if ("receivers".equals(dumpPackage)) {
-            onlyReceivers = true;
-            dumpPackage = null;
-            if (opti + 2 <= args.length) {
-                for (int i = opti; i < args.length; i++) {
-                    String arg = args[i];
-                    switch (arg) {
-                        case "--uid":
-                            filteredUid = getIntArg(pw, args, ++i, Process.INVALID_UID);
-                            if (filteredUid == Process.INVALID_UID) {
-                                return;
-                            }
-                            break;
-                        default:
-                            pw.printf("Invalid argument at index %d: %s\n", i, arg);
-                            return;
-                    }
-                }
-            }
-        }
-        if (DEBUG_BROADCAST) {
-            Slogf.d(TAG_BROADCAST, "dumpBroadcastsLocked(): dumpPackage=%s, onlyHistory=%b, "
-                    + "onlyReceivers=%b, filteredUid=%d", dumpPackage, onlyHistory, onlyReceivers,
-                    filteredUid);
-        }
-
-        pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
-        if (!onlyHistory && dumpAll) {
-            if (mRegisteredReceivers.size() > 0) {
-                boolean printed = false;
-                Iterator it = mRegisteredReceivers.values().iterator();
-                while (it.hasNext()) {
-                    ReceiverList r = (ReceiverList)it.next();
-                    if (dumpPackage != null && (r.app == null ||
-                            !dumpPackage.equals(r.app.info.packageName))) {
-                        continue;
-                    }
-                    if (filteredUid != Process.INVALID_UID && filteredUid != r.app.uid) {
-                        if (DEBUG_BROADCAST) {
-                            Slogf.v(TAG_BROADCAST, "dumpBroadcastsLocked(): skipping receiver whose"
-                                    + " uid (%d) is not %d: %s", r.app.uid, filteredUid, r.app);
-                        }
-                        continue;
-                    }
-                    if (!printed) {
-                        pw.println("  Registered Receivers:");
-                        needSep = true;
-                        printed = true;
-                        printedAnything = true;
-                    }
-                    pw.print("  * "); pw.println(r);
-                    r.dump(pw, "    ");
-                }
-            } else {
-                if (onlyReceivers) {
-                    pw.println("  (no registered receivers)");
-                }
-            }
-
-            if (!onlyReceivers) {
-                if (mReceiverResolver.dump(pw, needSep
-                        ? "\n  Receiver Resolver Table:" : "  Receiver Resolver Table:",
-                        "    ", dumpPackage, false, false)) {
-                    needSep = true;
-                    printedAnything = true;
-                }
-            }
-        }
-
-        if (!onlyReceivers) {
-            needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
-                    dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
-            printedAnything |= needSep;
-        }
-
-        needSep = true;
-
-        synchronized (mStickyBroadcasts) {
-            if (!onlyHistory && !onlyReceivers && mStickyBroadcasts != null
-                    && dumpPackage == null) {
-                for (int user = 0; user < mStickyBroadcasts.size(); user++) {
-                    if (needSep) {
-                        pw.println();
-                    }
-                    needSep = true;
-                    printedAnything = true;
-                    pw.print("  Sticky broadcasts for user ");
-                    pw.print(mStickyBroadcasts.keyAt(user));
-                    pw.println(":");
-                    StringBuilder sb = new StringBuilder(128);
-                    for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
-                            : mStickyBroadcasts.valueAt(user).entrySet()) {
-                        pw.print("  * Sticky action ");
-                        pw.print(ent.getKey());
-                        if (dumpAll) {
-                            pw.println(":");
-                            ArrayList<StickyBroadcast> broadcasts = ent.getValue();
-                            final int N = broadcasts.size();
-                            for (int i = 0; i < N; i++) {
-                                final Intent intent = broadcasts.get(i).intent;
-                                final boolean deferUntilActive = broadcasts.get(i).deferUntilActive;
-                                sb.setLength(0);
-                                sb.append("    Intent: ");
-                                intent.toShortString(sb, false, true, false, false);
-                                pw.print(sb);
-                                if (deferUntilActive) {
-                                    pw.print(" [D]");
-                                }
-                                pw.println();
-                                pw.print("      originalCallingUid: ");
-                                pw.println(broadcasts.get(i).originalCallingUid);
-                                pw.println();
-                                Bundle bundle = intent.getExtras();
-                                if (bundle != null) {
-                                    pw.print("      extras: ");
-                                    pw.println(bundle);
-                                }
-                            }
-                        } else {
-                            pw.println("");
-                        }
-                    }
-                }
-            }
-        }
-
-        if (!onlyHistory && !onlyReceivers && dumpAll) {
-            pw.println();
-            pw.println("  Queue " + mBroadcastQueue.toString() + ": "
-                    + mBroadcastQueue.describeStateLocked());
-            pw.println("  mHandler:");
-            mHandler.dump(new PrintWriterPrinter(pw), "    ");
-            needSep = true;
-            printedAnything = true;
-        }
-
-        if (!printedAnything) {
-            pw.println("  (nothing)");
-        }
-    }
-
-    @NeverCompile
-    void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage) {
-        if (mCurBroadcastStats == null) {
-            return;
-        }
-
-        pw.println("ACTIVITY MANAGER BROADCAST STATS STATE (dumpsys activity broadcast-stats)");
-        final long now = SystemClock.elapsedRealtime();
-        if (mLastBroadcastStats != null) {
-            pw.print("  Last stats (from ");
-            TimeUtils.formatDuration(mLastBroadcastStats.mStartRealtime, now, pw);
-            pw.print(" to ");
-            TimeUtils.formatDuration(mLastBroadcastStats.mEndRealtime, now, pw);
-            pw.print(", ");
-            TimeUtils.formatDuration(mLastBroadcastStats.mEndUptime
-                    - mLastBroadcastStats.mStartUptime, pw);
-            pw.println(" uptime):");
-            if (!mLastBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
-                pw.println("    (nothing)");
-            }
-            pw.println();
-        }
-        pw.print("  Current stats (from ");
-        TimeUtils.formatDuration(mCurBroadcastStats.mStartRealtime, now, pw);
-        pw.print(" to now, ");
-        TimeUtils.formatDuration(SystemClock.uptimeMillis()
-                - mCurBroadcastStats.mStartUptime, pw);
-        pw.println(" uptime):");
-        if (!mCurBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
-            pw.println("    (nothing)");
-        }
-    }
-
-    @NeverCompile
-    void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean fullCheckin, String dumpPackage) {
-        if (mCurBroadcastStats == null) {
-            return;
-        }
-
-        if (mLastBroadcastStats != null) {
-            mLastBroadcastStats.dumpCheckinStats(pw, dumpPackage);
-            if (fullCheckin) {
-                mLastBroadcastStats = null;
-                return;
-            }
-        }
-        mCurBroadcastStats.dumpCheckinStats(pw, dumpPackage);
-        if (fullCheckin) {
-            mCurBroadcastStats = null;
-        }
-    }
-
     void dumpPermissions(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
 
@@ -14594,33 +14201,6 @@
     // BROADCASTS
     // =========================================================
 
-    private boolean isInstantApp(ProcessRecord record, @Nullable String callerPackage, int uid) {
-        if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) {
-            return false;
-        }
-        // Easy case -- we have the app's ProcessRecord.
-        if (record != null) {
-            return record.info.isInstantApp();
-        }
-        // Otherwise check with PackageManager.
-        IPackageManager pm = AppGlobals.getPackageManager();
-        try {
-            if (callerPackage == null) {
-                final String[] packageNames = pm.getPackagesForUid(uid);
-                if (packageNames == null || packageNames.length == 0) {
-                    throw new IllegalArgumentException("Unable to determine caller package name");
-                }
-                // Instant Apps can't use shared uids, so its safe to only check the first package.
-                callerPackage = packageNames[0];
-            }
-            mAppOpsService.checkPackage(uid, callerPackage);
-            return pm.isInstantApp(callerPackage, UserHandle.getUserId(uid));
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Error looking up if " + callerPackage + " is an instant app.", e);
-            return true;
-        }
-    }
-
     /**
      * @deprecated Use {@link #registerReceiverWithFeature}
      */
@@ -14635,657 +14215,12 @@
     public Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
             String callerFeatureId, String receiverId, IIntentReceiver receiver,
             IntentFilter filter, String permission, int userId, int flags) {
-        traceRegistrationBegin(receiverId, receiver, filter, userId);
-        try {
-            return registerReceiverWithFeatureTraced(caller, callerPackage, callerFeatureId,
-                    receiverId, receiver, filter, permission, userId, flags);
-        } finally {
-            traceRegistrationEnd();
-        }
-    }
-
-    private static void traceRegistrationBegin(String receiverId, IIntentReceiver receiver,
-            IntentFilter filter, int userId) {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            final StringBuilder sb = new StringBuilder("registerReceiver: ");
-            sb.append(Binder.getCallingUid()); sb.append('/');
-            sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
-            final int actionsCount = filter.safeCountActions();
-            if (actionsCount > 0) {
-                for (int i = 0; i < actionsCount; ++i) {
-                    sb.append(filter.getAction(i));
-                    if (i != actionsCount - 1) sb.append(',');
-                }
-            } else {
-                sb.append("null");
-            }
-            sb.append('/');
-            sb.append('u'); sb.append(userId); sb.append('/');
-            sb.append(receiver == null ? "null" : receiver.asBinder());
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
-        }
-    }
-
-    private static void traceRegistrationEnd() {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
-    }
-
-    private Intent registerReceiverWithFeatureTraced(IApplicationThread caller,
-            String callerPackage, String callerFeatureId, String receiverId,
-            IIntentReceiver receiver, IntentFilter filter, String permission,
-            int userId, int flags) {
-        enforceNotIsolatedCaller("registerReceiver");
-        ArrayList<StickyBroadcast> stickyBroadcasts = null;
-        ProcessRecord callerApp = null;
-        final boolean visibleToInstantApps
-                = (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
-
-        int callingUid;
-        int callingPid;
-        boolean instantApp;
-        synchronized (mProcLock) {
-            callerApp = getRecordForAppLOSP(caller);
-            if (callerApp == null) {
-                Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
-                return null;
-            }
-            if (callerApp.info.uid != SYSTEM_UID
-                    && !callerApp.getPkgList().containsKey(callerPackage)
-                    && !"android".equals(callerPackage)) {
-                throw new SecurityException("Given caller package " + callerPackage
-                        + " is not running in process " + callerApp);
-            }
-            callingUid = callerApp.info.uid;
-            callingPid = callerApp.getPid();
-
-            instantApp = isInstantApp(callerApp, callerPackage, callingUid);
-        }
-        userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
-                ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
-
-        // Warn if system internals are registering for important broadcasts
-        // without also using a priority to ensure they process the event
-        // before normal apps hear about it
-        if (UserHandle.isCore(callingUid)) {
-            final int priority = filter.getPriority();
-            final boolean systemPriority = (priority >= IntentFilter.SYSTEM_HIGH_PRIORITY)
-                    || (priority <= IntentFilter.SYSTEM_LOW_PRIORITY);
-            if (!systemPriority) {
-                final int N = filter.countActions();
-                for (int i = 0; i < N; i++) {
-                    // TODO: expand to additional important broadcasts over time
-                    final String action = filter.getAction(i);
-                    if (action.startsWith("android.intent.action.USER_")
-                            || action.startsWith("android.intent.action.PACKAGE_")
-                            || action.startsWith("android.intent.action.UID_")
-                            || action.startsWith("android.intent.action.EXTERNAL_")
-                            || action.startsWith("android.bluetooth.")
-                            || action.equals(Intent.ACTION_SHUTDOWN)) {
-                        if (DEBUG_BROADCAST) {
-                            Slog.wtf(TAG,
-                                    "System internals registering for " + filter.toLongString()
-                                            + " with app priority; this will race with apps!",
-                                    new Throwable());
-                        }
-
-                        // When undefined, assume that system internals need
-                        // to hear about the event first; they can use
-                        // SYSTEM_LOW_PRIORITY if they need to hear last
-                        if (priority == 0) {
-                            filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-                        }
-                        break;
-                    }
-                }
-            }
-        }
-
-        Iterator<String> actions = filter.actionsIterator();
-        if (actions == null) {
-            ArrayList<String> noAction = new ArrayList<String>(1);
-            noAction.add(null);
-            actions = noAction.iterator();
-        }
-        boolean onlyProtectedBroadcasts = true;
-
-        // Collect stickies of users and check if broadcast is only registered for protected
-        // broadcasts
-        int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
-        synchronized (mStickyBroadcasts) {
-            while (actions.hasNext()) {
-                String action = actions.next();
-                for (int id : userIds) {
-                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
-                            mStickyBroadcasts.get(id);
-                    if (stickies != null) {
-                        ArrayList<StickyBroadcast> broadcasts = stickies.get(action);
-                        if (broadcasts != null) {
-                            if (stickyBroadcasts == null) {
-                                stickyBroadcasts = new ArrayList<>();
-                            }
-                            stickyBroadcasts.addAll(broadcasts);
-                        }
-                    }
-                }
-                if (onlyProtectedBroadcasts) {
-                    try {
-                        onlyProtectedBroadcasts &=
-                                AppGlobals.getPackageManager().isProtectedBroadcast(action);
-                    } catch (RemoteException e) {
-                        onlyProtectedBroadcasts = false;
-                        Slog.w(TAG, "Remote exception", e);
-                    }
-                }
-            }
-        }
-
-        if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
-            SdkSandboxManagerLocal sdkSandboxManagerLocal =
-                    LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
-            if (sdkSandboxManagerLocal == null) {
-                throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
-                        + " whether SDK sandbox uid can register to broadcast receivers.");
-            }
-            if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
-                    /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
-                throw new SecurityException("SDK sandbox not allowed to register receiver"
-                        + " with the given IntentFilter: " + filter.toLongString());
-            }
-        }
-
-        // If the change is enabled, but neither exported or not exported is set, we need to log
-        // an error so the consumer can know to explicitly set the value for their flag.
-        // If the caller is registering for a sticky broadcast with a null receiver, we won't
-        // require a flag
-        final boolean explicitExportStateDefined =
-                (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0;
-        if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
-                (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) {
-            throw new IllegalArgumentException(
-                    "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED"
-                            + "flag");
-        }
-
-        // Don't enforce the flag check if we're EITHER registering for only protected
-        // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
-        // not be used generally, so we will be marking them as exported by default
-        boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
-                DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
-
-        // A receiver that is visible to instant apps must also be exported.
-        final boolean unexportedReceiverVisibleToInstantApps =
-                ((flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && (
-                        (flags & Context.RECEIVER_NOT_EXPORTED) != 0);
-        if (unexportedReceiverVisibleToInstantApps && requireExplicitFlagForDynamicReceivers) {
-            throw new IllegalArgumentException(
-                    "Receiver can't specify both RECEIVER_VISIBLE_TO_INSTANT_APPS and "
-                            + "RECEIVER_NOT_EXPORTED flag");
-        }
-
-        if (!onlyProtectedBroadcasts) {
-            if (receiver == null && !explicitExportStateDefined) {
-                // sticky broadcast, no flag specified (flag isn't required)
-                flags |= Context.RECEIVER_EXPORTED;
-            } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
-                throw new SecurityException(
-                        callerPackage + ": One of RECEIVER_EXPORTED or "
-                                + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
-                                + "isn't being registered exclusively for system broadcasts");
-                // Assume default behavior-- flag check is not enforced
-            } else if (!requireExplicitFlagForDynamicReceivers && (
-                    (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
-                // Change is not enabled, assume exported unless otherwise specified.
-                flags |= Context.RECEIVER_EXPORTED;
-            }
-        } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
-            flags |= Context.RECEIVER_EXPORTED;
-        }
-
-        // Dynamic receivers are exported by default for versions prior to T
-        final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
-
-        ArrayList<StickyBroadcast> allSticky = null;
-        if (stickyBroadcasts != null) {
-            final ContentResolver resolver = mContext.getContentResolver();
-            // Look for any matching sticky broadcasts...
-            for (int i = 0, N = stickyBroadcasts.size(); i < N; i++) {
-                final StickyBroadcast broadcast = stickyBroadcasts.get(i);
-                Intent intent = broadcast.intent;
-                // Don't provided intents that aren't available to instant apps.
-                if (instantApp &&
-                        (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {
-                    continue;
-                }
-                // If intent has scheme "content", it will need to access
-                // provider that needs to lock mProviderMap in ActivityThread
-                // and also it may need to wait application response, so we
-                // cannot lock ActivityManagerService here.
-                final int match;
-                if (Flags.avoidResolvingType()) {
-                    match = filter.match(intent.getAction(), broadcast.resolvedDataType,
-                        intent.getScheme(), intent.getData(), intent.getCategories(),
-                        TAG, false /* supportsWildcards */, null /* ignoreActions */,
-                        intent.getExtras());
-                } else {
-                    match = filter.match(resolver, intent, true, TAG);
-                }
-                if (match >= 0) {
-                    if (allSticky == null) {
-                        allSticky = new ArrayList<>();
-                    }
-                    allSticky.add(broadcast);
-                }
-            }
-        }
-
-        // The first sticky in the list is returned directly back to the client.
-        Intent sticky = allSticky != null ? allSticky.get(0).intent : null;
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
-        if (receiver == null) {
-            return sticky;
-        }
-
-        // SafetyNet logging for b/177931370. If any process other than system_server tries to
-        // listen to this broadcast action, then log it.
-        if (callingPid != Process.myPid()) {
-            if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING")
-                    || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) {
-                EventLog.writeEvent(0x534e4554, "177931370", callingUid, "");
-            }
-        }
-
-        synchronized (this) {
-            IApplicationThread thread;
-            if (callerApp != null && ((thread = callerApp.getThread()) == null
-                    || thread.asBinder() != caller.asBinder())) {
-                // Original caller already died
-                return null;
-            }
-            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
-            if (rl == null) {
-                rl = new ReceiverList(this, callerApp, callingPid, callingUid,
-                        userId, receiver);
-                if (rl.app != null) {
-                    final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
-                    if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
-                        throw new IllegalStateException("Too many receivers, total of "
-                                + totalReceiversForApp + ", registered for pid: "
-                                + rl.pid + ", callerPackage: " + callerPackage);
-                    }
-                    rl.app.mReceivers.addReceiver(rl);
-                } else {
-                    try {
-                        receiver.asBinder().linkToDeath(rl, 0);
-                    } catch (RemoteException e) {
-                        return sticky;
-                    }
-                    rl.linkedToDeath = true;
-                }
-                mRegisteredReceivers.put(receiver.asBinder(), rl);
-            } else if (rl.uid != callingUid) {
-                throw new IllegalArgumentException(
-                        "Receiver requested to register for uid " + callingUid
-                        + " was previously registered for uid " + rl.uid
-                        + " callerPackage is " + callerPackage);
-            } else if (rl.pid != callingPid) {
-                throw new IllegalArgumentException(
-                        "Receiver requested to register for pid " + callingPid
-                        + " was previously registered for pid " + rl.pid
-                        + " callerPackage is " + callerPackage);
-            } else if (rl.userId != userId) {
-                throw new IllegalArgumentException(
-                        "Receiver requested to register for user " + userId
-                        + " was previously registered for user " + rl.userId
-                        + " callerPackage is " + callerPackage);
-            }
-            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
-                    receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
-                    exported);
-            if (rl.containsFilter(filter)) {
-                Slog.w(TAG, "Receiver with filter " + filter
-                        + " already registered for pid " + rl.pid
-                        + ", callerPackage is " + callerPackage);
-            } else {
-                rl.add(bf);
-                if (!bf.debugCheck()) {
-                    Slog.w(TAG, "==> For Dynamic broadcast");
-                }
-                mReceiverResolver.addFilter(getPackageManagerInternal().snapshot(), bf);
-            }
-
-            // Enqueue broadcasts for all existing stickies that match
-            // this filter.
-            if (allSticky != null) {
-                ArrayList receivers = new ArrayList();
-                receivers.add(bf);
-                sticky = null;
-
-                final int stickyCount = allSticky.size();
-                for (int i = 0; i < stickyCount; i++) {
-                    final StickyBroadcast broadcast = allSticky.get(i);
-                    final int originalStickyCallingUid = allSticky.get(i).originalCallingUid;
-                    // TODO(b/281889567): consider using checkComponentPermission instead of
-                    //  canAccessUnexportedComponents
-                    if (sticky == null && (exported || originalStickyCallingUid == callingUid
-                            || ActivityManager.canAccessUnexportedComponents(
-                            originalStickyCallingUid))) {
-                        sticky = broadcast.intent;
-                    }
-                    BroadcastQueue queue = mBroadcastQueue;
-                    BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
-                            null, null, -1, -1, false, null, null, null, null, OP_NONE,
-                            BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
-                            receivers, null, null, 0, null, null, false, true, true, -1,
-                            originalStickyCallingUid, BackgroundStartPrivileges.NONE,
-                            false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
-                            null /* filterExtrasForReceiver */,
-                            broadcast.originalCallingAppProcessState);
-                    queue.enqueueBroadcastLocked(r);
-                }
-            }
-
-            return sticky;
-        }
+        return mBroadcastController.registerReceiverWithFeature(caller, callerPackage,
+                callerFeatureId, receiverId, receiver, filter, permission, userId, flags);
     }
 
     public void unregisterReceiver(IIntentReceiver receiver) {
-        traceUnregistrationBegin(receiver);
-        try {
-            unregisterReceiverTraced(receiver);
-        } finally {
-            traceUnregistrationEnd();
-        }
-    }
-
-    private static void traceUnregistrationBegin(IIntentReceiver receiver) {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    TextUtils.formatSimple("unregisterReceiver: %d/%s", Binder.getCallingUid(),
-                            receiver == null ? "null" : receiver.asBinder()));
-        }
-    }
-
-    private static void traceUnregistrationEnd() {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
-    }
-
-    private void unregisterReceiverTraced(IIntentReceiver receiver) {
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver);
-
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            boolean doTrim = false;
-            synchronized(this) {
-                ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
-                if (rl != null) {
-                    final BroadcastRecord r = rl.curBroadcast;
-                    if (r != null) {
-                        final boolean doNext = r.queue.finishReceiverLocked(
-                                rl.app, r.resultCode, r.resultData, r.resultExtras,
-                                r.resultAbort, false);
-                        if (doNext) {
-                            doTrim = true;
-                        }
-                    }
-                    if (rl.app != null) {
-                        rl.app.mReceivers.removeReceiver(rl);
-                    }
-                    removeReceiverLocked(rl);
-                    if (rl.linkedToDeath) {
-                        rl.linkedToDeath = false;
-                        rl.receiver.asBinder().unlinkToDeath(rl, 0);
-                    }
-                }
-
-                // If we actually concluded any broadcasts, we might now be able
-                // to trim the recipients' apps from our working set
-                if (doTrim) {
-                    trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
-                    return;
-                }
-            }
-
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    void removeReceiverLocked(ReceiverList rl) {
-        mRegisteredReceivers.remove(rl.receiver.asBinder());
-        for (int i = rl.size() - 1; i >= 0; i--) {
-            mReceiverResolver.removeFilter(rl.get(i));
-        }
-    }
-
-    private final void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
-        mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
-    }
-
-    private List<ResolveInfo> collectReceiverComponents(
-            Intent intent, String resolvedType, int callingUid, int callingPid,
-            int[] users, int[] broadcastAllowList) {
-        // TODO: come back and remove this assumption to triage all broadcasts
-        long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
-
-        List<ResolveInfo> receivers = null;
-        HashSet<ComponentName> singleUserReceivers = null;
-        boolean scannedFirstReceivers = false;
-        for (int user : users) {
-            // Skip users that have Shell restrictions
-            if (callingUid == SHELL_UID
-                    && mUserController.hasUserRestriction(
-                    UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
-                continue;
-            }
-            List<ResolveInfo> newReceivers = mPackageManagerInt.queryIntentReceivers(
-                    intent, resolvedType, pmFlags, callingUid, callingPid, user, /* forSend */true);
-            if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
-                // If this is not the system user, we need to check for
-                // any receivers that should be filtered out.
-                for (int i = 0; i < newReceivers.size(); i++) {
-                    ResolveInfo ri = newReceivers.get(i);
-                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
-                        newReceivers.remove(i);
-                        i--;
-                    }
-                }
-            }
-            // Replace the alias receivers with their targets.
-            if (newReceivers != null) {
-                for (int i = newReceivers.size() - 1; i >= 0; i--) {
-                    final ResolveInfo ri = newReceivers.get(i);
-                    final Resolution<ResolveInfo> resolution =
-                            mComponentAliasResolver.resolveReceiver(intent, ri, resolvedType,
-                                    pmFlags, user, callingUid, callingPid);
-                    if (resolution == null) {
-                        // It was an alias, but the target was not found.
-                        newReceivers.remove(i);
-                        continue;
-                    }
-                    if (resolution.isAlias()) {
-                        newReceivers.set(i, resolution.getTarget());
-                    }
-                }
-            }
-            if (newReceivers != null && newReceivers.size() == 0) {
-                newReceivers = null;
-            }
-
-            if (receivers == null) {
-                receivers = newReceivers;
-            } else if (newReceivers != null) {
-                // We need to concatenate the additional receivers
-                // found with what we have do far.  This would be easy,
-                // but we also need to de-dup any receivers that are
-                // singleUser.
-                if (!scannedFirstReceivers) {
-                    // Collect any single user receivers we had already retrieved.
-                    scannedFirstReceivers = true;
-                    for (int i = 0; i < receivers.size(); i++) {
-                        ResolveInfo ri = receivers.get(i);
-                        if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
-                            ComponentName cn = new ComponentName(
-                                    ri.activityInfo.packageName, ri.activityInfo.name);
-                            if (singleUserReceivers == null) {
-                                singleUserReceivers = new HashSet<ComponentName>();
-                            }
-                            singleUserReceivers.add(cn);
-                        }
-                    }
-                }
-                // Add the new results to the existing results, tracking
-                // and de-dupping single user receivers.
-                for (int i = 0; i < newReceivers.size(); i++) {
-                    ResolveInfo ri = newReceivers.get(i);
-                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
-                        ComponentName cn = new ComponentName(
-                                ri.activityInfo.packageName, ri.activityInfo.name);
-                        if (singleUserReceivers == null) {
-                            singleUserReceivers = new HashSet<ComponentName>();
-                        }
-                        if (!singleUserReceivers.contains(cn)) {
-                            singleUserReceivers.add(cn);
-                            receivers.add(ri);
-                        }
-                    } else {
-                        receivers.add(ri);
-                    }
-                }
-            }
-        }
-        if (receivers != null && broadcastAllowList != null) {
-            for (int i = receivers.size() - 1; i >= 0; i--) {
-                final int receiverAppId = UserHandle.getAppId(
-                        receivers.get(i).activityInfo.applicationInfo.uid);
-                if (receiverAppId >= Process.FIRST_APPLICATION_UID
-                        && Arrays.binarySearch(broadcastAllowList, receiverAppId) < 0) {
-                    receivers.remove(i);
-                }
-            }
-        }
-        return receivers;
-    }
-
-    private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
-            String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
-        if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
-            // Don't yell about broadcasts sent via shell
-            return;
-        }
-
-        final String action = intent.getAction();
-        if (isProtectedBroadcast
-                || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
-                || Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
-                || Intent.ACTION_MEDIA_BUTTON.equals(action)
-                || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
-                || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
-                || Intent.ACTION_MASTER_CLEAR.equals(action)
-                || Intent.ACTION_FACTORY_RESET.equals(action)
-                || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
-                || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
-                || TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
-                || SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
-                || AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
-                || AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)) {
-            // Broadcast is either protected, or it's a public action that
-            // we've relaxed, so it's fine for system internals to send.
-            return;
-        }
-
-        // This broadcast may be a problem...  but there are often system components that
-        // want to send an internal broadcast to themselves, which is annoying to have to
-        // explicitly list each action as a protected broadcast, so we will check for that
-        // one safe case and allow it: an explicit broadcast, only being received by something
-        // that has protected itself.
-        if (intent.getPackage() != null || intent.getComponent() != null) {
-            if (receivers == null || receivers.size() == 0) {
-                // Intent is explicit and there's no receivers.
-                // This happens, e.g. , when a system component sends a broadcast to
-                // its own runtime receiver, and there's no manifest receivers for it,
-                // because this method is called twice for each broadcast,
-                // for runtime receivers and manifest receivers and the later check would find
-                // no receivers.
-                return;
-            }
-            boolean allProtected = true;
-            for (int i = receivers.size()-1; i >= 0; i--) {
-                Object target = receivers.get(i);
-                if (target instanceof ResolveInfo) {
-                    ResolveInfo ri = (ResolveInfo)target;
-                    if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
-                        allProtected = false;
-                        break;
-                    }
-                } else {
-                    BroadcastFilter bf = (BroadcastFilter)target;
-                    if (bf.exported && bf.requiredPermission == null) {
-                        allProtected = false;
-                        break;
-                    }
-                }
-            }
-            if (allProtected) {
-                // All safe!
-                return;
-            }
-        }
-
-        // The vast majority of broadcasts sent from system internals
-        // should be protected to avoid security holes, so yell loudly
-        // to ensure we examine these cases.
-        if (callerApp != null) {
-            Log.wtf(TAG, "Sending non-protected broadcast " + action
-                            + " from system " + callerApp.toShortString() + " pkg " + callerPackage,
-                    new Throwable());
-        } else {
-            Log.wtf(TAG, "Sending non-protected broadcast " + action
-                            + " from system uid " + UserHandle.formatUid(callingUid)
-                            + " pkg " + callerPackage,
-                    new Throwable());
-        }
-    }
-
-    // Apply permission policy around the use of specific broadcast options
-    void enforceBroadcastOptionPermissionsInternal(
-            @Nullable Bundle options, int callingUid) {
-        enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
-                callingUid);
-    }
-
-    void enforceBroadcastOptionPermissionsInternal(
-            @Nullable BroadcastOptions options, int callingUid) {
-        if (options != null && callingUid != Process.SYSTEM_UID) {
-            if (options.isAlarmBroadcast()) {
-                if (DEBUG_BROADCAST_LIGHT) {
-                    Slog.w(TAG, "Non-system caller " + callingUid
-                            + " may not flag broadcast as alarm");
-                }
-                throw new SecurityException(
-                        "Non-system callers may not flag broadcasts as alarm");
-            }
-            if (options.isInteractive()) {
-                enforceCallingPermission(
-                        android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
-                        "setInteractive");
-            }
-        }
+        mBroadcastController.unregisterReceiver(receiver);
     }
 
     @GuardedBy("this")
@@ -15296,1033 +14231,14 @@
             String[] excludedPackages, int appOp, Bundle bOptions, boolean ordered,
             boolean sticky, int callingPid,
             int callingUid, int realCallingUid, int realCallingPid, int userId) {
-        return broadcastIntentLocked(callerApp, callerPackage, callerFeatureId, intent,
-                resolvedType, null, resultTo, resultCode, resultData, resultExtras,
+        return mBroadcastController.broadcastIntentLocked(callerApp, callerPackage, callerFeatureId,
+                intent, resolvedType, null, resultTo, resultCode, resultData, resultExtras,
                 requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions,
                 ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId,
                 BackgroundStartPrivileges.NONE,
                 null /* broadcastAllowList */, null /* filterExtrasForReceiver */);
     }
 
-    @GuardedBy("this")
-    final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
-            @Nullable String callerFeatureId, Intent intent, String resolvedType,
-            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
-            Bundle resultExtras, String[] requiredPermissions,
-            String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
-            boolean ordered, boolean sticky, int callingPid, int callingUid,
-            int realCallingUid, int realCallingPid, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges,
-            @Nullable int[] broadcastAllowList,
-            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
-        final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky,
-                callingUid, realCallingUid, userId);
-        try {
-            final BroadcastSentEventRecord broadcastSentEventRecord =
-                    new BroadcastSentEventRecord();
-            final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
-                    intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
-                    resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
-                    appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
-                    callingPid, callingUid, realCallingUid, realCallingPid, userId,
-                    backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver,
-                    broadcastSentEventRecord);
-            broadcastSentEventRecord.setResult(res);
-            broadcastSentEventRecord.logToStatsd();
-            return res;
-        } finally {
-            traceBroadcastIntentEnd(cookie);
-        }
-    }
-
-    private static int traceBroadcastIntentBegin(Intent intent, IIntentReceiver resultTo,
-            boolean ordered, boolean sticky, int callingUid, int realCallingUid, int userId) {
-        if (!Flags.traceReceiverRegistration()) {
-            return BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            final StringBuilder sb = new StringBuilder("broadcastIntent: ");
-            sb.append(callingUid); sb.append('/');
-            final String action = intent.getAction();
-            sb.append(action == null ? null : action); sb.append('/');
-            sb.append("0x"); sb.append(Integer.toHexString(intent.getFlags())); sb.append('/');
-            sb.append(ordered ? "O" : "_");
-            sb.append(sticky ? "S" : "_");
-            sb.append(resultTo != null ? "C" : "_");
-            sb.append('/');
-            sb.append('u'); sb.append(userId);
-            if (callingUid != realCallingUid) {
-                sb.append('/');
-                sb.append("sender="); sb.append(realCallingUid);
-            }
-            return BroadcastQueue.traceBegin(sb.toString());
-        }
-        return 0;
-    }
-
-    private static void traceBroadcastIntentEnd(int cookie) {
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            BroadcastQueue.traceEnd(cookie);
-        }
-    }
-
-    @GuardedBy("this")
-    final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
-            @Nullable String callerFeatureId, Intent intent, String resolvedType,
-            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
-            Bundle resultExtras, String[] requiredPermissions,
-            String[] excludedPermissions, String[] excludedPackages, int appOp,
-            BroadcastOptions brOptions, boolean ordered, boolean sticky, int callingPid,
-            int callingUid, int realCallingUid, int realCallingPid, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges,
-            @Nullable int[] broadcastAllowList,
-            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
-            @NonNull BroadcastSentEventRecord broadcastSentEventRecord) {
-        // Ensure all internal loopers are registered for idle checks
-        BroadcastLoopers.addMyLooper();
-
-        if (Process.isSdkSandboxUid(realCallingUid)) {
-            final SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
-                    SdkSandboxManagerLocal.class);
-            if (sdkSandboxManagerLocal == null) {
-                throw new IllegalStateException("SdkSandboxManagerLocal not found when sending"
-                        + " a broadcast from an SDK sandbox uid.");
-            }
-            if (!sdkSandboxManagerLocal.canSendBroadcast(intent)) {
-                throw new SecurityException(
-                        "Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
-                        + " uid. Given caller package " + callerPackage + " (pid=" + callingPid
-                        + ", realCallingUid=" + realCallingUid + ", callingUid= " + callingUid
-                        + ")");
-            }
-        }
-
-        if ((resultTo != null) && (resultToApp == null)) {
-            if (resultTo.asBinder() instanceof BinderProxy) {
-                // Warn when requesting results without a way to deliver them
-                Slog.wtf(TAG, "Sending broadcast " + intent.getAction()
-                        + " with resultTo requires resultToApp", new Throwable());
-            } else {
-                // If not a BinderProxy above, then resultTo is an in-process
-                // receiver, so splice in system_server process
-                resultToApp = getProcessRecordLocked("system", SYSTEM_UID);
-            }
-        }
-
-        intent = new Intent(intent);
-        broadcastSentEventRecord.setIntent(intent);
-        broadcastSentEventRecord.setOriginalIntentFlags(intent.getFlags());
-        broadcastSentEventRecord.setSenderUid(callingUid);
-        broadcastSentEventRecord.setRealSenderUid(realCallingUid);
-        broadcastSentEventRecord.setSticky(sticky);
-        broadcastSentEventRecord.setOrdered(ordered);
-        broadcastSentEventRecord.setResultRequested(resultTo != null);
-        final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
-        broadcastSentEventRecord.setSenderProcState(callerAppProcessState);
-        broadcastSentEventRecord.setSenderUidState(getRealUidStateLocked(callerApp,
-                realCallingPid));
-
-        final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
-        // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
-        if (callerInstantApp) {
-            intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-        }
-
-        if (userId == UserHandle.USER_ALL && broadcastAllowList != null) {
-                Slog.e(TAG, "broadcastAllowList only applies when sending to individual users. "
-                        + "Assuming restrictive whitelist.");
-                broadcastAllowList = new int[]{};
-        }
-
-        // By default broadcasts do not go to stopped apps.
-        intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
-
-        // If we have not finished booting, don't allow this to launch new processes.
-        if (!mProcessesReady && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        }
-
-        if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
-                        + " ordered=" + ordered + " userid=" + userId
-                        + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
-        if ((resultTo != null) && !ordered) {
-            if (!UserHandle.isCore(callingUid)) {
-                String msg = "Unauthorized unordered resultTo broadcast "
-                             + intent + " sent from uid " + callingUid;
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
-            }
-        }
-
-        userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
-                ALLOW_NON_FULL, "broadcast", callerPackage);
-
-        // Make sure that the user who is receiving this broadcast or its parent is running.
-        // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
-        if (userId != UserHandle.USER_ALL && !mUserController.isUserOrItsParentRunning(userId)) {
-            if ((callingUid != SYSTEM_UID
-                    || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
-                    && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
-                Slog.w(TAG, "Skipping broadcast of " + intent
-                        + ": user " + userId + " and its parent (if any) are stopped");
-                scheduleCanceledResultTo(resultToApp, resultTo, intent, userId,
-                        brOptions, callingUid, callerPackage);
-                return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
-            }
-        }
-
-        final String action = intent.getAction();
-        if (brOptions != null) {
-            if (brOptions.getTemporaryAppAllowlistDuration() > 0) {
-                // See if the caller is allowed to do this.  Note we are checking against
-                // the actual real caller (not whoever provided the operation as say a
-                // PendingIntent), because that who is actually supplied the arguments.
-                if (checkComponentPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED
-                        && checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED
-                        && checkComponentPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    String msg = "Permission Denial: " + intent.getAction()
-                            + " broadcast from " + callerPackage + " (pid=" + callingPid
-                            + ", uid=" + callingUid + ")"
-                            + " requires "
-                            + CHANGE_DEVICE_IDLE_TEMP_WHITELIST + " or "
-                            + START_ACTIVITIES_FROM_BACKGROUND + " or "
-                            + START_FOREGROUND_SERVICES_FROM_BACKGROUND;
-                    Slog.w(TAG, msg);
-                    throw new SecurityException(msg);
-                }
-            }
-            if (brOptions.isDontSendToRestrictedApps()
-                    && !isUidActiveLOSP(callingUid)
-                    && isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
-                Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
-                        + " has background restrictions");
-                return ActivityManager.START_CANCELED;
-            }
-            if (brOptions.allowsBackgroundActivityStarts()) {
-                // See if the caller is allowed to do this.  Note we are checking against
-                // the actual real caller (not whoever provided the operation as say a
-                // PendingIntent), because that who is actually supplied the arguments.
-                if (checkComponentPermission(
-                        android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    String msg = "Permission Denial: " + intent.getAction()
-                            + " broadcast from " + callerPackage + " (pid=" + callingPid
-                            + ", uid=" + callingUid + ")"
-                            + " requires "
-                            + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
-                    Slog.w(TAG, msg);
-                    throw new SecurityException(msg);
-                } else {
-                    // We set the token to null since if it wasn't for it we'd allow anyway here
-                    backgroundStartPrivileges = BackgroundStartPrivileges.ALLOW_BAL;
-                }
-            }
-
-            if (brOptions.getIdForResponseEvent() > 0) {
-                enforcePermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS,
-                        callingPid, callingUid, "recordResponseEventWhileInBackground");
-            }
-        }
-
-        // Verify that protected broadcasts are only being sent by system code,
-        // and that system code is only sending protected broadcasts.
-        final boolean isProtectedBroadcast;
-        try {
-            isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Remote exception", e);
-            scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                    userId, brOptions, callingUid, callerPackage);
-            return ActivityManager.BROADCAST_SUCCESS;
-        }
-
-        final boolean isCallerSystem;
-        switch (UserHandle.getAppId(callingUid)) {
-            case ROOT_UID:
-            case SYSTEM_UID:
-            case PHONE_UID:
-            case BLUETOOTH_UID:
-            case NFC_UID:
-            case SE_UID:
-            case NETWORK_STACK_UID:
-                isCallerSystem = true;
-                break;
-            default:
-                isCallerSystem = (callerApp != null) && callerApp.isPersistent();
-                break;
-        }
-
-        // First line security check before anything else: stop non-system apps from
-        // sending protected broadcasts.
-        if (!isCallerSystem) {
-            if (isProtectedBroadcast) {
-                String msg = "Permission Denial: not allowed to send broadcast "
-                        + action + " from pid="
-                        + callingPid + ", uid=" + callingUid;
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
-
-            } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
-                    || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
-                // Special case for compatibility: we don't want apps to send this,
-                // but historically it has not been protected and apps may be using it
-                // to poke their own app widget.  So, instead of making it protected,
-                // just limit it to the caller.
-                if (callerPackage == null) {
-                    String msg = "Permission Denial: not allowed to send broadcast "
-                            + action + " from unknown caller.";
-                    Slog.w(TAG, msg);
-                    throw new SecurityException(msg);
-                } else if (intent.getComponent() != null) {
-                    // They are good enough to send to an explicit component...  verify
-                    // it is being sent to the calling app.
-                    if (!intent.getComponent().getPackageName().equals(
-                            callerPackage)) {
-                        String msg = "Permission Denial: not allowed to send broadcast "
-                                + action + " to "
-                                + intent.getComponent().getPackageName() + " from "
-                                + callerPackage;
-                        Slog.w(TAG, msg);
-                        throw new SecurityException(msg);
-                    }
-                } else {
-                    // Limit broadcast to their own package.
-                    intent.setPackage(callerPackage);
-                }
-            }
-        }
-
-        boolean timeoutExempt = false;
-
-        if (action != null) {
-            if (getBackgroundLaunchBroadcasts().contains(action)) {
-                if (DEBUG_BACKGROUND_CHECK) {
-                    Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
-                }
-                intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-            }
-
-            // TODO: b/329211459 - Remove this after background remote intent is fixed.
-            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
-                    && getWearRemoteIntentAction().equals(action)) {
-                final int callerProcState = callerApp != null
-                        ? callerApp.getCurProcState()
-                        : ActivityManager.PROCESS_STATE_NONEXISTENT;
-                if (ActivityManager.RunningAppProcessInfo.procStateToImportance(callerProcState)
-                        > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
-                    return ActivityManager.START_CANCELED;
-                }
-            }
-
-            switch (action) {
-                case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE:
-                    UserManagerInternal umInternal = LocalServices.getService(
-                            UserManagerInternal.class);
-                    UserInfo userInfo = umInternal.getUserInfo(userId);
-                    if (userInfo != null && userInfo.isCloneProfile()) {
-                        userId = umInternal.getProfileParentId(userId);
-                    }
-                    break;
-                case Intent.ACTION_UID_REMOVED:
-                case Intent.ACTION_PACKAGE_REMOVED:
-                case Intent.ACTION_PACKAGE_CHANGED:
-                case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
-                case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
-                case Intent.ACTION_PACKAGES_SUSPENDED:
-                case Intent.ACTION_PACKAGES_UNSUSPENDED:
-                    // Handle special intents: if this broadcast is from the package
-                    // manager about a package being removed, we need to remove all of
-                    // its activities from the history stack.
-                    if (checkComponentPermission(
-                            android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
-                            callingPid, callingUid, -1, true)
-                            != PackageManager.PERMISSION_GRANTED) {
-                        String msg = "Permission Denial: " + intent.getAction()
-                                + " broadcast from " + callerPackage + " (pid=" + callingPid
-                                + ", uid=" + callingUid + ")"
-                                + " requires "
-                                + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
-                        Slog.w(TAG, msg);
-                        throw new SecurityException(msg);
-                    }
-                    switch (action) {
-                        case Intent.ACTION_UID_REMOVED:
-                            final int uid = getUidFromIntent(intent);
-                            if (uid >= 0) {
-                                mBatteryStatsService.removeUid(uid);
-                                if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                                    mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
-                                            intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
-                                } else {
-                                    mAppOpsService.uidRemoved(uid);
-                                    mServices.onUidRemovedLocked(uid);
-                                }
-                            }
-                            break;
-                        case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
-                            // If resources are unavailable just force stop all those packages
-                            // and flush the attribute cache as well.
-                            String list[] =
-                                    intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                            if (list != null && list.length > 0) {
-                                for (int i = 0; i < list.length; i++) {
-                                    forceStopPackageLocked(list[i], -1, false, true, true,
-                                            false, false, false, userId, "storage unmount");
-                                }
-                                mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
-                                sendPackageBroadcastLocked(
-                                        ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE,
-                                        list, userId);
-                            }
-                            break;
-                        case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
-                            mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
-                            break;
-                        case Intent.ACTION_PACKAGE_REMOVED:
-                        case Intent.ACTION_PACKAGE_CHANGED:
-                            Uri data = intent.getData();
-                            String ssp;
-                            if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
-                                boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action);
-                                final boolean replacing =
-                                        intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-                                final boolean killProcess =
-                                        !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
-                                final boolean fullUninstall = removed && !replacing;
-
-                                if (removed) {
-                                    if (killProcess) {
-                                        forceStopPackageLocked(ssp, UserHandle.getAppId(
-                                                intent.getIntExtra(Intent.EXTRA_UID, -1)),
-                                                false, true, true, false, fullUninstall, false,
-                                                userId, "pkg removed");
-                                        getPackageManagerInternal()
-                                                .onPackageProcessKilledForUninstall(ssp);
-                                    } else {
-                                        // Kill any app zygotes always, since they can't fork new
-                                        // processes with references to the old code
-                                        forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
-                                                intent.getIntExtra(Intent.EXTRA_UID, -1)),
-                                                userId);
-                                    }
-                                    final int cmd = killProcess
-                                            ? ApplicationThreadConstants.PACKAGE_REMOVED
-                                            : ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL;
-                                    sendPackageBroadcastLocked(cmd,
-                                            new String[] {ssp}, userId);
-                                    if (fullUninstall) {
-                                        // Remove all permissions granted from/to this package
-                                        mUgmInternal.removeUriPermissionsForPackage(ssp, userId,
-                                                true, false);
-
-                                        mAtmInternal.removeRecentTasksByPackageName(ssp, userId);
-
-                                        mServices.forceStopPackageLocked(ssp, userId);
-                                        mAtmInternal.onPackageUninstalled(ssp, userId);
-                                        mBatteryStatsService.notePackageUninstalled(ssp);
-                                    }
-                                } else {
-                                    if (killProcess) {
-                                        int reason;
-                                        int subReason;
-                                        if (replacing) {
-                                            reason = ApplicationExitInfo.REASON_PACKAGE_UPDATED;
-                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
-                                        } else {
-                                            reason =
-                                                    ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE;
-                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
-                                        }
-
-                                        final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
-                                                -1);
-                                        synchronized (mProcLock) {
-                                            mProcessList.killPackageProcessesLSP(ssp,
-                                                    UserHandle.getAppId(extraUid),
-                                                    userId, ProcessList.INVALID_ADJ,
-                                                    reason,
-                                                    subReason,
-                                                    "change " + ssp);
-                                        }
-                                    }
-                                    cleanupDisabledPackageComponentsLocked(ssp, userId,
-                                            intent.getStringArrayExtra(
-                                                    Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
-                                    mServices.schedulePendingServiceStartLocked(ssp, userId);
-                                }
-                            }
-                            break;
-                        case Intent.ACTION_PACKAGES_SUSPENDED:
-                        case Intent.ACTION_PACKAGES_UNSUSPENDED:
-                            final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
-                                    intent.getAction());
-                            final String[] packageNames = intent.getStringArrayExtra(
-                                    Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                            final int userIdExtra = intent.getIntExtra(
-                                    Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-
-                            mAtmInternal.onPackagesSuspendedChanged(packageNames, suspended,
-                                    userIdExtra);
-
-                            final boolean quarantined = intent.getBooleanExtra(
-                                    Intent.EXTRA_QUARANTINED, false);
-                            if (suspended && quarantined && packageNames != null) {
-                                for (int i = 0; i < packageNames.length; i++) {
-                                    forceStopPackage(packageNames[i], userId,
-                                            ActivityManager.FLAG_OR_STOPPED, "quarantined");
-                                }
-                            }
-
-                            break;
-                    }
-                    break;
-                case Intent.ACTION_PACKAGE_REPLACED:
-                {
-                    final Uri data = intent.getData();
-                    final String ssp;
-                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
-                        ApplicationInfo aInfo = null;
-                        try {
-                            aInfo = AppGlobals.getPackageManager()
-                                    .getApplicationInfo(ssp, STOCK_PM_FLAGS, userId);
-                        } catch (RemoteException ignore) {}
-                        if (aInfo == null) {
-                            Slog.w(TAG, "Dropping ACTION_PACKAGE_REPLACED for non-existent pkg:"
-                                    + " ssp=" + ssp + " data=" + data);
-                            scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                                    userId, brOptions, callingUid, callerPackage);
-                            return ActivityManager.BROADCAST_SUCCESS;
-                        }
-                        updateAssociationForApp(aInfo);
-                        mAtmInternal.onPackageReplaced(aInfo);
-                        mServices.updateServiceApplicationInfoLocked(aInfo);
-                        sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
-                                new String[] {ssp}, userId);
-                    }
-                    break;
-                }
-                case Intent.ACTION_PACKAGE_ADDED:
-                {
-                    // Special case for adding a package: by default turn on compatibility mode.
-                    Uri data = intent.getData();
-                    String ssp;
-                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
-                        final boolean replacing =
-                                intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-                        mAtmInternal.onPackageAdded(ssp, replacing);
-
-                        try {
-                            ApplicationInfo ai = AppGlobals.getPackageManager().
-                                    getApplicationInfo(ssp, STOCK_PM_FLAGS, 0);
-                            mBatteryStatsService.notePackageInstalled(ssp,
-                                    ai != null ? ai.longVersionCode : 0);
-                        } catch (RemoteException e) {
-                        }
-                    }
-                    break;
-                }
-                case Intent.ACTION_PACKAGE_DATA_CLEARED:
-                {
-                    Uri data = intent.getData();
-                    String ssp;
-                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
-                        mAtmInternal.onPackageDataCleared(ssp, userId);
-                    }
-                    break;
-                }
-                case Intent.ACTION_TIMEZONE_CHANGED:
-                    // If this is the time zone changed action, queue up a message that will reset
-                    // the timezone of all currently running processes. This message will get
-                    // queued up before the broadcast happens.
-                    mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
-                    break;
-                case Intent.ACTION_TIME_CHANGED:
-                    // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
-                    // the tri-state value it may contain and "unknown".
-                    // For convenience we re-use the Intent extra values.
-                    final int NO_EXTRA_VALUE_FOUND = -1;
-                    final int timeFormatPreferenceMsgValue = intent.getIntExtra(
-                            Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
-                            NO_EXTRA_VALUE_FOUND /* defaultValue */);
-                    // Only send a message if the time preference is available.
-                    if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
-                        Message updateTimePreferenceMsg =
-                                mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
-                                        timeFormatPreferenceMsgValue, 0);
-                        mHandler.sendMessage(updateTimePreferenceMsg);
-                    }
-                    mBatteryStatsService.noteCurrentTimeChanged();
-                    break;
-                case ConnectivityManager.ACTION_CLEAR_DNS_CACHE:
-                    mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
-                    break;
-                case Proxy.PROXY_CHANGE_ACTION:
-                    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG));
-                    break;
-                case android.hardware.Camera.ACTION_NEW_PICTURE:
-                case android.hardware.Camera.ACTION_NEW_VIDEO:
-                    // In N we just turned these off; in O we are turing them back on partly,
-                    // only for registered receivers.  This will still address the main problem
-                    // (a spam of apps waking up when a picture is taken putting significant
-                    // memory pressure on the system at a bad point), while still allowing apps
-                    // that are already actively running to know about this happening.
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    break;
-                case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
-                    mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
-                    break;
-                case "com.android.launcher.action.INSTALL_SHORTCUT":
-                    // As of O, we no longer support this broadcasts, even for pre-O apps.
-                    // Apps should now be using ShortcutManager.pinRequestShortcut().
-                    Log.w(TAG, "Broadcast " + action
-                            + " no longer supported. It will not be delivered.");
-                    scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                            userId, brOptions, callingUid, callerPackage);
-                    return ActivityManager.BROADCAST_SUCCESS;
-                case Intent.ACTION_PRE_BOOT_COMPLETED:
-                    timeoutExempt = true;
-                    break;
-                case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
-                    if (!mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
-                            callerPackage)) {
-                        scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                                userId, brOptions, callingUid, callerPackage);
-                        // Returning success seems to be the pattern here
-                        return ActivityManager.BROADCAST_SUCCESS;
-                    }
-                    break;
-            }
-
-            if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
-                    Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
-                    Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
-                final int uid = getUidFromIntent(intent);
-                if (uid != -1) {
-                    final UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
-                    if (uidRec != null) {
-                        uidRec.updateHasInternetPermission();
-                    }
-                }
-            }
-        }
-
-        // Add to the sticky list if requested.
-        if (sticky) {
-            if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
-                    callingPid, callingUid)
-                    != PackageManager.PERMISSION_GRANTED) {
-                String msg =
-                        "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
-                            + " pid="
-                                + callingPid
-                                + ", uid="
-                                + callingUid
-                                + " requires "
-                                + android.Manifest.permission.BROADCAST_STICKY;
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
-            }
-            if (requiredPermissions != null && requiredPermissions.length > 0) {
-                Slog.w(TAG, "Can't broadcast sticky intent " + intent
-                        + " and enforce permissions " + Arrays.toString(requiredPermissions));
-                scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                        userId, brOptions, callingUid, callerPackage);
-                return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
-            }
-            if (intent.getComponent() != null) {
-                throw new SecurityException(
-                        "Sticky broadcasts can't target a specific component");
-            }
-            synchronized (mStickyBroadcasts) {
-                // We use userId directly here, since the "all" target is maintained
-                // as a separate set of sticky broadcasts.
-                if (userId != UserHandle.USER_ALL) {
-                    // But first, if this is not a broadcast to all users, then
-                    // make sure it doesn't conflict with an existing broadcast to
-                    // all users.
-                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(
-                            UserHandle.USER_ALL);
-                    if (stickies != null) {
-                        ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
-                        if (list != null) {
-                            int N = list.size();
-                            int i;
-                            for (i = 0; i < N; i++) {
-                                if (intent.filterEquals(list.get(i).intent)) {
-                                    throw new IllegalArgumentException("Sticky broadcast " + intent
-                                            + " for user " + userId
-                                            + " conflicts with existing global broadcast");
-                                }
-                            }
-                        }
-                    }
-                }
-                ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
-                        mStickyBroadcasts.get(userId);
-                if (stickies == null) {
-                    stickies = new ArrayMap<>();
-                    mStickyBroadcasts.put(userId, stickies);
-                }
-                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
-                if (list == null) {
-                    list = new ArrayList<>();
-                    stickies.put(intent.getAction(), list);
-                }
-                final boolean deferUntilActive = BroadcastRecord.calculateDeferUntilActive(
-                        callingUid, brOptions, resultTo, ordered,
-                        BroadcastRecord.calculateUrgent(intent, brOptions));
-                final int stickiesCount = list.size();
-                int i;
-                for (i = 0; i < stickiesCount; i++) {
-                    if (intent.filterEquals(list.get(i).intent)) {
-                        // This sticky already exists, replace it.
-                        list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
-                                callingUid, callerAppProcessState, resolvedType));
-                        break;
-                    }
-                }
-                if (i >= stickiesCount) {
-                    list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
-                            callingUid, callerAppProcessState, resolvedType));
-                }
-            }
-        }
-
-        int[] users;
-        if (userId == UserHandle.USER_ALL) {
-            // Caller wants broadcast to go to all started users.
-            users = mUserController.getStartedUserArray();
-        } else {
-            // Caller wants broadcast to go to one specific user.
-            users = new int[] {userId};
-        }
-
-        var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
-                true /* isReceiver */, true /* resolveForStart */, callingUid, callingPid);
-        args.platformCompat = mPlatformCompat;
-
-        // Figure out who all will receive this broadcast.
-        final int cookie = BroadcastQueue.traceBegin("queryReceivers");
-        List receivers = null;
-        List<BroadcastFilter> registeredReceivers = null;
-        // Need to resolve the intent to interested receivers...
-        if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-            receivers = collectReceiverComponents(
-                    intent, resolvedType, callingUid, callingPid, users, broadcastAllowList);
-        }
-        if (intent.getComponent() == null) {
-            final PackageDataSnapshot snapshot = getPackageManagerInternal().snapshot();
-            if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
-                // Query one target user at a time, excluding shell-restricted users
-                for (int i = 0; i < users.length; i++) {
-                    if (mUserController.hasUserRestriction(
-                            UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
-                        continue;
-                    }
-                    List<BroadcastFilter> registeredReceiversForUser =
-                            mReceiverResolver.queryIntent(snapshot, intent,
-                                    resolvedType, false /*defaultOnly*/, users[i]);
-                    if (registeredReceivers == null) {
-                        registeredReceivers = registeredReceiversForUser;
-                    } else if (registeredReceiversForUser != null) {
-                        registeredReceivers.addAll(registeredReceiversForUser);
-                    }
-                }
-            } else {
-                registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
-                        resolvedType, false /*defaultOnly*/, userId);
-            }
-            if (registeredReceivers != null) {
-                SaferIntentUtils.blockNullAction(args, registeredReceivers);
-            }
-        }
-        BroadcastQueue.traceEnd(cookie);
-
-        final boolean replacePending =
-                (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
-
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing broadcast: " + intent.getAction()
-                + " replacePending=" + replacePending);
-        if (registeredReceivers != null && broadcastAllowList != null) {
-            // if a uid whitelist was provided, remove anything in the application space that wasn't
-            // in it.
-            for (int i = registeredReceivers.size() - 1; i >= 0; i--) {
-                final int owningAppId = UserHandle.getAppId(registeredReceivers.get(i).owningUid);
-                if (owningAppId >= Process.FIRST_APPLICATION_UID
-                        && Arrays.binarySearch(broadcastAllowList, owningAppId) < 0) {
-                    registeredReceivers.remove(i);
-                }
-            }
-        }
-
-        int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
-
-        // Merge into one list.
-        int ir = 0;
-        if (receivers != null) {
-            // A special case for PACKAGE_ADDED: do not allow the package
-            // being added to see this broadcast.  This prevents them from
-            // using this as a back door to get run as soon as they are
-            // installed.  Maybe in the future we want to have a special install
-            // broadcast or such for apps, but we'd like to deliberately make
-            // this decision.
-            String skipPackages[] = null;
-            if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
-                    || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
-                    || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
-                Uri data = intent.getData();
-                if (data != null) {
-                    String pkgName = data.getSchemeSpecificPart();
-                    if (pkgName != null) {
-                        skipPackages = new String[] { pkgName };
-                    }
-                }
-            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
-                skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            }
-            if (skipPackages != null && (skipPackages.length > 0)) {
-                for (String skipPackage : skipPackages) {
-                    if (skipPackage != null) {
-                        int NT = receivers.size();
-                        for (int it=0; it<NT; it++) {
-                            ResolveInfo curt = (ResolveInfo)receivers.get(it);
-                            if (curt.activityInfo.packageName.equals(skipPackage)) {
-                                receivers.remove(it);
-                                it--;
-                                NT--;
-                            }
-                        }
-                    }
-                }
-            }
-
-            int NT = receivers != null ? receivers.size() : 0;
-            int it = 0;
-            ResolveInfo curt = null;
-            BroadcastFilter curr = null;
-            while (it < NT && ir < NR) {
-                if (curt == null) {
-                    curt = (ResolveInfo)receivers.get(it);
-                }
-                if (curr == null) {
-                    curr = registeredReceivers.get(ir);
-                }
-                if (curr.getPriority() >= curt.priority) {
-                    // Insert this broadcast record into the final list.
-                    receivers.add(it, curr);
-                    ir++;
-                    curr = null;
-                    it++;
-                    NT++;
-                } else {
-                    // Skip to the next ResolveInfo in the final list.
-                    it++;
-                    curt = null;
-                }
-            }
-        }
-        while (ir < NR) {
-            if (receivers == null) {
-                receivers = new ArrayList();
-            }
-            receivers.add(registeredReceivers.get(ir));
-            ir++;
-        }
-
-        if (isCallerSystem) {
-            checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
-                    isProtectedBroadcast, receivers);
-        }
-
-        if ((receivers != null && receivers.size() > 0)
-                || resultTo != null) {
-            BroadcastQueue queue = mBroadcastQueue;
-            SaferIntentUtils.filterNonExportedComponents(args, receivers);
-            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
-                    callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
-                    requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
-                    receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
-                    ordered, sticky, false, userId,
-                    backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
-                    callerAppProcessState);
-            broadcastSentEventRecord.setBroadcastRecord(r);
-
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
-            queue.enqueueBroadcastLocked(r);
-        } else {
-            // There was nobody interested in the broadcast, but we still want to record
-            // that it happened.
-            if (intent.getComponent() == null && intent.getPackage() == null
-                    && (intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-                // This was an implicit broadcast... let's record it for posterity.
-                addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
-            }
-        }
-
-        return ActivityManager.BROADCAST_SUCCESS;
-    }
-
-    @GuardedBy("this")
-    private void scheduleCanceledResultTo(ProcessRecord resultToApp, IIntentReceiver resultTo,
-            Intent intent, int userId, BroadcastOptions options, int callingUid,
-            String callingPackage) {
-        if (resultTo == null) {
-            return;
-        }
-        final ProcessRecord app = resultToApp;
-        final IApplicationThread thread  = (app != null) ? app.getOnewayThread() : null;
-        if (thread != null) {
-            try {
-                final boolean shareIdentity = (options != null && options.isShareIdentityEnabled());
-                thread.scheduleRegisteredReceiver(
-                        resultTo, intent, Activity.RESULT_CANCELED, null, null,
-                        false, false, true, userId, app.mState.getReportedProcState(),
-                        shareIdentity ? callingUid : Process.INVALID_UID,
-                        shareIdentity ? callingPackage : null);
-            } catch (RemoteException e) {
-                final String msg = "Failed to schedule result of " + intent + " via "
-                        + app + ": " + e;
-                app.killLocked("Can't schedule resultTo", ApplicationExitInfo.REASON_OTHER,
-                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
-                Slog.d(TAG, msg);
-            }
-        }
-    }
-
-    @GuardedBy("this")
-    private int getRealProcessStateLocked(ProcessRecord app, int pid) {
-        if (app == null) {
-            synchronized (mPidsSelfLocked) {
-                app = mPidsSelfLocked.get(pid);
-            }
-        }
-        if (app != null && app.getThread() != null && !app.isKilled()) {
-            return app.mState.getCurProcState();
-        }
-        return PROCESS_STATE_NONEXISTENT;
-    }
-
-    @GuardedBy("this")
-    private int getRealUidStateLocked(ProcessRecord app, int pid) {
-        if (app == null) {
-            synchronized (mPidsSelfLocked) {
-                app = mPidsSelfLocked.get(pid);
-            }
-        }
-        if (app != null && app.getThread() != null && !app.isKilled()) {
-            final UidRecord uidRecord = app.getUidRecord();
-            if (uidRecord != null) {
-                return uidRecord.getCurProcState();
-            }
-        }
-        return PROCESS_STATE_NONEXISTENT;
-    }
-
-    @VisibleForTesting
-    ArrayList<StickyBroadcast> getStickyBroadcastsForTest(String action, int userId) {
-        synchronized (mStickyBroadcasts) {
-            final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
-                    mStickyBroadcasts.get(userId);
-            if (stickyBroadcasts == null) {
-                return null;
-            }
-            return stickyBroadcasts.get(action);
-        }
-    }
-
-    /**
-     * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
-     */
-    private int getUidFromIntent(Intent intent) {
-        if (intent == null) {
-            return -1;
-        }
-        final Bundle intentExtras = intent.getExtras();
-        return intent.hasExtra(Intent.EXTRA_UID)
-                ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
-    }
-
-    final void rotateBroadcastStatsIfNeededLocked() {
-        final long now = SystemClock.elapsedRealtime();
-        if (mCurBroadcastStats == null ||
-                (mCurBroadcastStats.mStartRealtime +(24*60*60*1000) < now)) {
-            mLastBroadcastStats = mCurBroadcastStats;
-            if (mLastBroadcastStats != null) {
-                mLastBroadcastStats.mEndRealtime = SystemClock.elapsedRealtime();
-                mLastBroadcastStats.mEndUptime = SystemClock.uptimeMillis();
-            }
-            mCurBroadcastStats = new BroadcastStats();
-        }
-    }
-
-    final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
-            int skipCount, long dispatchTime) {
-        rotateBroadcastStatsIfNeededLocked();
-        mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
-    }
-
-    final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
-        rotateBroadcastStatsIfNeededLocked();
-        mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
-    }
-
-    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
-        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
-        final String callerPackage = info != null ? info.packageName : original.callerPackage;
-        if (callerPackage != null) {
-            mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
-                    original.callingUid, 0, callerPackage).sendToTarget();
-        }
-    }
-
-    final Intent verifyBroadcastLocked(Intent intent) {
-        if (intent != null) {
-            intent.prepareToEnterSystemServer();
-        }
-
-        int flags = intent.getFlags();
-
-        if (!mProcessesReady) {
-            // if the caller really truly claims to know what they're doing, go
-            // ahead and allow the broadcast without launching any receivers
-            if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) {
-                // This will be turned into a FLAG_RECEIVER_REGISTERED_ONLY later on if needed.
-            } else if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-                Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
-                        + " before boot completion");
-                throw new IllegalStateException("Cannot broadcast before boot completed");
-            }
-        }
-
-        if ((flags&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
-            throw new IllegalArgumentException(
-                    "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
-        }
-
-        if ((flags & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
-            switch (Binder.getCallingUid()) {
-                case ROOT_UID:
-                case SHELL_UID:
-                    break;
-                default:
-                    Slog.w(TAG, "Removing FLAG_RECEIVER_FROM_SHELL because caller is UID "
-                            + Binder.getCallingUid());
-                    intent.removeFlags(Intent.FLAG_RECEIVER_FROM_SHELL);
-                    break;
-            }
-        }
-
-        return intent;
-    }
-
     /**
      * @deprecated Use {@link #broadcastIntentWithFeature}
      */
@@ -16344,110 +14260,14 @@
             String[] requiredPermissions, String[] excludedPermissions,
             String[] excludedPackages, int appOp, Bundle bOptions,
             boolean serialized, boolean sticky, int userId) {
-        enforceNotIsolatedCaller("broadcastIntent");
-
-        synchronized(this) {
-            intent = verifyBroadcastLocked(intent);
-
-            final ProcessRecord callerApp = getRecordForAppLOSP(caller);
-            final int callingPid = Binder.getCallingPid();
-            final int callingUid = Binder.getCallingUid();
-
-            // We're delivering the result to the caller
-            final ProcessRecord resultToApp = callerApp;
-
-            // Permission regimes around sender-supplied broadcast options.
-            enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
-
-            final ComponentName cn = intent.getComponent();
-
-            Trace.traceBegin(
-                    Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
-
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                return broadcastIntentLocked(callerApp,
-                        callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
-                        intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
-                        resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
-                        appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid,
-                        callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-            }
-        }
-    }
-
-    // Not the binder call surface
-    int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
-            int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
-            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
-            String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
-            boolean serialized, boolean sticky, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges,
-            @Nullable int[] broadcastAllowList) {
-        synchronized(this) {
-            intent = verifyBroadcastLocked(intent);
-
-            final long origId = Binder.clearCallingIdentity();
-            String[] requiredPermissions = requiredPermission == null ? null
-                    : new String[] {requiredPermission};
-            try {
-                return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
-                        resultToApp, resultTo, resultCode, resultData, resultExtras,
-                        requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
-                        uid, realCallingUid, realCallingPid, userId,
-                        backgroundStartPrivileges, broadcastAllowList,
-                        null /* filterExtrasForReceiver */);
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
-        }
+        return mBroadcastController.broadcastIntentWithFeature(caller, callingFeatureId, intent,
+                resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions,
+                excludedPermissions, excludedPackages, appOp, bOptions, serialized, sticky, userId);
     }
 
     @Override
     public final void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {
-        // Refuse possible leaked file descriptors
-        if (intent != null && intent.hasFileDescriptors() == true) {
-            throw new IllegalArgumentException("File descriptors passed in Intent");
-        }
-
-        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, true, ALLOW_NON_FULL, "removeStickyBroadcast", null);
-
-        if (checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: unbroadcastIntent() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.BROADCAST_STICKY;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-        synchronized (mStickyBroadcasts) {
-            ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
-            if (stickies != null) {
-                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
-                if (list != null) {
-                    int N = list.size();
-                    int i;
-                    for (i = 0; i < N; i++) {
-                        if (intent.filterEquals(list.get(i).intent)) {
-                            list.remove(i);
-                            break;
-                        }
-                    }
-                    if (list.size() <= 0) {
-                        stickies.remove(intent.getAction());
-                    }
-                }
-                if (stickies.size() <= 0) {
-                    mStickyBroadcasts.remove(userId);
-                }
-            }
-        }
+        mBroadcastController.unbroadcastIntent(caller, intent, userId);
     }
 
     void backgroundServicesFinishedLocked(int userId) {
@@ -16456,31 +14276,32 @@
 
     public void finishReceiver(IBinder caller, int resultCode, String resultData,
             Bundle resultExtras, boolean resultAbort, int flags) {
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + caller);
+        mBroadcastController.finishReceiver(caller, resultCode, resultData, resultExtras,
+                resultAbort, flags);
+    }
 
-        // Refuse possible leaked file descriptors
-        if (resultExtras != null && resultExtras.hasFileDescriptors()) {
-            throw new IllegalArgumentException("File descriptors passed in Bundle");
-        }
+    @VisibleForTesting
+    ArrayList<BroadcastController.StickyBroadcast> getStickyBroadcastsForTest(String action,
+            int userId) {
+        return mBroadcastController.getStickyBroadcastsForTest(action, userId);
+    }
 
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized(this) {
-                final ProcessRecord callerApp = getRecordForAppLOSP(caller);
-                if (callerApp == null) {
-                    Slog.w(TAG, "finishReceiver: no app for " + caller);
-                    return;
-                }
+    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+        mBroadcastController.notifyBroadcastFinishedLocked(original);
+    }
 
-                mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
-                        resultData, resultExtras, resultAbort, true);
-                // updateOomAdjLocked() will be done here
-                trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
-            }
+    final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+            int skipCount, long dispatchTime) {
+        mBroadcastController.addBroadcastStatLocked(action, srcPackage, receiveCount, skipCount,
+                dispatchTime);
+    }
 
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
+    final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+        mBroadcastController.addBackgroundCheckViolationLocked(action, targetPackage);
+    }
+
+    void removeReceiverLocked(ReceiverList rl) {
+        mBroadcastController.removeReceiverLocked(rl);
     }
 
     // =========================================================
@@ -17845,7 +15666,7 @@
     }
 
     @GuardedBy("this")
-    private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
+    void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
         // First remove any unused application processes whose package
         // has been removed.
         boolean didSomething = false;
@@ -18835,7 +16656,7 @@
 
         @Override
         public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) {
-            enforceBroadcastOptionPermissionsInternal(options, callingUid);
+            mBroadcastController.enforceBroadcastOptionPermissionsInternal(options, callingUid);
         }
 
         /**
@@ -19225,7 +17046,7 @@
                 @Nullable int[] broadcastAllowList) {
             synchronized (ActivityManagerService.this) {
                 final ProcessRecord resultToApp = getRecordForAppLOSP(resultToThread);
-                return ActivityManagerService.this.broadcastIntentInPackage(packageName, featureId,
+                return mBroadcastController.broadcastIntentInPackage(packageName, featureId,
                         uid, realCallingUid, realCallingPid, intent, resolvedType, resultToApp,
                         resultTo, resultCode, resultData, resultExtras, requiredPermission,
                         bOptions, serialized, sticky, userId,
@@ -19242,13 +17063,13 @@
                 @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
                 @Nullable Bundle bOptions) {
             synchronized (ActivityManagerService.this) {
-                intent = verifyBroadcastLocked(intent);
+                intent = mBroadcastController.verifyBroadcastLocked(intent);
 
                 final int callingPid = Binder.getCallingPid();
                 final int callingUid = Binder.getCallingUid();
                 final long origId = Binder.clearCallingIdentity();
                 try {
-                    return ActivityManagerService.this.broadcastIntentLocked(null /*callerApp*/,
+                    return mBroadcastController.broadcastIntentLocked(null /*callerApp*/,
                             null /*callerPackage*/, null /*callingFeatureId*/, intent,
                             null /* resolvedType */, null /* resultToApp */, resultTo,
                             0 /* resultCode */, null /* resultData */,
@@ -21165,26 +18986,6 @@
         }
     }
 
-    /**
-     * Gets an {@code int} argument from the given {@code index} on {@code args}, logging an error
-     * message on {@code pw} when it cannot be parsed.
-     *
-     * Returns {@code int} argument or {@code invalidValue} if it could not be parsed.
-     */
-    private static int getIntArg(PrintWriter pw, String[] args, int index, int invalidValue) {
-        if (index > args.length) {
-            pw.println("Missing argument");
-            return invalidValue;
-        }
-        String arg = args[index];
-        try {
-            return Integer.parseInt(arg);
-        } catch (Exception e) {
-            pw.printf("Non-numeric argument at index %d: %s\n", index, arg);
-            return invalidValue;
-        }
-    }
-
     private void notifyMediaProjectionEvent(int uid, @NonNull IBinder projectionToken,
             @MediaProjectionTokenEvent int event) {
         synchronized (mMediaProjectionTokenMap) {
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
new file mode 100644
index 0000000..32026b2
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -0,0 +1,2410 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
+import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.os.Process.BLUETOOTH_UID;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.NETWORK_STACK_UID;
+import static android.os.Process.NFC_UID;
+import static android.os.Process.PHONE_UID;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SE_UID;
+import static android.os.Process.SHELL_UID;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerService.CLEAR_DNS_CACHE_MSG;
+import static com.android.server.am.ActivityManagerService.HANDLE_TRUST_STORAGE_UPDATE_MSG;
+import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
+import static com.android.server.am.ActivityManagerService.TAG;
+import static com.android.server.am.ActivityManagerService.UPDATE_HTTP_PROXY_MSG;
+import static com.android.server.am.ActivityManagerService.UPDATE_TIME_PREFERENCE_MSG;
+import static com.android.server.am.ActivityManagerService.UPDATE_TIME_ZONE;
+import static com.android.server.am.ActivityManagerService.checkComponentPermission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.ApplicationExitInfo;
+import android.app.ApplicationThreadConstants;
+import android.app.BackgroundStartPrivileges;
+import android.app.BroadcastOptions;
+import android.app.IApplicationThread;
+import android.app.compat.CompatChanges;
+import android.appwidget.AppWidgetManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+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.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.media.audiofx.AudioEffect;
+import android.net.ConnectivityManager;
+import android.net.Proxy;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.BinderProxy;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IntentResolver;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+import com.android.server.pm.Computer;
+import com.android.server.pm.SaferIntentUtils;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
+import com.android.server.utils.Slogf;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+class BroadcastController {
+    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
+
+    /**
+     * It is now required for apps to explicitly set either
+     * {@link android.content.Context#RECEIVER_EXPORTED} or
+     * {@link android.content.Context#RECEIVER_NOT_EXPORTED} when registering a receiver for an
+     * unprotected broadcast in code.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
+
+    // Maximum number of receivers an app can register.
+    private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
+
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final ActivityManagerService mService;
+    @NonNull
+    private BroadcastQueue mBroadcastQueue;
+
+    @GuardedBy("mService")
+    BroadcastStats mLastBroadcastStats;
+
+    @GuardedBy("mService")
+    BroadcastStats mCurBroadcastStats;
+
+    /**
+     * Broadcast actions that will always be deliverable to unlaunched/background apps
+     */
+    @GuardedBy("mService")
+    private ArraySet<String> mBackgroundLaunchBroadcasts;
+
+    /**
+     * State of all active sticky broadcasts per user.  Keys are the action of the
+     * sticky Intent, values are an ArrayList of all broadcasted intents with
+     * that action (which should usually be one).  The SparseArray is keyed
+     * by the user ID the sticky is for, and can include UserHandle.USER_ALL
+     * for stickies that are sent to all users.
+     */
+    @GuardedBy("mStickyBroadcasts")
+    final SparseArray<ArrayMap<String, ArrayList<StickyBroadcast>>> mStickyBroadcasts =
+            new SparseArray<>();
+
+    /**
+     * Keeps track of all IIntentReceivers that have been registered for broadcasts.
+     * Hash keys are the receiver IBinder, hash value is a ReceiverList.
+     */
+    @GuardedBy("mService")
+    final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
+
+    /**
+     * Resolver for broadcast intents to registered receivers.
+     * Holds BroadcastFilter (subclass of IntentFilter).
+     */
+    final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver =
+            new IntentResolver<>() {
+        @Override
+        protected boolean allowFilterResult(
+                BroadcastFilter filter, List<BroadcastFilter> dest) {
+            IBinder target = filter.receiverList.receiver.asBinder();
+            for (int i = dest.size() - 1; i >= 0; i--) {
+                if (dest.get(i).receiverList.receiver.asBinder() == target) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        protected BroadcastFilter newResult(@NonNull Computer computer, BroadcastFilter filter,
+                int match, int userId, long customFlags) {
+            if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
+                    || userId == filter.owningUserId) {
+                return super.newResult(computer, filter, match, userId, customFlags);
+            }
+            return null;
+        }
+
+        @Override
+        protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
+            return input;
+        }
+
+        @Override
+        protected BroadcastFilter[] newArray(int size) {
+            return new BroadcastFilter[size];
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
+            return packageName.equals(filter.packageName);
+        }
+    };
+
+    BroadcastController(Context context, ActivityManagerService service, BroadcastQueue queue) {
+        mContext = context;
+        mService = service;
+        mBroadcastQueue = queue;
+    }
+
+    void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
+        mBroadcastQueue = broadcastQueue;
+    }
+
+    Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
+            String callerFeatureId, String receiverId, IIntentReceiver receiver,
+            IntentFilter filter, String permission, int userId, int flags) {
+        traceRegistrationBegin(receiverId, receiver, filter, userId);
+        try {
+            return registerReceiverWithFeatureTraced(caller, callerPackage, callerFeatureId,
+                    receiverId, receiver, filter, permission, userId, flags);
+        } finally {
+            traceRegistrationEnd();
+        }
+    }
+
+    private static void traceRegistrationBegin(String receiverId, IIntentReceiver receiver,
+            IntentFilter filter, int userId) {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            final StringBuilder sb = new StringBuilder("registerReceiver: ");
+            sb.append(Binder.getCallingUid()); sb.append('/');
+            sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
+            final int actionsCount = filter.safeCountActions();
+            if (actionsCount > 0) {
+                for (int i = 0; i < actionsCount; ++i) {
+                    sb.append(filter.getAction(i));
+                    if (i != actionsCount - 1) sb.append(',');
+                }
+            } else {
+                sb.append("null");
+            }
+            sb.append('/');
+            sb.append('u'); sb.append(userId); sb.append('/');
+            sb.append(receiver == null ? "null" : receiver.asBinder());
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
+        }
+    }
+
+    private static void traceRegistrationEnd() {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+    }
+
+    private Intent registerReceiverWithFeatureTraced(IApplicationThread caller,
+            String callerPackage, String callerFeatureId, String receiverId,
+            IIntentReceiver receiver, IntentFilter filter, String permission,
+            int userId, int flags) {
+        mService.enforceNotIsolatedCaller("registerReceiver");
+        ArrayList<StickyBroadcast> stickyBroadcasts = null;
+        ProcessRecord callerApp = null;
+        final boolean visibleToInstantApps =
+                (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
+
+        int callingUid;
+        int callingPid;
+        boolean instantApp;
+        synchronized (mService.mProcLock) {
+            callerApp = mService.getRecordForAppLOSP(caller);
+            if (callerApp == null) {
+                Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
+                return null;
+            }
+            if (callerApp.info.uid != SYSTEM_UID
+                    && !callerApp.getPkgList().containsKey(callerPackage)
+                    && !"android".equals(callerPackage)) {
+                throw new SecurityException("Given caller package " + callerPackage
+                        + " is not running in process " + callerApp);
+            }
+            callingUid = callerApp.info.uid;
+            callingPid = callerApp.getPid();
+
+            instantApp = isInstantApp(callerApp, callerPackage, callingUid);
+        }
+        userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+                ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
+
+        // Warn if system internals are registering for important broadcasts
+        // without also using a priority to ensure they process the event
+        // before normal apps hear about it
+        if (UserHandle.isCore(callingUid)) {
+            final int priority = filter.getPriority();
+            final boolean systemPriority = (priority >= IntentFilter.SYSTEM_HIGH_PRIORITY)
+                    || (priority <= IntentFilter.SYSTEM_LOW_PRIORITY);
+            if (!systemPriority) {
+                final int N = filter.countActions();
+                for (int i = 0; i < N; i++) {
+                    // TODO: expand to additional important broadcasts over time
+                    final String action = filter.getAction(i);
+                    if (action.startsWith("android.intent.action.USER_")
+                            || action.startsWith("android.intent.action.PACKAGE_")
+                            || action.startsWith("android.intent.action.UID_")
+                            || action.startsWith("android.intent.action.EXTERNAL_")
+                            || action.startsWith("android.bluetooth.")
+                            || action.equals(Intent.ACTION_SHUTDOWN)) {
+                        if (DEBUG_BROADCAST) {
+                            Slog.wtf(TAG,
+                                    "System internals registering for " + filter.toLongString()
+                                            + " with app priority; this will race with apps!",
+                                    new Throwable());
+                        }
+
+                        // When undefined, assume that system internals need
+                        // to hear about the event first; they can use
+                        // SYSTEM_LOW_PRIORITY if they need to hear last
+                        if (priority == 0) {
+                            filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+
+        Iterator<String> actions = filter.actionsIterator();
+        if (actions == null) {
+            ArrayList<String> noAction = new ArrayList<String>(1);
+            noAction.add(null);
+            actions = noAction.iterator();
+        }
+        boolean onlyProtectedBroadcasts = true;
+
+        // Collect stickies of users and check if broadcast is only registered for protected
+        // broadcasts
+        int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
+        synchronized (mStickyBroadcasts) {
+            while (actions.hasNext()) {
+                String action = actions.next();
+                for (int id : userIds) {
+                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+                            mStickyBroadcasts.get(id);
+                    if (stickies != null) {
+                        ArrayList<StickyBroadcast> broadcasts = stickies.get(action);
+                        if (broadcasts != null) {
+                            if (stickyBroadcasts == null) {
+                                stickyBroadcasts = new ArrayList<>();
+                            }
+                            stickyBroadcasts.addAll(broadcasts);
+                        }
+                    }
+                }
+                if (onlyProtectedBroadcasts) {
+                    try {
+                        onlyProtectedBroadcasts &=
+                                AppGlobals.getPackageManager().isProtectedBroadcast(action);
+                    } catch (RemoteException e) {
+                        onlyProtectedBroadcasts = false;
+                        Slog.w(TAG, "Remote exception", e);
+                    }
+                }
+            }
+        }
+
+        if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
+            SdkSandboxManagerLocal sdkSandboxManagerLocal =
+                    LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+            if (sdkSandboxManagerLocal == null) {
+                throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
+                        + " whether SDK sandbox uid can register to broadcast receivers.");
+            }
+            if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
+                    /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
+                throw new SecurityException("SDK sandbox not allowed to register receiver"
+                        + " with the given IntentFilter: " + filter.toLongString());
+            }
+        }
+
+        // If the change is enabled, but neither exported or not exported is set, we need to log
+        // an error so the consumer can know to explicitly set the value for their flag.
+        // If the caller is registering for a sticky broadcast with a null receiver, we won't
+        // require a flag
+        final boolean explicitExportStateDefined =
+                (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0;
+        if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
+                (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) {
+            throw new IllegalArgumentException(
+                    "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED"
+                            + "flag");
+        }
+
+        // Don't enforce the flag check if we're EITHER registering for only protected
+        // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
+        // not be used generally, so we will be marking them as exported by default
+        boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
+                DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
+
+        // A receiver that is visible to instant apps must also be exported.
+        final boolean unexportedReceiverVisibleToInstantApps =
+                ((flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && (
+                        (flags & Context.RECEIVER_NOT_EXPORTED) != 0);
+        if (unexportedReceiverVisibleToInstantApps && requireExplicitFlagForDynamicReceivers) {
+            throw new IllegalArgumentException(
+                    "Receiver can't specify both RECEIVER_VISIBLE_TO_INSTANT_APPS and "
+                            + "RECEIVER_NOT_EXPORTED flag");
+        }
+
+        if (!onlyProtectedBroadcasts) {
+            if (receiver == null && !explicitExportStateDefined) {
+                // sticky broadcast, no flag specified (flag isn't required)
+                flags |= Context.RECEIVER_EXPORTED;
+            } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
+                throw new SecurityException(
+                        callerPackage + ": One of RECEIVER_EXPORTED or "
+                                + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
+                                + "isn't being registered exclusively for system broadcasts");
+                // Assume default behavior-- flag check is not enforced
+            } else if (!requireExplicitFlagForDynamicReceivers && (
+                    (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
+                // Change is not enabled, assume exported unless otherwise specified.
+                flags |= Context.RECEIVER_EXPORTED;
+            }
+        } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
+            flags |= Context.RECEIVER_EXPORTED;
+        }
+
+        // Dynamic receivers are exported by default for versions prior to T
+        final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
+
+        ArrayList<StickyBroadcast> allSticky = null;
+        if (stickyBroadcasts != null) {
+            final ContentResolver resolver = mContext.getContentResolver();
+            // Look for any matching sticky broadcasts...
+            for (int i = 0, N = stickyBroadcasts.size(); i < N; i++) {
+                final StickyBroadcast broadcast = stickyBroadcasts.get(i);
+                Intent intent = broadcast.intent;
+                // Don't provided intents that aren't available to instant apps.
+                if (instantApp && (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
+                        == 0) {
+                    continue;
+                }
+                // If intent has scheme "content", it will need to access
+                // provider that needs to lock mProviderMap in ActivityThread
+                // and also it may need to wait application response, so we
+                // cannot lock ActivityManagerService here.
+                final int match;
+                if (Flags.avoidResolvingType()) {
+                    match = filter.match(intent.getAction(), broadcast.resolvedDataType,
+                            intent.getScheme(), intent.getData(), intent.getCategories(),
+                            TAG, false /* supportsWildcards */, null /* ignoreActions */,
+                            intent.getExtras());
+                } else {
+                    match = filter.match(resolver, intent, true, TAG);
+                }
+                if (match >= 0) {
+                    if (allSticky == null) {
+                        allSticky = new ArrayList<>();
+                    }
+                    allSticky.add(broadcast);
+                }
+            }
+        }
+
+        // The first sticky in the list is returned directly back to the client.
+        Intent sticky = allSticky != null ? allSticky.get(0).intent : null;
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
+        if (receiver == null) {
+            return sticky;
+        }
+
+        // SafetyNet logging for b/177931370. If any process other than system_server tries to
+        // listen to this broadcast action, then log it.
+        if (callingPid != Process.myPid()) {
+            if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING")
+                    || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) {
+                EventLog.writeEvent(0x534e4554, "177931370", callingUid, "");
+            }
+        }
+
+        synchronized (mService) {
+            IApplicationThread thread;
+            if (callerApp != null && ((thread = callerApp.getThread()) == null
+                    || thread.asBinder() != caller.asBinder())) {
+                // Original caller already died
+                return null;
+            }
+            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+            if (rl == null) {
+                rl = new ReceiverList(mService, callerApp, callingPid, callingUid,
+                        userId, receiver);
+                if (rl.app != null) {
+                    final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
+                    if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
+                        throw new IllegalStateException("Too many receivers, total of "
+                                + totalReceiversForApp + ", registered for pid: "
+                                + rl.pid + ", callerPackage: " + callerPackage);
+                    }
+                    rl.app.mReceivers.addReceiver(rl);
+                } else {
+                    try {
+                        receiver.asBinder().linkToDeath(rl, 0);
+                    } catch (RemoteException e) {
+                        return sticky;
+                    }
+                    rl.linkedToDeath = true;
+                }
+                mRegisteredReceivers.put(receiver.asBinder(), rl);
+            } else if (rl.uid != callingUid) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for uid " + callingUid
+                                + " was previously registered for uid " + rl.uid
+                                + " callerPackage is " + callerPackage);
+            } else if (rl.pid != callingPid) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for pid " + callingPid
+                                + " was previously registered for pid " + rl.pid
+                                + " callerPackage is " + callerPackage);
+            } else if (rl.userId != userId) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for user " + userId
+                                + " was previously registered for user " + rl.userId
+                                + " callerPackage is " + callerPackage);
+            }
+            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
+                    receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
+                    exported);
+            if (rl.containsFilter(filter)) {
+                Slog.w(TAG, "Receiver with filter " + filter
+                        + " already registered for pid " + rl.pid
+                        + ", callerPackage is " + callerPackage);
+            } else {
+                rl.add(bf);
+                if (!bf.debugCheck()) {
+                    Slog.w(TAG, "==> For Dynamic broadcast");
+                }
+                mReceiverResolver.addFilter(mService.getPackageManagerInternal().snapshot(), bf);
+            }
+
+            // Enqueue broadcasts for all existing stickies that match
+            // this filter.
+            if (allSticky != null) {
+                ArrayList receivers = new ArrayList();
+                receivers.add(bf);
+                sticky = null;
+
+                final int stickyCount = allSticky.size();
+                for (int i = 0; i < stickyCount; i++) {
+                    final StickyBroadcast broadcast = allSticky.get(i);
+                    final int originalStickyCallingUid = allSticky.get(i).originalCallingUid;
+                    // TODO(b/281889567): consider using checkComponentPermission instead of
+                    //  canAccessUnexportedComponents
+                    if (sticky == null && (exported || originalStickyCallingUid == callingUid
+                            || ActivityManager.canAccessUnexportedComponents(
+                            originalStickyCallingUid))) {
+                        sticky = broadcast.intent;
+                    }
+                    BroadcastQueue queue = mBroadcastQueue;
+                    BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
+                            null, null, -1, -1, false, null, null, null, null, OP_NONE,
+                            BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
+                            receivers, null, null, 0, null, null, false, true, true, -1,
+                            originalStickyCallingUid, BackgroundStartPrivileges.NONE,
+                            false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
+                            null /* filterExtrasForReceiver */,
+                            broadcast.originalCallingAppProcessState);
+                    queue.enqueueBroadcastLocked(r);
+                }
+            }
+
+            return sticky;
+        }
+    }
+
+    void unregisterReceiver(IIntentReceiver receiver) {
+        traceUnregistrationBegin(receiver);
+        try {
+            unregisterReceiverTraced(receiver);
+        } finally {
+            traceUnregistrationEnd();
+        }
+    }
+
+    private static void traceUnregistrationBegin(IIntentReceiver receiver) {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    TextUtils.formatSimple("unregisterReceiver: %d/%s", Binder.getCallingUid(),
+                            receiver == null ? "null" : receiver.asBinder()));
+        }
+    }
+
+    private static void traceUnregistrationEnd() {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+    }
+
+    private void unregisterReceiverTraced(IIntentReceiver receiver) {
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver);
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            boolean doTrim = false;
+            synchronized (mService) {
+                ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+                if (rl != null) {
+                    final BroadcastRecord r = rl.curBroadcast;
+                    if (r != null) {
+                        final boolean doNext = r.queue.finishReceiverLocked(
+                                rl.app, r.resultCode, r.resultData, r.resultExtras,
+                                r.resultAbort, false);
+                        if (doNext) {
+                            doTrim = true;
+                        }
+                    }
+                    if (rl.app != null) {
+                        rl.app.mReceivers.removeReceiver(rl);
+                    }
+                    removeReceiverLocked(rl);
+                    if (rl.linkedToDeath) {
+                        rl.linkedToDeath = false;
+                        rl.receiver.asBinder().unlinkToDeath(rl, 0);
+                    }
+                }
+
+                // If we actually concluded any broadcasts, we might now be able
+                // to trim the recipients' apps from our working set
+                if (doTrim) {
+                    mService.trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
+                    return;
+                }
+            }
+
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    void removeReceiverLocked(ReceiverList rl) {
+        mRegisteredReceivers.remove(rl.receiver.asBinder());
+        for (int i = rl.size() - 1; i >= 0; i--) {
+            mReceiverResolver.removeFilter(rl.get(i));
+        }
+    }
+
+    int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
+            Intent intent, String resolvedType, IIntentReceiver resultTo,
+            int resultCode, String resultData, Bundle resultExtras,
+            String[] requiredPermissions, String[] excludedPermissions,
+            String[] excludedPackages, int appOp, Bundle bOptions,
+            boolean serialized, boolean sticky, int userId) {
+        mService.enforceNotIsolatedCaller("broadcastIntent");
+
+        synchronized (mService) {
+            intent = verifyBroadcastLocked(intent);
+
+            final ProcessRecord callerApp = mService.getRecordForAppLOSP(caller);
+            final int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
+
+            // We're delivering the result to the caller
+            final ProcessRecord resultToApp = callerApp;
+
+            // Permission regimes around sender-supplied broadcast options.
+            enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
+
+            final ComponentName cn = intent.getComponent();
+
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
+
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                return broadcastIntentLocked(callerApp,
+                        callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
+                        intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
+                        resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
+                        appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid,
+                        callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            }
+        }
+    }
+
+    // Not the binder call surface
+    int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
+            int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
+            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
+            String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
+            boolean serialized, boolean sticky, int userId,
+            BackgroundStartPrivileges backgroundStartPrivileges,
+            @Nullable int[] broadcastAllowList) {
+        synchronized (mService) {
+            intent = verifyBroadcastLocked(intent);
+
+            final long origId = Binder.clearCallingIdentity();
+            String[] requiredPermissions = requiredPermission == null ? null
+                    : new String[] {requiredPermission};
+            try {
+                return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+                        resultToApp, resultTo, resultCode, resultData, resultExtras,
+                        requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
+                        uid, realCallingUid, realCallingPid, userId,
+                        backgroundStartPrivileges, broadcastAllowList,
+                        null /* filterExtrasForReceiver */);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @GuardedBy("mService")
+    final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
+            @Nullable String callerFeatureId, Intent intent, String resolvedType,
+            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+            Bundle resultExtras, String[] requiredPermissions,
+            String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
+            boolean ordered, boolean sticky, int callingPid, int callingUid,
+            int realCallingUid, int realCallingPid, int userId,
+            BackgroundStartPrivileges backgroundStartPrivileges,
+            @Nullable int[] broadcastAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+        final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky,
+                callingUid, realCallingUid, userId);
+        try {
+            final BroadcastSentEventRecord broadcastSentEventRecord =
+                    new BroadcastSentEventRecord();
+            final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
+                    intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
+                    resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
+                    appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
+                    callingPid, callingUid, realCallingUid, realCallingPid, userId,
+                    backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver,
+                    broadcastSentEventRecord);
+            broadcastSentEventRecord.setResult(res);
+            broadcastSentEventRecord.logToStatsd();
+            return res;
+        } finally {
+            traceBroadcastIntentEnd(cookie);
+        }
+    }
+
+    private static int traceBroadcastIntentBegin(Intent intent, IIntentReceiver resultTo,
+            boolean ordered, boolean sticky, int callingUid, int realCallingUid, int userId) {
+        if (!Flags.traceReceiverRegistration()) {
+            return BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            final StringBuilder sb = new StringBuilder("broadcastIntent: ");
+            sb.append(callingUid); sb.append('/');
+            final String action = intent.getAction();
+            sb.append(action == null ? null : action); sb.append('/');
+            sb.append("0x"); sb.append(Integer.toHexString(intent.getFlags())); sb.append('/');
+            sb.append(ordered ? "O" : "_");
+            sb.append(sticky ? "S" : "_");
+            sb.append(resultTo != null ? "C" : "_");
+            sb.append('/');
+            sb.append('u'); sb.append(userId);
+            if (callingUid != realCallingUid) {
+                sb.append('/');
+                sb.append("sender="); sb.append(realCallingUid);
+            }
+            return BroadcastQueue.traceBegin(sb.toString());
+        }
+        return 0;
+    }
+
+    private static void traceBroadcastIntentEnd(int cookie) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            BroadcastQueue.traceEnd(cookie);
+        }
+    }
+
+    @GuardedBy("mService")
+    final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
+            @Nullable String callerFeatureId, Intent intent, String resolvedType,
+            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+            Bundle resultExtras, String[] requiredPermissions,
+            String[] excludedPermissions, String[] excludedPackages, int appOp,
+            BroadcastOptions brOptions, boolean ordered, boolean sticky, int callingPid,
+            int callingUid, int realCallingUid, int realCallingPid, int userId,
+            BackgroundStartPrivileges backgroundStartPrivileges,
+            @Nullable int[] broadcastAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            @NonNull BroadcastSentEventRecord broadcastSentEventRecord) {
+        // Ensure all internal loopers are registered for idle checks
+        BroadcastLoopers.addMyLooper();
+
+        if (Process.isSdkSandboxUid(realCallingUid)) {
+            final SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
+                    SdkSandboxManagerLocal.class);
+            if (sdkSandboxManagerLocal == null) {
+                throw new IllegalStateException("SdkSandboxManagerLocal not found when sending"
+                        + " a broadcast from an SDK sandbox uid.");
+            }
+            if (!sdkSandboxManagerLocal.canSendBroadcast(intent)) {
+                throw new SecurityException(
+                        "Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
+                                + " uid. Given caller package " + callerPackage
+                                + " (pid=" + callingPid + ", realCallingUid=" + realCallingUid
+                                + ", callingUid= " + callingUid + ")");
+            }
+        }
+
+        if ((resultTo != null) && (resultToApp == null)) {
+            if (resultTo.asBinder() instanceof BinderProxy) {
+                // Warn when requesting results without a way to deliver them
+                Slog.wtf(TAG, "Sending broadcast " + intent.getAction()
+                        + " with resultTo requires resultToApp", new Throwable());
+            } else {
+                // If not a BinderProxy above, then resultTo is an in-process
+                // receiver, so splice in system_server process
+                resultToApp = mService.getProcessRecordLocked("system", SYSTEM_UID);
+            }
+        }
+
+        intent = new Intent(intent);
+        broadcastSentEventRecord.setIntent(intent);
+        broadcastSentEventRecord.setOriginalIntentFlags(intent.getFlags());
+        broadcastSentEventRecord.setSenderUid(callingUid);
+        broadcastSentEventRecord.setRealSenderUid(realCallingUid);
+        broadcastSentEventRecord.setSticky(sticky);
+        broadcastSentEventRecord.setOrdered(ordered);
+        broadcastSentEventRecord.setResultRequested(resultTo != null);
+        final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
+        broadcastSentEventRecord.setSenderProcState(callerAppProcessState);
+        broadcastSentEventRecord.setSenderUidState(getRealUidStateLocked(callerApp,
+                realCallingPid));
+
+        final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
+        // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
+        if (callerInstantApp) {
+            intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+        }
+
+        if (userId == UserHandle.USER_ALL && broadcastAllowList != null) {
+            Slog.e(TAG, "broadcastAllowList only applies when sending to individual users. "
+                    + "Assuming restrictive whitelist.");
+            broadcastAllowList = new int[]{};
+        }
+
+        // By default broadcasts do not go to stopped apps.
+        intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+
+        // If we have not finished booting, don't allow this to launch new processes.
+        if (!mService.mProcessesReady
+                && (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        }
+
+        if (DEBUG_BROADCAST_LIGHT) {
+            Slog.v(TAG_BROADCAST,
+                    (sticky ? "Broadcast sticky: " : "Broadcast: ") + intent
+                            + " ordered=" + ordered + " userid=" + userId
+                            + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
+        }
+        if ((resultTo != null) && !ordered) {
+            if (!UserHandle.isCore(callingUid)) {
+                String msg = "Unauthorized unordered resultTo broadcast "
+                        + intent + " sent from uid " + callingUid;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+        }
+
+        userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+                ALLOW_NON_FULL, "broadcast", callerPackage);
+
+        // Make sure that the user who is receiving this broadcast or its parent is running.
+        // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
+        if (userId != UserHandle.USER_ALL && !mService.mUserController.isUserOrItsParentRunning(
+                userId)) {
+            if ((callingUid != SYSTEM_UID
+                    || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
+                    && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
+                Slog.w(TAG, "Skipping broadcast of " + intent
+                        + ": user " + userId + " and its parent (if any) are stopped");
+                scheduleCanceledResultTo(resultToApp, resultTo, intent, userId,
+                        brOptions, callingUid, callerPackage);
+                return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
+            }
+        }
+
+        final String action = intent.getAction();
+        if (brOptions != null) {
+            if (brOptions.getTemporaryAppAllowlistDuration() > 0) {
+                // See if the caller is allowed to do this.  Note we are checking against
+                // the actual real caller (not whoever provided the operation as say a
+                // PendingIntent), because that who is actually supplied the arguments.
+                if (checkComponentPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED
+                        && checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED
+                        && checkComponentPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    String msg = "Permission Denial: " + intent.getAction()
+                            + " broadcast from " + callerPackage + " (pid=" + callingPid
+                            + ", uid=" + callingUid + ")"
+                            + " requires "
+                            + CHANGE_DEVICE_IDLE_TEMP_WHITELIST + " or "
+                            + START_ACTIVITIES_FROM_BACKGROUND + " or "
+                            + START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                }
+            }
+            if (brOptions.isDontSendToRestrictedApps()
+                    && !mService.isUidActiveLOSP(callingUid)
+                    && mService.isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
+                Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
+                        + " has background restrictions");
+                return ActivityManager.START_CANCELED;
+            }
+            if (brOptions.allowsBackgroundActivityStarts()) {
+                // See if the caller is allowed to do this.  Note we are checking against
+                // the actual real caller (not whoever provided the operation as say a
+                // PendingIntent), because that who is actually supplied the arguments.
+                if (checkComponentPermission(
+                        android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    String msg = "Permission Denial: " + intent.getAction()
+                            + " broadcast from " + callerPackage + " (pid=" + callingPid
+                            + ", uid=" + callingUid + ")"
+                            + " requires "
+                            + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                } else {
+                    // We set the token to null since if it wasn't for it we'd allow anyway here
+                    backgroundStartPrivileges = BackgroundStartPrivileges.ALLOW_BAL;
+                }
+            }
+
+            if (brOptions.getIdForResponseEvent() > 0) {
+                mService.enforcePermission(
+                        android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS,
+                        callingPid, callingUid, "recordResponseEventWhileInBackground");
+            }
+        }
+
+        // Verify that protected broadcasts are only being sent by system code,
+        // and that system code is only sending protected broadcasts.
+        final boolean isProtectedBroadcast;
+        try {
+            isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Remote exception", e);
+            scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                    userId, brOptions, callingUid, callerPackage);
+            return ActivityManager.BROADCAST_SUCCESS;
+        }
+
+        final boolean isCallerSystem;
+        switch (UserHandle.getAppId(callingUid)) {
+            case ROOT_UID:
+            case SYSTEM_UID:
+            case PHONE_UID:
+            case BLUETOOTH_UID:
+            case NFC_UID:
+            case SE_UID:
+            case NETWORK_STACK_UID:
+                isCallerSystem = true;
+                break;
+            default:
+                isCallerSystem = (callerApp != null) && callerApp.isPersistent();
+                break;
+        }
+
+        // First line security check before anything else: stop non-system apps from
+        // sending protected broadcasts.
+        if (!isCallerSystem) {
+            if (isProtectedBroadcast) {
+                String msg = "Permission Denial: not allowed to send broadcast "
+                        + action + " from pid="
+                        + callingPid + ", uid=" + callingUid;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+
+            } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+                    || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+                // Special case for compatibility: we don't want apps to send this,
+                // but historically it has not been protected and apps may be using it
+                // to poke their own app widget.  So, instead of making it protected,
+                // just limit it to the caller.
+                if (callerPackage == null) {
+                    String msg = "Permission Denial: not allowed to send broadcast "
+                            + action + " from unknown caller.";
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                } else if (intent.getComponent() != null) {
+                    // They are good enough to send to an explicit component...  verify
+                    // it is being sent to the calling app.
+                    if (!intent.getComponent().getPackageName().equals(
+                            callerPackage)) {
+                        String msg = "Permission Denial: not allowed to send broadcast "
+                                + action + " to "
+                                + intent.getComponent().getPackageName() + " from "
+                                + callerPackage;
+                        Slog.w(TAG, msg);
+                        throw new SecurityException(msg);
+                    }
+                } else {
+                    // Limit broadcast to their own package.
+                    intent.setPackage(callerPackage);
+                }
+            }
+        }
+
+        boolean timeoutExempt = false;
+
+        if (action != null) {
+            if (getBackgroundLaunchBroadcasts().contains(action)) {
+                if (DEBUG_BACKGROUND_CHECK) {
+                    Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
+                }
+                intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            }
+
+            // TODO: b/329211459 - Remove this after background remote intent is fixed.
+            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                    && getWearRemoteIntentAction().equals(action)) {
+                final int callerProcState = callerApp != null
+                        ? callerApp.getCurProcState()
+                        : ActivityManager.PROCESS_STATE_NONEXISTENT;
+                if (ActivityManager.RunningAppProcessInfo.procStateToImportance(callerProcState)
+                        > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+                    return ActivityManager.START_CANCELED;
+                }
+            }
+
+            switch (action) {
+                case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE:
+                    UserManagerInternal umInternal = LocalServices.getService(
+                            UserManagerInternal.class);
+                    UserInfo userInfo = umInternal.getUserInfo(userId);
+                    if (userInfo != null && userInfo.isCloneProfile()) {
+                        userId = umInternal.getProfileParentId(userId);
+                    }
+                    break;
+                case Intent.ACTION_UID_REMOVED:
+                case Intent.ACTION_PACKAGE_REMOVED:
+                case Intent.ACTION_PACKAGE_CHANGED:
+                case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+                case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+                case Intent.ACTION_PACKAGES_SUSPENDED:
+                case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                    // Handle special intents: if this broadcast is from the package
+                    // manager about a package being removed, we need to remove all of
+                    // its activities from the history stack.
+                    if (checkComponentPermission(
+                            android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
+                            callingPid, callingUid, -1, true)
+                            != PackageManager.PERMISSION_GRANTED) {
+                        String msg = "Permission Denial: " + intent.getAction()
+                                + " broadcast from " + callerPackage + " (pid=" + callingPid
+                                + ", uid=" + callingUid + ")"
+                                + " requires "
+                                + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
+                        Slog.w(TAG, msg);
+                        throw new SecurityException(msg);
+                    }
+                    switch (action) {
+                        case Intent.ACTION_UID_REMOVED:
+                            final int uid = getUidFromIntent(intent);
+                            if (uid >= 0) {
+                                mService.mBatteryStatsService.removeUid(uid);
+                                if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                                    mService.mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
+                                            intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
+                                } else {
+                                    mService.mAppOpsService.uidRemoved(uid);
+                                    mService.mServices.onUidRemovedLocked(uid);
+                                }
+                            }
+                            break;
+                        case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+                            // If resources are unavailable just force stop all those packages
+                            // and flush the attribute cache as well.
+                            String[] list = intent.getStringArrayExtra(
+                                    Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                            if (list != null && list.length > 0) {
+                                for (int i = 0; i < list.length; i++) {
+                                    mService.forceStopPackageLocked(list[i], -1, false, true, true,
+                                            false, false, false, userId, "storage unmount");
+                                }
+                                mService.mAtmInternal.cleanupRecentTasksForUser(
+                                        UserHandle.USER_ALL);
+                                sendPackageBroadcastLocked(
+                                        ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE,
+                                        list, userId);
+                            }
+                            break;
+                        case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+                            mService.mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
+                            break;
+                        case Intent.ACTION_PACKAGE_REMOVED:
+                        case Intent.ACTION_PACKAGE_CHANGED:
+                            Uri data = intent.getData();
+                            String ssp;
+                            if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                                boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action);
+                                final boolean replacing =
+                                        intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                                final boolean killProcess =
+                                        !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
+                                final boolean fullUninstall = removed && !replacing;
+
+                                if (removed) {
+                                    if (killProcess) {
+                                        mService.forceStopPackageLocked(ssp, UserHandle.getAppId(
+                                                        intent.getIntExtra(Intent.EXTRA_UID, -1)),
+                                                false, true, true, false, fullUninstall, false,
+                                                userId, "pkg removed");
+                                        mService.getPackageManagerInternal()
+                                                .onPackageProcessKilledForUninstall(ssp);
+                                    } else {
+                                        // Kill any app zygotes always, since they can't fork new
+                                        // processes with references to the old code
+                                        mService.forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
+                                                        intent.getIntExtra(Intent.EXTRA_UID, -1)),
+                                                userId);
+                                    }
+                                    final int cmd = killProcess
+                                            ? ApplicationThreadConstants.PACKAGE_REMOVED
+                                            : ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL;
+                                    sendPackageBroadcastLocked(cmd,
+                                            new String[] {ssp}, userId);
+                                    if (fullUninstall) {
+                                        // Remove all permissions granted from/to this package
+                                        mService.mUgmInternal.removeUriPermissionsForPackage(ssp,
+                                                userId, true, false);
+
+                                        mService.mAtmInternal.removeRecentTasksByPackageName(ssp,
+                                                userId);
+
+                                        mService.mServices.forceStopPackageLocked(ssp, userId);
+                                        mService.mAtmInternal.onPackageUninstalled(ssp, userId);
+                                        mService.mBatteryStatsService.notePackageUninstalled(ssp);
+                                    }
+                                } else {
+                                    if (killProcess) {
+                                        int reason;
+                                        int subReason;
+                                        if (replacing) {
+                                            reason = ApplicationExitInfo.REASON_PACKAGE_UPDATED;
+                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+                                        } else {
+                                            reason =
+                                                    ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE;
+                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+                                        }
+
+                                        final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
+                                                -1);
+                                        synchronized (mService.mProcLock) {
+                                            mService.mProcessList.killPackageProcessesLSP(ssp,
+                                                    UserHandle.getAppId(extraUid),
+                                                    userId, ProcessList.INVALID_ADJ,
+                                                    reason,
+                                                    subReason,
+                                                    "change " + ssp);
+                                        }
+                                    }
+                                    mService.cleanupDisabledPackageComponentsLocked(ssp, userId,
+                                            intent.getStringArrayExtra(
+                                                    Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
+                                    mService.mServices.schedulePendingServiceStartLocked(
+                                            ssp, userId);
+                                }
+                            }
+                            break;
+                        case Intent.ACTION_PACKAGES_SUSPENDED:
+                        case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                            final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
+                                    intent.getAction());
+                            final String[] packageNames = intent.getStringArrayExtra(
+                                    Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                            final int userIdExtra = intent.getIntExtra(
+                                    Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
+                            mService.mAtmInternal.onPackagesSuspendedChanged(packageNames,
+                                    suspended, userIdExtra);
+
+                            final boolean quarantined = intent.getBooleanExtra(
+                                    Intent.EXTRA_QUARANTINED, false);
+                            if (suspended && quarantined && packageNames != null) {
+                                for (int i = 0; i < packageNames.length; i++) {
+                                    mService.forceStopPackage(packageNames[i], userId,
+                                            ActivityManager.FLAG_OR_STOPPED, "quarantined");
+                                }
+                            }
+
+                            break;
+                    }
+                    break;
+                case Intent.ACTION_PACKAGE_REPLACED: {
+                    final Uri data = intent.getData();
+                    final String ssp;
+                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                        ApplicationInfo aInfo = null;
+                        try {
+                            aInfo = AppGlobals.getPackageManager()
+                                    .getApplicationInfo(ssp, STOCK_PM_FLAGS, userId);
+                        } catch (RemoteException ignore) {
+                        }
+                        if (aInfo == null) {
+                            Slog.w(TAG, "Dropping ACTION_PACKAGE_REPLACED for non-existent pkg:"
+                                    + " ssp=" + ssp + " data=" + data);
+                            scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                                    userId, brOptions, callingUid, callerPackage);
+                            return ActivityManager.BROADCAST_SUCCESS;
+                        }
+                        mService.updateAssociationForApp(aInfo);
+                        mService.mAtmInternal.onPackageReplaced(aInfo);
+                        mService.mServices.updateServiceApplicationInfoLocked(aInfo);
+                        sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
+                                new String[] {ssp}, userId);
+                    }
+                    break;
+                }
+                case Intent.ACTION_PACKAGE_ADDED: {
+                    // Special case for adding a package: by default turn on compatibility mode.
+                    Uri data = intent.getData();
+                    String ssp;
+                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                        final boolean replacing =
+                                intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                        mService.mAtmInternal.onPackageAdded(ssp, replacing);
+
+                        try {
+                            ApplicationInfo ai = AppGlobals.getPackageManager()
+                                    .getApplicationInfo(ssp, STOCK_PM_FLAGS, 0);
+                            mService.mBatteryStatsService.notePackageInstalled(ssp,
+                                    ai != null ? ai.longVersionCode : 0);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                    break;
+                }
+                case Intent.ACTION_PACKAGE_DATA_CLEARED: {
+                    Uri data = intent.getData();
+                    String ssp;
+                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                        mService.mAtmInternal.onPackageDataCleared(ssp, userId);
+                    }
+                    break;
+                }
+                case Intent.ACTION_TIMEZONE_CHANGED:
+                    // If this is the time zone changed action, queue up a message that will reset
+                    // the timezone of all currently running processes. This message will get
+                    // queued up before the broadcast happens.
+                    mService.mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
+                    break;
+                case Intent.ACTION_TIME_CHANGED:
+                    // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
+                    // the tri-state value it may contain and "unknown".
+                    // For convenience we re-use the Intent extra values.
+                    final int NO_EXTRA_VALUE_FOUND = -1;
+                    final int timeFormatPreferenceMsgValue = intent.getIntExtra(
+                            Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
+                            NO_EXTRA_VALUE_FOUND /* defaultValue */);
+                    // Only send a message if the time preference is available.
+                    if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
+                        Message updateTimePreferenceMsg =
+                                mService.mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
+                                        timeFormatPreferenceMsgValue, 0);
+                        mService.mHandler.sendMessage(updateTimePreferenceMsg);
+                    }
+                    mService.mBatteryStatsService.noteCurrentTimeChanged();
+                    break;
+                case ConnectivityManager.ACTION_CLEAR_DNS_CACHE:
+                    mService.mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
+                    break;
+                case Proxy.PROXY_CHANGE_ACTION:
+                    mService.mHandler.sendMessage(mService.mHandler.obtainMessage(
+                            UPDATE_HTTP_PROXY_MSG));
+                    break;
+                case android.hardware.Camera.ACTION_NEW_PICTURE:
+                case android.hardware.Camera.ACTION_NEW_VIDEO:
+                    // In N we just turned these off; in O we are turing them back on partly,
+                    // only for registered receivers.  This will still address the main problem
+                    // (a spam of apps waking up when a picture is taken putting significant
+                    // memory pressure on the system at a bad point), while still allowing apps
+                    // that are already actively running to know about this happening.
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    break;
+                case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
+                    mService.mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
+                    break;
+                case "com.android.launcher.action.INSTALL_SHORTCUT":
+                    // As of O, we no longer support this broadcasts, even for pre-O apps.
+                    // Apps should now be using ShortcutManager.pinRequestShortcut().
+                    Log.w(TAG, "Broadcast " + action
+                            + " no longer supported. It will not be delivered.");
+                    scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                            userId, brOptions, callingUid, callerPackage);
+                    return ActivityManager.BROADCAST_SUCCESS;
+                case Intent.ACTION_PRE_BOOT_COMPLETED:
+                    timeoutExempt = true;
+                    break;
+                case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
+                    if (!mService.mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
+                            callerPackage)) {
+                        scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                                userId, brOptions, callingUid, callerPackage);
+                        // Returning success seems to be the pattern here
+                        return ActivityManager.BROADCAST_SUCCESS;
+                    }
+                    break;
+            }
+
+            if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+                    || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+                    || Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+                final int uid = getUidFromIntent(intent);
+                if (uid != -1) {
+                    final UidRecord uidRec = mService.mProcessList.getUidRecordLOSP(uid);
+                    if (uidRec != null) {
+                        uidRec.updateHasInternetPermission();
+                    }
+                }
+            }
+        }
+
+        // Add to the sticky list if requested.
+        if (sticky) {
+            if (mService.checkPermission(android.Manifest.permission.BROADCAST_STICKY,
+                    callingPid, callingUid)
+                    != PackageManager.PERMISSION_GRANTED) {
+                String msg =
+                        "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
+                                + " pid="
+                                + callingPid
+                                + ", uid="
+                                + callingUid
+                                + " requires "
+                                + android.Manifest.permission.BROADCAST_STICKY;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+            if (requiredPermissions != null && requiredPermissions.length > 0) {
+                Slog.w(TAG, "Can't broadcast sticky intent " + intent
+                        + " and enforce permissions " + Arrays.toString(requiredPermissions));
+                scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                        userId, brOptions, callingUid, callerPackage);
+                return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
+            }
+            if (intent.getComponent() != null) {
+                throw new SecurityException(
+                        "Sticky broadcasts can't target a specific component");
+            }
+            synchronized (mStickyBroadcasts) {
+                // We use userId directly here, since the "all" target is maintained
+                // as a separate set of sticky broadcasts.
+                if (userId != UserHandle.USER_ALL) {
+                    // But first, if this is not a broadcast to all users, then
+                    // make sure it doesn't conflict with an existing broadcast to
+                    // all users.
+                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(
+                            UserHandle.USER_ALL);
+                    if (stickies != null) {
+                        ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+                        if (list != null) {
+                            int N = list.size();
+                            int i;
+                            for (i = 0; i < N; i++) {
+                                if (intent.filterEquals(list.get(i).intent)) {
+                                    throw new IllegalArgumentException("Sticky broadcast " + intent
+                                            + " for user " + userId
+                                            + " conflicts with existing global broadcast");
+                                }
+                            }
+                        }
+                    }
+                }
+                ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+                        mStickyBroadcasts.get(userId);
+                if (stickies == null) {
+                    stickies = new ArrayMap<>();
+                    mStickyBroadcasts.put(userId, stickies);
+                }
+                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+                if (list == null) {
+                    list = new ArrayList<>();
+                    stickies.put(intent.getAction(), list);
+                }
+                final boolean deferUntilActive = BroadcastRecord.calculateDeferUntilActive(
+                        callingUid, brOptions, resultTo, ordered,
+                        BroadcastRecord.calculateUrgent(intent, brOptions));
+                final int stickiesCount = list.size();
+                int i;
+                for (i = 0; i < stickiesCount; i++) {
+                    if (intent.filterEquals(list.get(i).intent)) {
+                        // This sticky already exists, replace it.
+                        list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
+                                callingUid, callerAppProcessState, resolvedType));
+                        break;
+                    }
+                }
+                if (i >= stickiesCount) {
+                    list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
+                            callingUid, callerAppProcessState, resolvedType));
+                }
+            }
+        }
+
+        int[] users;
+        if (userId == UserHandle.USER_ALL) {
+            // Caller wants broadcast to go to all started users.
+            users = mService.mUserController.getStartedUserArray();
+        } else {
+            // Caller wants broadcast to go to one specific user.
+            users = new int[] {userId};
+        }
+
+        var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
+                true /* isReceiver */, true /* resolveForStart */, callingUid, callingPid);
+        args.platformCompat = mService.mPlatformCompat;
+
+        // Figure out who all will receive this broadcast.
+        final int cookie = BroadcastQueue.traceBegin("queryReceivers");
+        List receivers = null;
+        List<BroadcastFilter> registeredReceivers = null;
+        // Need to resolve the intent to interested receivers...
+        if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+            receivers = collectReceiverComponents(
+                    intent, resolvedType, callingUid, callingPid, users, broadcastAllowList);
+        }
+        if (intent.getComponent() == null) {
+            final PackageDataSnapshot snapshot = mService.getPackageManagerInternal().snapshot();
+            if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
+                // Query one target user at a time, excluding shell-restricted users
+                for (int i = 0; i < users.length; i++) {
+                    if (mService.mUserController.hasUserRestriction(
+                            UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
+                        continue;
+                    }
+                    List<BroadcastFilter> registeredReceiversForUser =
+                            mReceiverResolver.queryIntent(snapshot, intent,
+                                    resolvedType, false /*defaultOnly*/, users[i]);
+                    if (registeredReceivers == null) {
+                        registeredReceivers = registeredReceiversForUser;
+                    } else if (registeredReceiversForUser != null) {
+                        registeredReceivers.addAll(registeredReceiversForUser);
+                    }
+                }
+            } else {
+                registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
+                        resolvedType, false /*defaultOnly*/, userId);
+            }
+            if (registeredReceivers != null) {
+                SaferIntentUtils.blockNullAction(args, registeredReceivers);
+            }
+        }
+        BroadcastQueue.traceEnd(cookie);
+
+        final boolean replacePending =
+                (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
+
+        if (DEBUG_BROADCAST) {
+            Slog.v(TAG_BROADCAST, "Enqueueing broadcast: " + intent.getAction()
+                    + " replacePending=" + replacePending);
+        }
+        if (registeredReceivers != null && broadcastAllowList != null) {
+            // if a uid whitelist was provided, remove anything in the application space that wasn't
+            // in it.
+            for (int i = registeredReceivers.size() - 1; i >= 0; i--) {
+                final int owningAppId = UserHandle.getAppId(registeredReceivers.get(i).owningUid);
+                if (owningAppId >= Process.FIRST_APPLICATION_UID
+                        && Arrays.binarySearch(broadcastAllowList, owningAppId) < 0) {
+                    registeredReceivers.remove(i);
+                }
+            }
+        }
+
+        int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
+
+        // Merge into one list.
+        int ir = 0;
+        if (receivers != null) {
+            // A special case for PACKAGE_ADDED: do not allow the package
+            // being added to see this broadcast.  This prevents them from
+            // using this as a back door to get run as soon as they are
+            // installed.  Maybe in the future we want to have a special install
+            // broadcast or such for apps, but we'd like to deliberately make
+            // this decision.
+            String[] skipPackages = null;
+            if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
+                    || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
+                    || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
+                Uri data = intent.getData();
+                if (data != null) {
+                    String pkgName = data.getSchemeSpecificPart();
+                    if (pkgName != null) {
+                        skipPackages = new String[] { pkgName };
+                    }
+                }
+            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
+                skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+            }
+            if (skipPackages != null && (skipPackages.length > 0)) {
+                for (String skipPackage : skipPackages) {
+                    if (skipPackage != null) {
+                        int NT = receivers.size();
+                        for (int it = 0; it < NT; it++) {
+                            ResolveInfo curt = (ResolveInfo) receivers.get(it);
+                            if (curt.activityInfo.packageName.equals(skipPackage)) {
+                                receivers.remove(it);
+                                it--;
+                                NT--;
+                            }
+                        }
+                    }
+                }
+            }
+
+            int NT = receivers != null ? receivers.size() : 0;
+            int it = 0;
+            ResolveInfo curt = null;
+            BroadcastFilter curr = null;
+            while (it < NT && ir < NR) {
+                if (curt == null) {
+                    curt = (ResolveInfo) receivers.get(it);
+                }
+                if (curr == null) {
+                    curr = registeredReceivers.get(ir);
+                }
+                if (curr.getPriority() >= curt.priority) {
+                    // Insert this broadcast record into the final list.
+                    receivers.add(it, curr);
+                    ir++;
+                    curr = null;
+                    it++;
+                    NT++;
+                } else {
+                    // Skip to the next ResolveInfo in the final list.
+                    it++;
+                    curt = null;
+                }
+            }
+        }
+        while (ir < NR) {
+            if (receivers == null) {
+                receivers = new ArrayList();
+            }
+            receivers.add(registeredReceivers.get(ir));
+            ir++;
+        }
+
+        if (isCallerSystem) {
+            checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
+                    isProtectedBroadcast, receivers);
+        }
+
+        if ((receivers != null && receivers.size() > 0)
+                || resultTo != null) {
+            BroadcastQueue queue = mBroadcastQueue;
+            SaferIntentUtils.filterNonExportedComponents(args, receivers);
+            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
+                    callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
+                    requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
+                    receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
+                    ordered, sticky, false, userId,
+                    backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
+                    callerAppProcessState);
+            broadcastSentEventRecord.setBroadcastRecord(r);
+
+            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
+            queue.enqueueBroadcastLocked(r);
+        } else {
+            // There was nobody interested in the broadcast, but we still want to record
+            // that it happened.
+            if (intent.getComponent() == null && intent.getPackage() == null
+                    && (intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+                // This was an implicit broadcast... let's record it for posterity.
+                addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
+            }
+        }
+
+        return ActivityManager.BROADCAST_SUCCESS;
+    }
+
+    @GuardedBy("mService")
+    private void scheduleCanceledResultTo(ProcessRecord resultToApp, IIntentReceiver resultTo,
+            Intent intent, int userId, BroadcastOptions options, int callingUid,
+            String callingPackage) {
+        if (resultTo == null) {
+            return;
+        }
+        final ProcessRecord app = resultToApp;
+        final IApplicationThread thread  = (app != null) ? app.getOnewayThread() : null;
+        if (thread != null) {
+            try {
+                final boolean shareIdentity = (options != null && options.isShareIdentityEnabled());
+                thread.scheduleRegisteredReceiver(
+                        resultTo, intent, Activity.RESULT_CANCELED, null, null,
+                        false, false, true, userId, app.mState.getReportedProcState(),
+                        shareIdentity ? callingUid : Process.INVALID_UID,
+                        shareIdentity ? callingPackage : null);
+            } catch (RemoteException e) {
+                final String msg = "Failed to schedule result of " + intent + " via "
+                        + app + ": " + e;
+                app.killLocked("Can't schedule resultTo", ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
+                Slog.d(TAG, msg);
+            }
+        }
+    }
+
+    @GuardedBy("mService")
+    private int getRealProcessStateLocked(ProcessRecord app, int pid) {
+        if (app == null) {
+            synchronized (mService.mPidsSelfLocked) {
+                app = mService.mPidsSelfLocked.get(pid);
+            }
+        }
+        if (app != null && app.getThread() != null && !app.isKilled()) {
+            return app.mState.getCurProcState();
+        }
+        return PROCESS_STATE_NONEXISTENT;
+    }
+
+    @GuardedBy("mService")
+    private int getRealUidStateLocked(ProcessRecord app, int pid) {
+        if (app == null) {
+            synchronized (mService.mPidsSelfLocked) {
+                app = mService.mPidsSelfLocked.get(pid);
+            }
+        }
+        if (app != null && app.getThread() != null && !app.isKilled()) {
+            final UidRecord uidRecord = app.getUidRecord();
+            if (uidRecord != null) {
+                return uidRecord.getCurProcState();
+            }
+        }
+        return PROCESS_STATE_NONEXISTENT;
+    }
+
+    @VisibleForTesting
+    ArrayList<StickyBroadcast> getStickyBroadcastsForTest(String action, int userId) {
+        synchronized (mStickyBroadcasts) {
+            final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
+                    mStickyBroadcasts.get(userId);
+            if (stickyBroadcasts == null) {
+                return null;
+            }
+            return stickyBroadcasts.get(action);
+        }
+    }
+
+    void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {
+        // Refuse possible leaked file descriptors
+        if (intent != null && intent.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, true, ALLOW_NON_FULL,
+                "removeStickyBroadcast", null);
+
+        if (mService.checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: unbroadcastIntent() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.BROADCAST_STICKY;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        synchronized (mStickyBroadcasts) {
+            ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
+            if (stickies != null) {
+                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+                if (list != null) {
+                    int N = list.size();
+                    int i;
+                    for (i = 0; i < N; i++) {
+                        if (intent.filterEquals(list.get(i).intent)) {
+                            list.remove(i);
+                            break;
+                        }
+                    }
+                    if (list.size() <= 0) {
+                        stickies.remove(intent.getAction());
+                    }
+                }
+                if (stickies.size() <= 0) {
+                    mStickyBroadcasts.remove(userId);
+                }
+            }
+        }
+    }
+
+    void finishReceiver(IBinder caller, int resultCode, String resultData,
+            Bundle resultExtras, boolean resultAbort, int flags) {
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + caller);
+
+        // Refuse possible leaked file descriptors
+        if (resultExtras != null && resultExtras.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Bundle");
+        }
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mService) {
+                final ProcessRecord callerApp = mService.getRecordForAppLOSP(caller);
+                if (callerApp == null) {
+                    Slog.w(TAG, "finishReceiver: no app for " + caller);
+                    return;
+                }
+
+                mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
+                        resultData, resultExtras, resultAbort, true);
+                // updateOomAdjLocked() will be done here
+                mService.trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
+            }
+
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    /**
+     * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
+     */
+    private int getUidFromIntent(Intent intent) {
+        if (intent == null) {
+            return -1;
+        }
+        final Bundle intentExtras = intent.getExtras();
+        return intent.hasExtra(Intent.EXTRA_UID)
+                ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
+    }
+
+    final void rotateBroadcastStatsIfNeededLocked() {
+        final long now = SystemClock.elapsedRealtime();
+        if (mCurBroadcastStats == null
+                || (mCurBroadcastStats.mStartRealtime + (24 * 60 * 60 * 1000) < now)) {
+            mLastBroadcastStats = mCurBroadcastStats;
+            if (mLastBroadcastStats != null) {
+                mLastBroadcastStats.mEndRealtime = SystemClock.elapsedRealtime();
+                mLastBroadcastStats.mEndUptime = SystemClock.uptimeMillis();
+            }
+            mCurBroadcastStats = new BroadcastStats();
+        }
+    }
+
+    final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+            int skipCount, long dispatchTime) {
+        rotateBroadcastStatsIfNeededLocked();
+        mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
+    }
+
+    final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+        rotateBroadcastStatsIfNeededLocked();
+        mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
+    }
+
+    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+        final String callerPackage = info != null ? info.packageName : original.callerPackage;
+        if (callerPackage != null) {
+            mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+                    original.callingUid, 0, callerPackage).sendToTarget();
+        }
+    }
+
+    final Intent verifyBroadcastLocked(Intent intent) {
+        if (intent != null) {
+            intent.prepareToEnterSystemServer();
+        }
+
+        int flags = intent.getFlags();
+
+        if (!mService.mProcessesReady) {
+            // if the caller really truly claims to know what they're doing, go
+            // ahead and allow the broadcast without launching any receivers
+            if ((flags & Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) {
+                // This will be turned into a FLAG_RECEIVER_REGISTERED_ONLY later on if needed.
+            } else if ((flags & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+                Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
+                        + " before boot completion");
+                throw new IllegalStateException("Cannot broadcast before boot completed");
+            }
+        }
+
+        if ((flags & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
+            throw new IllegalArgumentException(
+                    "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+        }
+
+        if ((flags & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
+            switch (Binder.getCallingUid()) {
+                case ROOT_UID:
+                case SHELL_UID:
+                    break;
+                default:
+                    Slog.w(TAG, "Removing FLAG_RECEIVER_FROM_SHELL because caller is UID "
+                            + Binder.getCallingUid());
+                    intent.removeFlags(Intent.FLAG_RECEIVER_FROM_SHELL);
+                    break;
+            }
+        }
+
+        return intent;
+    }
+
+    private ArraySet<String> getBackgroundLaunchBroadcasts() {
+        if (mBackgroundLaunchBroadcasts == null) {
+            mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
+        }
+        return mBackgroundLaunchBroadcasts;
+    }
+
+    private boolean isInstantApp(ProcessRecord record, @Nullable String callerPackage, int uid) {
+        if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) {
+            return false;
+        }
+        // Easy case -- we have the app's ProcessRecord.
+        if (record != null) {
+            return record.info.isInstantApp();
+        }
+        // Otherwise check with PackageManager.
+        IPackageManager pm = AppGlobals.getPackageManager();
+        try {
+            if (callerPackage == null) {
+                final String[] packageNames = pm.getPackagesForUid(uid);
+                if (packageNames == null || packageNames.length == 0) {
+                    throw new IllegalArgumentException("Unable to determine caller package name");
+                }
+                // Instant Apps can't use shared uids, so its safe to only check the first package.
+                callerPackage = packageNames[0];
+            }
+            mService.mAppOpsService.checkPackage(uid, callerPackage);
+            return pm.isInstantApp(callerPackage, UserHandle.getUserId(uid));
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error looking up if " + callerPackage + " is an instant app.", e);
+            return true;
+        }
+    }
+
+    private String getWearRemoteIntentAction() {
+        return mContext.getResources().getString(
+                com.android.internal.R.string.config_wearRemoteIntentAction);
+    }
+
+    private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
+        mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
+    }private List<ResolveInfo> collectReceiverComponents(
+            Intent intent, String resolvedType, int callingUid, int callingPid,
+            int[] users, int[] broadcastAllowList) {
+        // TODO: come back and remove this assumption to triage all broadcasts
+        long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
+
+        List<ResolveInfo> receivers = null;
+        HashSet<ComponentName> singleUserReceivers = null;
+        boolean scannedFirstReceivers = false;
+        for (int user : users) {
+            // Skip users that have Shell restrictions
+            if (callingUid == SHELL_UID
+                    && mService.mUserController.hasUserRestriction(
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
+                continue;
+            }
+            List<ResolveInfo> newReceivers = mService.mPackageManagerInt.queryIntentReceivers(
+                    intent, resolvedType, pmFlags, callingUid, callingPid, user, /* forSend */true);
+            if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
+                // If this is not the system user, we need to check for
+                // any receivers that should be filtered out.
+                for (int i = 0; i < newReceivers.size(); i++) {
+                    ResolveInfo ri = newReceivers.get(i);
+                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
+                        newReceivers.remove(i);
+                        i--;
+                    }
+                }
+            }
+            // Replace the alias receivers with their targets.
+            if (newReceivers != null) {
+                for (int i = newReceivers.size() - 1; i >= 0; i--) {
+                    final ResolveInfo ri = newReceivers.get(i);
+                    final ComponentAliasResolver.Resolution<ResolveInfo> resolution =
+                            mService.mComponentAliasResolver.resolveReceiver(intent, ri,
+                                    resolvedType, pmFlags, user, callingUid, callingPid);
+                    if (resolution == null) {
+                        // It was an alias, but the target was not found.
+                        newReceivers.remove(i);
+                        continue;
+                    }
+                    if (resolution.isAlias()) {
+                        newReceivers.set(i, resolution.getTarget());
+                    }
+                }
+            }
+            if (newReceivers != null && newReceivers.size() == 0) {
+                newReceivers = null;
+            }
+
+            if (receivers == null) {
+                receivers = newReceivers;
+            } else if (newReceivers != null) {
+                // We need to concatenate the additional receivers
+                // found with what we have do far.  This would be easy,
+                // but we also need to de-dup any receivers that are
+                // singleUser.
+                if (!scannedFirstReceivers) {
+                    // Collect any single user receivers we had already retrieved.
+                    scannedFirstReceivers = true;
+                    for (int i = 0; i < receivers.size(); i++) {
+                        ResolveInfo ri = receivers.get(i);
+                        if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+                            ComponentName cn = new ComponentName(
+                                    ri.activityInfo.packageName, ri.activityInfo.name);
+                            if (singleUserReceivers == null) {
+                                singleUserReceivers = new HashSet<ComponentName>();
+                            }
+                            singleUserReceivers.add(cn);
+                        }
+                    }
+                }
+                // Add the new results to the existing results, tracking
+                // and de-dupping single user receivers.
+                for (int i = 0; i < newReceivers.size(); i++) {
+                    ResolveInfo ri = newReceivers.get(i);
+                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+                        ComponentName cn = new ComponentName(
+                                ri.activityInfo.packageName, ri.activityInfo.name);
+                        if (singleUserReceivers == null) {
+                            singleUserReceivers = new HashSet<ComponentName>();
+                        }
+                        if (!singleUserReceivers.contains(cn)) {
+                            singleUserReceivers.add(cn);
+                            receivers.add(ri);
+                        }
+                    } else {
+                        receivers.add(ri);
+                    }
+                }
+            }
+        }
+        if (receivers != null && broadcastAllowList != null) {
+            for (int i = receivers.size() - 1; i >= 0; i--) {
+                final int receiverAppId = UserHandle.getAppId(
+                        receivers.get(i).activityInfo.applicationInfo.uid);
+                if (receiverAppId >= Process.FIRST_APPLICATION_UID
+                        && Arrays.binarySearch(broadcastAllowList, receiverAppId) < 0) {
+                    receivers.remove(i);
+                }
+            }
+        }
+        return receivers;
+    }
+
+    private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
+            String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
+        if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
+            // Don't yell about broadcasts sent via shell
+            return;
+        }
+
+        final String action = intent.getAction();
+        if (isProtectedBroadcast
+                || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+                || Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
+                || Intent.ACTION_MEDIA_BUTTON.equals(action)
+                || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
+                || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
+                || Intent.ACTION_MASTER_CLEAR.equals(action)
+                || Intent.ACTION_FACTORY_RESET.equals(action)
+                || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+                || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
+                || TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
+                || SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
+                || AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
+                || AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)) {
+            // Broadcast is either protected, or it's a public action that
+            // we've relaxed, so it's fine for system internals to send.
+            return;
+        }
+
+        // This broadcast may be a problem...  but there are often system components that
+        // want to send an internal broadcast to themselves, which is annoying to have to
+        // explicitly list each action as a protected broadcast, so we will check for that
+        // one safe case and allow it: an explicit broadcast, only being received by something
+        // that has protected itself.
+        if (intent.getPackage() != null || intent.getComponent() != null) {
+            if (receivers == null || receivers.size() == 0) {
+                // Intent is explicit and there's no receivers.
+                // This happens, e.g. , when a system component sends a broadcast to
+                // its own runtime receiver, and there's no manifest receivers for it,
+                // because this method is called twice for each broadcast,
+                // for runtime receivers and manifest receivers and the later check would find
+                // no receivers.
+                return;
+            }
+            boolean allProtected = true;
+            for (int i = receivers.size() - 1; i >= 0; i--) {
+                Object target = receivers.get(i);
+                if (target instanceof ResolveInfo) {
+                    ResolveInfo ri = (ResolveInfo) target;
+                    if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
+                        allProtected = false;
+                        break;
+                    }
+                } else {
+                    BroadcastFilter bf = (BroadcastFilter) target;
+                    if (bf.exported && bf.requiredPermission == null) {
+                        allProtected = false;
+                        break;
+                    }
+                }
+            }
+            if (allProtected) {
+                // All safe!
+                return;
+            }
+        }
+
+        // The vast majority of broadcasts sent from system internals
+        // should be protected to avoid security holes, so yell loudly
+        // to ensure we examine these cases.
+        if (callerApp != null) {
+            Log.wtf(TAG, "Sending non-protected broadcast " + action
+                            + " from system " + callerApp.toShortString() + " pkg " + callerPackage,
+                    new Throwable());
+        } else {
+            Log.wtf(TAG, "Sending non-protected broadcast " + action
+                            + " from system uid " + UserHandle.formatUid(callingUid)
+                            + " pkg " + callerPackage,
+                    new Throwable());
+        }
+    }
+
+    // Apply permission policy around the use of specific broadcast options
+    void enforceBroadcastOptionPermissionsInternal(
+            @Nullable Bundle options, int callingUid) {
+        enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
+                callingUid);
+    }
+
+    private void enforceBroadcastOptionPermissionsInternal(
+            @Nullable BroadcastOptions options, int callingUid) {
+        if (options != null && callingUid != Process.SYSTEM_UID) {
+            if (options.isAlarmBroadcast()) {
+                if (DEBUG_BROADCAST_LIGHT) {
+                    Slog.w(TAG, "Non-system caller " + callingUid
+                            + " may not flag broadcast as alarm");
+                }
+                throw new SecurityException(
+                        "Non-system callers may not flag broadcasts as alarm");
+            }
+            if (options.isInteractive()) {
+                mService.enforceCallingPermission(
+                        android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
+                        "setInteractive");
+            }
+        }
+    }
+
+    void startBroadcastObservers() {
+        mBroadcastQueue.start(mContext.getContentResolver());
+    }
+
+    void removeStickyBroadcasts(int userId) {
+        synchronized (mStickyBroadcasts) {
+            mStickyBroadcasts.remove(userId);
+        }
+    }
+
+    @NeverCompile
+    void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage) {
+        boolean dumpConstants = true;
+        boolean dumpHistory = true;
+        boolean needSep = false;
+        boolean onlyHistory = false;
+        boolean printedAnything = false;
+        boolean onlyReceivers = false;
+        int filteredUid = Process.INVALID_UID;
+
+        if ("history".equals(dumpPackage)) {
+            if (opti < args.length && "-s".equals(args[opti])) {
+                dumpAll = false;
+            }
+            onlyHistory = true;
+            dumpPackage = null;
+        }
+        if ("receivers".equals(dumpPackage)) {
+            onlyReceivers = true;
+            dumpPackage = null;
+            if (opti + 2 <= args.length) {
+                for (int i = opti; i < args.length; i++) {
+                    String arg = args[i];
+                    switch (arg) {
+                        case "--uid":
+                            filteredUid = getIntArg(pw, args, ++i, Process.INVALID_UID);
+                            if (filteredUid == Process.INVALID_UID) {
+                                return;
+                            }
+                            break;
+                        default:
+                            pw.printf("Invalid argument at index %d: %s\n", i, arg);
+                            return;
+                    }
+                }
+            }
+        }
+        if (DEBUG_BROADCAST) {
+            Slogf.d(TAG_BROADCAST, "dumpBroadcastsLocked(): dumpPackage=%s, onlyHistory=%b, "
+                            + "onlyReceivers=%b, filteredUid=%d", dumpPackage, onlyHistory,
+                    onlyReceivers, filteredUid);
+        }
+
+        pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
+        if (!onlyHistory && dumpAll) {
+            if (mRegisteredReceivers.size() > 0) {
+                boolean printed = false;
+                Iterator it = mRegisteredReceivers.values().iterator();
+                while (it.hasNext()) {
+                    ReceiverList r = (ReceiverList) it.next();
+                    if (dumpPackage != null && (r.app == null
+                            || !dumpPackage.equals(r.app.info.packageName))) {
+                        continue;
+                    }
+                    if (filteredUid != Process.INVALID_UID && filteredUid != r.app.uid) {
+                        if (DEBUG_BROADCAST) {
+                            Slogf.v(TAG_BROADCAST, "dumpBroadcastsLocked(): skipping receiver whose"
+                                    + " uid (%d) is not %d: %s", r.app.uid, filteredUid, r.app);
+                        }
+                        continue;
+                    }
+                    if (!printed) {
+                        pw.println("  Registered Receivers:");
+                        needSep = true;
+                        printed = true;
+                        printedAnything = true;
+                    }
+                    pw.print("  * "); pw.println(r);
+                    r.dump(pw, "    ");
+                }
+            } else {
+                if (onlyReceivers) {
+                    pw.println("  (no registered receivers)");
+                }
+            }
+
+            if (!onlyReceivers) {
+                if (mReceiverResolver.dump(pw, needSep
+                                ? "\n  Receiver Resolver Table:" : "  Receiver Resolver Table:",
+                        "    ", dumpPackage, false, false)) {
+                    needSep = true;
+                    printedAnything = true;
+                }
+            }
+        }
+
+        if (!onlyReceivers) {
+            needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
+                    dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
+            printedAnything |= needSep;
+        }
+
+        needSep = true;
+
+        synchronized (mStickyBroadcasts) {
+            if (!onlyHistory && !onlyReceivers && mStickyBroadcasts != null
+                    && dumpPackage == null) {
+                for (int user = 0; user < mStickyBroadcasts.size(); user++) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    needSep = true;
+                    printedAnything = true;
+                    pw.print("  Sticky broadcasts for user ");
+                    pw.print(mStickyBroadcasts.keyAt(user));
+                    pw.println(":");
+                    StringBuilder sb = new StringBuilder(128);
+                    for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
+                            : mStickyBroadcasts.valueAt(user).entrySet()) {
+                        pw.print("  * Sticky action ");
+                        pw.print(ent.getKey());
+                        if (dumpAll) {
+                            pw.println(":");
+                            ArrayList<StickyBroadcast> broadcasts = ent.getValue();
+                            final int N = broadcasts.size();
+                            for (int i = 0; i < N; i++) {
+                                final Intent intent = broadcasts.get(i).intent;
+                                final boolean deferUntilActive = broadcasts.get(i).deferUntilActive;
+                                sb.setLength(0);
+                                sb.append("    Intent: ");
+                                intent.toShortString(sb, false, true, false, false);
+                                pw.print(sb);
+                                if (deferUntilActive) {
+                                    pw.print(" [D]");
+                                }
+                                pw.println();
+                                pw.print("      originalCallingUid: ");
+                                pw.println(broadcasts.get(i).originalCallingUid);
+                                pw.println();
+                                Bundle bundle = intent.getExtras();
+                                if (bundle != null) {
+                                    pw.print("      extras: ");
+                                    pw.println(bundle);
+                                }
+                            }
+                        } else {
+                            pw.println("");
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!onlyHistory && !onlyReceivers && dumpAll) {
+            pw.println();
+            pw.println("  Queue " + mBroadcastQueue.toString() + ": "
+                    + mBroadcastQueue.describeStateLocked());
+            pw.println("  mHandler:");
+            mService.mHandler.dump(new PrintWriterPrinter(pw), "    ");
+            needSep = true;
+            printedAnything = true;
+        }
+
+        if (!printedAnything) {
+            pw.println("  (nothing)");
+        }
+    }
+
+    /**
+     * Gets an {@code int} argument from the given {@code index} on {@code args}, logging an error
+     * message on {@code pw} when it cannot be parsed.
+     *
+     * Returns {@code int} argument or {@code invalidValue} if it could not be parsed.
+     */
+    private static int getIntArg(PrintWriter pw, String[] args, int index, int invalidValue) {
+        if (index > args.length) {
+            pw.println("Missing argument");
+            return invalidValue;
+        }
+        String arg = args[index];
+        try {
+            return Integer.parseInt(arg);
+        } catch (Exception e) {
+            pw.printf("Non-numeric argument at index %d: %s\n", index, arg);
+            return invalidValue;
+        }
+    }
+
+    @NeverCompile
+    void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage) {
+        if (mCurBroadcastStats == null) {
+            return;
+        }
+
+        pw.println("ACTIVITY MANAGER BROADCAST STATS STATE (dumpsys activity broadcast-stats)");
+        final long now = SystemClock.elapsedRealtime();
+        if (mLastBroadcastStats != null) {
+            pw.print("  Last stats (from ");
+            TimeUtils.formatDuration(mLastBroadcastStats.mStartRealtime, now, pw);
+            pw.print(" to ");
+            TimeUtils.formatDuration(mLastBroadcastStats.mEndRealtime, now, pw);
+            pw.print(", ");
+            TimeUtils.formatDuration(mLastBroadcastStats.mEndUptime
+                    - mLastBroadcastStats.mStartUptime, pw);
+            pw.println(" uptime):");
+            if (!mLastBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
+                pw.println("    (nothing)");
+            }
+            pw.println();
+        }
+        pw.print("  Current stats (from ");
+        TimeUtils.formatDuration(mCurBroadcastStats.mStartRealtime, now, pw);
+        pw.print(" to now, ");
+        TimeUtils.formatDuration(SystemClock.uptimeMillis()
+                - mCurBroadcastStats.mStartUptime, pw);
+        pw.println(" uptime):");
+        if (!mCurBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
+            pw.println("    (nothing)");
+        }
+    }
+
+    @NeverCompile
+    void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean fullCheckin, String dumpPackage) {
+        if (mCurBroadcastStats == null) {
+            return;
+        }
+
+        if (mLastBroadcastStats != null) {
+            mLastBroadcastStats.dumpCheckinStats(pw, dumpPackage);
+            if (fullCheckin) {
+                mLastBroadcastStats = null;
+                return;
+            }
+        }
+        mCurBroadcastStats.dumpCheckinStats(pw, dumpPackage);
+        if (fullCheckin) {
+            mCurBroadcastStats = null;
+        }
+    }
+
+    void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
+        if (mRegisteredReceivers.size() > 0) {
+            Iterator it = mRegisteredReceivers.values().iterator();
+            while (it.hasNext()) {
+                ReceiverList r = (ReceiverList) it.next();
+                r.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_LIST);
+            }
+        }
+        mReceiverResolver.dumpDebug(proto,
+                ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
+        mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
+        synchronized (mStickyBroadcasts) {
+            for (int user = 0; user < mStickyBroadcasts.size(); user++) {
+                long token = proto.start(
+                        ActivityManagerServiceDumpBroadcastsProto.STICKY_BROADCASTS);
+                proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
+                for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
+                        : mStickyBroadcasts.valueAt(user).entrySet()) {
+                    long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
+                    proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
+                    for (StickyBroadcast broadcast : ent.getValue()) {
+                        broadcast.intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
+                                false, true, true, false);
+                    }
+                    proto.end(actionToken);
+                }
+                proto.end(token);
+            }
+        }
+
+        long handlerToken = proto.start(ActivityManagerServiceDumpBroadcastsProto.HANDLER);
+        proto.write(ActivityManagerServiceDumpBroadcastsProto.MainHandler.HANDLER,
+                mService.mHandler.toString());
+        mService.mHandler.getLooper().dumpDebug(proto,
+                ActivityManagerServiceDumpBroadcastsProto.MainHandler.LOOPER);
+        proto.end(handlerToken);
+    }
+
+    @VisibleForTesting
+    static final class StickyBroadcast {
+        public Intent intent;
+        public boolean deferUntilActive;
+        public int originalCallingUid;
+        /** The snapshot process state of the app who sent this broadcast */
+        public int originalCallingAppProcessState;
+        public String resolvedDataType;
+
+        public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
+                int originalCallingUid, int originalCallingAppProcessState,
+                String resolvedDataType) {
+            final StickyBroadcast b = new StickyBroadcast();
+            b.intent = intent;
+            b.deferUntilActive = deferUntilActive;
+            b.originalCallingUid = originalCallingUid;
+            b.originalCallingAppProcessState = originalCallingAppProcessState;
+            b.resolvedDataType = resolvedDataType;
+            return b;
+        }
+
+        @Override
+        public String toString() {
+            return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
+                    + originalCallingUid + ", originalCallingAppProcessState="
+                    + originalCallingAppProcessState + ", type=" + resolvedDataType + "}";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index ee0159d..4665a72 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -3065,10 +3065,9 @@
             case DumpState.DUMP_PREFERRED_XML:
             {
                 pw.flush();
-                FileOutputStream fout = new FileOutputStream(fd);
-                BufferedOutputStream str = new BufferedOutputStream(fout);
                 TypedXmlSerializer serializer = Xml.newFastSerializer();
-                try {
+                try (BufferedOutputStream str =
+                             new BufferedOutputStream(new FileOutputStream(fd))) {
                     serializer.setOutput(str, StandardCharsets.UTF_8.name());
                     serializer.startDocument(null, true);
                     serializer.setFeature(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index e610a32..809e13c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -28,8 +28,8 @@
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
-import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.content.ContentResolver.SCHEME_CONTENT;
+import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.os.UserHandle.USER_ALL;
 import static android.util.DebugUtils.valueToString;
 
@@ -68,11 +68,9 @@
 import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -84,7 +82,6 @@
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.app.ForegroundServiceDelegationOptions;
-import android.app.IApplicationThread;
 import android.app.IUidObserver;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -129,7 +126,7 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.sdksandbox.flags.Flags;
 import com.android.server.LocalServices;
-import com.android.server.am.ActivityManagerService.StickyBroadcast;
+import com.android.server.am.BroadcastController.StickyBroadcast;
 import com.android.server.am.ProcessList.IsolatedUidRange;
 import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
 import com.android.server.am.UidObserverController.ChangeRecord;