Merge "Added subscription to binder death which removes dead callbacks"
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index d5c386f..a652055 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -424,7 +424,7 @@
             final int userId = createManagedProfile();
             // Start the profile initially, then stop it. Similar to setQuietModeEnabled.
             startUserInBackgroundAndWaitForUnlock(userId);
-            stopUser(userId, true);
+            stopUserAfterWaitingForBroadcastIdle(userId, true);
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
@@ -479,7 +479,7 @@
             installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
             startUserInBackgroundAndWaitForUnlock(userId);
             startApp(userId, DUMMY_PACKAGE_NAME);
-            stopUser(userId, true);
+            stopUserAfterWaitingForBroadcastIdle(userId, true);
             SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
@@ -676,6 +676,19 @@
         return success[0];
     }
 
+    /**
+     * Waits for broadcast idle before stopping a user, to prevent timeouts on stop user.
+     * Stopping a user heavily depends on broadcast queue, and that gets crowded after user creation
+     * or user switches, which leads to a timeout on stopping user and cause the tests to be flaky.
+     * Do not call this method while timing is on. i.e. between mRunner.resumeTiming() and
+     * mRunner.pauseTiming(). Otherwise it would cause the test results to be spiky.
+     */
+    private void stopUserAfterWaitingForBroadcastIdle(int userId, boolean force)
+            throws RemoteException {
+        ShellHelper.runShellCommand("am wait-for-broadcast-idle");
+        stopUser(userId, force);
+    }
+
     private void stopUser(int userId, boolean force) throws RemoteException {
         final CountDownLatch latch = new CountDownLatch(1);
         mIam.stopUser(userId, force /* force */, new IStopUserCallback.Stub() {
@@ -711,7 +724,7 @@
         attestTrue("Didn't switch back to user, " + origUser, origUser == mAm.getCurrentUser());
 
         if (stopNewUser) {
-            stopUser(testUser, true);
+            stopUserAfterWaitingForBroadcastIdle(testUser, true);
             attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
         }
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 795c430..317f8eb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32264,6 +32264,7 @@
     method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
     method public android.os.Bundle getUserRestrictions();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
+    method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.List<android.os.UserHandle> getVisibleUsers();
     method public boolean hasUserRestriction(String);
     method public boolean isDemoUser();
     method public static boolean isHeadlessSystemUserMode();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a0cc4f0..2b2f202 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -320,6 +320,7 @@
     field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND";
     field public static final String START_CROSS_PROFILE_ACTIVITIES = "android.permission.START_CROSS_PROFILE_ACTIVITIES";
     field public static final String START_REVIEW_PERMISSION_DECISIONS = "android.permission.START_REVIEW_PERMISSION_DECISIONS";
+    field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
     field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE";
     field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
     field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
@@ -507,7 +508,7 @@
 
   public class ActivityOptions {
     method public int getLaunchTaskId();
-    method @RequiresPermission("android.permission.START_TASKS_FROM_RECENTS") public void setLaunchTaskId(int);
+    method @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public void setLaunchTaskId(int);
   }
 
   public class AlarmManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index fefdfd8..f45298a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -42,7 +42,6 @@
     field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
     field public static final String SET_GAME_SERVICE = "android.permission.SET_GAME_SERVICE";
     field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
-    field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
     field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
@@ -3204,6 +3203,11 @@
     method public android.widget.ListView getMenuListView();
   }
 
+  public class RatingBar extends android.widget.AbsSeekBar {
+    field public static final String PLURALS_MAX = "max";
+    field public static final String PLURALS_RATING = "rating";
+  }
+
   public class Spinner extends android.widget.AbsSpinner implements android.content.DialogInterface.OnClickListener {
     method public boolean isPopupShowing();
   }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d6b90a2..2a5916d 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1906,7 +1906,6 @@
             OP_SCHEDULE_EXACT_ALARM,
             OP_MANAGE_MEDIA,
             OP_TURN_SCREEN_ON,
-            OP_GET_USAGE_STATS,
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 8ffc6a2..c1a3c19 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1008,6 +1008,22 @@
         }
     }
 
+    /**
+     * Checks whether the calling companion application is currently bound.
+     *
+     * @return true if application is bound, false otherwise
+     * @hide
+     */
+    @UserHandleAware
+    public boolean isCompanionApplicationBound() {
+        try {
+            return mService.isCompanionApplicationBound(
+                    mContext.getOpPackageName(), mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private boolean checkFeaturePresent() {
         boolean featurePresent = mService != null;
         if (!featurePresent && DEBUG) {
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 6e6e187..17e3132 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -80,4 +80,6 @@
     void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd);
 
     void detachSystemDataTransport(String packageName, int userId, int associationId);
+
+    boolean isCompanionApplicationBound(String packageName, int userId);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 5839b87..8e2a5ea 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -8065,7 +8065,7 @@
             }
 
             PackageParser.Package pkg = parser.parsePackage(apkFile, 0, false);
-            if ((flagsBits & GET_SIGNATURES) != 0) {
+            if ((flagsBits & GET_SIGNATURES) != 0 || (flagsBits & GET_SIGNING_CERTIFICATES) != 0) {
                 PackageParser.collectCertificates(pkg, false /* skipVerify */);
             }
             return PackageParser.generatePackageInfo(pkg, null, (int) flagsBits, 0, 0, null,
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index fa6b118..88649cb 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -129,6 +129,7 @@
     boolean isUserRunning(int userId);
     boolean isUserForeground(int userId);
     boolean isUserVisible(int userId);
+    List<UserHandle> getVisibleUsers();
     boolean isUserNameSet(int userId);
     boolean hasRestrictedProfiles(int userId);
     boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 738f9c9..f6aaee8 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2886,6 +2886,21 @@
     }
 
     /**
+     * Gets the visible users (as defined by {@link #isUserVisible()}.
+     *
+     * @return visible users at the moment.
+     */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS})
+    public @NonNull List<UserHandle> getVisibleUsers() {
+        try {
+            return mService.getVisibleUsers();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return whether the context user is running in an "unlocked" state.
      * <p>
      * On devices with direct boot, a user is unlocked only after they've
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index fa84407..7314ad8 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -732,6 +732,10 @@
      * @hide Pending API consideration. Currently only used internally by the system.
      */
     protected boolean hasDividerBeforeChildAt(int childIndex) {
+        if (mShowDividers == SHOW_DIVIDER_NONE) {
+            // Short-circuit to save iteration over child views.
+            return false;
+        }
         if (childIndex == getVirtualChildCount()) {
             // Check whether the end divider should draw.
             return (mShowDividers & SHOW_DIVIDER_END) != 0;
@@ -746,6 +750,24 @@
     }
 
     /**
+     * Determines whether or not there's a divider after a specified child index.
+     *
+     * @param childIndex Index of child to check for following divider
+     * @return true if there should be a divider after the child at childIndex
+     */
+    private boolean hasDividerAfterChildAt(int childIndex) {
+        if (mShowDividers == SHOW_DIVIDER_NONE) {
+            // Short-circuit to save iteration over child views.
+            return false;
+        }
+        if (allViewsAreGoneAfter(childIndex)) {
+            // This is the last view that's not gone, check if end divider is enabled.
+            return (mShowDividers & SHOW_DIVIDER_END) != 0;
+        }
+        return (mShowDividers & SHOW_DIVIDER_MIDDLE) != 0;
+    }
+
+    /**
      * Checks whether all (virtual) child views before the given index are gone.
      */
     private boolean allViewsAreGoneBefore(int childIndex) {
@@ -759,6 +781,20 @@
     }
 
     /**
+     * Checks whether all (virtual) child views after the given index are gone.
+     */
+    private boolean allViewsAreGoneAfter(int childIndex) {
+        final int count = getVirtualChildCount();
+        for (int i = childIndex + 1; i < count; i++) {
+            final View child = getVirtualChildAt(i);
+            if (child != null && child.getVisibility() != GONE) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Measures the children when the orientation of this LinearLayout is set
      * to {@link #VERTICAL}.
      *
@@ -1295,6 +1331,7 @@
         if (useLargestChild &&
                 (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
             mTotalLength = 0;
+            nonSkippedChildCount = 0;
 
             for (int i = 0; i < count; ++i) {
                 final View child = getVirtualChildAt(i);
@@ -1308,6 +1345,11 @@
                     continue;
                 }
 
+                nonSkippedChildCount++;
+                if (hasDividerBeforeChildAt(i)) {
+                    mTotalLength += mDividerWidth;
+                }
+
                 final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                         child.getLayoutParams();
                 if (isExactly) {
@@ -1319,6 +1361,10 @@
                             lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
                 }
             }
+
+            if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
+                mTotalLength += mDividerWidth;
+            }
         }
 
         // Add in our padding
@@ -1347,6 +1393,7 @@
             maxHeight = -1;
 
             mTotalLength = 0;
+            nonSkippedChildCount = 0;
 
             for (int i = 0; i < count; ++i) {
                 final View child = getVirtualChildAt(i);
@@ -1354,6 +1401,11 @@
                     continue;
                 }
 
+                nonSkippedChildCount++;
+                if (hasDividerBeforeChildAt(i)) {
+                    mTotalLength += mDividerWidth;
+                }
+
                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                 final float childWeight = lp.weight;
                 if (childWeight > 0) {
@@ -1423,6 +1475,10 @@
                 }
             }
 
+            if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
+                mTotalLength += mDividerWidth;
+            }
+
             // Add in our padding
             mTotalLength += mPaddingLeft + mPaddingRight;
             // TODO: Should we update widthSize with the new total length?
@@ -1810,7 +1866,13 @@
                         break;
                 }
 
-                if (hasDividerBeforeChildAt(childIndex)) {
+                if (isLayoutRtl) {
+                    // Because rtl rendering occurs in the reverse direction, we need to check
+                    // after the child rather than before (since after=left in this context)
+                    if (hasDividerAfterChildAt(childIndex)) {
+                        childLeft += mDividerWidth;
+                    }
+                } else if (hasDividerBeforeChildAt(childIndex)) {
                     childLeft += mDividerWidth;
                 }
 
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index f946fe6..ec0d862 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -16,17 +16,22 @@
 
 package android.widget;
 
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.shapes.RectShape;
 import android.graphics.drawable.shapes.Shape;
 import android.util.AttributeSet;
+import android.util.PluralsMessageFormatter;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.inspector.InspectableProperty;
 
 import com.android.internal.R;
 
+import java.util.HashMap;
+
+
 /**
  * A RatingBar is an extension of SeekBar and ProgressBar that shows a rating in
  * stars. The user can touch/drag or use arrow keys to set the rating when using
@@ -53,6 +58,20 @@
 public class RatingBar extends AbsSeekBar {
 
     /**
+     * Key used for generating Text-to-Speech output regarding the current star rating.
+     * @hide
+     */
+    @TestApi
+    public static final String PLURALS_RATING = "rating";
+
+    /**
+     * Key used for generating Text-to-Speech output regarding the maximum star count.
+     * @hide
+     */
+    @TestApi
+    public static final String PLURALS_MAX = "max";
+
+    /**
      * A callback that notifies clients when the rating has been changed. This
      * includes changes that were initiated by the user through a touch gesture
      * or arrow key/trackball as well as changes that were initiated
@@ -354,6 +373,16 @@
         if (canUserSetProgress()) {
             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_PROGRESS);
         }
+
+        final float scaledMax = getMax() * getStepSize();
+        final HashMap<String, Object> params = new HashMap();
+        params.put(PLURALS_RATING, getRating());
+        params.put(PLURALS_MAX, scaledMax);
+        info.setStateDescription(PluralsMessageFormatter.format(
+                getContext().getResources(),
+                params,
+                R.string.rating_label
+        ));
     }
 
     @Override
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1c7c582..cd1c23c 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4891,24 +4891,24 @@
      *
      * <p>Line-break style specifies the line-break strategies that can be used
      * for text wrapping. The line-break style affects rule-based line breaking
-     * by specifying the strictness of line-breaking rules.</p>
+     * by specifying the strictness of line-breaking rules.
      *
-     * <p>The following are types of line-break styles:</p>
+     * <p>The following are types of line-break styles:
      * <ul>
-     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}</li>
-     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}</li>
-     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}</li>
+     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}
+     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}
+     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}
      * </ul>
      *
      * <p>The default line-break style is
      * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
-     * line-breaking rules are used.</p>
+     * line-breaking rules are used.
      *
      * <p>See the
      * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
-     * line-break property</a> for more information.</p>
+     * line-break property</a> for more information.
      *
-     * @param lineBreakStyle The line break style for the text.
+     * @param lineBreakStyle The line-break style for the text.
      */
     public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
         if (mLineBreakStyle != lineBreakStyle) {
@@ -4927,17 +4927,17 @@
      * <p>The line-break word style affects dictionary-based line breaking by
      * providing phrase-based line-breaking opportunities. Use
      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
-     * phrase-based line breaking.</p>
+     * phrase-based line breaking.
      *
      * <p>The default line-break word style is
      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
-     * no line-breaking word style is used.</p>
+     * no line-breaking word style is used.
      *
      * <p>See the
      * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
-     * word-break property</a> for more information.</p>
+     * word-break property</a> for more information.
      *
-     * @param lineBreakWordStyle The line break word style for the text.
+     * @param lineBreakWordStyle The line-break word style for the text.
      */
     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
         mUserSpeficiedLineBreakwordStyle = true;
diff --git a/core/res/Android.bp b/core/res/Android.bp
index c42517d..93ce783 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -73,18 +73,18 @@
         ":remote-color-resources-compile-colors",
     ],
     out: ["remote-color-resources.apk"],
-    cmd: "$(location aapt2) link -o $(out) --manifest $(in)"
+    cmd: "$(location aapt2) link -o $(out) --manifest $(in)",
 }
 
 genrule {
     name: "remote-color-resources-arsc",
     srcs: [":remote-color-resources-apk"],
     out: ["res/raw/remote_views_color_resources.arsc"],
-    cmd: "mkdir -p $(genDir)/remote-color-resources-arsc && "
-        + "unzip -x $(in) resources.arsc -d $(genDir)/remote-color-resources-arsc && "
-        + "mkdir -p $$(dirname $(out)) && "
-        + "mv $(genDir)/remote-color-resources-arsc/resources.arsc $(out) && "
-        + "echo 'Created $(out)'"
+    cmd: "mkdir -p $(genDir)/remote-color-resources-arsc && " +
+        "unzip -x $(in) resources.arsc -d $(genDir)/remote-color-resources-arsc && " +
+        "mkdir -p $$(dirname $(out)) && " +
+        "mv $(genDir)/remote-color-resources-arsc/resources.arsc $(out) && " +
+        "echo 'Created $(out)'",
 }
 
 genrule {
@@ -95,11 +95,11 @@
         "remote_color_resources_res/symbols.xml",
     ],
     out: ["remote_views_color_resources.zip"],
-    cmd: "INPUTS=($(in)) && "
-        + "RES_DIR=$$(dirname $$(dirname $${INPUTS[0]})) && "
-        + "mkdir -p $$RES_DIR/values && "
-        + "cp $${INPUTS[1]} $$RES_DIR/values && "
-        + "$(location soong_zip) -o $(out) -C $$RES_DIR -D $$RES_DIR"
+    cmd: "INPUTS=($(in)) && " +
+        "RES_DIR=$$(dirname $$(dirname $${INPUTS[0]})) && " +
+        "mkdir -p $$RES_DIR/values && " +
+        "cp $${INPUTS[1]} $$RES_DIR/values && " +
+        "$(location soong_zip) -o $(out) -C $$RES_DIR -D $$RES_DIR",
 }
 
 android_app {
@@ -154,31 +154,21 @@
     cmd: "cp $(in) $(out)",
 }
 
-// This logic can be removed once robolectric's transition to binary resources is complete
-filegroup {
-    name: "robolectric_framework_raw_res_files",
-    srcs: [
-        "assets/**/*",
-        "res/**/*",
-        ":remote-color-resources-arsc",
-    ],
-}
-
 // Generate a text file containing a list of permissions that non-system apps
 // are allowed to obtain.
 genrule {
-  name: "permission-list-normal",
-  out: ["permission-list-normal.txt"],
-  srcs: ["AndroidManifest.xml"],
-  cmd: "cat $(in) " +
-       // xmllint has trouble accessing attributes under the android namespace.
-       // Strip these prefixes prior to processing with xmllint.
-       " | sed -r 's/android:(name|protectionLevel)/\\1/g' " +
-       " | $(location xmllint) /dev/stdin --xpath " +
-       " '//permission[not(contains(@protectionLevel, \"signature\"))]/@name'" +
-       // The result of xmllint is name="value" pairs. Format these to just the
-       // permission name, one per-line.
-       " | sed -r 's/\\s*name=\\s*//g' | tr -d '\"'" +
-       " > $(out)",
-  tools: ["xmllint"]
+    name: "permission-list-normal",
+    out: ["permission-list-normal.txt"],
+    srcs: ["AndroidManifest.xml"],
+    cmd: "cat $(in) " +
+        // xmllint has trouble accessing attributes under the android namespace.
+        // Strip these prefixes prior to processing with xmllint.
+        " | sed -r 's/android:(name|protectionLevel)/\\1/g' " +
+        " | $(location xmllint) /dev/stdin --xpath " +
+        " '//permission[not(contains(@protectionLevel, \"signature\"))]/@name'" +
+        // The result of xmllint is name="value" pairs. Format these to just the
+        // permission name, one per-line.
+        " | sed -r 's/\\s*name=\\s*//g' | tr -d '\"'" +
+        " > $(out)",
+    tools: ["xmllint"],
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3fbd797..cd518ce 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2975,7 +2975,7 @@
     <permission android:name="android.permission.REAL_GET_TASKS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @TestApi Allows an application to start a task from a ActivityManager#RecentTaskInfo.
+    <!-- @SystemApi Allows an application to start a task from a ActivityManager#RecentTaskInfo.
          @hide -->
     <permission android:name="android.permission.START_TASKS_FROM_RECENTS"
         android:protectionLevel="signature|privileged|recents" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 11a3361..e5b1cf9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3365,6 +3365,13 @@
     <!-- Default not selected text used by accessibility for an element that can be selected or unselected. [CHAR LIMIT=NONE] -->
     <string name="not_selected">not selected</string>
 
+    <!-- Default label text used by accessibility for ratingbars. [CHAR LIMIT=NONE] -->
+    <string name ="rating_label">{ rating, plural,
+        =1 {One star out of {max}}
+        other {# stars out of {max}}
+    }
+    </string>
+
     <!-- Default state description for indeterminate progressbar. [CHAR LIMIT=NONE] -->
     <string name="in_progress">in progress</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3afa6ef..d7836c1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -925,6 +925,7 @@
   <java-symbol type="string" name="mobile_provisioning_apn" />
   <java-symbol type="string" name="mobile_provisioning_url" />
   <java-symbol type="string" name="quick_contacts_not_available" />
+  <java-symbol type="string" name="rating_label" />
   <java-symbol type="string" name="reboot_to_update_package" />
   <java-symbol type="string" name="reboot_to_update_prepare" />
   <java-symbol type="string" name="reboot_to_update_title" />
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 48aecd6..d0327159 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -28,7 +28,7 @@
  *
  * <p>See the
  * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
- * line-break property</a> for more information.</p>
+ * line-break property</a> for more information.
  */
 public final class LineBreakConfig {
 
@@ -72,7 +72,7 @@
      *
      * <p>Support for this line-break word style depends on locale. If the
      * current locale does not support phrase-based text wrapping, this setting
-     * has no effect.</p>
+     * has no effect.
      */
     public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
 
@@ -194,7 +194,7 @@
      * Constructor with line-break parameters.
      *
      * <p>Use {@link LineBreakConfig.Builder} to create the
-     * {@code LineBreakConfig} instance.</p>
+     * {@code LineBreakConfig} instance.
      */
     private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
             @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 677f0a2..29d6765 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -155,10 +155,10 @@
             new ReleasedFlag(603, false);
 
     public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_BACKEND =
-            new UnreleasedFlag(604, true);
+            new UnreleasedFlag(604, false);
 
     public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_FRONTEND =
-            new UnreleasedFlag(605, true);
+            new UnreleasedFlag(605, false);
 
     /***************************************/
     // 700 - dialer/calls
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 8b37aab..35f32ca 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -53,8 +53,7 @@
 import android.view.WindowManager;
 import android.widget.Toast;
 
-import androidx.annotation.NonNull;
-
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.systemui.R;
@@ -137,7 +136,7 @@
     }
 
     @Override
-    public IBinder onBind(@NonNull Intent intent) {
+    public IBinder onBind(Intent intent) {
         registerReceiver(mCloseSystemDialogs, new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS),
                 Context.RECEIVER_EXPORTED);
         final Messenger m = new Messenger(mHandler);
@@ -184,13 +183,23 @@
         }
     }
 
-    /** Respond to incoming Message via Binder (Messenger) */
     @MainThread
     private boolean handleMessage(Message msg) {
         final Messenger replyTo = msg.replyTo;
-        final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri);
-        RequestCallback requestCallback = new RequestCallbackImpl(replyTo);
+        final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri);
+        RequestCallback callback = new RequestCallbackImpl(replyTo);
 
+        ScreenshotHelper.ScreenshotRequest request =
+                (ScreenshotHelper.ScreenshotRequest) msg.obj;
+
+        handleRequest(request, onSaved, callback);
+        return true;
+    }
+
+    @MainThread
+    @VisibleForTesting
+    void handleRequest(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> onSaved,
+            RequestCallback callback) {
         // If the storage for this user is locked, we have no place to store
         // the screenshot, so skip taking it instead of showing a misleading
         // animation and error notification.
@@ -198,8 +207,8 @@
             Log.w(TAG, "Skipping screenshot because storage is locked!");
             mNotificationsController.notifyScreenshotError(
                     R.string.screenshot_failed_to_save_user_locked_text);
-            requestCallback.reportError();
-            return true;
+            callback.reportError();
+            return;
         }
 
         if (mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER_ALL)) {
@@ -211,33 +220,26 @@
                         () -> mContext.getString(R.string.screenshot_blocked_by_admin));
                 mHandler.post(() ->
                         Toast.makeText(mContext, blockedByAdminText, Toast.LENGTH_SHORT).show());
-                requestCallback.reportError();
+                callback.reportError();
             });
-            return true;
+            return;
         }
 
-        ScreenshotHelper.ScreenshotRequest screenshotRequest =
-                (ScreenshotHelper.ScreenshotRequest) msg.obj;
-
-        ComponentName topComponent = screenshotRequest.getTopComponent();
-        mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0,
-                topComponent == null ? "" : topComponent.getPackageName());
-
         if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) {
             Log.d(TAG, "handleMessage: Using request processor");
-            mProcessor.processAsync(screenshotRequest,
-                    (request) -> dispatchToController(request, uriConsumer, requestCallback));
-            return true;
+            mProcessor.processAsync(request,
+                    (r) -> dispatchToController(r, onSaved, callback));
         }
 
-        dispatchToController(screenshotRequest, uriConsumer, requestCallback);
-        return true;
+        dispatchToController(request, onSaved, callback);
     }
 
     private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
             Consumer<Uri> uriConsumer, RequestCallback callback) {
 
         ComponentName topComponent = request.getTopComponent();
+        mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(request.getSource()), 0,
+                topComponent == null ? "" : topComponent.getPackageName());
 
         switch (request.getType()) {
             case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 9e5dab1..f8449ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -90,6 +90,18 @@
         stableIndex = -1
     }
 
+    /**
+     * Erases bookkeeping traces stored on an entry when it is removed from the notif list.
+     * This can happen if the entry is removed from a group that was broken up or if the entry was
+     * filtered out during any of the filtering steps.
+     */
+    fun detach() {
+        parent = null
+        section = null
+        promoter = null
+        // stableIndex = -1  // TODO(b/241229236): Clear this once we fix the stability fragility
+    }
+
     companion object {
         @JvmStatic
         fun create(): ListAttachState {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 14cc6bf..3eaa988 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -958,9 +958,7 @@
      * filtered out during any of the filtering steps.
      */
     private void annulAddition(ListEntry entry) {
-        entry.setParent(null);
-        entry.getAttachState().setSection(null);
-        entry.getAttachState().setPromoter(null);
+        entry.getAttachState().detach();
     }
 
     private void assignSections() {
@@ -1198,9 +1196,9 @@
                 o2.getSectionIndex());
         if (cmp != 0) return cmp;
 
-        int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
-        int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
-        cmp = Integer.compare(index1, index2);
+        cmp = Integer.compare(
+                getStableOrderIndex(o1),
+                getStableOrderIndex(o2));
         if (cmp != 0) return cmp;
 
         NotifComparator sectionComparator = getSectionComparator(o1, o2);
@@ -1214,31 +1212,32 @@
             if (cmp != 0) return cmp;
         }
 
-        final NotificationEntry rep1 = o1.getRepresentativeEntry();
-        final NotificationEntry rep2 = o2.getRepresentativeEntry();
-            cmp = rep1.getRanking().getRank() - rep2.getRanking().getRank();
+        cmp = Integer.compare(
+                o1.getRepresentativeEntry().getRanking().getRank(),
+                o2.getRepresentativeEntry().getRanking().getRank());
         if (cmp != 0) return cmp;
 
-        cmp = Long.compare(
-                rep2.getSbn().getNotification().when,
-                rep1.getSbn().getNotification().when);
+        cmp = -1 * Long.compare(
+                o1.getRepresentativeEntry().getSbn().getNotification().when,
+                o2.getRepresentativeEntry().getSbn().getNotification().when);
         return cmp;
     };
 
 
     private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
-        int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
-        int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
-        int cmp = Integer.compare(index1, index2);
+        int cmp = Integer.compare(
+                getStableOrderIndex(o1),
+                getStableOrderIndex(o2));
         if (cmp != 0) return cmp;
 
-        cmp = o1.getRepresentativeEntry().getRanking().getRank()
-                - o2.getRepresentativeEntry().getRanking().getRank();
+        cmp = Integer.compare(
+                o1.getRepresentativeEntry().getRanking().getRank(),
+                o2.getRepresentativeEntry().getRanking().getRank());
         if (cmp != 0) return cmp;
 
-        cmp = Long.compare(
-                o2.getRepresentativeEntry().getSbn().getNotification().when,
-                o1.getRepresentativeEntry().getSbn().getNotification().when);
+        cmp = -1 * Long.compare(
+                o1.getRepresentativeEntry().getSbn().getNotification().when,
+                o2.getRepresentativeEntry().getSbn().getNotification().when);
         return cmp;
     };
 
@@ -1248,8 +1247,16 @@
      */
     private boolean mForceReorderable = false;
 
-    private boolean canReorder(ListEntry entry) {
-        return mForceReorderable || getStabilityManager().isEntryReorderingAllowed(entry);
+    private int getStableOrderIndex(ListEntry entry) {
+        if (mForceReorderable) {
+            // this is used to determine if the list is correctly sorted
+            return -1;
+        }
+        if (getStabilityManager().isEntryReorderingAllowed(entry)) {
+            // let the stability manager constrain or allow reordering
+            return -1;
+        }
+        return entry.getPreviousAttachState().getStableIndex();
     }
 
     private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java b/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java
deleted file mode 100644
index d731753..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util;
-
-import static androidx.lifecycle.Lifecycle.State.DESTROYED;
-import static androidx.lifecycle.Lifecycle.State.RESUMED;
-
-import android.view.View;
-import android.view.View.OnAttachStateChangeListener;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
-
-/**
- * Tools for generating lifecycle from sysui objects.
- */
-public class SysuiLifecycle {
-
-    private SysuiLifecycle() {
-    }
-
-    /**
-     * Get a lifecycle that will be put into the resumed state when the view is attached
-     * and goes to the destroyed state when the view is detached.
-     */
-    public static LifecycleOwner viewAttachLifecycle(View v) {
-        return new ViewLifecycle(v);
-    }
-
-    private static class ViewLifecycle implements LifecycleOwner, OnAttachStateChangeListener {
-        private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
-
-        ViewLifecycle(View v) {
-            v.addOnAttachStateChangeListener(this);
-            if (v.isAttachedToWindow()) {
-                mLifecycle.markState(RESUMED);
-            }
-        }
-
-        @NonNull
-        @Override
-        public Lifecycle getLifecycle() {
-            return mLifecycle;
-        }
-
-        @Override
-        public void onViewAttachedToWindow(View v) {
-            mLifecycle.markState(RESUMED);
-        }
-
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            mLifecycle.markState(DESTROYED);
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
new file mode 100644
index 0000000..83e56da
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.screenshot
+
+import android.app.Application
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN
+import android.app.admin.DevicePolicyResourcesManager
+import android.content.ComponentName
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.HARDWARE
+import android.graphics.ColorSpace
+import android.graphics.Insets
+import android.graphics.Rect
+import android.hardware.HardwareBuffer
+import android.os.UserHandle
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
+import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.util.ScreenshotHelper
+import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW
+import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+
+private const val USER_ID = 1
+private const val TASK_ID = 1
+
+@RunWith(AndroidTestingRunner::class)
+class TakeScreenshotServiceTest : SysuiTestCase() {
+
+    private val application = mock<Application>()
+    private val controller = mock<ScreenshotController>()
+    private val userManager = mock<UserManager>()
+    private val requestProcessor = mock<RequestProcessor>()
+    private val devicePolicyManager = mock<DevicePolicyManager>()
+    private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>()
+    private val notificationsController = mock<ScreenshotNotificationsController>()
+    private val callback = mock<RequestCallback>()
+
+    private val eventLogger = UiEventLoggerFake()
+    private val flags = FakeFeatureFlags()
+    private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
+
+    private val service = TakeScreenshotService(
+        controller, userManager, devicePolicyManager, eventLogger,
+        notificationsController, mContext, Runnable::run, flags, requestProcessor)
+
+    @Before
+    fun setUp() {
+        whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
+        whenever(devicePolicyManager.getScreenCaptureDisabled(
+            /* admin component (null: any admin) */ isNull(), eq(UserHandle.USER_ALL)))
+            .thenReturn(false)
+        whenever(userManager.isUserUnlocked).thenReturn(true)
+
+        flags.set(SCREENSHOT_REQUEST_PROCESSOR, false)
+
+        service.attach(
+            mContext,
+            /* thread = */ null,
+            /* className = */ null,
+            /* token = */ null,
+            application,
+            /* activityManager = */ null)
+    }
+
+    @Test
+    fun testServiceLifecycle() {
+        service.onCreate()
+        service.onBind(null /* unused: Intent */)
+
+        service.onUnbind(null /* unused: Intent */)
+        verify(controller).removeWindow()
+
+        service.onDestroy()
+        verify(controller).onDestroy()
+    }
+
+    @Test
+    fun takeScreenshotFullscreen() {
+        val request = ScreenshotRequest(
+            TAKE_SCREENSHOT_FULLSCREEN,
+            SCREENSHOT_KEY_CHORD,
+            topComponent)
+
+        service.handleRequest(request, { /* onSaved */ }, callback)
+
+        verify(controller).takeScreenshotFullscreen(
+            eq(topComponent),
+            /* onSavedListener = */ any(),
+            /* requestCallback = */ any())
+
+        assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+        val logEvent = eventLogger.get(0)
+
+        assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
+            logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
+        assertEquals("Expected supplied package name",
+            topComponent.packageName, eventLogger.get(0).packageName)
+    }
+
+    @Test
+    fun takeScreenshotPartial() {
+        val request = ScreenshotRequest(
+            TAKE_SCREENSHOT_SELECTED_REGION,
+            SCREENSHOT_KEY_CHORD,
+            /* topComponent = */ null)
+
+        service.handleRequest(request, { /* onSaved */ }, callback)
+
+        verify(controller).takeScreenshotPartial(
+            /* topComponent = */ isNull(),
+            /* onSavedListener = */ any(),
+            /* requestCallback = */ any())
+
+        assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+        val logEvent = eventLogger.get(0)
+
+        assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
+            logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
+        assertEquals("Expected empty package name in UiEvent", "", eventLogger.get(0).packageName)
+    }
+
+    @Test
+    fun takeScreenshotProvidedImage() {
+        val bounds = Rect(50, 50, 150, 150)
+        val bitmap = makeHardwareBitmap(100, 100)
+        val bitmapBundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
+
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW,
+            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, topComponent)
+
+        service.handleRequest(request, { /* onSaved */ }, callback)
+
+        verify(controller).handleImageAsScreenshot(
+            argThat { b -> b.equalsHardwareBitmap(bitmap) },
+            eq(bounds),
+            eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent),
+            /* onSavedListener = */ any(), /* requestCallback = */ any())
+
+        assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+        val logEvent = eventLogger.get(0)
+
+        assertEquals("Expected SCREENSHOT_REQUESTED_* UiEvent",
+            logEvent.eventId, SCREENSHOT_REQUESTED_OVERVIEW.id)
+        assertEquals("Expected supplied package name",
+            topComponent.packageName, eventLogger.get(0).packageName)
+    }
+
+    @Test
+    fun takeScreenshotFullscreen_userLocked() {
+        whenever(userManager.isUserUnlocked).thenReturn(false)
+
+        val request = ScreenshotRequest(
+            TAKE_SCREENSHOT_FULLSCREEN,
+            SCREENSHOT_KEY_CHORD,
+            topComponent)
+
+        service.handleRequest(request, { /* onSaved */ }, callback)
+
+        verify(notificationsController).notifyScreenshotError(anyInt())
+        verify(callback).reportError()
+        verifyZeroInteractions(controller)
+    }
+
+    @Test
+    fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
+        whenever(devicePolicyManager.getScreenCaptureDisabled(
+            isNull(), eq(UserHandle.USER_ALL))
+        ).thenReturn(true)
+
+        whenever(devicePolicyResourcesManager.getString(
+            eq(SCREENSHOT_BLOCKED_BY_ADMIN),
+            /* Supplier<String> */ any(),
+        )).thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+
+        val request = ScreenshotRequest(
+            TAKE_SCREENSHOT_FULLSCREEN,
+            SCREENSHOT_KEY_CHORD,
+            topComponent)
+
+        service.handleRequest(request, { /* onSaved */ }, callback)
+
+        // error shown: Toast.makeText(...).show(), untestable
+        verify(callback).reportError()
+        verifyZeroInteractions(controller)
+    }
+}
+
+private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
+    return config == HARDWARE &&
+            other.config == HARDWARE &&
+            hardwareBuffer == other.hardwareBuffer &&
+            colorSpace == other.colorSpace
+}
+
+/** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
+private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
+    val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
+        HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+    return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java
deleted file mode 100644
index 4f509ea..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util;
-
-import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
-import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
-import static androidx.lifecycle.Lifecycle.Event.ON_START;
-import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
-
-import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.testing.ViewUtils;
-import android.view.View;
-
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleEventObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class SysuiLifecycleTest extends SysuiTestCase {
-
-    private View mView;
-
-    @Before
-    public void setUp() {
-        mView = new View(mContext);
-    }
-
-    @After
-    public void tearDown() {
-        if (mView.isAttachedToWindow()) {
-            ViewUtils.detachView(mView);
-            TestableLooper.get(this).processAllMessages();
-        }
-    }
-
-    @Test
-    public void testAttach() {
-        LifecycleEventObserver observer = mock(LifecycleEventObserver.class);
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        lifecycle.getLifecycle().addObserver(observer);
-
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_CREATE));
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_START));
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_RESUME));
-    }
-
-    @Test
-    public void testDetach() {
-        LifecycleEventObserver observer = mock(LifecycleEventObserver.class);
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        lifecycle.getLifecycle().addObserver(observer);
-
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-
-        ViewUtils.detachView(mView);
-        TestableLooper.get(this).processAllMessages();
-
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_PAUSE));
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_STOP));
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_DESTROY));
-    }
-
-    @Test
-    public void testStateBeforeAttach() {
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // THEN the lifecycle state should be INITIAZED
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(
-                Lifecycle.State.INITIALIZED);
-    }
-
-    @Test
-    public void testStateAfterAttach() {
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // AND the view is attached
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        // THEN the lifecycle state should be RESUMED
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED);
-    }
-
-    @Test
-    public void testStateAfterDetach() {
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // AND the view is detached
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        ViewUtils.detachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        // THEN the lifecycle state should be DESTROYED
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.DESTROYED);
-    }
-
-    @Test
-    public void testStateAfterReattach() {
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // AND the view is re-attached
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        ViewUtils.detachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        // THEN the lifecycle state should still be DESTROYED, err RESUMED?
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED);
-    }
-
-    @Test
-    public void testStateWhenViewAlreadyAttached() {
-        // GIVEN that a view is already attached
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // THEN the lifecycle state should be RESUMED
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED);
-    }
-
-    @Test
-    public void testStateWhenViewAlreadyDetached() {
-        // GIVEN that a view is already detached
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        ViewUtils.detachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // THEN the lifecycle state should be INITIALIZED
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(
-                Lifecycle.State.INITIALIZED);
-    }
-}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index fa043f8..265fbc5 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -17,7 +17,6 @@
 
 package com.android.server.companion;
 
-import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
@@ -88,7 +87,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArraySet;
-import android.util.Base64;
 import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -756,6 +754,11 @@
             mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
         }
 
+        @Override
+        public boolean isCompanionApplicationBound(String packageName, int userId) {
+            return mCompanionAppController.isCompanionApplicationBound(userId, packageName);
+        }
+
         private void registerDevicePresenceListenerActive(String packageName, String deviceAddress,
                 boolean active) throws RemoteException {
             if (DEBUG) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 734f455..8521cea 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2401,13 +2401,13 @@
         mEnableOffloadQueue = SystemProperties.getBoolean(
                 "persist.device_config.activity_manager_native_boot.offload_queue_enabled", true);
 
-        mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
+        mFgBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
                 "foreground", foreConstants, false);
-        mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
+        mBgBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
                 "background", backConstants, true);
-        mBgOffloadBroadcastQueue = new BroadcastQueue(this, mHandler,
+        mBgOffloadBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
                 "offload_bg", offloadConstants, true);
-        mFgOffloadBroadcastQueue = new BroadcastQueue(this, mHandler,
+        mFgOffloadBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
                 "offload_fg", foreConstants, true);
         mBroadcastQueues[0] = mFgBroadcastQueue;
         mBroadcastQueues[1] = mBgBroadcastQueue;
@@ -10627,7 +10627,7 @@
             pw.println();
             for (BroadcastQueue queue : mBroadcastQueues) {
                 pw.println("  mBroadcastsScheduled [" + queue.mQueueName + "]="
-                        + queue.mBroadcastsScheduled);
+                        + queue.hasBroadcastsScheduled());
             }
             pw.println("  mHandler:");
             mHandler.dump(new PrintWriterPrinter(pw), "    ");
@@ -15186,7 +15186,7 @@
 
         // It's not the current receiver, but it might be starting up to become one
         for (BroadcastQueue queue : mBroadcastQueues) {
-            final BroadcastRecord r = queue.mPendingBroadcast;
+            final BroadcastRecord r = queue.getPendingBroadcastLocked();
             if (r != null && r.curApp == app) {
                 // found it; report which queue it's in
                 receivingQueues.add(queue);
@@ -15301,7 +15301,7 @@
     @GuardedBy("this")
     final boolean canGcNowLocked() {
         for (BroadcastQueue q : mBroadcastQueues) {
-            if (!q.mParallelBroadcasts.isEmpty() || !q.mDispatcher.isIdle()) {
+            if (!q.isIdle()) {
                 return false;
             }
         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 31d9f96..7500cdd 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -16,236 +16,37 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
-import static android.text.TextUtils.formatSimple;
-
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-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_PERMISSIONS_REVIEW;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
-
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AppGlobals;
-import android.app.AppOpsManager;
-import android.app.BroadcastOptions;
-import android.app.IApplicationThread;
-import android.app.PendingIntent;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
-import android.app.usage.UsageEvents.Event;
-import android.app.usage.UsageStatsManagerInternal;
-import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.IIntentReceiver;
-import android.content.IIntentSender;
 import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerExemptionManager.ReasonCode;
-import android.os.PowerExemptionManager.TempAllowListType;
-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.permission.IPermissionManager;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.Slog;
-import android.util.SparseIntArray;
-import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.os.TimeoutRecord;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
 import java.util.Set;
 
 /**
- * BROADCASTS
- *
- * We keep three broadcast queues and associated bookkeeping, one for those at
- * foreground priority, and one for normal (background-priority) broadcasts, and one to
- * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED.
+ * Queue of broadcast intents and associated bookkeeping.
  */
-public final class BroadcastQueue {
-    private static final String TAG = "BroadcastQueue";
-    private static final String TAG_MU = TAG + POSTFIX_MU;
-    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
-
-    static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 50;
-    static final int MAX_BROADCAST_SUMMARY_HISTORY
-            = ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
-
+public abstract class BroadcastQueue {
     final ActivityManagerService mService;
-
-    /**
-     * Behavioral parameters such as timeouts and deferral policy, tracking Settings
-     * for runtime configurability
-     */
+    final Handler mHandler;
     final BroadcastConstants mConstants;
-
-    /**
-     * Recognizable moniker for this queue
-     */
     final String mQueueName;
 
-    /**
-     * If true, we can delay broadcasts while waiting services to finish in the previous
-     * receiver's process.
-     */
-    final boolean mDelayBehindServices;
-
-    /**
-     * Lists of all active broadcasts that are to be executed immediately
-     * (without waiting for another broadcast to finish).  Currently this only
-     * contains broadcasts to registered receivers, to avoid spinning up
-     * a bunch of processes to execute IntentReceiver components.  Background-
-     * and foreground-priority broadcasts are queued separately.
-     */
-    final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
-
-    /**
-     * Tracking of the ordered broadcast queue, including deferral policy and alarm
-     * prioritization.
-     */
-    final BroadcastDispatcher mDispatcher;
-
-    /**
-     * Refcounting for completion callbacks of split/deferred broadcasts.  The key
-     * is an opaque integer token assigned lazily when a broadcast is first split
-     * into multiple BroadcastRecord objects.
-     */
-    final SparseIntArray mSplitRefcounts = new SparseIntArray();
-    private int mNextToken = 0;
-
-    /**
-     * Historical data of past broadcasts, for debugging.  This is a ring buffer
-     * whose last element is at mHistoryNext.
-     */
-    final BroadcastRecord[] mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY];
-    int mHistoryNext = 0;
-
-    /**
-     * Summary of historical data of past broadcasts, for debugging.  This is a
-     * ring buffer whose last element is at mSummaryHistoryNext.
-     */
-    final Intent[] mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY];
-    int mSummaryHistoryNext = 0;
-
-    /**
-     * Various milestone timestamps of entries in the mBroadcastSummaryHistory ring
-     * buffer, also tracked via the mSummaryHistoryNext index.  These are all in wall
-     * clock time, not elapsed.
-     */
-    final long[] mSummaryHistoryEnqueueTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
-    final long[] mSummaryHistoryDispatchTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
-    final long[] mSummaryHistoryFinishTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
-
-    /**
-     * Set when we current have a BROADCAST_INTENT_MSG in flight.
-     */
-    boolean mBroadcastsScheduled = false;
-
-    /**
-     * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
-     */
-    boolean mPendingBroadcastTimeoutMessage;
-
-    /**
-     * Intent broadcasts that we have tried to start, but are
-     * waiting for the application's process to be created.  We only
-     * need one per scheduling class (instead of a list) because we always
-     * process broadcasts one at a time, so no others can be started while
-     * waiting for this one.
-     */
-    BroadcastRecord mPendingBroadcast = null;
-
-    /**
-     * The receiver index that is pending, to restart the broadcast if needed.
-     */
-    int mPendingBroadcastRecvIndex;
-
-    static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
-    static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
-
-    // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
-    boolean mLogLatencyMetrics = true;
-
-    final BroadcastHandler mHandler;
-
-    private final class BroadcastHandler extends Handler {
-        public BroadcastHandler(Looper looper) {
-            super(looper, null, true);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case BROADCAST_INTENT_MSG: {
-                    if (DEBUG_BROADCAST) Slog.v(
-                            TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
-                            + mQueueName + "]");
-                    processNextBroadcast(true);
-                } break;
-                case BROADCAST_TIMEOUT_MSG: {
-                    synchronized (mService) {
-                        broadcastTimeoutLocked(true);
-                    }
-                } break;
-            }
-        }
-    }
-
     BroadcastQueue(ActivityManagerService service, Handler handler,
-            String name, BroadcastConstants constants, boolean allowDelayBehindServices) {
+            String name, BroadcastConstants constants) {
         mService = service;
-        mHandler = new BroadcastHandler(handler.getLooper());
+        mHandler = handler;
         mQueueName = name;
-        mDelayBehindServices = allowDelayBehindServices;
-
         mConstants = constants;
-        mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
     }
 
     void start(ContentResolver resolver) {
-        mDispatcher.start();
         mConstants.startObserving(mHandler, resolver);
     }
 
@@ -254,2322 +55,62 @@
         return mQueueName;
     }
 
-    public boolean isPendingBroadcastProcessLocked(int pid) {
-        return mPendingBroadcast != null && mPendingBroadcast.curApp.getPid() == pid;
-    }
+    public abstract boolean isDelayBehindServices();
 
-    boolean isPendingBroadcastProcessLocked(ProcessRecord app) {
-        return mPendingBroadcast != null && mPendingBroadcast.curApp == app;
-    }
+    public abstract boolean hasBroadcastsScheduled();
 
-    public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
-        r.enqueueClockTime = System.currentTimeMillis();
-        r.enqueueTime = SystemClock.uptimeMillis();
-        r.enqueueRealTime = SystemClock.elapsedRealtime();
-        mParallelBroadcasts.add(r);
-        enqueueBroadcastHelper(r);
-    }
+    public abstract BroadcastRecord getPendingBroadcastLocked();
 
-    public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
-        r.enqueueClockTime = System.currentTimeMillis();
-        r.enqueueTime = SystemClock.uptimeMillis();
-        r.enqueueRealTime = SystemClock.elapsedRealtime();
-        mDispatcher.enqueueOrderedBroadcastLocked(r);
-        enqueueBroadcastHelper(r);
-    }
+    public abstract BroadcastRecord getActiveBroadcastLocked();
 
-    /**
-     * Don't call this method directly; call enqueueParallelBroadcastLocked or
-     * enqueueOrderedBroadcastLocked.
-     */
-    private void enqueueBroadcastHelper(BroadcastRecord r) {
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                System.identityHashCode(r));
-        }
-    }
+    public abstract boolean isPendingBroadcastProcessLocked(int pid);
 
-    /**
-     * Find the same intent from queued parallel broadcast, replace with a new one and return
-     * the old one.
-     */
-    public final BroadcastRecord replaceParallelBroadcastLocked(BroadcastRecord r) {
-        return replaceBroadcastLocked(mParallelBroadcasts, r, "PARALLEL");
-    }
+    public abstract boolean isPendingBroadcastProcessLocked(ProcessRecord app);
 
-    /**
-     * Find the same intent from queued ordered broadcast, replace with a new one and return
-     * the old one.
-     */
-    public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) {
-        return mDispatcher.replaceBroadcastLocked(r, "ORDERED");
-    }
+    public abstract void enqueueParallelBroadcastLocked(BroadcastRecord r);
 
-    private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
-            BroadcastRecord r, String typeForLogging) {
-        final Intent intent = r.intent;
-        for (int i = queue.size() - 1; i > 0; i--) {
-            final BroadcastRecord old = queue.get(i);
-            if (old.userId == r.userId && intent.filterEquals(old.intent)) {
-                if (DEBUG_BROADCAST) {
-                    Slog.v(TAG_BROADCAST, "***** DROPPING "
-                            + typeForLogging + " [" + mQueueName + "]: " + intent);
-                }
-                queue.set(i, r);
-                return old;
-            }
-        }
-        return null;
-    }
+    public abstract void enqueueOrderedBroadcastLocked(BroadcastRecord r);
 
-    private final void processCurBroadcastLocked(BroadcastRecord r,
-            ProcessRecord app) throws RemoteException {
-        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                "Process cur broadcast " + r + " for app " + app);
-        final IApplicationThread thread = app.getThread();
-        if (thread == null) {
-            throw new RemoteException();
-        }
-        if (app.isInFullBackup()) {
-            skipReceiverLocked(r);
-            return;
-        }
+    public abstract BroadcastRecord replaceParallelBroadcastLocked(BroadcastRecord r);
 
-        r.receiver = thread.asBinder();
-        r.curApp = app;
-        final ProcessReceiverRecord prr = app.mReceivers;
-        prr.addCurReceiver(r);
-        app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
-        // Don't bump its LRU position if it's in the background restricted.
-        if (mService.mInternal.getRestrictionLevel(app.info.packageName, app.userId)
-                < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
-            mService.updateLruProcessLocked(app, false, null);
-        }
-        // Make sure the oom adj score is updated before delivering the broadcast.
-        // Force an update, even if there are other pending requests, overall it still saves time,
-        // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
-        mService.enqueueOomAdjTargetLocked(app);
-        mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
+    public abstract BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r);
 
-        // Tell the application to launch this receiver.
-        maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
-        r.intent.setComponent(r.curComponent);
+    public abstract void updateUidReadyForBootCompletedBroadcastLocked(int uid);
 
-        boolean started = false;
-        try {
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                    "Delivering to component " + r.curComponent
-                    + ": " + r);
-            mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
-                                      PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
-            thread.scheduleReceiver(prepareReceiverIntent(r.intent, r.curFilteredExtras),
-                    r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
-                    r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
-                    app.mState.getReportedProcState());
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Process cur broadcast " + r + " DELIVERED for app " + app);
-            started = true;
-        } finally {
-            if (!started) {
-                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                        "Process cur broadcast " + r + ": NOT STARTED!");
-                r.receiver = null;
-                r.curApp = null;
-                prr.removeCurReceiver(r);
-            }
-        }
+    public abstract boolean sendPendingBroadcastsLocked(ProcessRecord app);
 
-        // if something bad happens here, launch the app and try again
-        if (app.isKilled()) {
-            throw new RemoteException("app gets killed during broadcasting");
-        }
-    }
+    public abstract void skipPendingBroadcastLocked(int pid);
 
-    /**
-     * Called by ActivityManagerService to notify that the uid has process started, if there is any
-     * deferred BOOT_COMPLETED broadcast, the BroadcastDispatcher can dispatch the broadcast now.
-     * @param uid
-     */
-    public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
-        mDispatcher.updateUidReadyForBootCompletedBroadcastLocked(uid);
-    }
+    public abstract void skipCurrentReceiverLocked(ProcessRecord app);
 
-    public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
-        boolean didSomething = false;
-        final BroadcastRecord br = mPendingBroadcast;
-        if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
-            if (br.curApp != app) {
-                Slog.e(TAG, "App mismatch when sending pending broadcast to "
-                        + app.processName + ", intended target is " + br.curApp.processName);
-                return false;
-            }
-            try {
-                mPendingBroadcast = null;
-                br.mIsReceiverAppRunning = false;
-                processCurBroadcastLocked(br, app);
-                didSomething = true;
-            } catch (Exception e) {
-                Slog.w(TAG, "Exception in new application when starting receiver "
-                        + br.curComponent.flattenToShortString(), e);
-                logBroadcastReceiverDiscardLocked(br);
-                finishReceiverLocked(br, br.resultCode, br.resultData,
-                        br.resultExtras, br.resultAbort, false);
-                scheduleBroadcastsLocked();
-                // We need to reset the state if we failed to start the receiver.
-                br.state = BroadcastRecord.IDLE;
-                throw new RuntimeException(e.getMessage());
-            }
-        }
-        return didSomething;
-    }
+    public abstract void scheduleBroadcastsLocked();
 
-    public void skipPendingBroadcastLocked(int pid) {
-        final BroadcastRecord br = mPendingBroadcast;
-        if (br != null && br.curApp.getPid() == pid) {
-            br.state = BroadcastRecord.IDLE;
-            br.nextReceiver = mPendingBroadcastRecvIndex;
-            mPendingBroadcast = null;
-            scheduleBroadcastsLocked();
-        }
-    }
+    public abstract BroadcastRecord getMatchingOrderedReceiver(IBinder receiver);
 
-    // Skip the current receiver, if any, that is in flight to the given process
-    public void skipCurrentReceiverLocked(ProcessRecord app) {
-        BroadcastRecord r = null;
-        final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked();
-        if (curActive != null && curActive.curApp == app) {
-            // confirmed: the current active broadcast is to the given app
-            r = curActive;
-        }
+    public abstract boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
+            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices);
 
-        // If the current active broadcast isn't this BUT we're waiting for
-        // mPendingBroadcast to spin up the target app, that's what we use.
-        if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
-                    "[" + mQueueName + "] skip & discard pending app " + r);
-            r = mPendingBroadcast;
-        }
+    public abstract void backgroundServicesFinishedLocked(int userId);
 
-        if (r != null) {
-            skipReceiverLocked(r);
-        }
-    }
-
-    private void skipReceiverLocked(BroadcastRecord r) {
-        logBroadcastReceiverDiscardLocked(r);
-        finishReceiverLocked(r, r.resultCode, r.resultData,
-                r.resultExtras, r.resultAbort, false);
-        scheduleBroadcastsLocked();
-    }
-
-    public void scheduleBroadcastsLocked() {
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
-                + mQueueName + "]: current="
-                + mBroadcastsScheduled);
-
-        if (mBroadcastsScheduled) {
-            return;
-        }
-        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
-        mBroadcastsScheduled = true;
-    }
-
-    public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) {
-        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
-        if (br != null && br.receiver == receiver) {
-            return br;
-        }
-        return null;
-    }
-
-    // > 0 only, no worry about "eventual" recycling
-    private int nextSplitTokenLocked() {
-        int next = mNextToken + 1;
-        if (next <= 0) {
-            next = 1;
-        }
-        mNextToken = next;
-        return next;
-    }
-
-    private void postActivityStartTokenRemoval(ProcessRecord app, BroadcastRecord r) {
-        // the receiver had run for less than allowed bg activity start timeout,
-        // so allow the process to still start activities from bg for some more time
-        String msgToken = (app.toShortString() + r.toString()).intern();
-        // first, if there exists a past scheduled request to remove this token, drop
-        // that request - we don't want the token to be swept from under our feet...
-        mHandler.removeCallbacksAndMessages(msgToken);
-        // ...then schedule the removal of the token after the extended timeout
-        mHandler.postAtTime(() -> {
-            synchronized (mService) {
-                app.removeAllowBackgroundActivityStartsToken(r);
-            }
-        }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
-    }
-
-    public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
-            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
-        final int state = r.state;
-        final ActivityInfo receiver = r.curReceiver;
-        final long finishTime = SystemClock.uptimeMillis();
-        final long elapsed = finishTime - r.receiverTime;
-        r.state = BroadcastRecord.IDLE;
-        final int curIndex = r.nextReceiver - 1;
-        if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
-            final Object curReceiver = r.receivers.get(curIndex);
-            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
-                    r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
-                    ActivityManagerService.getShortAction(r.intent.getAction()),
-                    curReceiver instanceof BroadcastFilter
-                    ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
-                    : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
-                    r.mIsReceiverAppRunning
-                    ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM
-                    : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
-                    r.dispatchTime - r.enqueueTime,
-                    r.receiverTime - r.dispatchTime,
-                    finishTime - r.receiverTime);
-        }
-        if (state == BroadcastRecord.IDLE) {
-            Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
-        }
-        if (r.allowBackgroundActivityStarts && r.curApp != null) {
-            if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
-                // if the receiver has run for more than allowed bg activity start timeout,
-                // just remove the token for this process now and we're done
-                r.curApp.removeAllowBackgroundActivityStartsToken(r);
-            } else {
-                // It gets more time; post the removal to happen at the appropriate moment
-                postActivityStartTokenRemoval(r.curApp, r);
-            }
-        }
-        // If we're abandoning this broadcast before any receivers were actually spun up,
-        // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply.
-        if (r.nextReceiver > 0) {
-            r.duration[r.nextReceiver - 1] = elapsed;
-        }
-
-        // if this receiver was slow, impose deferral policy on the app.  This will kick in
-        // when processNextBroadcastLocked() next finds this uid as a receiver identity.
-        if (!r.timeoutExempt) {
-            // r.curApp can be null if finish has raced with process death - benign
-            // edge case, and we just ignore it because we're already cleaning up
-            // as expected.
-            if (r.curApp != null
-                    && mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
-                // Core system packages are exempt from deferral policy
-                if (!UserHandle.isCore(r.curApp.uid)) {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Broadcast receiver " + (r.nextReceiver - 1)
-                                + " was slow: " + receiver + " br=" + r);
-                    }
-                    mDispatcher.startDeferring(r.curApp.uid);
-                } else {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Core uid " + r.curApp.uid
-                                + " receiver was slow but not deferring: "
-                                + receiver + " br=" + r);
-                    }
-                }
-            }
-        } else {
-            if (DEBUG_BROADCAST_DEFERRAL) {
-                Slog.i(TAG_BROADCAST, "Finished broadcast " + r.intent.getAction()
-                        + " is exempt from deferral policy");
-            }
-        }
-
-        r.receiver = null;
-        r.intent.setComponent(null);
-        if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
-            r.curApp.mReceivers.removeCurReceiver(r);
-            mService.enqueueOomAdjTargetLocked(r.curApp);
-        }
-        if (r.curFilter != null) {
-            r.curFilter.receiverList.curBroadcast = null;
-        }
-        r.curFilter = null;
-        r.curReceiver = null;
-        r.curApp = null;
-        r.curFilteredExtras = null;
-        mPendingBroadcast = null;
-
-        r.resultCode = resultCode;
-        r.resultData = resultData;
-        r.resultExtras = resultExtras;
-        if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
-            r.resultAbort = resultAbort;
-        } else {
-            r.resultAbort = false;
-        }
-
-        // If we want to wait behind services *AND* we're finishing the head/
-        // active broadcast on its queue
-        if (waitForServices && r.curComponent != null && r.queue.mDelayBehindServices
-                && r.queue.mDispatcher.getActiveBroadcastLocked() == r) {
-            ActivityInfo nextReceiver;
-            if (r.nextReceiver < r.receivers.size()) {
-                Object obj = r.receivers.get(r.nextReceiver);
-                nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
-            } else {
-                nextReceiver = null;
-            }
-            // Don't do this if the next receive is in the same process as the current one.
-            if (receiver == null || nextReceiver == null
-                    || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
-                    || !receiver.processName.equals(nextReceiver.processName)) {
-                // In this case, we are ready to process the next receiver for the current broadcast,
-                // but are on a queue that would like to wait for services to finish before moving
-                // on.  If there are background services currently starting, then we will go into a
-                // special state where we hold off on continuing this broadcast until they are done.
-                if (mService.mServices.hasBackgroundServicesLocked(r.userId)) {
-                    Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
-                    r.state = BroadcastRecord.WAITING_SERVICES;
-                    return false;
-                }
-            }
-        }
-
-        r.curComponent = null;
-
-        // We will process the next receiver right now if this is finishing
-        // an app receiver (which is always asynchronous) or after we have
-        // come back from calling a receiver.
-        return state == BroadcastRecord.APP_RECEIVE
-                || state == BroadcastRecord.CALL_DONE_RECEIVE;
-    }
-
-    public void backgroundServicesFinishedLocked(int userId) {
-        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
-        if (br != null) {
-            if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
-                Slog.i(TAG, "Resuming delayed broadcast");
-                br.curComponent = null;
-                br.state = BroadcastRecord.IDLE;
-                processNextBroadcastLocked(false, false);
-            }
-        }
-    }
-
-    void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
+    public abstract void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
             Intent intent, int resultCode, String data, Bundle extras,
             boolean ordered, boolean sticky, int sendingUser,
             int receiverUid, int callingUid, long dispatchDelay,
-            long receiveDelay) throws RemoteException {
-        // Send the intent to the receiver asynchronously using one-way binder calls.
-        if (app != null) {
-            final IApplicationThread thread = app.getThread();
-            if (thread != null) {
-                // If we have an app thread, do the call through that so it is
-                // correctly ordered with other one-way calls.
-                try {
-                    thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
-                            data, extras, ordered, sticky, sendingUser,
-                            app.mState.getReportedProcState());
-                // TODO: Uncomment this when (b/28322359) is fixed and we aren't getting
-                // DeadObjectException when the process isn't actually dead.
-                //} catch (DeadObjectException ex) {
-                // Failed to call into the process.  It's dying so just let it die and move on.
-                //    throw ex;
-                } catch (RemoteException ex) {
-                    // Failed to call into the process. It's either dying or wedged. Kill it gently.
-                    synchronized (mService) {
-                        Slog.w(TAG, "Can't deliver broadcast to " + app.processName
-                                + " (pid " + app.getPid() + "). Crashing it.");
-                        app.scheduleCrashLocked("can't deliver broadcast",
-                                CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
-                    }
-                    throw ex;
-                }
-            } else {
-                // Application has died. Receiver doesn't exist.
-                throw new RemoteException("app.thread must not be null");
-            }
-        } else {
-            receiver.performReceive(intent, resultCode, data, extras, ordered,
-                    sticky, sendingUser);
-        }
-        if (!ordered) {
-            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
-                    receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
-                    callingUid == -1 ? Process.SYSTEM_UID : callingUid,
-                    ActivityManagerService.getShortAction(intent.getAction()),
-                    BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
-                    BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
-                    dispatchDelay, receiveDelay, 0 /* finish_delay */);
-        }
-    }
+            long receiveDelay) throws RemoteException;
 
-    private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
-            BroadcastFilter filter, boolean ordered, int index) {
-        boolean skip = false;
-        if (r.options != null && !r.options.testRequireCompatChange(filter.owningUid)) {
-            Slog.w(TAG, "Compat change filtered: broadcasting " + r.intent.toString()
-                    + " to uid " + filter.owningUid + " due to compat change "
-                    + r.options.getRequireCompatChangeId());
-            skip = true;
-        }
-        if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
-                filter.packageName, filter.owningUid)) {
-            Slog.w(TAG, "Association not allowed: broadcasting "
-                    + r.intent.toString()
-                    + " from " + r.callerPackage + " (pid=" + r.callingPid
-                    + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
-                    + filter);
-            skip = true;
-        }
-        if (!skip && !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
-                r.callingPid, r.resolvedType, filter.receiverList.uid)) {
-            Slog.w(TAG, "Firewall blocked: broadcasting "
-                    + r.intent.toString()
-                    + " from " + r.callerPackage + " (pid=" + r.callingPid
-                    + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
-                    + filter);
-            skip = true;
-        }
-        // Check that the sender has permission to send to this receiver
-        if (filter.requiredPermission != null) {
-            int perm = mService.checkComponentPermission(filter.requiredPermission,
-                    r.callingPid, r.callingUid, -1, true);
-            if (perm != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid="
-                        + r.callingPid + ", uid=" + r.callingUid + ")"
-                        + " requires " + filter.requiredPermission
-                        + " due to registered receiver " + filter);
-                skip = true;
-            } else {
-                final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission);
-                if (opCode != AppOpsManager.OP_NONE
-                        && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid,
-                        r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver")
-                        != AppOpsManager.MODE_ALLOWED) {
-                    Slog.w(TAG, "Appop Denial: broadcasting "
-                            + r.intent.toString()
-                            + " from " + r.callerPackage + " (pid="
-                            + r.callingPid + ", uid=" + r.callingUid + ")"
-                            + " requires appop " + AppOpsManager.permissionToOp(
-                                    filter.requiredPermission)
-                            + " due to registered receiver " + filter);
-                    skip = true;
-                }
-            }
-        }
+    public abstract void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj);
 
-        if (!skip && (filter.receiverList.app == null || filter.receiverList.app.isKilled()
-                || filter.receiverList.app.mErrorState.isCrashing())) {
-            Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
-                    + " to " + filter.receiverList + ": process gone or crashing");
-            skip = true;
-        }
+    public abstract boolean cleanupDisabledPackageReceiversLocked(
+            String packageName, Set<String> filterByClasses, int userId, boolean doit);
 
-        // Ensure that broadcasts are only sent to other Instant Apps if they are marked as
-        // visible to Instant Apps.
-        final boolean visibleToInstantApps =
-                (r.intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
+    public abstract boolean isIdle();
 
-        if (!skip && !visibleToInstantApps && filter.instantApp
-                && filter.receiverList.uid != r.callingUid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
-                    + r.intent.toString()
-                    + " to " + filter.receiverList.app
-                    + " (pid=" + filter.receiverList.pid
-                    + ", uid=" + filter.receiverList.uid + ")"
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")"
-                    + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS");
-            skip = true;
-        }
+    public abstract void cancelDeferrals();
 
-        if (!skip && !filter.visibleToInstantApp && r.callerInstantApp
-                && filter.receiverList.uid != r.callingUid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
-                    + r.intent.toString()
-                    + " to " + filter.receiverList.app
-                    + " (pid=" + filter.receiverList.pid
-                    + ", uid=" + filter.receiverList.uid + ")"
-                    + " requires receiver be visible to instant apps"
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            skip = true;
-        }
+    public abstract String describeState();
 
-        // Check that the receiver has the required permission(s) to receive this broadcast.
-        if (!skip && r.requiredPermissions != null && r.requiredPermissions.length > 0) {
-            for (int i = 0; i < r.requiredPermissions.length; i++) {
-                String requiredPermission = r.requiredPermissions[i];
-                int perm = mService.checkComponentPermission(requiredPermission,
-                        filter.receiverList.pid, filter.receiverList.uid, -1, true);
-                if (perm != PackageManager.PERMISSION_GRANTED) {
-                    Slog.w(TAG, "Permission Denial: receiving "
-                            + r.intent.toString()
-                            + " to " + filter.receiverList.app
-                            + " (pid=" + filter.receiverList.pid
-                            + ", uid=" + filter.receiverList.uid + ")"
-                            + " requires " + requiredPermission
-                            + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    skip = true;
-                    break;
-                }
-                int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
-                if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
-                        && mService.getAppOpsManager().noteOpNoThrow(appOp,
-                        filter.receiverList.uid, filter.packageName, filter.featureId,
-                        "Broadcast delivered to registered receiver " + filter.receiverId)
-                        != AppOpsManager.MODE_ALLOWED) {
-                    Slog.w(TAG, "Appop Denial: receiving "
-                            + r.intent.toString()
-                            + " to " + filter.receiverList.app
-                            + " (pid=" + filter.receiverList.pid
-                            + ", uid=" + filter.receiverList.uid + ")"
-                            + " requires appop " + AppOpsManager.permissionToOp(
-                            requiredPermission)
-                            + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    skip = true;
-                    break;
-                }
-            }
-        }
-        if (!skip && (r.requiredPermissions == null || r.requiredPermissions.length == 0)) {
-            int perm = mService.checkComponentPermission(null,
-                    filter.receiverList.pid, filter.receiverList.uid, -1, true);
-            if (perm != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: security check failed when receiving "
-                        + r.intent.toString()
-                        + " to " + filter.receiverList.app
-                        + " (pid=" + filter.receiverList.pid
-                        + ", uid=" + filter.receiverList.uid + ")"
-                        + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                skip = true;
-            }
-        }
-        // Check that the receiver does *not* have any excluded permissions
-        if (!skip && r.excludedPermissions != null && r.excludedPermissions.length > 0) {
-            for (int i = 0; i < r.excludedPermissions.length; i++) {
-                String excludedPermission = r.excludedPermissions[i];
-                final int perm = mService.checkComponentPermission(excludedPermission,
-                        filter.receiverList.pid, filter.receiverList.uid, -1, true);
+    public abstract void dumpDebug(ProtoOutputStream proto, long fieldId);
 
-                int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
-                if (appOp != AppOpsManager.OP_NONE) {
-                    // When there is an app op associated with the permission,
-                    // skip when both the permission and the app op are
-                    // granted.
-                    if ((perm == PackageManager.PERMISSION_GRANTED) && (
-                            mService.getAppOpsManager().checkOpNoThrow(appOp,
-                                    filter.receiverList.uid,
-                                    filter.packageName)
-                                    == AppOpsManager.MODE_ALLOWED)) {
-                        Slog.w(TAG, "Appop Denial: receiving "
-                                + r.intent.toString()
-                                + " to " + filter.receiverList.app
-                                + " (pid=" + filter.receiverList.pid
-                                + ", uid=" + filter.receiverList.uid + ")"
-                                + " excludes appop " + AppOpsManager.permissionToOp(
-                                excludedPermission)
-                                + " due to sender " + r.callerPackage
-                                + " (uid " + r.callingUid + ")");
-                        skip = true;
-                        break;
-                    }
-                } else {
-                    // When there is no app op associated with the permission,
-                    // skip when permission is granted.
-                    if (perm == PackageManager.PERMISSION_GRANTED) {
-                        Slog.w(TAG, "Permission Denial: receiving "
-                                + r.intent.toString()
-                                + " to " + filter.receiverList.app
-                                + " (pid=" + filter.receiverList.pid
-                                + ", uid=" + filter.receiverList.uid + ")"
-                                + " excludes " + excludedPermission
-                                + " due to sender " + r.callerPackage
-                                + " (uid " + r.callingUid + ")");
-                        skip = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        // Check that the receiver does *not* belong to any of the excluded packages
-        if (!skip && r.excludedPackages != null && r.excludedPackages.length > 0) {
-            if (ArrayUtils.contains(r.excludedPackages, filter.packageName)) {
-                Slog.w(TAG, "Skipping delivery of excluded package "
-                        + r.intent.toString()
-                        + " to " + filter.receiverList.app
-                        + " (pid=" + filter.receiverList.pid
-                        + ", uid=" + filter.receiverList.uid + ")"
-                        + " excludes package " + filter.packageName
-                        + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                skip = true;
-            }
-        }
-
-        // If the broadcast also requires an app op check that as well.
-        if (!skip && r.appOp != AppOpsManager.OP_NONE
-                && mService.getAppOpsManager().noteOpNoThrow(r.appOp,
-                filter.receiverList.uid, filter.packageName, filter.featureId,
-                "Broadcast delivered to registered receiver " + filter.receiverId)
-                != AppOpsManager.MODE_ALLOWED) {
-            Slog.w(TAG, "Appop Denial: receiving "
-                    + r.intent.toString()
-                    + " to " + filter.receiverList.app
-                    + " (pid=" + filter.receiverList.pid
-                    + ", uid=" + filter.receiverList.uid + ")"
-                    + " requires appop " + AppOpsManager.opToName(r.appOp)
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            skip = true;
-        }
-
-        // Ensure that broadcasts are only sent to other apps if they are explicitly marked as
-        // exported, or are System level broadcasts
-        if (!skip && !filter.exported && mService.checkComponentPermission(null, r.callingPid,
-                r.callingUid, filter.receiverList.uid, filter.exported)
-                != PackageManager.PERMISSION_GRANTED) {
-            Slog.w(TAG, "Exported Denial: sending "
-                    + r.intent.toString()
-                    + ", action: " + r.intent.getAction()
-                    + " from " + r.callerPackage
-                    + " (uid=" + r.callingUid + ")"
-                    + " due to receiver " + filter.receiverList.app
-                    + " (uid " + filter.receiverList.uid + ")"
-                    + " not specifying RECEIVER_EXPORTED");
-            skip = true;
-        }
-
-        // Filter packages in the intent extras, skipping delivery if none of the packages is
-        // visible to the receiver.
-        Bundle filteredExtras = null;
-        if (!skip && r.filterExtrasForReceiver != null) {
-            final Bundle extras = r.intent.getExtras();
-            if (extras != null) {
-                filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
-                if (filteredExtras == null) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.v(TAG, "Skipping delivery to "
-                                + filter.receiverList.app
-                                + " : receiver is filtered by the package visibility");
-                    }
-                    skip = true;
-                }
-            }
-        }
-
-        if (skip) {
-            r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
-            return;
-        }
-
-        // If permissions need a review before any of the app components can run, we drop
-        // the broadcast and if the calling app is in the foreground and the broadcast is
-        // explicit we launch the review UI passing it a pending intent to send the skipped
-        // broadcast.
-        if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
-                filter.owningUserId)) {
-            r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
-            return;
-        }
-
-        r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
-
-        // If this is not being sent as an ordered broadcast, then we
-        // don't want to touch the fields that keep track of the current
-        // state of ordered broadcasts.
-        if (ordered) {
-            r.receiver = filter.receiverList.receiver.asBinder();
-            r.curFilter = filter;
-            filter.receiverList.curBroadcast = r;
-            r.state = BroadcastRecord.CALL_IN_RECEIVE;
-            if (filter.receiverList.app != null) {
-                // Bump hosting application to no longer be in background
-                // scheduling class.  Note that we can't do that if there
-                // isn't an app...  but we can only be in that case for
-                // things that directly call the IActivityManager API, which
-                // are already core system stuff so don't matter for this.
-                r.curApp = filter.receiverList.app;
-                filter.receiverList.app.mReceivers.addCurReceiver(r);
-                mService.enqueueOomAdjTargetLocked(r.curApp);
-                mService.updateOomAdjPendingTargetsLocked(
-                        OOM_ADJ_REASON_START_RECEIVER);
-            }
-        } else if (filter.receiverList.app != null) {
-            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app,
-                    OOM_ADJ_REASON_START_RECEIVER);
-        }
-
-        try {
-            if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
-                    "Delivering to " + filter + " : " + r);
-            if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) {
-                // Skip delivery if full backup in progress
-                // If it's an ordered broadcast, we need to continue to the next receiver.
-                if (ordered) {
-                    skipReceiverLocked(r);
-                }
-            } else {
-                r.receiverTime = SystemClock.uptimeMillis();
-                maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
-                maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
-                maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
-                performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
-                        prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
-                        r.resultExtras, r.ordered, r.initialSticky, r.userId,
-                        filter.receiverList.uid, r.callingUid,
-                        r.dispatchTime - r.enqueueTime,
-                        r.receiverTime - r.dispatchTime);
-                // parallel broadcasts are fire-and-forget, not bookended by a call to
-                // finishReceiverLocked(), so we manage their activity-start token here
-                if (filter.receiverList.app != null
-                        && r.allowBackgroundActivityStarts && !r.ordered) {
-                    postActivityStartTokenRemoval(filter.receiverList.app, r);
-                }
-            }
-            if (ordered) {
-                r.state = BroadcastRecord.CALL_DONE_RECEIVE;
-            }
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
-            // Clean up ProcessRecord state related to this broadcast attempt
-            if (filter.receiverList.app != null) {
-                filter.receiverList.app.removeAllowBackgroundActivityStartsToken(r);
-                if (ordered) {
-                    filter.receiverList.app.mReceivers.removeCurReceiver(r);
-                    // Something wrong, its oom adj could be downgraded, but not in a hurry.
-                    mService.enqueueOomAdjTargetLocked(r.curApp);
-                }
-            }
-            // And BroadcastRecord state related to ordered delivery, if appropriate
-            if (ordered) {
-                r.receiver = null;
-                r.curFilter = null;
-                filter.receiverList.curBroadcast = null;
-            }
-        }
-    }
-
-    private boolean requestStartTargetPermissionsReviewIfNeededLocked(
-            BroadcastRecord receiverRecord, String receivingPackageName,
-            final int receivingUserId) {
-        if (!mService.getPackageManagerInternal().isPermissionsReviewRequired(
-                receivingPackageName, receivingUserId)) {
-            return true;
-        }
-
-        final boolean callerForeground = receiverRecord.callerApp != null
-                ? receiverRecord.callerApp.mState.getSetSchedGroup()
-                != ProcessList.SCHED_GROUP_BACKGROUND : true;
-
-        // Show a permission review UI only for explicit broadcast from a foreground app
-        if (callerForeground && receiverRecord.intent.getComponent() != null) {
-            IIntentSender target = mService.mPendingIntentController.getIntentSender(
-                    ActivityManager.INTENT_SENDER_BROADCAST, receiverRecord.callerPackage,
-                    receiverRecord.callerFeatureId, receiverRecord.callingUid,
-                    receiverRecord.userId, null, null, 0,
-                    new Intent[]{receiverRecord.intent},
-                    new String[]{receiverRecord.intent.resolveType(mService.mContext
-                            .getContentResolver())},
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
-                            | PendingIntent.FLAG_IMMUTABLE, null);
-
-            final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
-                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName);
-            intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
-
-            if (DEBUG_PERMISSIONS_REVIEW) {
-                Slog.i(TAG, "u" + receivingUserId + " Launching permission review for package "
-                        + receivingPackageName);
-            }
-
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mService.mContext.startActivityAsUser(intent, new UserHandle(receivingUserId));
-                }
-            });
-        } else {
-            Slog.w(TAG, "u" + receivingUserId + " Receiving a broadcast in package"
-                    + receivingPackageName + " requires a permissions review");
-        }
-
-        return false;
-    }
-
-    void maybeScheduleTempAllowlistLocked(int uid, BroadcastRecord r,
-            @Nullable BroadcastOptions brOptions) {
-        if (brOptions == null || brOptions.getTemporaryAppAllowlistDuration() <= 0) {
-            return;
-        }
-        long duration = brOptions.getTemporaryAppAllowlistDuration();
-        final @TempAllowListType int type = brOptions.getTemporaryAppAllowlistType();
-        final @ReasonCode int reasonCode = brOptions.getTemporaryAppAllowlistReasonCode();
-        final String reason = brOptions.getTemporaryAppAllowlistReason();
-
-        if (duration > Integer.MAX_VALUE) {
-            duration = Integer.MAX_VALUE;
-        }
-        // XXX ideally we should pause the broadcast until everything behind this is done,
-        // or else we will likely start dispatching the broadcast before we have opened
-        // access to the app (there is a lot of asynchronicity behind this).  It is probably
-        // not that big a deal, however, because the main purpose here is to allow apps
-        // to hold wake locks, and they will be able to acquire their wake lock immediately
-        // it just won't be enabled until we get through this work.
-        StringBuilder b = new StringBuilder();
-        b.append("broadcast:");
-        UserHandle.formatUid(b, r.callingUid);
-        b.append(":");
-        if (r.intent.getAction() != null) {
-            b.append(r.intent.getAction());
-        } else if (r.intent.getComponent() != null) {
-            r.intent.getComponent().appendShortString(b);
-        } else if (r.intent.getData() != null) {
-            b.append(r.intent.getData());
-        }
-        b.append(",reason:");
-        b.append(reason);
-        if (DEBUG_BROADCAST) {
-            Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration
-                    + " type=" + type + " : " + b.toString());
-        }
-        mService.tempAllowlistUidLocked(uid, duration, reasonCode, b.toString(), type,
-                r.callingUid);
-    }
-
-    /**
-     * Return true if all given permissions are signature-only perms.
-     */
-    final boolean isSignaturePerm(String[] perms) {
-        if (perms == null) {
-            return false;
-        }
-        IPermissionManager pm = AppGlobals.getPermissionManager();
-        for (int i = perms.length-1; i >= 0; i--) {
-            try {
-                PermissionInfo pi = pm.getPermissionInfo(perms[i], "android", 0);
-                if (pi == null) {
-                    // a required permission that no package has actually
-                    // defined cannot be signature-required.
-                    return false;
-                }
-                if ((pi.protectionLevel & (PermissionInfo.PROTECTION_MASK_BASE
-                        | PermissionInfo.PROTECTION_FLAG_PRIVILEGED))
-                        != PermissionInfo.PROTECTION_SIGNATURE) {
-                    // If this a signature permission and NOT allowed for privileged apps, it
-                    // is okay...  otherwise, nope!
-                    return false;
-                }
-            } catch (RemoteException e) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private void processNextBroadcast(boolean fromMsg) {
-        synchronized (mService) {
-            processNextBroadcastLocked(fromMsg, false);
-        }
-    }
-
-    static String broadcastDescription(BroadcastRecord r, ComponentName component) {
-        return r.intent.toString()
-                + " from " + r.callerPackage + " (pid=" + r.callingPid
-                + ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
-    }
-
-    private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
-            @Nullable Bundle filteredExtras) {
-        final Intent intent = new Intent(originalIntent);
-        if (filteredExtras != null) {
-            intent.replaceExtras(filteredExtras);
-        }
-        return intent;
-    }
-
-    final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
-        BroadcastRecord r;
-
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["
-                + mQueueName + "]: "
-                + mParallelBroadcasts.size() + " parallel broadcasts; "
-                + mDispatcher.describeStateLocked());
-
-        mService.updateCpuStats();
-
-        if (fromMsg) {
-            mBroadcastsScheduled = false;
-        }
-
-        // First, deliver any non-serialized broadcasts right away.
-        while (mParallelBroadcasts.size() > 0) {
-            r = mParallelBroadcasts.remove(0);
-            r.dispatchTime = SystemClock.uptimeMillis();
-            r.dispatchRealTime = SystemClock.elapsedRealtime();
-            r.dispatchClockTime = System.currentTimeMillis();
-            r.mIsReceiverAppRunning = true;
-
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                    System.identityHashCode(r));
-                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
-                    System.identityHashCode(r));
-            }
-
-            final int N = r.receivers.size();
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
-                    + mQueueName + "] " + r);
-            for (int i=0; i<N; i++) {
-                Object target = r.receivers.get(i);
-                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                        "Delivering non-ordered on [" + mQueueName + "] to registered "
-                        + target + ": " + r);
-                deliverToRegisteredReceiverLocked(r,
-                        (BroadcastFilter) target, false, i);
-            }
-            addBroadcastToHistoryLocked(r);
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
-                    + mQueueName + "] " + r);
-        }
-
-        // Now take care of the next serialized one...
-
-        // If we are waiting for a process to come up to handle the next
-        // broadcast, then do nothing at this point.  Just in case, we
-        // check that the process we're waiting for still exists.
-        if (mPendingBroadcast != null) {
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                    "processNextBroadcast [" + mQueueName + "]: waiting for "
-                    + mPendingBroadcast.curApp);
-
-            boolean isDead;
-            if (mPendingBroadcast.curApp.getPid() > 0) {
-                synchronized (mService.mPidsSelfLocked) {
-                    ProcessRecord proc = mService.mPidsSelfLocked.get(
-                            mPendingBroadcast.curApp.getPid());
-                    isDead = proc == null || proc.mErrorState.isCrashing();
-                }
-            } else {
-                final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
-                        mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
-                isDead = proc == null || !proc.isPendingStart();
-            }
-            if (!isDead) {
-                // It's still alive, so keep waiting
-                return;
-            } else {
-                Slog.w(TAG, "pending app  ["
-                        + mQueueName + "]" + mPendingBroadcast.curApp
-                        + " died before responding to broadcast");
-                mPendingBroadcast.state = BroadcastRecord.IDLE;
-                mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
-                mPendingBroadcast = null;
-            }
-        }
-
-        boolean looped = false;
-
-        do {
-            final long now = SystemClock.uptimeMillis();
-            r = mDispatcher.getNextBroadcastLocked(now);
-
-            if (r == null) {
-                // No more broadcasts are deliverable right now, so all done!
-                mDispatcher.scheduleDeferralCheckLocked(false);
-                synchronized (mService.mAppProfiler.mProfilerLock) {
-                    mService.mAppProfiler.scheduleAppGcsLPf();
-                }
-                if (looped && !skipOomAdj) {
-                    // If we had finished the last ordered broadcast, then
-                    // make sure all processes have correct oom and sched
-                    // adjustments.
-                    mService.updateOomAdjPendingTargetsLocked(
-                            OOM_ADJ_REASON_START_RECEIVER);
-                }
-
-                // when we have no more ordered broadcast on this queue, stop logging
-                if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
-                    mLogLatencyMetrics = false;
-                }
-
-                return;
-            }
-
-            boolean forceReceive = false;
-
-            // Ensure that even if something goes awry with the timeout
-            // detection, we catch "hung" broadcasts here, discard them,
-            // and continue to make progress.
-            //
-            // This is only done if the system is ready so that early-stage receivers
-            // don't get executed with timeouts; and of course other timeout-
-            // exempt broadcasts are ignored.
-            int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
-            if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
-                if ((numReceivers > 0) &&
-                        (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
-                    Slog.w(TAG, "Hung broadcast ["
-                            + mQueueName + "] discarded after timeout failure:"
-                            + " now=" + now
-                            + " dispatchTime=" + r.dispatchTime
-                            + " startTime=" + r.receiverTime
-                            + " intent=" + r.intent
-                            + " numReceivers=" + numReceivers
-                            + " nextReceiver=" + r.nextReceiver
-                            + " state=" + r.state);
-                    broadcastTimeoutLocked(false); // forcibly finish this broadcast
-                    forceReceive = true;
-                    r.state = BroadcastRecord.IDLE;
-                }
-            }
-
-            if (r.state != BroadcastRecord.IDLE) {
-                if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
-                        "processNextBroadcast("
-                        + mQueueName + ") called when not idle (state="
-                        + r.state + ")");
-                return;
-            }
-
-            // Is the current broadcast is done for any reason?
-            if (r.receivers == null || r.nextReceiver >= numReceivers
-                    || r.resultAbort || forceReceive) {
-                // Send the final result if requested
-                if (r.resultTo != null) {
-                    boolean sendResult = true;
-
-                    // if this was part of a split/deferral complex, update the refcount and only
-                    // send the completion when we clear all of them
-                    if (r.splitToken != 0) {
-                        int newCount = mSplitRefcounts.get(r.splitToken) - 1;
-                        if (newCount == 0) {
-                            // done!  clear out this record's bookkeeping and deliver
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG_BROADCAST,
-                                        "Sending broadcast completion for split token "
-                                        + r.splitToken + " : " + r.intent.getAction());
-                            }
-                            mSplitRefcounts.delete(r.splitToken);
-                        } else {
-                            // still have some split broadcast records in flight; update refcount
-                            // and hold off on the callback
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG_BROADCAST,
-                                        "Result refcount now " + newCount + " for split token "
-                                        + r.splitToken + " : " + r.intent.getAction()
-                                        + " - not sending completion yet");
-                            }
-                            sendResult = false;
-                            mSplitRefcounts.put(r.splitToken, newCount);
-                        }
-                    }
-                    if (sendResult) {
-                        if (r.callerApp != null) {
-                            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
-                                    r.callerApp, OOM_ADJ_REASON_FINISH_RECEIVER);
-                        }
-                        try {
-                            if (DEBUG_BROADCAST) {
-                                Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
-                                        + r.intent.getAction() + " app=" + r.callerApp);
-                            }
-                            if (r.dispatchTime == 0) {
-                                // The dispatch time here could be 0, in case it's a parallel
-                                // broadcast but it has a result receiver. Set it to now.
-                                r.dispatchTime = now;
-                            }
-                            r.mIsReceiverAppRunning = true;
-                            performReceiveLocked(r.callerApp, r.resultTo,
-                                    new Intent(r.intent), r.resultCode,
-                                    r.resultData, r.resultExtras, false, false, r.userId,
-                                    r.callingUid, r.callingUid,
-                                    r.dispatchTime - r.enqueueTime,
-                                    now - r.dispatchTime);
-                            logBootCompletedBroadcastCompletionLatencyIfPossible(r);
-                            // Set this to null so that the reference
-                            // (local and remote) isn't kept in the mBroadcastHistory.
-                            r.resultTo = null;
-                        } catch (RemoteException e) {
-                            r.resultTo = null;
-                            Slog.w(TAG, "Failure ["
-                                    + mQueueName + "] sending broadcast result of "
-                                    + r.intent, e);
-                        }
-                    }
-                }
-
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
-                cancelBroadcastTimeoutLocked();
-
-                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                        "Finished with ordered broadcast " + r);
-
-                // ... and on to the next...
-                addBroadcastToHistoryLocked(r);
-                if (r.intent.getComponent() == null && r.intent.getPackage() == null
-                        && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-                    // This was an implicit broadcast... let's record it for posterity.
-                    mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
-                            r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
-                }
-                mDispatcher.retireBroadcastLocked(r);
-                r = null;
-                looped = true;
-                continue;
-            }
-
-            // Check whether the next receiver is under deferral policy, and handle that
-            // accordingly.  If the current broadcast was already part of deferred-delivery
-            // tracking, we know that it must now be deliverable as-is without re-deferral.
-            if (!r.deferred) {
-                final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
-                if (mDispatcher.isDeferringLocked(receiverUid)) {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid
-                                + " at " + r.nextReceiver + " is under deferral");
-                    }
-                    // If this is the only (remaining) receiver in the broadcast, "splitting"
-                    // doesn't make sense -- just defer it as-is and retire it as the
-                    // currently active outgoing broadcast.
-                    BroadcastRecord defer;
-                    if (r.nextReceiver + 1 == numReceivers) {
-                        if (DEBUG_BROADCAST_DEFERRAL) {
-                            Slog.i(TAG_BROADCAST, "Sole receiver of " + r
-                                    + " is under deferral; setting aside and proceeding");
-                        }
-                        defer = r;
-                        mDispatcher.retireBroadcastLocked(r);
-                    } else {
-                        // Nontrivial case; split out 'uid's receivers to a new broadcast record
-                        // and defer that, then loop and pick up continuing delivery of the current
-                        // record (now absent those receivers).
-
-                        // The split operation is guaranteed to match at least at 'nextReceiver'
-                        defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);
-                        if (DEBUG_BROADCAST_DEFERRAL) {
-                            Slog.i(TAG_BROADCAST, "Post split:");
-                            Slog.i(TAG_BROADCAST, "Original broadcast receivers:");
-                            for (int i = 0; i < r.receivers.size(); i++) {
-                                Slog.i(TAG_BROADCAST, "  " + r.receivers.get(i));
-                            }
-                            Slog.i(TAG_BROADCAST, "Split receivers:");
-                            for (int i = 0; i < defer.receivers.size(); i++) {
-                                Slog.i(TAG_BROADCAST, "  " + defer.receivers.get(i));
-                            }
-                        }
-                        // Track completion refcount as well if relevant
-                        if (r.resultTo != null) {
-                            int token = r.splitToken;
-                            if (token == 0) {
-                                // first split of this record; refcount for 'r' and 'deferred'
-                                r.splitToken = defer.splitToken = nextSplitTokenLocked();
-                                mSplitRefcounts.put(r.splitToken, 2);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    Slog.i(TAG_BROADCAST,
-                                            "Broadcast needs split refcount; using new token "
-                                            + r.splitToken);
-                                }
-                            } else {
-                                // new split from an already-refcounted situation; increment count
-                                final int curCount = mSplitRefcounts.get(token);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    if (curCount == 0) {
-                                        Slog.wtf(TAG_BROADCAST,
-                                                "Split refcount is zero with token for " + r);
-                                    }
-                                }
-                                mSplitRefcounts.put(token, curCount + 1);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    Slog.i(TAG_BROADCAST, "New split count for token " + token
-                                            + " is " + (curCount + 1));
-                                }
-                            }
-                        }
-                    }
-                    mDispatcher.addDeferredBroadcast(receiverUid, defer);
-                    r = null;
-                    looped = true;
-                    continue;
-                }
-            }
-        } while (r == null);
-
-        // Get the next receiver...
-        int recIdx = r.nextReceiver++;
-
-        // Keep track of when this receiver started, and make sure there
-        // is a timeout message pending to kill it if need be.
-        r.receiverTime = SystemClock.uptimeMillis();
-        if (recIdx == 0) {
-            r.dispatchTime = r.receiverTime;
-            r.dispatchRealTime = SystemClock.elapsedRealtime();
-            r.dispatchClockTime = System.currentTimeMillis();
-
-            if (mLogLatencyMetrics) {
-                FrameworkStatsLog.write(
-                        FrameworkStatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
-                        r.dispatchClockTime - r.enqueueClockTime);
-            }
-
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                    System.identityHashCode(r));
-                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
-                    System.identityHashCode(r));
-            }
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
-                    + mQueueName + "] " + r);
-        }
-        if (! mPendingBroadcastTimeoutMessage) {
-            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
-                    "Submitting BROADCAST_TIMEOUT_MSG ["
-                    + mQueueName + "] for " + r + " at " + timeoutTime);
-            setBroadcastTimeoutLocked(timeoutTime);
-        }
-
-        final BroadcastOptions brOptions = r.options;
-        final Object nextReceiver = r.receivers.get(recIdx);
-
-        if (nextReceiver instanceof BroadcastFilter) {
-            // Simple case: this is a registered receiver who gets
-            // a direct call.
-            BroadcastFilter filter = (BroadcastFilter)nextReceiver;
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Delivering ordered ["
-                    + mQueueName + "] to registered "
-                    + filter + ": " + r);
-            r.mIsReceiverAppRunning = true;
-            deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
-            if (r.receiver == null || !r.ordered) {
-                // The receiver has already finished, so schedule to
-                // process the next one.
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
-                        + mQueueName + "]: ordered="
-                        + r.ordered + " receiver=" + r.receiver);
-                r.state = BroadcastRecord.IDLE;
-                scheduleBroadcastsLocked();
-            } else {
-                if (filter.receiverList != null) {
-                    maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
-                    // r is guaranteed ordered at this point, so we know finishReceiverLocked()
-                    // will get a callback and handle the activity start token lifecycle.
-                }
-            }
-            return;
-        }
-
-        // Hard case: need to instantiate the receiver, possibly
-        // starting its application process to host it.
-
-        ResolveInfo info =
-            (ResolveInfo)nextReceiver;
-        ComponentName component = new ComponentName(
-                info.activityInfo.applicationInfo.packageName,
-                info.activityInfo.name);
-
-        boolean skip = false;
-        if (brOptions != null &&
-                (info.activityInfo.applicationInfo.targetSdkVersion
-                        < brOptions.getMinManifestReceiverApiLevel() ||
-                info.activityInfo.applicationInfo.targetSdkVersion
-                        > brOptions.getMaxManifestReceiverApiLevel())) {
-            Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
-                    + " targets " + info.activityInfo.applicationInfo.targetSdkVersion
-                    + " but delivery restricted to ["
-                    + brOptions.getMinManifestReceiverApiLevel() + ", "
-                    + brOptions.getMaxManifestReceiverApiLevel()
-                    + "] broadcasting " + broadcastDescription(r, component));
-            skip = true;
-        }
-        if (brOptions != null &&
-                !brOptions.testRequireCompatChange(info.activityInfo.applicationInfo.uid)) {
-            Slog.w(TAG, "Compat change filtered: broadcasting " + broadcastDescription(r, component)
-                    + " to uid " + info.activityInfo.applicationInfo.uid + " due to compat change "
-                    + r.options.getRequireCompatChangeId());
-            skip = true;
-        }
-        if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
-                component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
-            Slog.w(TAG, "Association not allowed: broadcasting "
-                    + broadcastDescription(r, component));
-            skip = true;
-        }
-        if (!skip) {
-            skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
-                    r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
-            if (skip) {
-                Slog.w(TAG, "Firewall blocked: broadcasting "
-                        + broadcastDescription(r, component));
-            }
-        }
-        int perm = mService.checkComponentPermission(info.activityInfo.permission,
-                r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
-                info.activityInfo.exported);
-        if (!skip && perm != PackageManager.PERMISSION_GRANTED) {
-            if (!info.activityInfo.exported) {
-                Slog.w(TAG, "Permission Denial: broadcasting "
-                        + broadcastDescription(r, component)
-                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
-            } else {
-                Slog.w(TAG, "Permission Denial: broadcasting "
-                        + broadcastDescription(r, component)
-                        + " requires " + info.activityInfo.permission);
-            }
-            skip = true;
-        } else if (!skip && info.activityInfo.permission != null) {
-            final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);
-            if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode,
-                    r.callingUid, r.callerPackage, r.callerFeatureId,
-                    "Broadcast delivered to " + info.activityInfo.name)
-                    != AppOpsManager.MODE_ALLOWED) {
-                Slog.w(TAG, "Appop Denial: broadcasting "
-                        + broadcastDescription(r, component)
-                        + " requires appop " + AppOpsManager.permissionToOp(
-                                info.activityInfo.permission));
-                skip = true;
-            }
-        }
-
-        boolean isSingleton = false;
-        try {
-            isSingleton = mService.isSingleton(info.activityInfo.processName,
-                    info.activityInfo.applicationInfo,
-                    info.activityInfo.name, info.activityInfo.flags);
-        } catch (SecurityException e) {
-            Slog.w(TAG, e.getMessage());
-            skip = true;
-        }
-        if ((info.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
-            if (ActivityManager.checkUidPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS,
-                    info.activityInfo.applicationInfo.uid)
-                            != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString()
-                        + " requests FLAG_SINGLE_USER, but app does not hold "
-                        + android.Manifest.permission.INTERACT_ACROSS_USERS);
-                skip = true;
-            }
-        }
-        if (!skip && info.activityInfo.applicationInfo.isInstantApp()
-                && r.callingUid != info.activityInfo.applicationInfo.uid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
-                    + r.intent
-                    + " to " + component.flattenToShortString()
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")"
-                    + " Instant Apps do not support manifest receivers");
-            skip = true;
-        }
-        if (!skip && r.callerInstantApp
-                && (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0
-                && r.callingUid != info.activityInfo.applicationInfo.uid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
-                    + r.intent
-                    + " to " + component.flattenToShortString()
-                    + " requires receiver have visibleToInstantApps set"
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            skip = true;
-        }
-        if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
-            // If the target process is crashing, just skip it.
-            Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r
-                    + " to " + r.curApp + ": process crashing");
-            skip = true;
-        }
-        if (!skip) {
-            boolean isAvailable = false;
-            try {
-                isAvailable = AppGlobals.getPackageManager().isPackageAvailable(
-                        info.activityInfo.packageName,
-                        UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
-            } catch (Exception e) {
-                // all such failures mean we skip this receiver
-                Slog.w(TAG, "Exception getting recipient info for "
-                        + info.activityInfo.packageName, e);
-            }
-            if (!isAvailable) {
-                Slog.w(TAG_BROADCAST,
-                        "Skipping delivery to " + info.activityInfo.packageName + " / "
-                        + info.activityInfo.applicationInfo.uid
-                        + " : package no longer available");
-                skip = true;
-            }
-        }
-
-        // If permissions need a review before any of the app components can run, we drop
-        // the broadcast and if the calling app is in the foreground and the broadcast is
-        // explicit we launch the review UI passing it a pending intent to send the skipped
-        // broadcast.
-        if (!skip) {
-            if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
-                    info.activityInfo.packageName, UserHandle.getUserId(
-                            info.activityInfo.applicationInfo.uid))) {
-                Slog.w(TAG_BROADCAST,
-                        "Skipping delivery: permission review required for "
-                                + broadcastDescription(r, component));
-                skip = true;
-            }
-        }
-
-        // This is safe to do even if we are skipping the broadcast, and we need
-        // this information now to evaluate whether it is going to be allowed to run.
-        final int receiverUid = info.activityInfo.applicationInfo.uid;
-        // If it's a singleton, it needs to be the same app or a special app
-        if (r.callingUid != Process.SYSTEM_UID && isSingleton
-                && mService.isValidSingletonCall(r.callingUid, receiverUid)) {
-            info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);
-        }
-        String targetProcess = info.activityInfo.processName;
-        ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
-                info.activityInfo.applicationInfo.uid);
-
-        if (!skip) {
-            final int allowed = mService.getAppStartModeLOSP(
-                    info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
-                    info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
-            if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
-                // We won't allow this receiver to be launched if the app has been
-                // completely disabled from launches, or it was not explicitly sent
-                // to it and the app is in a state that should not receive it
-                // (depending on how getAppStartModeLOSP has determined that).
-                if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
-                    Slog.w(TAG, "Background execution disabled: receiving "
-                            + r.intent + " to "
-                            + component.flattenToShortString());
-                    skip = true;
-                } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
-                        || (r.intent.getComponent() == null
-                            && r.intent.getPackage() == null
-                            && ((r.intent.getFlags()
-                                    & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
-                            && !isSignaturePerm(r.requiredPermissions))) {
-                    mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
-                            component.getPackageName());
-                    Slog.w(TAG, "Background execution not allowed: receiving "
-                            + r.intent + " to "
-                            + component.flattenToShortString());
-                    skip = true;
-                }
-            }
-        }
-
-        if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction())
-                && !mService.mUserController
-                .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
-                        0 /* flags */)) {
-            skip = true;
-            Slog.w(TAG,
-                    "Skipping delivery to " + info.activityInfo.packageName + " / "
-                            + info.activityInfo.applicationInfo.uid + " : user is not running");
-        }
-
-        if (!skip && r.excludedPermissions != null && r.excludedPermissions.length > 0) {
-            for (int i = 0; i < r.excludedPermissions.length; i++) {
-                String excludedPermission = r.excludedPermissions[i];
-                try {
-                    perm = AppGlobals.getPackageManager()
-                        .checkPermission(excludedPermission,
-                                info.activityInfo.applicationInfo.packageName,
-                                UserHandle
-                                .getUserId(info.activityInfo.applicationInfo.uid));
-                } catch (RemoteException e) {
-                    perm = PackageManager.PERMISSION_DENIED;
-                }
-
-                int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
-                if (appOp != AppOpsManager.OP_NONE) {
-                    // When there is an app op associated with the permission,
-                    // skip when both the permission and the app op are
-                    // granted.
-                    if ((perm == PackageManager.PERMISSION_GRANTED) && (
-                                mService.getAppOpsManager().checkOpNoThrow(appOp,
-                                info.activityInfo.applicationInfo.uid,
-                                info.activityInfo.packageName)
-                            == AppOpsManager.MODE_ALLOWED)) {
-                        skip = true;
-                        break;
-                    }
-                } else {
-                    // When there is no app op associated with the permission,
-                    // skip when permission is granted.
-                    if (perm == PackageManager.PERMISSION_GRANTED) {
-                        skip = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        // Check that the receiver does *not* belong to any of the excluded packages
-        if (!skip && r.excludedPackages != null && r.excludedPackages.length > 0) {
-            if (ArrayUtils.contains(r.excludedPackages, component.getPackageName())) {
-                Slog.w(TAG, "Skipping delivery of excluded package "
-                        + r.intent + " to "
-                        + component.flattenToShortString()
-                        + " excludes package " + component.getPackageName()
-                        + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                skip = true;
-            }
-        }
-
-        if (!skip && info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
-                r.requiredPermissions != null && r.requiredPermissions.length > 0) {
-            for (int i = 0; i < r.requiredPermissions.length; i++) {
-                String requiredPermission = r.requiredPermissions[i];
-                try {
-                    perm = AppGlobals.getPackageManager().
-                            checkPermission(requiredPermission,
-                                    info.activityInfo.applicationInfo.packageName,
-                                    UserHandle
-                                    .getUserId(info.activityInfo.applicationInfo.uid));
-                } catch (RemoteException e) {
-                    perm = PackageManager.PERMISSION_DENIED;
-                }
-                if (perm != PackageManager.PERMISSION_GRANTED) {
-                    Slog.w(TAG, "Permission Denial: receiving "
-                            + r.intent + " to "
-                            + component.flattenToShortString()
-                            + " requires " + requiredPermission
-                            + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    skip = true;
-                    break;
-                }
-                int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
-                if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
-                    if (!noteOpForManifestReceiver(appOp, r, info, component)) {
-                        skip = true;
-                        break;
-                    }
-                }
-            }
-        }
-        if (!skip && r.appOp != AppOpsManager.OP_NONE) {
-            if (!noteOpForManifestReceiver(r.appOp, r, info, component)) {
-                skip = true;
-            }
-        }
-
-        // Filter packages in the intent extras, skipping delivery if none of the packages is
-        // visible to the receiver.
-        Bundle filteredExtras = null;
-        if (!skip && r.filterExtrasForReceiver != null) {
-            final Bundle extras = r.intent.getExtras();
-            if (extras != null) {
-                filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
-                if (filteredExtras == null) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.v(TAG, "Skipping delivery to "
-                                + info.activityInfo.packageName + " / " + receiverUid
-                                + " : receiver is filtered by the package visibility");
-                    }
-                    skip = true;
-                }
-            }
-        }
-
-        if (skip) {
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Skipping delivery of ordered [" + mQueueName + "] "
-                    + r + " for reason described above");
-            r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
-            r.receiver = null;
-            r.curFilter = null;
-            r.state = BroadcastRecord.IDLE;
-            r.manifestSkipCount++;
-            scheduleBroadcastsLocked();
-            return;
-        }
-        r.manifestCount++;
-
-        r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
-        r.state = BroadcastRecord.APP_RECEIVE;
-        r.curComponent = component;
-        r.curReceiver = info.activityInfo;
-        r.curFilteredExtras = filteredExtras;
-        if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
-            Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
-                    + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
-                    + receiverUid);
-        }
-        final boolean isActivityCapable =
-                (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0);
-        maybeScheduleTempAllowlistLocked(receiverUid, r, brOptions);
-
-        // Report that a component is used for explicit broadcasts.
-        if (r.intent.getComponent() != null && r.curComponent != null
-                && !TextUtils.equals(r.curComponent.getPackageName(), r.callerPackage)) {
-            mService.mUsageStatsService.reportEvent(
-                    r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
-        }
-
-        // Broadcast is being executed, its package can't be stopped.
-        try {
-            AppGlobals.getPackageManager().setPackageStoppedState(
-                    r.curComponent.getPackageName(), false, r.userId);
-        } catch (RemoteException e) {
-        } catch (IllegalArgumentException e) {
-            Slog.w(TAG, "Failed trying to unstop package "
-                    + r.curComponent.getPackageName() + ": " + e);
-        }
-
-        // Is this receiver's application already running?
-        if (app != null && app.getThread() != null && !app.isKilled()) {
-            try {
-                app.addPackage(info.activityInfo.packageName,
-                        info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
-                maybeAddAllowBackgroundActivityStartsToken(app, r);
-                r.mIsReceiverAppRunning = true;
-                processCurBroadcastLocked(r, app);
-                return;
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Exception when sending broadcast to "
-                      + r.curComponent, e);
-            } catch (RuntimeException e) {
-                Slog.wtf(TAG, "Failed sending broadcast to "
-                        + r.curComponent + " with " + r.intent, e);
-                // If some unexpected exception happened, just skip
-                // this broadcast.  At this point we are not in the call
-                // from a client, so throwing an exception out from here
-                // will crash the entire system instead of just whoever
-                // sent the broadcast.
-                logBroadcastReceiverDiscardLocked(r);
-                finishReceiverLocked(r, r.resultCode, r.resultData,
-                        r.resultExtras, r.resultAbort, false);
-                scheduleBroadcastsLocked();
-                // We need to reset the state if we failed to start the receiver.
-                r.state = BroadcastRecord.IDLE;
-                return;
-            }
-
-            // If a dead object exception was thrown -- fall through to
-            // restart the application.
-        }
-
-        // Not running -- get it started, to be executed when the app comes up.
-        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                "Need to start app ["
-                + mQueueName + "] " + targetProcess + " for broadcast " + r);
-        r.curApp = mService.startProcessLocked(targetProcess,
-                info.activityInfo.applicationInfo, true,
-                r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
-                new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
-                        r.intent.getAction(), getHostingRecordTriggerType(r)),
-                isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
-                (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
-        if (r.curApp == null) {
-            // Ah, this recipient is unavailable.  Finish it if necessary,
-            // and mark the broadcast record as ready for the next.
-            Slog.w(TAG, "Unable to launch app "
-                    + info.activityInfo.applicationInfo.packageName + "/"
-                    + receiverUid + " for broadcast "
-                    + r.intent + ": process is bad");
-            logBroadcastReceiverDiscardLocked(r);
-            finishReceiverLocked(r, r.resultCode, r.resultData,
-                    r.resultExtras, r.resultAbort, false);
-            scheduleBroadcastsLocked();
-            r.state = BroadcastRecord.IDLE;
-            return;
-        }
-
-        maybeAddAllowBackgroundActivityStartsToken(r.curApp, r);
-        mPendingBroadcast = r;
-        mPendingBroadcastRecvIndex = recIdx;
-    }
-
-    private String getHostingRecordTriggerType(BroadcastRecord r) {
-        if (r.alarm) {
-            return HostingRecord.TRIGGER_TYPE_ALARM;
-        } else if (r.pushMessage) {
-            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
-        } else if (r.pushMessageOverQuota) {
-            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
-        }
-        return HostingRecord.TRIGGER_TYPE_UNKNOWN;
-    }
-
-    @Nullable
-    private String getTargetPackage(BroadcastRecord r) {
-        if (r.intent == null) {
-            return null;
-        }
-        if (r.intent.getPackage() != null) {
-            return r.intent.getPackage();
-        } else if (r.intent.getComponent() != null) {
-            return r.intent.getComponent().getPackageName();
-        }
-        return null;
-    }
-
-    private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
-        // Only log after last receiver.
-        // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
-        // last BroadcastRecord of the split broadcast which has non-null resultTo.
-        final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
-        if (r.nextReceiver < numReceivers) {
-            return;
-        }
-        final String action = r.intent.getAction();
-        int event = 0;
-        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
-            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
-        } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
-            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
-        }
-        if (event != 0) {
-            final int dispatchLatency = (int)(r.dispatchTime - r.enqueueTime);
-            final int completeLatency = (int)
-                    (SystemClock.uptimeMillis() - r.enqueueTime);
-            final int dispatchRealLatency = (int)(r.dispatchRealTime - r.enqueueRealTime);
-            final int completeRealLatency = (int)
-                    (SystemClock.elapsedRealtime() - r.enqueueRealTime);
-            int userType = FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
-            // This method is called very infrequently, no performance issue we call
-            // LocalServices.getService() here.
-            final UserManagerInternal umInternal = LocalServices.getService(
-                    UserManagerInternal.class);
-            final UserInfo userInfo = umInternal.getUserInfo(r.userId);
-            if (userInfo != null) {
-                userType = UserManager.getUserTypeForStatsd(userInfo.userType);
-            }
-            Slog.i(TAG_BROADCAST,
-                    "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
-                            + action
-                            + " dispatchLatency:" + dispatchLatency
-                            + " completeLatency:" + completeLatency
-                            + " dispatchRealLatency:" + dispatchRealLatency
-                            + " completeRealLatency:" + completeRealLatency
-                            + " receiversSize:" + numReceivers
-                            + " userId:" + r.userId
-                            + " userType:" + (userInfo != null? userInfo.userType : null));
-            FrameworkStatsLog.write(
-                    BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
-                    event,
-                    dispatchLatency,
-                    completeLatency,
-                    dispatchRealLatency,
-                    completeRealLatency,
-                    r.userId,
-                    userType);
-        }
-    }
-
-    private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
-        if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
-            return;
-        }
-        final String targetPackage = getTargetPackage(r);
-        // Ignore non-explicit broadcasts
-        if (targetPackage == null) {
-            return;
-        }
-        getUsageStatsManagerInternal().reportBroadcastDispatched(
-                r.callingUid, targetPackage, UserHandle.of(r.userId),
-                r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
-                mService.getUidStateLocked(targetUid));
-    }
-
-    @NonNull
-    private UsageStatsManagerInternal getUsageStatsManagerInternal() {
-        final UsageStatsManagerInternal usageStatsManagerInternal =
-                LocalServices.getService(UsageStatsManagerInternal.class);
-        return usageStatsManagerInternal;
-    }
-
-    private boolean noteOpForManifestReceiver(int appOp, BroadcastRecord r, ResolveInfo info,
-            ComponentName component) {
-        if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) {
-            return noteOpForManifestReceiverInner(appOp, r, info, component, null);
-        } else {
-            // Attribution tags provided, noteOp each tag
-            for (String tag : info.activityInfo.attributionTags) {
-                if (!noteOpForManifestReceiverInner(appOp, r, info, component, tag)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    private boolean noteOpForManifestReceiverInner(int appOp, BroadcastRecord r, ResolveInfo info,
-            ComponentName component, String tag) {
-        if (mService.getAppOpsManager().noteOpNoThrow(appOp,
-                    info.activityInfo.applicationInfo.uid,
-                    info.activityInfo.packageName,
-                    tag,
-                    "Broadcast delivered to " + info.activityInfo.name)
-                != AppOpsManager.MODE_ALLOWED) {
-            Slog.w(TAG, "Appop Denial: receiving "
-                    + r.intent + " to "
-                    + component.flattenToShortString()
-                    + " requires appop " + AppOpsManager.opToName(appOp)
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            return false;
-        }
-        return true;
-    }
-
-    private void maybeAddAllowBackgroundActivityStartsToken(ProcessRecord proc, BroadcastRecord r) {
-        if (r == null || proc == null || !r.allowBackgroundActivityStarts) {
-            return;
-        }
-        String msgToken = (proc.toShortString() + r.toString()).intern();
-        // first, if there exists a past scheduled request to remove this token, drop
-        // that request - we don't want the token to be swept from under our feet...
-        mHandler.removeCallbacksAndMessages(msgToken);
-        // ...then add the token
-        proc.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
-    }
-
-    final void setBroadcastTimeoutLocked(long timeoutTime) {
-        if (! mPendingBroadcastTimeoutMessage) {
-            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
-            mHandler.sendMessageAtTime(msg, timeoutTime);
-            mPendingBroadcastTimeoutMessage = true;
-        }
-    }
-
-    final void cancelBroadcastTimeoutLocked() {
-        if (mPendingBroadcastTimeoutMessage) {
-            mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
-            mPendingBroadcastTimeoutMessage = false;
-        }
-    }
-
-    final void broadcastTimeoutLocked(boolean fromMsg) {
-        if (fromMsg) {
-            mPendingBroadcastTimeoutMessage = false;
-        }
-
-        if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) {
-            return;
-        }
-
-        long now = SystemClock.uptimeMillis();
-        BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
-        if (fromMsg) {
-            if (!mService.mProcessesReady) {
-                // Only process broadcast timeouts if the system is ready; some early
-                // broadcasts do heavy work setting up system facilities
-                return;
-            }
-
-            // If the broadcast is generally exempt from timeout tracking, we're done
-            if (r.timeoutExempt) {
-                if (DEBUG_BROADCAST) {
-                    Slog.i(TAG_BROADCAST, "Broadcast timeout but it's exempt: "
-                            + r.intent.getAction());
-                }
-                return;
-            }
-
-            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
-            if (timeoutTime > now) {
-                // We can observe premature timeouts because we do not cancel and reset the
-                // broadcast timeout message after each receiver finishes.  Instead, we set up
-                // an initial timeout then kick it down the road a little further as needed
-                // when it expires.
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
-                        "Premature timeout ["
-                        + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
-                        + timeoutTime);
-                setBroadcastTimeoutLocked(timeoutTime);
-                return;
-            }
-        }
-
-        if (r.state == BroadcastRecord.WAITING_SERVICES) {
-            // In this case the broadcast had already finished, but we had decided to wait
-            // for started services to finish as well before going on.  So if we have actually
-            // waited long enough time timeout the broadcast, let's give up on the whole thing
-            // and just move on to the next.
-            Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null
-                    ? r.curComponent.flattenToShortString() : "(null)"));
-            r.curComponent = null;
-            r.state = BroadcastRecord.IDLE;
-            processNextBroadcastLocked(false, false);
-            return;
-        }
-
-        // If the receiver app is being debugged we quietly ignore unresponsiveness, just
-        // tidying up and moving on to the next broadcast without crashing or ANRing this
-        // app just because it's stopped at a breakpoint.
-        final boolean debugging = (r.curApp != null && r.curApp.isDebugging());
-
-        long timeoutDurationMs = now - r.receiverTime;
-        Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver
-                + ", started " + timeoutDurationMs + "ms ago");
-        r.receiverTime = now;
-        if (!debugging) {
-            r.anrCount++;
-        }
-
-        ProcessRecord app = null;
-        TimeoutRecord timeoutRecord = null;
-
-        Object curReceiver;
-        if (r.nextReceiver > 0) {
-            curReceiver = r.receivers.get(r.nextReceiver-1);
-            r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT;
-        } else {
-            curReceiver = r.curReceiver;
-        }
-        Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
-        logBroadcastReceiverDiscardLocked(r);
-        if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
-            BroadcastFilter bf = (BroadcastFilter)curReceiver;
-            if (bf.receiverList.pid != 0
-                    && bf.receiverList.pid != ActivityManagerService.MY_PID) {
-                synchronized (mService.mPidsSelfLocked) {
-                    app = mService.mPidsSelfLocked.get(
-                            bf.receiverList.pid);
-                }
-            }
-        } else {
-            app = r.curApp;
-        }
-
-        if (app != null) {
-            String anrMessage =
-                    "Broadcast of " + r.intent.toString() + ", waited " + timeoutDurationMs
-                            + "ms";
-            timeoutRecord = TimeoutRecord.forBroadcastReceiver(anrMessage);
-        }
-
-        if (mPendingBroadcast == r) {
-            mPendingBroadcast = null;
-        }
-
-        // Move on to the next receiver.
-        finishReceiverLocked(r, r.resultCode, r.resultData,
-                r.resultExtras, r.resultAbort, false);
-        scheduleBroadcastsLocked();
-
-        if (!debugging && timeoutRecord != null) {
-            mService.mAnrHelper.appNotResponding(app, timeoutRecord);
-        }
-    }
-
-    private final int ringAdvance(int x, final int increment, final int ringSize) {
-        x += increment;
-        if (x < 0) return (ringSize - 1);
-        else if (x >= ringSize) return 0;
-        else return x;
-    }
-
-    private final void addBroadcastToHistoryLocked(BroadcastRecord original) {
-        if (original.callingUid < 0) {
-            // This was from a registerReceiver() call; ignore it.
-            return;
-        }
-        original.finishTime = SystemClock.uptimeMillis();
-
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                createBroadcastTraceTitle(original, BroadcastRecord.DELIVERY_DELIVERED),
-                System.identityHashCode(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();
-        }
-
-        // Note sometimes (only for sticky broadcasts?) we reuse BroadcastRecords,
-        // So don't change the incoming record directly.
-        final BroadcastRecord historyRecord = original.maybeStripForHistory();
-
-        mBroadcastHistory[mHistoryNext] = historyRecord;
-        mHistoryNext = ringAdvance(mHistoryNext, 1, MAX_BROADCAST_HISTORY);
-
-        mBroadcastSummaryHistory[mSummaryHistoryNext] = historyRecord.intent;
-        mSummaryHistoryEnqueueTime[mSummaryHistoryNext] = historyRecord.enqueueClockTime;
-        mSummaryHistoryDispatchTime[mSummaryHistoryNext] = historyRecord.dispatchClockTime;
-        mSummaryHistoryFinishTime[mSummaryHistoryNext] = System.currentTimeMillis();
-        mSummaryHistoryNext = ringAdvance(mSummaryHistoryNext, 1, MAX_BROADCAST_SUMMARY_HISTORY);
-    }
-
-    boolean cleanupDisabledPackageReceiversLocked(
-            String packageName, Set<String> filterByClasses, int userId, boolean doit) {
-        boolean didSomething = false;
-        for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
-            didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
-                    packageName, filterByClasses, userId, doit);
-            if (!doit && didSomething) {
-                return true;
-            }
-        }
-
-        didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
-                filterByClasses, userId, doit);
-
-        return didSomething;
-    }
-
-    final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
-        final int logIndex = r.nextReceiver - 1;
-        if (logIndex >= 0 && logIndex < r.receivers.size()) {
-            Object curReceiver = r.receivers.get(logIndex);
-            if (curReceiver instanceof BroadcastFilter) {
-                BroadcastFilter bf = (BroadcastFilter) curReceiver;
-                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
-                        bf.owningUserId, System.identityHashCode(r),
-                        r.intent.getAction(), logIndex, System.identityHashCode(bf));
-            } else {
-                ResolveInfo ri = (ResolveInfo) curReceiver;
-                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
-                        UserHandle.getUserId(ri.activityInfo.applicationInfo.uid),
-                        System.identityHashCode(r), r.intent.getAction(), logIndex, ri.toString());
-            }
-        } else {
-            if (logIndex < 0) Slog.w(TAG,
-                    "Discarding broadcast before first receiver is invoked: " + r);
-            EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
-                    -1, System.identityHashCode(r),
-                    r.intent.getAction(),
-                    r.nextReceiver,
-                    "NONE");
-        }
-    }
-
-    private String createBroadcastTraceTitle(BroadcastRecord record, int state) {
-        return formatSimple("Broadcast %s from %s (%s) %s",
-                state == BroadcastRecord.DELIVERY_PENDING ? "in queue" : "dispatched",
-                record.callerPackage == null ? "" : record.callerPackage,
-                record.callerApp == null ? "process unknown" : record.callerApp.toShortString(),
-                record.intent == null ? "" : record.intent.getAction());
-    }
-
-    boolean isIdle() {
-        return mParallelBroadcasts.isEmpty() && mDispatcher.isIdle()
-                && (mPendingBroadcast == null);
-    }
-
-    // Used by wait-for-broadcast-idle : fast-forward all current deferrals to
-    // be immediately deliverable.
-    void cancelDeferrals() {
-        synchronized (mService) {
-            mDispatcher.cancelDeferralsLocked();
-            scheduleBroadcastsLocked();
-        }
-    }
-
-    String describeState() {
-        synchronized (mService) {
-            return mParallelBroadcasts.size() + " parallel; "
-                    + mDispatcher.describeStateLocked();
-        }
-    }
-
-    void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        long token = proto.start(fieldId);
-        proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
-        int N;
-        N = mParallelBroadcasts.size();
-        for (int i = N - 1; i >= 0; i--) {
-            mParallelBroadcasts.get(i).dumpDebug(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
-        }
-        mDispatcher.dumpDebug(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
-        if (mPendingBroadcast != null) {
-            mPendingBroadcast.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCAST);
-        }
-
-        int lastIndex = mHistoryNext;
-        int ringIndex = lastIndex;
-        do {
-            // increasing index = more recent entry, and we want to print the most
-            // recent first and work backwards, so we roll through the ring backwards.
-            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
-            BroadcastRecord r = mBroadcastHistory[ringIndex];
-            if (r != null) {
-                r.dumpDebug(proto, BroadcastQueueProto.HISTORICAL_BROADCASTS);
-            }
-        } while (ringIndex != lastIndex);
-
-        lastIndex = ringIndex = mSummaryHistoryNext;
-        do {
-            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
-            Intent intent = mBroadcastSummaryHistory[ringIndex];
-            if (intent == null) {
-                continue;
-            }
-            long summaryToken = proto.start(BroadcastQueueProto.HISTORICAL_BROADCASTS_SUMMARY);
-            intent.dumpDebug(proto, BroadcastQueueProto.BroadcastSummary.INTENT,
-                    false, true, true, false);
-            proto.write(BroadcastQueueProto.BroadcastSummary.ENQUEUE_CLOCK_TIME_MS,
-                    mSummaryHistoryEnqueueTime[ringIndex]);
-            proto.write(BroadcastQueueProto.BroadcastSummary.DISPATCH_CLOCK_TIME_MS,
-                    mSummaryHistoryDispatchTime[ringIndex]);
-            proto.write(BroadcastQueueProto.BroadcastSummary.FINISH_CLOCK_TIME_MS,
-                    mSummaryHistoryFinishTime[ringIndex]);
-            proto.end(summaryToken);
-        } while (ringIndex != lastIndex);
-        proto.end(token);
-    }
-
-    final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-        if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
-                || mPendingBroadcast != null) {
-            boolean printed = false;
-            for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
-                BroadcastRecord br = mParallelBroadcasts.get(i);
-                if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
-                    continue;
-                }
-                if (!printed) {
-                    if (needSep) {
-                        pw.println();
-                    }
-                    needSep = true;
-                    printed = true;
-                    pw.println("  Active broadcasts [" + mQueueName + "]:");
-                }
-                pw.println("  Active Broadcast " + mQueueName + " #" + i + ":");
-                br.dump(pw, "    ", sdf);
-            }
-
-            mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf);
-
-            if (dumpPackage == null || (mPendingBroadcast != null
-                    && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
-                pw.println();
-                pw.println("  Pending broadcast [" + mQueueName + "]:");
-                if (mPendingBroadcast != null) {
-                    mPendingBroadcast.dump(pw, "    ", sdf);
-                } else {
-                    pw.println("    (null)");
-                }
-                needSep = true;
-            }
-        }
-
-        mConstants.dump(pw);
-
-        int i;
-        boolean printed = false;
-
-        i = -1;
-        int lastIndex = mHistoryNext;
-        int ringIndex = lastIndex;
-        do {
-            // increasing index = more recent entry, and we want to print the most
-            // recent first and work backwards, so we roll through the ring backwards.
-            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
-            BroadcastRecord r = mBroadcastHistory[ringIndex];
-            if (r == null) {
-                continue;
-            }
-
-            i++; // genuine record of some sort even if we're filtering it out
-            if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) {
-                continue;
-            }
-            if (!printed) {
-                if (needSep) {
-                    pw.println();
-                }
-                needSep = true;
-                pw.println("  Historical broadcasts [" + mQueueName + "]:");
-                printed = true;
-            }
-            if (dumpAll) {
-                pw.print("  Historical Broadcast " + mQueueName + " #");
-                        pw.print(i); pw.println(":");
-                r.dump(pw, "    ", sdf);
-            } else {
-                pw.print("  #"); pw.print(i); pw.print(": "); pw.println(r);
-                pw.print("    ");
-                pw.println(r.intent.toShortString(false, true, true, false));
-                if (r.targetComp != null && r.targetComp != r.intent.getComponent()) {
-                    pw.print("    targetComp: "); pw.println(r.targetComp.toShortString());
-                }
-                Bundle bundle = r.intent.getExtras();
-                if (bundle != null) {
-                    pw.print("    extras: "); pw.println(bundle.toString());
-                }
-            }
-        } while (ringIndex != lastIndex);
-
-        if (dumpPackage == null) {
-            lastIndex = ringIndex = mSummaryHistoryNext;
-            if (dumpAll) {
-                printed = false;
-                i = -1;
-            } else {
-                // roll over the 'i' full dumps that have already been issued
-                for (int j = i;
-                        j > 0 && ringIndex != lastIndex;) {
-                    ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
-                    BroadcastRecord r = mBroadcastHistory[ringIndex];
-                    if (r == null) {
-                        continue;
-                    }
-                    j--;
-                }
-            }
-            // done skipping; dump the remainder of the ring. 'i' is still the ordinal within
-            // the overall broadcast history.
-            do {
-                ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
-                Intent intent = mBroadcastSummaryHistory[ringIndex];
-                if (intent == null) {
-                    continue;
-                }
-                if (!printed) {
-                    if (needSep) {
-                        pw.println();
-                    }
-                    needSep = true;
-                    pw.println("  Historical broadcasts summary [" + mQueueName + "]:");
-                    printed = true;
-                }
-                if (!dumpAll && i >= 50) {
-                    pw.println("  ...");
-                    break;
-                }
-                i++;
-                pw.print("  #"); pw.print(i); pw.print(": ");
-                pw.println(intent.toShortString(false, true, true, false));
-                pw.print("    ");
-                TimeUtils.formatDuration(mSummaryHistoryDispatchTime[ringIndex]
-                        - mSummaryHistoryEnqueueTime[ringIndex], pw);
-                pw.print(" dispatch ");
-                TimeUtils.formatDuration(mSummaryHistoryFinishTime[ringIndex]
-                        - mSummaryHistoryDispatchTime[ringIndex], pw);
-                pw.println(" finish");
-                pw.print("    enq=");
-                pw.print(sdf.format(new Date(mSummaryHistoryEnqueueTime[ringIndex])));
-                pw.print(" disp=");
-                pw.print(sdf.format(new Date(mSummaryHistoryDispatchTime[ringIndex])));
-                pw.print(" fin=");
-                pw.println(sdf.format(new Date(mSummaryHistoryFinishTime[ringIndex])));
-                Bundle bundle = intent.getExtras();
-                if (bundle != null) {
-                    pw.print("    extras: "); pw.println(bundle.toString());
-                }
-            } while (ringIndex != lastIndex);
-        }
-
-        return needSep;
-    }
+    public abstract boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage, boolean needSep);
 }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
new file mode 100644
index 0000000..73a413a
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -0,0 +1,2575 @@
+/*
+ * Copyright (C) 2012 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.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+import static android.text.TextUtils.formatSimple;
+
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
+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_PERMISSIONS_REVIEW;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
+import android.app.RemoteServiceException.CannotDeliverBroadcastException;
+import android.app.usage.UsageEvents.Event;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.PowerExemptionManager.TempAllowListType;
+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.permission.IPermissionManager;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.os.TimeoutRecord;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Set;
+
+/**
+ * BROADCASTS
+ *
+ * We keep three broadcast queues and associated bookkeeping, one for those at
+ * foreground priority, and one for normal (background-priority) broadcasts, and one to
+ * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED.
+ */
+public class BroadcastQueueImpl extends BroadcastQueue {
+    private static final String TAG = "BroadcastQueue";
+    private static final String TAG_MU = TAG + POSTFIX_MU;
+    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
+
+    static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 50;
+    static final int MAX_BROADCAST_SUMMARY_HISTORY
+            = ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
+
+    /**
+     * If true, we can delay broadcasts while waiting services to finish in the previous
+     * receiver's process.
+     */
+    final boolean mDelayBehindServices;
+
+    /**
+     * Lists of all active broadcasts that are to be executed immediately
+     * (without waiting for another broadcast to finish).  Currently this only
+     * contains broadcasts to registered receivers, to avoid spinning up
+     * a bunch of processes to execute IntentReceiver components.  Background-
+     * and foreground-priority broadcasts are queued separately.
+     */
+    final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
+
+    /**
+     * Tracking of the ordered broadcast queue, including deferral policy and alarm
+     * prioritization.
+     */
+    final BroadcastDispatcher mDispatcher;
+
+    /**
+     * Refcounting for completion callbacks of split/deferred broadcasts.  The key
+     * is an opaque integer token assigned lazily when a broadcast is first split
+     * into multiple BroadcastRecord objects.
+     */
+    final SparseIntArray mSplitRefcounts = new SparseIntArray();
+    private int mNextToken = 0;
+
+    /**
+     * Historical data of past broadcasts, for debugging.  This is a ring buffer
+     * whose last element is at mHistoryNext.
+     */
+    final BroadcastRecord[] mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY];
+    int mHistoryNext = 0;
+
+    /**
+     * Summary of historical data of past broadcasts, for debugging.  This is a
+     * ring buffer whose last element is at mSummaryHistoryNext.
+     */
+    final Intent[] mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY];
+    int mSummaryHistoryNext = 0;
+
+    /**
+     * Various milestone timestamps of entries in the mBroadcastSummaryHistory ring
+     * buffer, also tracked via the mSummaryHistoryNext index.  These are all in wall
+     * clock time, not elapsed.
+     */
+    final long[] mSummaryHistoryEnqueueTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
+    final long[] mSummaryHistoryDispatchTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
+    final long[] mSummaryHistoryFinishTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
+
+    /**
+     * Set when we current have a BROADCAST_INTENT_MSG in flight.
+     */
+    boolean mBroadcastsScheduled = false;
+
+    /**
+     * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
+     */
+    boolean mPendingBroadcastTimeoutMessage;
+
+    /**
+     * Intent broadcasts that we have tried to start, but are
+     * waiting for the application's process to be created.  We only
+     * need one per scheduling class (instead of a list) because we always
+     * process broadcasts one at a time, so no others can be started while
+     * waiting for this one.
+     */
+    BroadcastRecord mPendingBroadcast = null;
+
+    /**
+     * The receiver index that is pending, to restart the broadcast if needed.
+     */
+    int mPendingBroadcastRecvIndex;
+
+    static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
+    static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
+
+    // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
+    boolean mLogLatencyMetrics = true;
+
+    final BroadcastHandler mHandler;
+
+    private final class BroadcastHandler extends Handler {
+        public BroadcastHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case BROADCAST_INTENT_MSG: {
+                    if (DEBUG_BROADCAST) Slog.v(
+                            TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
+                            + mQueueName + "]");
+                    processNextBroadcast(true);
+                } break;
+                case BROADCAST_TIMEOUT_MSG: {
+                    synchronized (mService) {
+                        broadcastTimeoutLocked(true);
+                    }
+                } break;
+            }
+        }
+    }
+
+    BroadcastQueueImpl(ActivityManagerService service, Handler handler,
+            String name, BroadcastConstants constants, boolean allowDelayBehindServices) {
+        super(service, handler, name, constants);
+        mHandler = new BroadcastHandler(handler.getLooper());
+        mDelayBehindServices = allowDelayBehindServices;
+        mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
+    }
+
+    void start(ContentResolver resolver) {
+        mDispatcher.start();
+        mConstants.startObserving(mHandler, resolver);
+    }
+
+    @Override
+    public String toString() {
+        return mQueueName;
+    }
+
+    public boolean isDelayBehindServices() {
+        return mDelayBehindServices;
+    }
+
+    public boolean hasBroadcastsScheduled() {
+        return mBroadcastsScheduled;
+    }
+
+    public BroadcastRecord getPendingBroadcastLocked() {
+        return mPendingBroadcast;
+    }
+
+    public BroadcastRecord getActiveBroadcastLocked() {
+        return mDispatcher.getActiveBroadcastLocked();
+    }
+
+    public boolean isPendingBroadcastProcessLocked(int pid) {
+        return mPendingBroadcast != null && mPendingBroadcast.curApp.getPid() == pid;
+    }
+
+    public boolean isPendingBroadcastProcessLocked(ProcessRecord app) {
+        return mPendingBroadcast != null && mPendingBroadcast.curApp == app;
+    }
+
+    public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
+        r.enqueueClockTime = System.currentTimeMillis();
+        r.enqueueTime = SystemClock.uptimeMillis();
+        r.enqueueRealTime = SystemClock.elapsedRealtime();
+        mParallelBroadcasts.add(r);
+        enqueueBroadcastHelper(r);
+    }
+
+    public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
+        r.enqueueClockTime = System.currentTimeMillis();
+        r.enqueueTime = SystemClock.uptimeMillis();
+        r.enqueueRealTime = SystemClock.elapsedRealtime();
+        mDispatcher.enqueueOrderedBroadcastLocked(r);
+        enqueueBroadcastHelper(r);
+    }
+
+    /**
+     * Don't call this method directly; call enqueueParallelBroadcastLocked or
+     * enqueueOrderedBroadcastLocked.
+     */
+    private void enqueueBroadcastHelper(BroadcastRecord r) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
+                System.identityHashCode(r));
+        }
+    }
+
+    /**
+     * Find the same intent from queued parallel broadcast, replace with a new one and return
+     * the old one.
+     */
+    public final BroadcastRecord replaceParallelBroadcastLocked(BroadcastRecord r) {
+        return replaceBroadcastLocked(mParallelBroadcasts, r, "PARALLEL");
+    }
+
+    /**
+     * Find the same intent from queued ordered broadcast, replace with a new one and return
+     * the old one.
+     */
+    public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) {
+        return mDispatcher.replaceBroadcastLocked(r, "ORDERED");
+    }
+
+    private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
+            BroadcastRecord r, String typeForLogging) {
+        final Intent intent = r.intent;
+        for (int i = queue.size() - 1; i > 0; i--) {
+            final BroadcastRecord old = queue.get(i);
+            if (old.userId == r.userId && intent.filterEquals(old.intent)) {
+                if (DEBUG_BROADCAST) {
+                    Slog.v(TAG_BROADCAST, "***** DROPPING "
+                            + typeForLogging + " [" + mQueueName + "]: " + intent);
+                }
+                queue.set(i, r);
+                return old;
+            }
+        }
+        return null;
+    }
+
+    private final void processCurBroadcastLocked(BroadcastRecord r,
+            ProcessRecord app) throws RemoteException {
+        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                "Process cur broadcast " + r + " for app " + app);
+        final IApplicationThread thread = app.getThread();
+        if (thread == null) {
+            throw new RemoteException();
+        }
+        if (app.isInFullBackup()) {
+            skipReceiverLocked(r);
+            return;
+        }
+
+        r.receiver = thread.asBinder();
+        r.curApp = app;
+        final ProcessReceiverRecord prr = app.mReceivers;
+        prr.addCurReceiver(r);
+        app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
+        // Don't bump its LRU position if it's in the background restricted.
+        if (mService.mInternal.getRestrictionLevel(app.info.packageName, app.userId)
+                < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+            mService.updateLruProcessLocked(app, false, null);
+        }
+        // Make sure the oom adj score is updated before delivering the broadcast.
+        // Force an update, even if there are other pending requests, overall it still saves time,
+        // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
+        mService.enqueueOomAdjTargetLocked(app);
+        mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
+
+        // Tell the application to launch this receiver.
+        maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
+        r.intent.setComponent(r.curComponent);
+
+        boolean started = false;
+        try {
+            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
+                    "Delivering to component " + r.curComponent
+                    + ": " + r);
+            mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
+                                      PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
+            thread.scheduleReceiver(prepareReceiverIntent(r.intent, r.curFilteredExtras),
+                    r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
+                    r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
+                    app.mState.getReportedProcState());
+            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                    "Process cur broadcast " + r + " DELIVERED for app " + app);
+            started = true;
+        } finally {
+            if (!started) {
+                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                        "Process cur broadcast " + r + ": NOT STARTED!");
+                r.receiver = null;
+                r.curApp = null;
+                prr.removeCurReceiver(r);
+            }
+        }
+
+        // if something bad happens here, launch the app and try again
+        if (app.isKilled()) {
+            throw new RemoteException("app gets killed during broadcasting");
+        }
+    }
+
+    /**
+     * Called by ActivityManagerService to notify that the uid has process started, if there is any
+     * deferred BOOT_COMPLETED broadcast, the BroadcastDispatcher can dispatch the broadcast now.
+     * @param uid
+     */
+    public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
+        mDispatcher.updateUidReadyForBootCompletedBroadcastLocked(uid);
+    }
+
+    public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
+        boolean didSomething = false;
+        final BroadcastRecord br = mPendingBroadcast;
+        if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
+            if (br.curApp != app) {
+                Slog.e(TAG, "App mismatch when sending pending broadcast to "
+                        + app.processName + ", intended target is " + br.curApp.processName);
+                return false;
+            }
+            try {
+                mPendingBroadcast = null;
+                br.mIsReceiverAppRunning = false;
+                processCurBroadcastLocked(br, app);
+                didSomething = true;
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception in new application when starting receiver "
+                        + br.curComponent.flattenToShortString(), e);
+                logBroadcastReceiverDiscardLocked(br);
+                finishReceiverLocked(br, br.resultCode, br.resultData,
+                        br.resultExtras, br.resultAbort, false);
+                scheduleBroadcastsLocked();
+                // We need to reset the state if we failed to start the receiver.
+                br.state = BroadcastRecord.IDLE;
+                throw new RuntimeException(e.getMessage());
+            }
+        }
+        return didSomething;
+    }
+
+    public void skipPendingBroadcastLocked(int pid) {
+        final BroadcastRecord br = mPendingBroadcast;
+        if (br != null && br.curApp.getPid() == pid) {
+            br.state = BroadcastRecord.IDLE;
+            br.nextReceiver = mPendingBroadcastRecvIndex;
+            mPendingBroadcast = null;
+            scheduleBroadcastsLocked();
+        }
+    }
+
+    // Skip the current receiver, if any, that is in flight to the given process
+    public void skipCurrentReceiverLocked(ProcessRecord app) {
+        BroadcastRecord r = null;
+        final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked();
+        if (curActive != null && curActive.curApp == app) {
+            // confirmed: the current active broadcast is to the given app
+            r = curActive;
+        }
+
+        // If the current active broadcast isn't this BUT we're waiting for
+        // mPendingBroadcast to spin up the target app, that's what we use.
+        if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
+            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+                    "[" + mQueueName + "] skip & discard pending app " + r);
+            r = mPendingBroadcast;
+        }
+
+        if (r != null) {
+            skipReceiverLocked(r);
+        }
+    }
+
+    private void skipReceiverLocked(BroadcastRecord r) {
+        logBroadcastReceiverDiscardLocked(r);
+        finishReceiverLocked(r, r.resultCode, r.resultData,
+                r.resultExtras, r.resultAbort, false);
+        scheduleBroadcastsLocked();
+    }
+
+    public void scheduleBroadcastsLocked() {
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
+                + mQueueName + "]: current="
+                + mBroadcastsScheduled);
+
+        if (mBroadcastsScheduled) {
+            return;
+        }
+        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
+        mBroadcastsScheduled = true;
+    }
+
+    public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) {
+        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
+        if (br != null && br.receiver == receiver) {
+            return br;
+        }
+        return null;
+    }
+
+    // > 0 only, no worry about "eventual" recycling
+    private int nextSplitTokenLocked() {
+        int next = mNextToken + 1;
+        if (next <= 0) {
+            next = 1;
+        }
+        mNextToken = next;
+        return next;
+    }
+
+    private void postActivityStartTokenRemoval(ProcessRecord app, BroadcastRecord r) {
+        // the receiver had run for less than allowed bg activity start timeout,
+        // so allow the process to still start activities from bg for some more time
+        String msgToken = (app.toShortString() + r.toString()).intern();
+        // first, if there exists a past scheduled request to remove this token, drop
+        // that request - we don't want the token to be swept from under our feet...
+        mHandler.removeCallbacksAndMessages(msgToken);
+        // ...then schedule the removal of the token after the extended timeout
+        mHandler.postAtTime(() -> {
+            synchronized (mService) {
+                app.removeAllowBackgroundActivityStartsToken(r);
+            }
+        }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
+    }
+
+    public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
+            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
+        final int state = r.state;
+        final ActivityInfo receiver = r.curReceiver;
+        final long finishTime = SystemClock.uptimeMillis();
+        final long elapsed = finishTime - r.receiverTime;
+        r.state = BroadcastRecord.IDLE;
+        final int curIndex = r.nextReceiver - 1;
+        if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
+            final Object curReceiver = r.receivers.get(curIndex);
+            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
+                    r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
+                    ActivityManagerService.getShortAction(r.intent.getAction()),
+                    curReceiver instanceof BroadcastFilter
+                    ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
+                    : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
+                    r.mIsReceiverAppRunning
+                    ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM
+                    : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
+                    r.dispatchTime - r.enqueueTime,
+                    r.receiverTime - r.dispatchTime,
+                    finishTime - r.receiverTime);
+        }
+        if (state == BroadcastRecord.IDLE) {
+            Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
+        }
+        if (r.allowBackgroundActivityStarts && r.curApp != null) {
+            if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
+                // if the receiver has run for more than allowed bg activity start timeout,
+                // just remove the token for this process now and we're done
+                r.curApp.removeAllowBackgroundActivityStartsToken(r);
+            } else {
+                // It gets more time; post the removal to happen at the appropriate moment
+                postActivityStartTokenRemoval(r.curApp, r);
+            }
+        }
+        // If we're abandoning this broadcast before any receivers were actually spun up,
+        // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply.
+        if (r.nextReceiver > 0) {
+            r.duration[r.nextReceiver - 1] = elapsed;
+        }
+
+        // if this receiver was slow, impose deferral policy on the app.  This will kick in
+        // when processNextBroadcastLocked() next finds this uid as a receiver identity.
+        if (!r.timeoutExempt) {
+            // r.curApp can be null if finish has raced with process death - benign
+            // edge case, and we just ignore it because we're already cleaning up
+            // as expected.
+            if (r.curApp != null
+                    && mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
+                // Core system packages are exempt from deferral policy
+                if (!UserHandle.isCore(r.curApp.uid)) {
+                    if (DEBUG_BROADCAST_DEFERRAL) {
+                        Slog.i(TAG_BROADCAST, "Broadcast receiver " + (r.nextReceiver - 1)
+                                + " was slow: " + receiver + " br=" + r);
+                    }
+                    mDispatcher.startDeferring(r.curApp.uid);
+                } else {
+                    if (DEBUG_BROADCAST_DEFERRAL) {
+                        Slog.i(TAG_BROADCAST, "Core uid " + r.curApp.uid
+                                + " receiver was slow but not deferring: "
+                                + receiver + " br=" + r);
+                    }
+                }
+            }
+        } else {
+            if (DEBUG_BROADCAST_DEFERRAL) {
+                Slog.i(TAG_BROADCAST, "Finished broadcast " + r.intent.getAction()
+                        + " is exempt from deferral policy");
+            }
+        }
+
+        r.receiver = null;
+        r.intent.setComponent(null);
+        if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
+            r.curApp.mReceivers.removeCurReceiver(r);
+            mService.enqueueOomAdjTargetLocked(r.curApp);
+        }
+        if (r.curFilter != null) {
+            r.curFilter.receiverList.curBroadcast = null;
+        }
+        r.curFilter = null;
+        r.curReceiver = null;
+        r.curApp = null;
+        r.curFilteredExtras = null;
+        mPendingBroadcast = null;
+
+        r.resultCode = resultCode;
+        r.resultData = resultData;
+        r.resultExtras = resultExtras;
+        if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
+            r.resultAbort = resultAbort;
+        } else {
+            r.resultAbort = false;
+        }
+
+        // If we want to wait behind services *AND* we're finishing the head/
+        // active broadcast on its queue
+        if (waitForServices && r.curComponent != null && r.queue.isDelayBehindServices()
+                && r.queue.getActiveBroadcastLocked() == r) {
+            ActivityInfo nextReceiver;
+            if (r.nextReceiver < r.receivers.size()) {
+                Object obj = r.receivers.get(r.nextReceiver);
+                nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
+            } else {
+                nextReceiver = null;
+            }
+            // Don't do this if the next receive is in the same process as the current one.
+            if (receiver == null || nextReceiver == null
+                    || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
+                    || !receiver.processName.equals(nextReceiver.processName)) {
+                // In this case, we are ready to process the next receiver for the current broadcast,
+                // but are on a queue that would like to wait for services to finish before moving
+                // on.  If there are background services currently starting, then we will go into a
+                // special state where we hold off on continuing this broadcast until they are done.
+                if (mService.mServices.hasBackgroundServicesLocked(r.userId)) {
+                    Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
+                    r.state = BroadcastRecord.WAITING_SERVICES;
+                    return false;
+                }
+            }
+        }
+
+        r.curComponent = null;
+
+        // We will process the next receiver right now if this is finishing
+        // an app receiver (which is always asynchronous) or after we have
+        // come back from calling a receiver.
+        return state == BroadcastRecord.APP_RECEIVE
+                || state == BroadcastRecord.CALL_DONE_RECEIVE;
+    }
+
+    public void backgroundServicesFinishedLocked(int userId) {
+        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
+        if (br != null) {
+            if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
+                Slog.i(TAG, "Resuming delayed broadcast");
+                br.curComponent = null;
+                br.state = BroadcastRecord.IDLE;
+                processNextBroadcastLocked(false, false);
+            }
+        }
+    }
+
+    public void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
+            Intent intent, int resultCode, String data, Bundle extras,
+            boolean ordered, boolean sticky, int sendingUser,
+            int receiverUid, int callingUid, long dispatchDelay,
+            long receiveDelay) throws RemoteException {
+        // Send the intent to the receiver asynchronously using one-way binder calls.
+        if (app != null) {
+            final IApplicationThread thread = app.getThread();
+            if (thread != null) {
+                // If we have an app thread, do the call through that so it is
+                // correctly ordered with other one-way calls.
+                try {
+                    thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
+                            data, extras, ordered, sticky, sendingUser,
+                            app.mState.getReportedProcState());
+                // TODO: Uncomment this when (b/28322359) is fixed and we aren't getting
+                // DeadObjectException when the process isn't actually dead.
+                //} catch (DeadObjectException ex) {
+                // Failed to call into the process.  It's dying so just let it die and move on.
+                //    throw ex;
+                } catch (RemoteException ex) {
+                    // Failed to call into the process. It's either dying or wedged. Kill it gently.
+                    synchronized (mService) {
+                        Slog.w(TAG, "Can't deliver broadcast to " + app.processName
+                                + " (pid " + app.getPid() + "). Crashing it.");
+                        app.scheduleCrashLocked("can't deliver broadcast",
+                                CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
+                    }
+                    throw ex;
+                }
+            } else {
+                // Application has died. Receiver doesn't exist.
+                throw new RemoteException("app.thread must not be null");
+            }
+        } else {
+            receiver.performReceive(intent, resultCode, data, extras, ordered,
+                    sticky, sendingUser);
+        }
+        if (!ordered) {
+            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
+                    receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
+                    callingUid == -1 ? Process.SYSTEM_UID : callingUid,
+                    ActivityManagerService.getShortAction(intent.getAction()),
+                    BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
+                    BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
+                    dispatchDelay, receiveDelay, 0 /* finish_delay */);
+        }
+    }
+
+    private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
+            BroadcastFilter filter, boolean ordered, int index) {
+        boolean skip = false;
+        if (r.options != null && !r.options.testRequireCompatChange(filter.owningUid)) {
+            Slog.w(TAG, "Compat change filtered: broadcasting " + r.intent.toString()
+                    + " to uid " + filter.owningUid + " due to compat change "
+                    + r.options.getRequireCompatChangeId());
+            skip = true;
+        }
+        if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
+                filter.packageName, filter.owningUid)) {
+            Slog.w(TAG, "Association not allowed: broadcasting "
+                    + r.intent.toString()
+                    + " from " + r.callerPackage + " (pid=" + r.callingPid
+                    + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
+                    + filter);
+            skip = true;
+        }
+        if (!skip && !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
+                r.callingPid, r.resolvedType, filter.receiverList.uid)) {
+            Slog.w(TAG, "Firewall blocked: broadcasting "
+                    + r.intent.toString()
+                    + " from " + r.callerPackage + " (pid=" + r.callingPid
+                    + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
+                    + filter);
+            skip = true;
+        }
+        // Check that the sender has permission to send to this receiver
+        if (filter.requiredPermission != null) {
+            int perm = mService.checkComponentPermission(filter.requiredPermission,
+                    r.callingPid, r.callingUid, -1, true);
+            if (perm != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "Permission Denial: broadcasting "
+                        + r.intent.toString()
+                        + " from " + r.callerPackage + " (pid="
+                        + r.callingPid + ", uid=" + r.callingUid + ")"
+                        + " requires " + filter.requiredPermission
+                        + " due to registered receiver " + filter);
+                skip = true;
+            } else {
+                final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission);
+                if (opCode != AppOpsManager.OP_NONE
+                        && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid,
+                        r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver")
+                        != AppOpsManager.MODE_ALLOWED) {
+                    Slog.w(TAG, "Appop Denial: broadcasting "
+                            + r.intent.toString()
+                            + " from " + r.callerPackage + " (pid="
+                            + r.callingPid + ", uid=" + r.callingUid + ")"
+                            + " requires appop " + AppOpsManager.permissionToOp(
+                                    filter.requiredPermission)
+                            + " due to registered receiver " + filter);
+                    skip = true;
+                }
+            }
+        }
+
+        if (!skip && (filter.receiverList.app == null || filter.receiverList.app.isKilled()
+                || filter.receiverList.app.mErrorState.isCrashing())) {
+            Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
+                    + " to " + filter.receiverList + ": process gone or crashing");
+            skip = true;
+        }
+
+        // Ensure that broadcasts are only sent to other Instant Apps if they are marked as
+        // visible to Instant Apps.
+        final boolean visibleToInstantApps =
+                (r.intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
+
+        if (!skip && !visibleToInstantApps && filter.instantApp
+                && filter.receiverList.uid != r.callingUid) {
+            Slog.w(TAG, "Instant App Denial: receiving "
+                    + r.intent.toString()
+                    + " to " + filter.receiverList.app
+                    + " (pid=" + filter.receiverList.pid
+                    + ", uid=" + filter.receiverList.uid + ")"
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")"
+                    + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS");
+            skip = true;
+        }
+
+        if (!skip && !filter.visibleToInstantApp && r.callerInstantApp
+                && filter.receiverList.uid != r.callingUid) {
+            Slog.w(TAG, "Instant App Denial: receiving "
+                    + r.intent.toString()
+                    + " to " + filter.receiverList.app
+                    + " (pid=" + filter.receiverList.pid
+                    + ", uid=" + filter.receiverList.uid + ")"
+                    + " requires receiver be visible to instant apps"
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")");
+            skip = true;
+        }
+
+        // Check that the receiver has the required permission(s) to receive this broadcast.
+        if (!skip && r.requiredPermissions != null && r.requiredPermissions.length > 0) {
+            for (int i = 0; i < r.requiredPermissions.length; i++) {
+                String requiredPermission = r.requiredPermissions[i];
+                int perm = mService.checkComponentPermission(requiredPermission,
+                        filter.receiverList.pid, filter.receiverList.uid, -1, true);
+                if (perm != PackageManager.PERMISSION_GRANTED) {
+                    Slog.w(TAG, "Permission Denial: receiving "
+                            + r.intent.toString()
+                            + " to " + filter.receiverList.app
+                            + " (pid=" + filter.receiverList.pid
+                            + ", uid=" + filter.receiverList.uid + ")"
+                            + " requires " + requiredPermission
+                            + " due to sender " + r.callerPackage
+                            + " (uid " + r.callingUid + ")");
+                    skip = true;
+                    break;
+                }
+                int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
+                if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
+                        && mService.getAppOpsManager().noteOpNoThrow(appOp,
+                        filter.receiverList.uid, filter.packageName, filter.featureId,
+                        "Broadcast delivered to registered receiver " + filter.receiverId)
+                        != AppOpsManager.MODE_ALLOWED) {
+                    Slog.w(TAG, "Appop Denial: receiving "
+                            + r.intent.toString()
+                            + " to " + filter.receiverList.app
+                            + " (pid=" + filter.receiverList.pid
+                            + ", uid=" + filter.receiverList.uid + ")"
+                            + " requires appop " + AppOpsManager.permissionToOp(
+                            requiredPermission)
+                            + " due to sender " + r.callerPackage
+                            + " (uid " + r.callingUid + ")");
+                    skip = true;
+                    break;
+                }
+            }
+        }
+        if (!skip && (r.requiredPermissions == null || r.requiredPermissions.length == 0)) {
+            int perm = mService.checkComponentPermission(null,
+                    filter.receiverList.pid, filter.receiverList.uid, -1, true);
+            if (perm != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "Permission Denial: security check failed when receiving "
+                        + r.intent.toString()
+                        + " to " + filter.receiverList.app
+                        + " (pid=" + filter.receiverList.pid
+                        + ", uid=" + filter.receiverList.uid + ")"
+                        + " due to sender " + r.callerPackage
+                        + " (uid " + r.callingUid + ")");
+                skip = true;
+            }
+        }
+        // Check that the receiver does *not* have any excluded permissions
+        if (!skip && r.excludedPermissions != null && r.excludedPermissions.length > 0) {
+            for (int i = 0; i < r.excludedPermissions.length; i++) {
+                String excludedPermission = r.excludedPermissions[i];
+                final int perm = mService.checkComponentPermission(excludedPermission,
+                        filter.receiverList.pid, filter.receiverList.uid, -1, true);
+
+                int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
+                if (appOp != AppOpsManager.OP_NONE) {
+                    // When there is an app op associated with the permission,
+                    // skip when both the permission and the app op are
+                    // granted.
+                    if ((perm == PackageManager.PERMISSION_GRANTED) && (
+                            mService.getAppOpsManager().checkOpNoThrow(appOp,
+                                    filter.receiverList.uid,
+                                    filter.packageName)
+                                    == AppOpsManager.MODE_ALLOWED)) {
+                        Slog.w(TAG, "Appop Denial: receiving "
+                                + r.intent.toString()
+                                + " to " + filter.receiverList.app
+                                + " (pid=" + filter.receiverList.pid
+                                + ", uid=" + filter.receiverList.uid + ")"
+                                + " excludes appop " + AppOpsManager.permissionToOp(
+                                excludedPermission)
+                                + " due to sender " + r.callerPackage
+                                + " (uid " + r.callingUid + ")");
+                        skip = true;
+                        break;
+                    }
+                } else {
+                    // When there is no app op associated with the permission,
+                    // skip when permission is granted.
+                    if (perm == PackageManager.PERMISSION_GRANTED) {
+                        Slog.w(TAG, "Permission Denial: receiving "
+                                + r.intent.toString()
+                                + " to " + filter.receiverList.app
+                                + " (pid=" + filter.receiverList.pid
+                                + ", uid=" + filter.receiverList.uid + ")"
+                                + " excludes " + excludedPermission
+                                + " due to sender " + r.callerPackage
+                                + " (uid " + r.callingUid + ")");
+                        skip = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        // Check that the receiver does *not* belong to any of the excluded packages
+        if (!skip && r.excludedPackages != null && r.excludedPackages.length > 0) {
+            if (ArrayUtils.contains(r.excludedPackages, filter.packageName)) {
+                Slog.w(TAG, "Skipping delivery of excluded package "
+                        + r.intent.toString()
+                        + " to " + filter.receiverList.app
+                        + " (pid=" + filter.receiverList.pid
+                        + ", uid=" + filter.receiverList.uid + ")"
+                        + " excludes package " + filter.packageName
+                        + " due to sender " + r.callerPackage
+                        + " (uid " + r.callingUid + ")");
+                skip = true;
+            }
+        }
+
+        // If the broadcast also requires an app op check that as well.
+        if (!skip && r.appOp != AppOpsManager.OP_NONE
+                && mService.getAppOpsManager().noteOpNoThrow(r.appOp,
+                filter.receiverList.uid, filter.packageName, filter.featureId,
+                "Broadcast delivered to registered receiver " + filter.receiverId)
+                != AppOpsManager.MODE_ALLOWED) {
+            Slog.w(TAG, "Appop Denial: receiving "
+                    + r.intent.toString()
+                    + " to " + filter.receiverList.app
+                    + " (pid=" + filter.receiverList.pid
+                    + ", uid=" + filter.receiverList.uid + ")"
+                    + " requires appop " + AppOpsManager.opToName(r.appOp)
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")");
+            skip = true;
+        }
+
+        // Ensure that broadcasts are only sent to other apps if they are explicitly marked as
+        // exported, or are System level broadcasts
+        if (!skip && !filter.exported && mService.checkComponentPermission(null, r.callingPid,
+                r.callingUid, filter.receiverList.uid, filter.exported)
+                != PackageManager.PERMISSION_GRANTED) {
+            Slog.w(TAG, "Exported Denial: sending "
+                    + r.intent.toString()
+                    + ", action: " + r.intent.getAction()
+                    + " from " + r.callerPackage
+                    + " (uid=" + r.callingUid + ")"
+                    + " due to receiver " + filter.receiverList.app
+                    + " (uid " + filter.receiverList.uid + ")"
+                    + " not specifying RECEIVER_EXPORTED");
+            skip = true;
+        }
+
+        // Filter packages in the intent extras, skipping delivery if none of the packages is
+        // visible to the receiver.
+        Bundle filteredExtras = null;
+        if (!skip && r.filterExtrasForReceiver != null) {
+            final Bundle extras = r.intent.getExtras();
+            if (extras != null) {
+                filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
+                if (filteredExtras == null) {
+                    if (DEBUG_BROADCAST) {
+                        Slog.v(TAG, "Skipping delivery to "
+                                + filter.receiverList.app
+                                + " : receiver is filtered by the package visibility");
+                    }
+                    skip = true;
+                }
+            }
+        }
+
+        if (skip) {
+            r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
+            return;
+        }
+
+        // If permissions need a review before any of the app components can run, we drop
+        // the broadcast and if the calling app is in the foreground and the broadcast is
+        // explicit we launch the review UI passing it a pending intent to send the skipped
+        // broadcast.
+        if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
+                filter.owningUserId)) {
+            r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
+            return;
+        }
+
+        r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
+
+        // If this is not being sent as an ordered broadcast, then we
+        // don't want to touch the fields that keep track of the current
+        // state of ordered broadcasts.
+        if (ordered) {
+            r.receiver = filter.receiverList.receiver.asBinder();
+            r.curFilter = filter;
+            filter.receiverList.curBroadcast = r;
+            r.state = BroadcastRecord.CALL_IN_RECEIVE;
+            if (filter.receiverList.app != null) {
+                // Bump hosting application to no longer be in background
+                // scheduling class.  Note that we can't do that if there
+                // isn't an app...  but we can only be in that case for
+                // things that directly call the IActivityManager API, which
+                // are already core system stuff so don't matter for this.
+                r.curApp = filter.receiverList.app;
+                filter.receiverList.app.mReceivers.addCurReceiver(r);
+                mService.enqueueOomAdjTargetLocked(r.curApp);
+                mService.updateOomAdjPendingTargetsLocked(
+                        OOM_ADJ_REASON_START_RECEIVER);
+            }
+        } else if (filter.receiverList.app != null) {
+            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app,
+                    OOM_ADJ_REASON_START_RECEIVER);
+        }
+
+        try {
+            if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
+                    "Delivering to " + filter + " : " + r);
+            if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) {
+                // Skip delivery if full backup in progress
+                // If it's an ordered broadcast, we need to continue to the next receiver.
+                if (ordered) {
+                    skipReceiverLocked(r);
+                }
+            } else {
+                r.receiverTime = SystemClock.uptimeMillis();
+                maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
+                maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
+                maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
+                performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
+                        prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
+                        r.resultExtras, r.ordered, r.initialSticky, r.userId,
+                        filter.receiverList.uid, r.callingUid,
+                        r.dispatchTime - r.enqueueTime,
+                        r.receiverTime - r.dispatchTime);
+                // parallel broadcasts are fire-and-forget, not bookended by a call to
+                // finishReceiverLocked(), so we manage their activity-start token here
+                if (filter.receiverList.app != null
+                        && r.allowBackgroundActivityStarts && !r.ordered) {
+                    postActivityStartTokenRemoval(filter.receiverList.app, r);
+                }
+            }
+            if (ordered) {
+                r.state = BroadcastRecord.CALL_DONE_RECEIVE;
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
+            // Clean up ProcessRecord state related to this broadcast attempt
+            if (filter.receiverList.app != null) {
+                filter.receiverList.app.removeAllowBackgroundActivityStartsToken(r);
+                if (ordered) {
+                    filter.receiverList.app.mReceivers.removeCurReceiver(r);
+                    // Something wrong, its oom adj could be downgraded, but not in a hurry.
+                    mService.enqueueOomAdjTargetLocked(r.curApp);
+                }
+            }
+            // And BroadcastRecord state related to ordered delivery, if appropriate
+            if (ordered) {
+                r.receiver = null;
+                r.curFilter = null;
+                filter.receiverList.curBroadcast = null;
+            }
+        }
+    }
+
+    private boolean requestStartTargetPermissionsReviewIfNeededLocked(
+            BroadcastRecord receiverRecord, String receivingPackageName,
+            final int receivingUserId) {
+        if (!mService.getPackageManagerInternal().isPermissionsReviewRequired(
+                receivingPackageName, receivingUserId)) {
+            return true;
+        }
+
+        final boolean callerForeground = receiverRecord.callerApp != null
+                ? receiverRecord.callerApp.mState.getSetSchedGroup()
+                != ProcessList.SCHED_GROUP_BACKGROUND : true;
+
+        // Show a permission review UI only for explicit broadcast from a foreground app
+        if (callerForeground && receiverRecord.intent.getComponent() != null) {
+            IIntentSender target = mService.mPendingIntentController.getIntentSender(
+                    ActivityManager.INTENT_SENDER_BROADCAST, receiverRecord.callerPackage,
+                    receiverRecord.callerFeatureId, receiverRecord.callingUid,
+                    receiverRecord.userId, null, null, 0,
+                    new Intent[]{receiverRecord.intent},
+                    new String[]{receiverRecord.intent.resolveType(mService.mContext
+                            .getContentResolver())},
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+                            | PendingIntent.FLAG_IMMUTABLE, null);
+
+            final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName);
+            intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
+
+            if (DEBUG_PERMISSIONS_REVIEW) {
+                Slog.i(TAG, "u" + receivingUserId + " Launching permission review for package "
+                        + receivingPackageName);
+            }
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mService.mContext.startActivityAsUser(intent, new UserHandle(receivingUserId));
+                }
+            });
+        } else {
+            Slog.w(TAG, "u" + receivingUserId + " Receiving a broadcast in package"
+                    + receivingPackageName + " requires a permissions review");
+        }
+
+        return false;
+    }
+
+    void maybeScheduleTempAllowlistLocked(int uid, BroadcastRecord r,
+            @Nullable BroadcastOptions brOptions) {
+        if (brOptions == null || brOptions.getTemporaryAppAllowlistDuration() <= 0) {
+            return;
+        }
+        long duration = brOptions.getTemporaryAppAllowlistDuration();
+        final @TempAllowListType int type = brOptions.getTemporaryAppAllowlistType();
+        final @ReasonCode int reasonCode = brOptions.getTemporaryAppAllowlistReasonCode();
+        final String reason = brOptions.getTemporaryAppAllowlistReason();
+
+        if (duration > Integer.MAX_VALUE) {
+            duration = Integer.MAX_VALUE;
+        }
+        // XXX ideally we should pause the broadcast until everything behind this is done,
+        // or else we will likely start dispatching the broadcast before we have opened
+        // access to the app (there is a lot of asynchronicity behind this).  It is probably
+        // not that big a deal, however, because the main purpose here is to allow apps
+        // to hold wake locks, and they will be able to acquire their wake lock immediately
+        // it just won't be enabled until we get through this work.
+        StringBuilder b = new StringBuilder();
+        b.append("broadcast:");
+        UserHandle.formatUid(b, r.callingUid);
+        b.append(":");
+        if (r.intent.getAction() != null) {
+            b.append(r.intent.getAction());
+        } else if (r.intent.getComponent() != null) {
+            r.intent.getComponent().appendShortString(b);
+        } else if (r.intent.getData() != null) {
+            b.append(r.intent.getData());
+        }
+        b.append(",reason:");
+        b.append(reason);
+        if (DEBUG_BROADCAST) {
+            Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration
+                    + " type=" + type + " : " + b.toString());
+        }
+        mService.tempAllowlistUidLocked(uid, duration, reasonCode, b.toString(), type,
+                r.callingUid);
+    }
+
+    /**
+     * Return true if all given permissions are signature-only perms.
+     */
+    final boolean isSignaturePerm(String[] perms) {
+        if (perms == null) {
+            return false;
+        }
+        IPermissionManager pm = AppGlobals.getPermissionManager();
+        for (int i = perms.length-1; i >= 0; i--) {
+            try {
+                PermissionInfo pi = pm.getPermissionInfo(perms[i], "android", 0);
+                if (pi == null) {
+                    // a required permission that no package has actually
+                    // defined cannot be signature-required.
+                    return false;
+                }
+                if ((pi.protectionLevel & (PermissionInfo.PROTECTION_MASK_BASE
+                        | PermissionInfo.PROTECTION_FLAG_PRIVILEGED))
+                        != PermissionInfo.PROTECTION_SIGNATURE) {
+                    // If this a signature permission and NOT allowed for privileged apps, it
+                    // is okay...  otherwise, nope!
+                    return false;
+                }
+            } catch (RemoteException e) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void processNextBroadcast(boolean fromMsg) {
+        synchronized (mService) {
+            processNextBroadcastLocked(fromMsg, false);
+        }
+    }
+
+    static String broadcastDescription(BroadcastRecord r, ComponentName component) {
+        return r.intent.toString()
+                + " from " + r.callerPackage + " (pid=" + r.callingPid
+                + ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
+    }
+
+    private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
+            @Nullable Bundle filteredExtras) {
+        final Intent intent = new Intent(originalIntent);
+        if (filteredExtras != null) {
+            intent.replaceExtras(filteredExtras);
+        }
+        return intent;
+    }
+
+    public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
+        BroadcastRecord r;
+
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["
+                + mQueueName + "]: "
+                + mParallelBroadcasts.size() + " parallel broadcasts; "
+                + mDispatcher.describeStateLocked());
+
+        mService.updateCpuStats();
+
+        if (fromMsg) {
+            mBroadcastsScheduled = false;
+        }
+
+        // First, deliver any non-serialized broadcasts right away.
+        while (mParallelBroadcasts.size() > 0) {
+            r = mParallelBroadcasts.remove(0);
+            r.dispatchTime = SystemClock.uptimeMillis();
+            r.dispatchRealTime = SystemClock.elapsedRealtime();
+            r.dispatchClockTime = System.currentTimeMillis();
+            r.mIsReceiverAppRunning = true;
+
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
+                    System.identityHashCode(r));
+                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
+                    System.identityHashCode(r));
+            }
+
+            final int N = r.receivers.size();
+            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
+                    + mQueueName + "] " + r);
+            for (int i=0; i<N; i++) {
+                Object target = r.receivers.get(i);
+                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                        "Delivering non-ordered on [" + mQueueName + "] to registered "
+                        + target + ": " + r);
+                deliverToRegisteredReceiverLocked(r,
+                        (BroadcastFilter) target, false, i);
+            }
+            addBroadcastToHistoryLocked(r);
+            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
+                    + mQueueName + "] " + r);
+        }
+
+        // Now take care of the next serialized one...
+
+        // If we are waiting for a process to come up to handle the next
+        // broadcast, then do nothing at this point.  Just in case, we
+        // check that the process we're waiting for still exists.
+        if (mPendingBroadcast != null) {
+            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
+                    "processNextBroadcast [" + mQueueName + "]: waiting for "
+                    + mPendingBroadcast.curApp);
+
+            boolean isDead;
+            if (mPendingBroadcast.curApp.getPid() > 0) {
+                synchronized (mService.mPidsSelfLocked) {
+                    ProcessRecord proc = mService.mPidsSelfLocked.get(
+                            mPendingBroadcast.curApp.getPid());
+                    isDead = proc == null || proc.mErrorState.isCrashing();
+                }
+            } else {
+                final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
+                        mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
+                isDead = proc == null || !proc.isPendingStart();
+            }
+            if (!isDead) {
+                // It's still alive, so keep waiting
+                return;
+            } else {
+                Slog.w(TAG, "pending app  ["
+                        + mQueueName + "]" + mPendingBroadcast.curApp
+                        + " died before responding to broadcast");
+                mPendingBroadcast.state = BroadcastRecord.IDLE;
+                mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
+                mPendingBroadcast = null;
+            }
+        }
+
+        boolean looped = false;
+
+        do {
+            final long now = SystemClock.uptimeMillis();
+            r = mDispatcher.getNextBroadcastLocked(now);
+
+            if (r == null) {
+                // No more broadcasts are deliverable right now, so all done!
+                mDispatcher.scheduleDeferralCheckLocked(false);
+                synchronized (mService.mAppProfiler.mProfilerLock) {
+                    mService.mAppProfiler.scheduleAppGcsLPf();
+                }
+                if (looped && !skipOomAdj) {
+                    // If we had finished the last ordered broadcast, then
+                    // make sure all processes have correct oom and sched
+                    // adjustments.
+                    mService.updateOomAdjPendingTargetsLocked(
+                            OOM_ADJ_REASON_START_RECEIVER);
+                }
+
+                // when we have no more ordered broadcast on this queue, stop logging
+                if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
+                    mLogLatencyMetrics = false;
+                }
+
+                return;
+            }
+
+            boolean forceReceive = false;
+
+            // Ensure that even if something goes awry with the timeout
+            // detection, we catch "hung" broadcasts here, discard them,
+            // and continue to make progress.
+            //
+            // This is only done if the system is ready so that early-stage receivers
+            // don't get executed with timeouts; and of course other timeout-
+            // exempt broadcasts are ignored.
+            int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+            if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
+                if ((numReceivers > 0) &&
+                        (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
+                    Slog.w(TAG, "Hung broadcast ["
+                            + mQueueName + "] discarded after timeout failure:"
+                            + " now=" + now
+                            + " dispatchTime=" + r.dispatchTime
+                            + " startTime=" + r.receiverTime
+                            + " intent=" + r.intent
+                            + " numReceivers=" + numReceivers
+                            + " nextReceiver=" + r.nextReceiver
+                            + " state=" + r.state);
+                    broadcastTimeoutLocked(false); // forcibly finish this broadcast
+                    forceReceive = true;
+                    r.state = BroadcastRecord.IDLE;
+                }
+            }
+
+            if (r.state != BroadcastRecord.IDLE) {
+                if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
+                        "processNextBroadcast("
+                        + mQueueName + ") called when not idle (state="
+                        + r.state + ")");
+                return;
+            }
+
+            // Is the current broadcast is done for any reason?
+            if (r.receivers == null || r.nextReceiver >= numReceivers
+                    || r.resultAbort || forceReceive) {
+                // Send the final result if requested
+                if (r.resultTo != null) {
+                    boolean sendResult = true;
+
+                    // if this was part of a split/deferral complex, update the refcount and only
+                    // send the completion when we clear all of them
+                    if (r.splitToken != 0) {
+                        int newCount = mSplitRefcounts.get(r.splitToken) - 1;
+                        if (newCount == 0) {
+                            // done!  clear out this record's bookkeeping and deliver
+                            if (DEBUG_BROADCAST_DEFERRAL) {
+                                Slog.i(TAG_BROADCAST,
+                                        "Sending broadcast completion for split token "
+                                        + r.splitToken + " : " + r.intent.getAction());
+                            }
+                            mSplitRefcounts.delete(r.splitToken);
+                        } else {
+                            // still have some split broadcast records in flight; update refcount
+                            // and hold off on the callback
+                            if (DEBUG_BROADCAST_DEFERRAL) {
+                                Slog.i(TAG_BROADCAST,
+                                        "Result refcount now " + newCount + " for split token "
+                                        + r.splitToken + " : " + r.intent.getAction()
+                                        + " - not sending completion yet");
+                            }
+                            sendResult = false;
+                            mSplitRefcounts.put(r.splitToken, newCount);
+                        }
+                    }
+                    if (sendResult) {
+                        if (r.callerApp != null) {
+                            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
+                                    r.callerApp, OOM_ADJ_REASON_FINISH_RECEIVER);
+                        }
+                        try {
+                            if (DEBUG_BROADCAST) {
+                                Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
+                                        + r.intent.getAction() + " app=" + r.callerApp);
+                            }
+                            if (r.dispatchTime == 0) {
+                                // The dispatch time here could be 0, in case it's a parallel
+                                // broadcast but it has a result receiver. Set it to now.
+                                r.dispatchTime = now;
+                            }
+                            r.mIsReceiverAppRunning = true;
+                            performReceiveLocked(r.callerApp, r.resultTo,
+                                    new Intent(r.intent), r.resultCode,
+                                    r.resultData, r.resultExtras, false, false, r.userId,
+                                    r.callingUid, r.callingUid,
+                                    r.dispatchTime - r.enqueueTime,
+                                    now - r.dispatchTime);
+                            logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+                            // Set this to null so that the reference
+                            // (local and remote) isn't kept in the mBroadcastHistory.
+                            r.resultTo = null;
+                        } catch (RemoteException e) {
+                            r.resultTo = null;
+                            Slog.w(TAG, "Failure ["
+                                    + mQueueName + "] sending broadcast result of "
+                                    + r.intent, e);
+                        }
+                    }
+                }
+
+                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
+                cancelBroadcastTimeoutLocked();
+
+                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
+                        "Finished with ordered broadcast " + r);
+
+                // ... and on to the next...
+                addBroadcastToHistoryLocked(r);
+                if (r.intent.getComponent() == null && r.intent.getPackage() == null
+                        && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+                    // This was an implicit broadcast... let's record it for posterity.
+                    mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
+                            r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
+                }
+                mDispatcher.retireBroadcastLocked(r);
+                r = null;
+                looped = true;
+                continue;
+            }
+
+            // Check whether the next receiver is under deferral policy, and handle that
+            // accordingly.  If the current broadcast was already part of deferred-delivery
+            // tracking, we know that it must now be deliverable as-is without re-deferral.
+            if (!r.deferred) {
+                final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
+                if (mDispatcher.isDeferringLocked(receiverUid)) {
+                    if (DEBUG_BROADCAST_DEFERRAL) {
+                        Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid
+                                + " at " + r.nextReceiver + " is under deferral");
+                    }
+                    // If this is the only (remaining) receiver in the broadcast, "splitting"
+                    // doesn't make sense -- just defer it as-is and retire it as the
+                    // currently active outgoing broadcast.
+                    BroadcastRecord defer;
+                    if (r.nextReceiver + 1 == numReceivers) {
+                        if (DEBUG_BROADCAST_DEFERRAL) {
+                            Slog.i(TAG_BROADCAST, "Sole receiver of " + r
+                                    + " is under deferral; setting aside and proceeding");
+                        }
+                        defer = r;
+                        mDispatcher.retireBroadcastLocked(r);
+                    } else {
+                        // Nontrivial case; split out 'uid's receivers to a new broadcast record
+                        // and defer that, then loop and pick up continuing delivery of the current
+                        // record (now absent those receivers).
+
+                        // The split operation is guaranteed to match at least at 'nextReceiver'
+                        defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);
+                        if (DEBUG_BROADCAST_DEFERRAL) {
+                            Slog.i(TAG_BROADCAST, "Post split:");
+                            Slog.i(TAG_BROADCAST, "Original broadcast receivers:");
+                            for (int i = 0; i < r.receivers.size(); i++) {
+                                Slog.i(TAG_BROADCAST, "  " + r.receivers.get(i));
+                            }
+                            Slog.i(TAG_BROADCAST, "Split receivers:");
+                            for (int i = 0; i < defer.receivers.size(); i++) {
+                                Slog.i(TAG_BROADCAST, "  " + defer.receivers.get(i));
+                            }
+                        }
+                        // Track completion refcount as well if relevant
+                        if (r.resultTo != null) {
+                            int token = r.splitToken;
+                            if (token == 0) {
+                                // first split of this record; refcount for 'r' and 'deferred'
+                                r.splitToken = defer.splitToken = nextSplitTokenLocked();
+                                mSplitRefcounts.put(r.splitToken, 2);
+                                if (DEBUG_BROADCAST_DEFERRAL) {
+                                    Slog.i(TAG_BROADCAST,
+                                            "Broadcast needs split refcount; using new token "
+                                            + r.splitToken);
+                                }
+                            } else {
+                                // new split from an already-refcounted situation; increment count
+                                final int curCount = mSplitRefcounts.get(token);
+                                if (DEBUG_BROADCAST_DEFERRAL) {
+                                    if (curCount == 0) {
+                                        Slog.wtf(TAG_BROADCAST,
+                                                "Split refcount is zero with token for " + r);
+                                    }
+                                }
+                                mSplitRefcounts.put(token, curCount + 1);
+                                if (DEBUG_BROADCAST_DEFERRAL) {
+                                    Slog.i(TAG_BROADCAST, "New split count for token " + token
+                                            + " is " + (curCount + 1));
+                                }
+                            }
+                        }
+                    }
+                    mDispatcher.addDeferredBroadcast(receiverUid, defer);
+                    r = null;
+                    looped = true;
+                    continue;
+                }
+            }
+        } while (r == null);
+
+        // Get the next receiver...
+        int recIdx = r.nextReceiver++;
+
+        // Keep track of when this receiver started, and make sure there
+        // is a timeout message pending to kill it if need be.
+        r.receiverTime = SystemClock.uptimeMillis();
+        if (recIdx == 0) {
+            r.dispatchTime = r.receiverTime;
+            r.dispatchRealTime = SystemClock.elapsedRealtime();
+            r.dispatchClockTime = System.currentTimeMillis();
+
+            if (mLogLatencyMetrics) {
+                FrameworkStatsLog.write(
+                        FrameworkStatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
+                        r.dispatchClockTime - r.enqueueClockTime);
+            }
+
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
+                    System.identityHashCode(r));
+                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
+                    System.identityHashCode(r));
+            }
+            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
+                    + mQueueName + "] " + r);
+        }
+        if (! mPendingBroadcastTimeoutMessage) {
+            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
+            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+                    "Submitting BROADCAST_TIMEOUT_MSG ["
+                    + mQueueName + "] for " + r + " at " + timeoutTime);
+            setBroadcastTimeoutLocked(timeoutTime);
+        }
+
+        final BroadcastOptions brOptions = r.options;
+        final Object nextReceiver = r.receivers.get(recIdx);
+
+        if (nextReceiver instanceof BroadcastFilter) {
+            // Simple case: this is a registered receiver who gets
+            // a direct call.
+            BroadcastFilter filter = (BroadcastFilter)nextReceiver;
+            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                    "Delivering ordered ["
+                    + mQueueName + "] to registered "
+                    + filter + ": " + r);
+            r.mIsReceiverAppRunning = true;
+            deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
+            if (r.receiver == null || !r.ordered) {
+                // The receiver has already finished, so schedule to
+                // process the next one.
+                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
+                        + mQueueName + "]: ordered="
+                        + r.ordered + " receiver=" + r.receiver);
+                r.state = BroadcastRecord.IDLE;
+                scheduleBroadcastsLocked();
+            } else {
+                if (filter.receiverList != null) {
+                    maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
+                    // r is guaranteed ordered at this point, so we know finishReceiverLocked()
+                    // will get a callback and handle the activity start token lifecycle.
+                }
+            }
+            return;
+        }
+
+        // Hard case: need to instantiate the receiver, possibly
+        // starting its application process to host it.
+
+        ResolveInfo info =
+            (ResolveInfo)nextReceiver;
+        ComponentName component = new ComponentName(
+                info.activityInfo.applicationInfo.packageName,
+                info.activityInfo.name);
+
+        boolean skip = false;
+        if (brOptions != null &&
+                (info.activityInfo.applicationInfo.targetSdkVersion
+                        < brOptions.getMinManifestReceiverApiLevel() ||
+                info.activityInfo.applicationInfo.targetSdkVersion
+                        > brOptions.getMaxManifestReceiverApiLevel())) {
+            Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
+                    + " targets " + info.activityInfo.applicationInfo.targetSdkVersion
+                    + " but delivery restricted to ["
+                    + brOptions.getMinManifestReceiverApiLevel() + ", "
+                    + brOptions.getMaxManifestReceiverApiLevel()
+                    + "] broadcasting " + broadcastDescription(r, component));
+            skip = true;
+        }
+        if (brOptions != null &&
+                !brOptions.testRequireCompatChange(info.activityInfo.applicationInfo.uid)) {
+            Slog.w(TAG, "Compat change filtered: broadcasting " + broadcastDescription(r, component)
+                    + " to uid " + info.activityInfo.applicationInfo.uid + " due to compat change "
+                    + r.options.getRequireCompatChangeId());
+            skip = true;
+        }
+        if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
+                component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
+            Slog.w(TAG, "Association not allowed: broadcasting "
+                    + broadcastDescription(r, component));
+            skip = true;
+        }
+        if (!skip) {
+            skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
+                    r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
+            if (skip) {
+                Slog.w(TAG, "Firewall blocked: broadcasting "
+                        + broadcastDescription(r, component));
+            }
+        }
+        int perm = mService.checkComponentPermission(info.activityInfo.permission,
+                r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
+                info.activityInfo.exported);
+        if (!skip && perm != PackageManager.PERMISSION_GRANTED) {
+            if (!info.activityInfo.exported) {
+                Slog.w(TAG, "Permission Denial: broadcasting "
+                        + broadcastDescription(r, component)
+                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
+            } else {
+                Slog.w(TAG, "Permission Denial: broadcasting "
+                        + broadcastDescription(r, component)
+                        + " requires " + info.activityInfo.permission);
+            }
+            skip = true;
+        } else if (!skip && info.activityInfo.permission != null) {
+            final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);
+            if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode,
+                    r.callingUid, r.callerPackage, r.callerFeatureId,
+                    "Broadcast delivered to " + info.activityInfo.name)
+                    != AppOpsManager.MODE_ALLOWED) {
+                Slog.w(TAG, "Appop Denial: broadcasting "
+                        + broadcastDescription(r, component)
+                        + " requires appop " + AppOpsManager.permissionToOp(
+                                info.activityInfo.permission));
+                skip = true;
+            }
+        }
+
+        boolean isSingleton = false;
+        try {
+            isSingleton = mService.isSingleton(info.activityInfo.processName,
+                    info.activityInfo.applicationInfo,
+                    info.activityInfo.name, info.activityInfo.flags);
+        } catch (SecurityException e) {
+            Slog.w(TAG, e.getMessage());
+            skip = true;
+        }
+        if ((info.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
+            if (ActivityManager.checkUidPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS,
+                    info.activityInfo.applicationInfo.uid)
+                            != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString()
+                        + " requests FLAG_SINGLE_USER, but app does not hold "
+                        + android.Manifest.permission.INTERACT_ACROSS_USERS);
+                skip = true;
+            }
+        }
+        if (!skip && info.activityInfo.applicationInfo.isInstantApp()
+                && r.callingUid != info.activityInfo.applicationInfo.uid) {
+            Slog.w(TAG, "Instant App Denial: receiving "
+                    + r.intent
+                    + " to " + component.flattenToShortString()
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")"
+                    + " Instant Apps do not support manifest receivers");
+            skip = true;
+        }
+        if (!skip && r.callerInstantApp
+                && (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0
+                && r.callingUid != info.activityInfo.applicationInfo.uid) {
+            Slog.w(TAG, "Instant App Denial: receiving "
+                    + r.intent
+                    + " to " + component.flattenToShortString()
+                    + " requires receiver have visibleToInstantApps set"
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")");
+            skip = true;
+        }
+        if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
+            // If the target process is crashing, just skip it.
+            Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r
+                    + " to " + r.curApp + ": process crashing");
+            skip = true;
+        }
+        if (!skip) {
+            boolean isAvailable = false;
+            try {
+                isAvailable = AppGlobals.getPackageManager().isPackageAvailable(
+                        info.activityInfo.packageName,
+                        UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
+            } catch (Exception e) {
+                // all such failures mean we skip this receiver
+                Slog.w(TAG, "Exception getting recipient info for "
+                        + info.activityInfo.packageName, e);
+            }
+            if (!isAvailable) {
+                Slog.w(TAG_BROADCAST,
+                        "Skipping delivery to " + info.activityInfo.packageName + " / "
+                        + info.activityInfo.applicationInfo.uid
+                        + " : package no longer available");
+                skip = true;
+            }
+        }
+
+        // If permissions need a review before any of the app components can run, we drop
+        // the broadcast and if the calling app is in the foreground and the broadcast is
+        // explicit we launch the review UI passing it a pending intent to send the skipped
+        // broadcast.
+        if (!skip) {
+            if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
+                    info.activityInfo.packageName, UserHandle.getUserId(
+                            info.activityInfo.applicationInfo.uid))) {
+                Slog.w(TAG_BROADCAST,
+                        "Skipping delivery: permission review required for "
+                                + broadcastDescription(r, component));
+                skip = true;
+            }
+        }
+
+        // This is safe to do even if we are skipping the broadcast, and we need
+        // this information now to evaluate whether it is going to be allowed to run.
+        final int receiverUid = info.activityInfo.applicationInfo.uid;
+        // If it's a singleton, it needs to be the same app or a special app
+        if (r.callingUid != Process.SYSTEM_UID && isSingleton
+                && mService.isValidSingletonCall(r.callingUid, receiverUid)) {
+            info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);
+        }
+        String targetProcess = info.activityInfo.processName;
+        ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
+                info.activityInfo.applicationInfo.uid);
+
+        if (!skip) {
+            final int allowed = mService.getAppStartModeLOSP(
+                    info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
+                    info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
+            if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
+                // We won't allow this receiver to be launched if the app has been
+                // completely disabled from launches, or it was not explicitly sent
+                // to it and the app is in a state that should not receive it
+                // (depending on how getAppStartModeLOSP has determined that).
+                if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
+                    Slog.w(TAG, "Background execution disabled: receiving "
+                            + r.intent + " to "
+                            + component.flattenToShortString());
+                    skip = true;
+                } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
+                        || (r.intent.getComponent() == null
+                            && r.intent.getPackage() == null
+                            && ((r.intent.getFlags()
+                                    & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
+                            && !isSignaturePerm(r.requiredPermissions))) {
+                    mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
+                            component.getPackageName());
+                    Slog.w(TAG, "Background execution not allowed: receiving "
+                            + r.intent + " to "
+                            + component.flattenToShortString());
+                    skip = true;
+                }
+            }
+        }
+
+        if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction())
+                && !mService.mUserController
+                .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
+                        0 /* flags */)) {
+            skip = true;
+            Slog.w(TAG,
+                    "Skipping delivery to " + info.activityInfo.packageName + " / "
+                            + info.activityInfo.applicationInfo.uid + " : user is not running");
+        }
+
+        if (!skip && r.excludedPermissions != null && r.excludedPermissions.length > 0) {
+            for (int i = 0; i < r.excludedPermissions.length; i++) {
+                String excludedPermission = r.excludedPermissions[i];
+                try {
+                    perm = AppGlobals.getPackageManager()
+                        .checkPermission(excludedPermission,
+                                info.activityInfo.applicationInfo.packageName,
+                                UserHandle
+                                .getUserId(info.activityInfo.applicationInfo.uid));
+                } catch (RemoteException e) {
+                    perm = PackageManager.PERMISSION_DENIED;
+                }
+
+                int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
+                if (appOp != AppOpsManager.OP_NONE) {
+                    // When there is an app op associated with the permission,
+                    // skip when both the permission and the app op are
+                    // granted.
+                    if ((perm == PackageManager.PERMISSION_GRANTED) && (
+                                mService.getAppOpsManager().checkOpNoThrow(appOp,
+                                info.activityInfo.applicationInfo.uid,
+                                info.activityInfo.packageName)
+                            == AppOpsManager.MODE_ALLOWED)) {
+                        skip = true;
+                        break;
+                    }
+                } else {
+                    // When there is no app op associated with the permission,
+                    // skip when permission is granted.
+                    if (perm == PackageManager.PERMISSION_GRANTED) {
+                        skip = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        // Check that the receiver does *not* belong to any of the excluded packages
+        if (!skip && r.excludedPackages != null && r.excludedPackages.length > 0) {
+            if (ArrayUtils.contains(r.excludedPackages, component.getPackageName())) {
+                Slog.w(TAG, "Skipping delivery of excluded package "
+                        + r.intent + " to "
+                        + component.flattenToShortString()
+                        + " excludes package " + component.getPackageName()
+                        + " due to sender " + r.callerPackage
+                        + " (uid " + r.callingUid + ")");
+                skip = true;
+            }
+        }
+
+        if (!skip && info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
+                r.requiredPermissions != null && r.requiredPermissions.length > 0) {
+            for (int i = 0; i < r.requiredPermissions.length; i++) {
+                String requiredPermission = r.requiredPermissions[i];
+                try {
+                    perm = AppGlobals.getPackageManager().
+                            checkPermission(requiredPermission,
+                                    info.activityInfo.applicationInfo.packageName,
+                                    UserHandle
+                                    .getUserId(info.activityInfo.applicationInfo.uid));
+                } catch (RemoteException e) {
+                    perm = PackageManager.PERMISSION_DENIED;
+                }
+                if (perm != PackageManager.PERMISSION_GRANTED) {
+                    Slog.w(TAG, "Permission Denial: receiving "
+                            + r.intent + " to "
+                            + component.flattenToShortString()
+                            + " requires " + requiredPermission
+                            + " due to sender " + r.callerPackage
+                            + " (uid " + r.callingUid + ")");
+                    skip = true;
+                    break;
+                }
+                int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
+                if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
+                    if (!noteOpForManifestReceiver(appOp, r, info, component)) {
+                        skip = true;
+                        break;
+                    }
+                }
+            }
+        }
+        if (!skip && r.appOp != AppOpsManager.OP_NONE) {
+            if (!noteOpForManifestReceiver(r.appOp, r, info, component)) {
+                skip = true;
+            }
+        }
+
+        // Filter packages in the intent extras, skipping delivery if none of the packages is
+        // visible to the receiver.
+        Bundle filteredExtras = null;
+        if (!skip && r.filterExtrasForReceiver != null) {
+            final Bundle extras = r.intent.getExtras();
+            if (extras != null) {
+                filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
+                if (filteredExtras == null) {
+                    if (DEBUG_BROADCAST) {
+                        Slog.v(TAG, "Skipping delivery to "
+                                + info.activityInfo.packageName + " / " + receiverUid
+                                + " : receiver is filtered by the package visibility");
+                    }
+                    skip = true;
+                }
+            }
+        }
+
+        if (skip) {
+            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                    "Skipping delivery of ordered [" + mQueueName + "] "
+                    + r + " for reason described above");
+            r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
+            r.receiver = null;
+            r.curFilter = null;
+            r.state = BroadcastRecord.IDLE;
+            r.manifestSkipCount++;
+            scheduleBroadcastsLocked();
+            return;
+        }
+        r.manifestCount++;
+
+        r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
+        r.state = BroadcastRecord.APP_RECEIVE;
+        r.curComponent = component;
+        r.curReceiver = info.activityInfo;
+        r.curFilteredExtras = filteredExtras;
+        if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
+            Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
+                    + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
+                    + receiverUid);
+        }
+        final boolean isActivityCapable =
+                (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0);
+        maybeScheduleTempAllowlistLocked(receiverUid, r, brOptions);
+
+        // Report that a component is used for explicit broadcasts.
+        if (r.intent.getComponent() != null && r.curComponent != null
+                && !TextUtils.equals(r.curComponent.getPackageName(), r.callerPackage)) {
+            mService.mUsageStatsService.reportEvent(
+                    r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
+        }
+
+        // Broadcast is being executed, its package can't be stopped.
+        try {
+            AppGlobals.getPackageManager().setPackageStoppedState(
+                    r.curComponent.getPackageName(), false, r.userId);
+        } catch (RemoteException e) {
+        } catch (IllegalArgumentException e) {
+            Slog.w(TAG, "Failed trying to unstop package "
+                    + r.curComponent.getPackageName() + ": " + e);
+        }
+
+        // Is this receiver's application already running?
+        if (app != null && app.getThread() != null && !app.isKilled()) {
+            try {
+                app.addPackage(info.activityInfo.packageName,
+                        info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
+                maybeAddAllowBackgroundActivityStartsToken(app, r);
+                r.mIsReceiverAppRunning = true;
+                processCurBroadcastLocked(r, app);
+                return;
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Exception when sending broadcast to "
+                      + r.curComponent, e);
+            } catch (RuntimeException e) {
+                Slog.wtf(TAG, "Failed sending broadcast to "
+                        + r.curComponent + " with " + r.intent, e);
+                // If some unexpected exception happened, just skip
+                // this broadcast.  At this point we are not in the call
+                // from a client, so throwing an exception out from here
+                // will crash the entire system instead of just whoever
+                // sent the broadcast.
+                logBroadcastReceiverDiscardLocked(r);
+                finishReceiverLocked(r, r.resultCode, r.resultData,
+                        r.resultExtras, r.resultAbort, false);
+                scheduleBroadcastsLocked();
+                // We need to reset the state if we failed to start the receiver.
+                r.state = BroadcastRecord.IDLE;
+                return;
+            }
+
+            // If a dead object exception was thrown -- fall through to
+            // restart the application.
+        }
+
+        // Not running -- get it started, to be executed when the app comes up.
+        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                "Need to start app ["
+                + mQueueName + "] " + targetProcess + " for broadcast " + r);
+        r.curApp = mService.startProcessLocked(targetProcess,
+                info.activityInfo.applicationInfo, true,
+                r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
+                new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
+                        r.intent.getAction(), getHostingRecordTriggerType(r)),
+                isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
+                (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
+        if (r.curApp == null) {
+            // Ah, this recipient is unavailable.  Finish it if necessary,
+            // and mark the broadcast record as ready for the next.
+            Slog.w(TAG, "Unable to launch app "
+                    + info.activityInfo.applicationInfo.packageName + "/"
+                    + receiverUid + " for broadcast "
+                    + r.intent + ": process is bad");
+            logBroadcastReceiverDiscardLocked(r);
+            finishReceiverLocked(r, r.resultCode, r.resultData,
+                    r.resultExtras, r.resultAbort, false);
+            scheduleBroadcastsLocked();
+            r.state = BroadcastRecord.IDLE;
+            return;
+        }
+
+        maybeAddAllowBackgroundActivityStartsToken(r.curApp, r);
+        mPendingBroadcast = r;
+        mPendingBroadcastRecvIndex = recIdx;
+    }
+
+    private String getHostingRecordTriggerType(BroadcastRecord r) {
+        if (r.alarm) {
+            return HostingRecord.TRIGGER_TYPE_ALARM;
+        } else if (r.pushMessage) {
+            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
+        } else if (r.pushMessageOverQuota) {
+            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+        }
+        return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+    }
+
+    @Nullable
+    private String getTargetPackage(BroadcastRecord r) {
+        if (r.intent == null) {
+            return null;
+        }
+        if (r.intent.getPackage() != null) {
+            return r.intent.getPackage();
+        } else if (r.intent.getComponent() != null) {
+            return r.intent.getComponent().getPackageName();
+        }
+        return null;
+    }
+
+    private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
+        // Only log after last receiver.
+        // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
+        // last BroadcastRecord of the split broadcast which has non-null resultTo.
+        final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+        if (r.nextReceiver < numReceivers) {
+            return;
+        }
+        final String action = r.intent.getAction();
+        int event = 0;
+        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
+            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
+        } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+        }
+        if (event != 0) {
+            final int dispatchLatency = (int)(r.dispatchTime - r.enqueueTime);
+            final int completeLatency = (int)
+                    (SystemClock.uptimeMillis() - r.enqueueTime);
+            final int dispatchRealLatency = (int)(r.dispatchRealTime - r.enqueueRealTime);
+            final int completeRealLatency = (int)
+                    (SystemClock.elapsedRealtime() - r.enqueueRealTime);
+            int userType = FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
+            // This method is called very infrequently, no performance issue we call
+            // LocalServices.getService() here.
+            final UserManagerInternal umInternal = LocalServices.getService(
+                    UserManagerInternal.class);
+            final UserInfo userInfo = umInternal.getUserInfo(r.userId);
+            if (userInfo != null) {
+                userType = UserManager.getUserTypeForStatsd(userInfo.userType);
+            }
+            Slog.i(TAG_BROADCAST,
+                    "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
+                            + action
+                            + " dispatchLatency:" + dispatchLatency
+                            + " completeLatency:" + completeLatency
+                            + " dispatchRealLatency:" + dispatchRealLatency
+                            + " completeRealLatency:" + completeRealLatency
+                            + " receiversSize:" + numReceivers
+                            + " userId:" + r.userId
+                            + " userType:" + (userInfo != null? userInfo.userType : null));
+            FrameworkStatsLog.write(
+                    BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
+                    event,
+                    dispatchLatency,
+                    completeLatency,
+                    dispatchRealLatency,
+                    completeRealLatency,
+                    r.userId,
+                    userType);
+        }
+    }
+
+    private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
+        if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
+            return;
+        }
+        final String targetPackage = getTargetPackage(r);
+        // Ignore non-explicit broadcasts
+        if (targetPackage == null) {
+            return;
+        }
+        getUsageStatsManagerInternal().reportBroadcastDispatched(
+                r.callingUid, targetPackage, UserHandle.of(r.userId),
+                r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
+                mService.getUidStateLocked(targetUid));
+    }
+
+    @NonNull
+    private UsageStatsManagerInternal getUsageStatsManagerInternal() {
+        final UsageStatsManagerInternal usageStatsManagerInternal =
+                LocalServices.getService(UsageStatsManagerInternal.class);
+        return usageStatsManagerInternal;
+    }
+
+    private boolean noteOpForManifestReceiver(int appOp, BroadcastRecord r, ResolveInfo info,
+            ComponentName component) {
+        if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) {
+            return noteOpForManifestReceiverInner(appOp, r, info, component, null);
+        } else {
+            // Attribution tags provided, noteOp each tag
+            for (String tag : info.activityInfo.attributionTags) {
+                if (!noteOpForManifestReceiverInner(appOp, r, info, component, tag)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    private boolean noteOpForManifestReceiverInner(int appOp, BroadcastRecord r, ResolveInfo info,
+            ComponentName component, String tag) {
+        if (mService.getAppOpsManager().noteOpNoThrow(appOp,
+                    info.activityInfo.applicationInfo.uid,
+                    info.activityInfo.packageName,
+                    tag,
+                    "Broadcast delivered to " + info.activityInfo.name)
+                != AppOpsManager.MODE_ALLOWED) {
+            Slog.w(TAG, "Appop Denial: receiving "
+                    + r.intent + " to "
+                    + component.flattenToShortString()
+                    + " requires appop " + AppOpsManager.opToName(appOp)
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")");
+            return false;
+        }
+        return true;
+    }
+
+    private void maybeAddAllowBackgroundActivityStartsToken(ProcessRecord proc, BroadcastRecord r) {
+        if (r == null || proc == null || !r.allowBackgroundActivityStarts) {
+            return;
+        }
+        String msgToken = (proc.toShortString() + r.toString()).intern();
+        // first, if there exists a past scheduled request to remove this token, drop
+        // that request - we don't want the token to be swept from under our feet...
+        mHandler.removeCallbacksAndMessages(msgToken);
+        // ...then add the token
+        proc.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
+    }
+
+    final void setBroadcastTimeoutLocked(long timeoutTime) {
+        if (! mPendingBroadcastTimeoutMessage) {
+            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
+            mHandler.sendMessageAtTime(msg, timeoutTime);
+            mPendingBroadcastTimeoutMessage = true;
+        }
+    }
+
+    final void cancelBroadcastTimeoutLocked() {
+        if (mPendingBroadcastTimeoutMessage) {
+            mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
+            mPendingBroadcastTimeoutMessage = false;
+        }
+    }
+
+    final void broadcastTimeoutLocked(boolean fromMsg) {
+        if (fromMsg) {
+            mPendingBroadcastTimeoutMessage = false;
+        }
+
+        if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) {
+            return;
+        }
+
+        long now = SystemClock.uptimeMillis();
+        BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
+        if (fromMsg) {
+            if (!mService.mProcessesReady) {
+                // Only process broadcast timeouts if the system is ready; some early
+                // broadcasts do heavy work setting up system facilities
+                return;
+            }
+
+            // If the broadcast is generally exempt from timeout tracking, we're done
+            if (r.timeoutExempt) {
+                if (DEBUG_BROADCAST) {
+                    Slog.i(TAG_BROADCAST, "Broadcast timeout but it's exempt: "
+                            + r.intent.getAction());
+                }
+                return;
+            }
+
+            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
+            if (timeoutTime > now) {
+                // We can observe premature timeouts because we do not cancel and reset the
+                // broadcast timeout message after each receiver finishes.  Instead, we set up
+                // an initial timeout then kick it down the road a little further as needed
+                // when it expires.
+                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+                        "Premature timeout ["
+                        + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
+                        + timeoutTime);
+                setBroadcastTimeoutLocked(timeoutTime);
+                return;
+            }
+        }
+
+        if (r.state == BroadcastRecord.WAITING_SERVICES) {
+            // In this case the broadcast had already finished, but we had decided to wait
+            // for started services to finish as well before going on.  So if we have actually
+            // waited long enough time timeout the broadcast, let's give up on the whole thing
+            // and just move on to the next.
+            Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null
+                    ? r.curComponent.flattenToShortString() : "(null)"));
+            r.curComponent = null;
+            r.state = BroadcastRecord.IDLE;
+            processNextBroadcastLocked(false, false);
+            return;
+        }
+
+        // If the receiver app is being debugged we quietly ignore unresponsiveness, just
+        // tidying up and moving on to the next broadcast without crashing or ANRing this
+        // app just because it's stopped at a breakpoint.
+        final boolean debugging = (r.curApp != null && r.curApp.isDebugging());
+
+        long timeoutDurationMs = now - r.receiverTime;
+        Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver
+                + ", started " + timeoutDurationMs + "ms ago");
+        r.receiverTime = now;
+        if (!debugging) {
+            r.anrCount++;
+        }
+
+        ProcessRecord app = null;
+        TimeoutRecord timeoutRecord = null;
+
+        Object curReceiver;
+        if (r.nextReceiver > 0) {
+            curReceiver = r.receivers.get(r.nextReceiver-1);
+            r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT;
+        } else {
+            curReceiver = r.curReceiver;
+        }
+        Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
+        logBroadcastReceiverDiscardLocked(r);
+        if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
+            BroadcastFilter bf = (BroadcastFilter)curReceiver;
+            if (bf.receiverList.pid != 0
+                    && bf.receiverList.pid != ActivityManagerService.MY_PID) {
+                synchronized (mService.mPidsSelfLocked) {
+                    app = mService.mPidsSelfLocked.get(
+                            bf.receiverList.pid);
+                }
+            }
+        } else {
+            app = r.curApp;
+        }
+
+        if (app != null) {
+            String anrMessage =
+                    "Broadcast of " + r.intent.toString() + ", waited " + timeoutDurationMs
+                            + "ms";
+            timeoutRecord = TimeoutRecord.forBroadcastReceiver(anrMessage);
+        }
+
+        if (mPendingBroadcast == r) {
+            mPendingBroadcast = null;
+        }
+
+        // Move on to the next receiver.
+        finishReceiverLocked(r, r.resultCode, r.resultData,
+                r.resultExtras, r.resultAbort, false);
+        scheduleBroadcastsLocked();
+
+        if (!debugging && timeoutRecord != null) {
+            mService.mAnrHelper.appNotResponding(app, timeoutRecord);
+        }
+    }
+
+    private final int ringAdvance(int x, final int increment, final int ringSize) {
+        x += increment;
+        if (x < 0) return (ringSize - 1);
+        else if (x >= ringSize) return 0;
+        else return x;
+    }
+
+    private final void addBroadcastToHistoryLocked(BroadcastRecord original) {
+        if (original.callingUid < 0) {
+            // This was from a registerReceiver() call; ignore it.
+            return;
+        }
+        original.finishTime = SystemClock.uptimeMillis();
+
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                createBroadcastTraceTitle(original, BroadcastRecord.DELIVERY_DELIVERED),
+                System.identityHashCode(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();
+        }
+
+        // Note sometimes (only for sticky broadcasts?) we reuse BroadcastRecords,
+        // So don't change the incoming record directly.
+        final BroadcastRecord historyRecord = original.maybeStripForHistory();
+
+        mBroadcastHistory[mHistoryNext] = historyRecord;
+        mHistoryNext = ringAdvance(mHistoryNext, 1, MAX_BROADCAST_HISTORY);
+
+        mBroadcastSummaryHistory[mSummaryHistoryNext] = historyRecord.intent;
+        mSummaryHistoryEnqueueTime[mSummaryHistoryNext] = historyRecord.enqueueClockTime;
+        mSummaryHistoryDispatchTime[mSummaryHistoryNext] = historyRecord.dispatchClockTime;
+        mSummaryHistoryFinishTime[mSummaryHistoryNext] = System.currentTimeMillis();
+        mSummaryHistoryNext = ringAdvance(mSummaryHistoryNext, 1, MAX_BROADCAST_SUMMARY_HISTORY);
+    }
+
+    public boolean cleanupDisabledPackageReceiversLocked(
+            String packageName, Set<String> filterByClasses, int userId, boolean doit) {
+        boolean didSomething = false;
+        for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
+            didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
+                    packageName, filterByClasses, userId, doit);
+            if (!doit && didSomething) {
+                return true;
+            }
+        }
+
+        didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
+                filterByClasses, userId, doit);
+
+        return didSomething;
+    }
+
+    final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
+        final int logIndex = r.nextReceiver - 1;
+        if (logIndex >= 0 && logIndex < r.receivers.size()) {
+            Object curReceiver = r.receivers.get(logIndex);
+            if (curReceiver instanceof BroadcastFilter) {
+                BroadcastFilter bf = (BroadcastFilter) curReceiver;
+                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
+                        bf.owningUserId, System.identityHashCode(r),
+                        r.intent.getAction(), logIndex, System.identityHashCode(bf));
+            } else {
+                ResolveInfo ri = (ResolveInfo) curReceiver;
+                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
+                        UserHandle.getUserId(ri.activityInfo.applicationInfo.uid),
+                        System.identityHashCode(r), r.intent.getAction(), logIndex, ri.toString());
+            }
+        } else {
+            if (logIndex < 0) Slog.w(TAG,
+                    "Discarding broadcast before first receiver is invoked: " + r);
+            EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
+                    -1, System.identityHashCode(r),
+                    r.intent.getAction(),
+                    r.nextReceiver,
+                    "NONE");
+        }
+    }
+
+    private String createBroadcastTraceTitle(BroadcastRecord record, int state) {
+        return formatSimple("Broadcast %s from %s (%s) %s",
+                state == BroadcastRecord.DELIVERY_PENDING ? "in queue" : "dispatched",
+                record.callerPackage == null ? "" : record.callerPackage,
+                record.callerApp == null ? "process unknown" : record.callerApp.toShortString(),
+                record.intent == null ? "" : record.intent.getAction());
+    }
+
+    public boolean isIdle() {
+        return mParallelBroadcasts.isEmpty() && mDispatcher.isIdle()
+                && (mPendingBroadcast == null);
+    }
+
+    // Used by wait-for-broadcast-idle : fast-forward all current deferrals to
+    // be immediately deliverable.
+    public void cancelDeferrals() {
+        synchronized (mService) {
+            mDispatcher.cancelDeferralsLocked();
+            scheduleBroadcastsLocked();
+        }
+    }
+
+    public String describeState() {
+        synchronized (mService) {
+            return mParallelBroadcasts.size() + " parallel; "
+                    + mDispatcher.describeStateLocked();
+        }
+    }
+
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
+        int N;
+        N = mParallelBroadcasts.size();
+        for (int i = N - 1; i >= 0; i--) {
+            mParallelBroadcasts.get(i).dumpDebug(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
+        }
+        mDispatcher.dumpDebug(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
+        if (mPendingBroadcast != null) {
+            mPendingBroadcast.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCAST);
+        }
+
+        int lastIndex = mHistoryNext;
+        int ringIndex = lastIndex;
+        do {
+            // increasing index = more recent entry, and we want to print the most
+            // recent first and work backwards, so we roll through the ring backwards.
+            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
+            BroadcastRecord r = mBroadcastHistory[ringIndex];
+            if (r != null) {
+                r.dumpDebug(proto, BroadcastQueueProto.HISTORICAL_BROADCASTS);
+            }
+        } while (ringIndex != lastIndex);
+
+        lastIndex = ringIndex = mSummaryHistoryNext;
+        do {
+            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
+            Intent intent = mBroadcastSummaryHistory[ringIndex];
+            if (intent == null) {
+                continue;
+            }
+            long summaryToken = proto.start(BroadcastQueueProto.HISTORICAL_BROADCASTS_SUMMARY);
+            intent.dumpDebug(proto, BroadcastQueueProto.BroadcastSummary.INTENT,
+                    false, true, true, false);
+            proto.write(BroadcastQueueProto.BroadcastSummary.ENQUEUE_CLOCK_TIME_MS,
+                    mSummaryHistoryEnqueueTime[ringIndex]);
+            proto.write(BroadcastQueueProto.BroadcastSummary.DISPATCH_CLOCK_TIME_MS,
+                    mSummaryHistoryDispatchTime[ringIndex]);
+            proto.write(BroadcastQueueProto.BroadcastSummary.FINISH_CLOCK_TIME_MS,
+                    mSummaryHistoryFinishTime[ringIndex]);
+            proto.end(summaryToken);
+        } while (ringIndex != lastIndex);
+        proto.end(token);
+    }
+
+    public boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+        if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
+                || mPendingBroadcast != null) {
+            boolean printed = false;
+            for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
+                BroadcastRecord br = mParallelBroadcasts.get(i);
+                if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
+                    continue;
+                }
+                if (!printed) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    needSep = true;
+                    printed = true;
+                    pw.println("  Active broadcasts [" + mQueueName + "]:");
+                }
+                pw.println("  Active Broadcast " + mQueueName + " #" + i + ":");
+                br.dump(pw, "    ", sdf);
+            }
+
+            mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf);
+
+            if (dumpPackage == null || (mPendingBroadcast != null
+                    && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
+                pw.println();
+                pw.println("  Pending broadcast [" + mQueueName + "]:");
+                if (mPendingBroadcast != null) {
+                    mPendingBroadcast.dump(pw, "    ", sdf);
+                } else {
+                    pw.println("    (null)");
+                }
+                needSep = true;
+            }
+        }
+
+        mConstants.dump(pw);
+
+        int i;
+        boolean printed = false;
+
+        i = -1;
+        int lastIndex = mHistoryNext;
+        int ringIndex = lastIndex;
+        do {
+            // increasing index = more recent entry, and we want to print the most
+            // recent first and work backwards, so we roll through the ring backwards.
+            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
+            BroadcastRecord r = mBroadcastHistory[ringIndex];
+            if (r == null) {
+                continue;
+            }
+
+            i++; // genuine record of some sort even if we're filtering it out
+            if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) {
+                continue;
+            }
+            if (!printed) {
+                if (needSep) {
+                    pw.println();
+                }
+                needSep = true;
+                pw.println("  Historical broadcasts [" + mQueueName + "]:");
+                printed = true;
+            }
+            if (dumpAll) {
+                pw.print("  Historical Broadcast " + mQueueName + " #");
+                        pw.print(i); pw.println(":");
+                r.dump(pw, "    ", sdf);
+            } else {
+                pw.print("  #"); pw.print(i); pw.print(": "); pw.println(r);
+                pw.print("    ");
+                pw.println(r.intent.toShortString(false, true, true, false));
+                if (r.targetComp != null && r.targetComp != r.intent.getComponent()) {
+                    pw.print("    targetComp: "); pw.println(r.targetComp.toShortString());
+                }
+                Bundle bundle = r.intent.getExtras();
+                if (bundle != null) {
+                    pw.print("    extras: "); pw.println(bundle.toString());
+                }
+            }
+        } while (ringIndex != lastIndex);
+
+        if (dumpPackage == null) {
+            lastIndex = ringIndex = mSummaryHistoryNext;
+            if (dumpAll) {
+                printed = false;
+                i = -1;
+            } else {
+                // roll over the 'i' full dumps that have already been issued
+                for (int j = i;
+                        j > 0 && ringIndex != lastIndex;) {
+                    ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
+                    BroadcastRecord r = mBroadcastHistory[ringIndex];
+                    if (r == null) {
+                        continue;
+                    }
+                    j--;
+                }
+            }
+            // done skipping; dump the remainder of the ring. 'i' is still the ordinal within
+            // the overall broadcast history.
+            do {
+                ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
+                Intent intent = mBroadcastSummaryHistory[ringIndex];
+                if (intent == null) {
+                    continue;
+                }
+                if (!printed) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    needSep = true;
+                    pw.println("  Historical broadcasts summary [" + mQueueName + "]:");
+                    printed = true;
+                }
+                if (!dumpAll && i >= 50) {
+                    pw.println("  ...");
+                    break;
+                }
+                i++;
+                pw.print("  #"); pw.print(i); pw.print(": ");
+                pw.println(intent.toShortString(false, true, true, false));
+                pw.print("    ");
+                TimeUtils.formatDuration(mSummaryHistoryDispatchTime[ringIndex]
+                        - mSummaryHistoryEnqueueTime[ringIndex], pw);
+                pw.print(" dispatch ");
+                TimeUtils.formatDuration(mSummaryHistoryFinishTime[ringIndex]
+                        - mSummaryHistoryDispatchTime[ringIndex], pw);
+                pw.println(" finish");
+                pw.print("    enq=");
+                pw.print(sdf.format(new Date(mSummaryHistoryEnqueueTime[ringIndex])));
+                pw.print(" disp=");
+                pw.print(sdf.format(new Date(mSummaryHistoryDispatchTime[ringIndex])));
+                pw.print(" fin=");
+                pw.println(sdf.format(new Date(mSummaryHistoryFinishTime[ringIndex])));
+                Bundle bundle = intent.getExtras();
+                if (bundle != null) {
+                    pw.print("    extras: "); pw.println(bundle.toString());
+                }
+            } while (ringIndex != lastIndex);
+        }
+
+        return needSep;
+    }
+}
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 66ef5e7..11eb782 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -582,13 +582,18 @@
         }
 
         @Override
-        public boolean isCameraDisabled() {
+        public boolean isCameraDisabled(int userId) {
             DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
             if (dpm == null) {
                 Slog.e(TAG, "Failed to get the device policy manager service");
                 return false;
             }
-            return dpm.getCameraDisabled(null);
+            try {
+                return dpm.getCameraDisabled(null, userId);
+            } catch (Exception e) {
+                e.printStackTrace();
+                return false;
+            }
         }
     };
 
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
new file mode 100644
index 0000000..e46bb74
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.contexthub;
+
+import android.hardware.location.NanoAppMessage;
+
+/**
+ * A class to log events and useful metrics within the Context Hub service.
+ *
+ * The class holds a queue of the last NUM_EVENTS_TO_STORE events for each
+ * event category: nanoapp load, nanoapp unload, message from a nanoapp,
+ * message to a nanoapp, and context hub restarts. The dump() function
+ * will be called during debug dumps, giving access to the event information
+ * and aggregate data since the instantiation of this class.
+ *
+ * @hide
+ */
+public class ContextHubEventLogger {
+    private static final String TAG = "ContextHubEventLogger";
+
+    ContextHubEventLogger() {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Logs a nanoapp load event
+     *
+     * @param contextHubId      the ID of the context hub
+     * @param nanoAppId         the ID of the nanoapp
+     * @param nanoAppVersion    the version of the nanoapp
+     * @param nanoAppSize       the size in bytes of the nanoapp
+     * @param success           whether the load was successful
+     */
+    public void logNanoAppLoad(int contextHubId, long nanoAppId, int nanoAppVersion,
+                               long nanoAppSize, boolean success) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Logs a nanoapp unload event
+     *
+     * @param contextHubId      the ID of the context hub
+     * @param nanoAppId         the ID of the nanoapp
+     * @param success           whether the unload was successful
+     */
+    public void logNanoAppUnload(int contextHubId, long nanoAppId, boolean success) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Logs the event where a nanoapp sends a message to a client
+     *
+     * @param contextHubId      the ID of the context hub
+     * @param message           the message that was sent
+     */
+    public void logMessageFromNanoApp(int contextHubId, NanoAppMessage message) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Logs the event where a client sends a message to a nanoapp
+     *
+     * @param contextHubId      the ID of the context hub
+     * @param message           the message that was sent
+     * @param success           whether the message was sent successfully
+     */
+    public void logMessageToNanoApp(int contextHubId, NanoAppMessage message, boolean success) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Logs a context hub restart event
+     *
+     * @param contextHubId      the ID of the context hub
+     */
+    public void logContextHubRestarts(int contextHubId) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Creates a string representation of the logged events
+     *
+     * @return the dumped events
+     */
+    public String dump() {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
+    public String toString() {
+        return dump();
+    }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e423ea9..672c13c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -911,7 +911,7 @@
     private @NonNull List<UserInfo> getUsersInternal(boolean excludePartial, boolean excludeDying,
             boolean excludePreCreated) {
         synchronized (mUsersLock) {
-            ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+            ArrayList<UserInfo> users = new ArrayList<>(mUsers.size());
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
                 UserInfo ui = mUsers.valueAt(i).info;
@@ -1767,6 +1767,32 @@
     }
 
     @Override
+    public List<UserHandle> getVisibleUsers() {
+        if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) {
+            throw new SecurityException("Caller needs MANAGE_USERS or INTERACT_ACROSS_USERS "
+                    + "permission to get list of visible users");
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            // TODO(b/2399825580): refactor into UserDisplayAssigner
+            synchronized (mUsersLock) {
+                int usersSize = mUsers.size();
+                ArrayList<UserHandle> visibleUsers = new ArrayList<>(usersSize);
+                for (int i = 0; i < usersSize; i++) {
+                    UserInfo ui = mUsers.valueAt(i).info;
+                    if (!ui.partial && !ui.preCreated && !mRemovingUserIds.get(ui.id)
+                            && isUserVisibleUnchecked(ui.id)) {
+                        visibleUsers.add(UserHandle.of(ui.id));
+                    }
+                }
+                return visibleUsers;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public @NonNull String getUserName() {
         final int callingUid = Binder.getCallingUid();
         if (!hasQueryOrCreateUsersPermission()
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4f67f1d..fbaf1ce 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8144,7 +8144,8 @@
         }
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)
+                || isCameraServerUid(caller));
 
         if (parent) {
             Preconditions.checkCallAuthorization(
@@ -9666,6 +9667,10 @@
         return UserHandle.isSameApp(caller.getUid(), Process.SHELL_UID);
     }
 
+    private boolean isCameraServerUid(CallerIdentity caller) {
+        return UserHandle.isSameApp(caller.getUid(), Process.CAMERASERVER_UID);
+    }
+
     private @UserIdInt int getCurrentForegroundUserId() {
         try {
             UserInfo currentUser = mInjector.getIActivityManager().getCurrentUser();
diff --git a/tools/lint/OWNERS b/tools/lint/OWNERS
index 7c04519..2c526a1 100644
--- a/tools/lint/OWNERS
+++ b/tools/lint/OWNERS
@@ -2,4 +2,5 @@
 jsharkey@google.com
 
 per-file *CallingSettingsNonUserGetterMethods* = file:/packages/SettingsProvider/OWNERS
+per-file *RegisterReceiverFlagDetector* = jacobhobbie@google.com