diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 15dfd72e365ea..a994b1db7ca33 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -731,6 +731,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { return; } + if (maybeSkipReceiver(queue, null, r, index)) { + mRunningColdStart = null; + return; + } + final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo; final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName(); @@ -790,41 +795,53 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * skipped (and therefore no more work is required). */ private boolean maybeSkipReceiver(@NonNull BroadcastProcessQueue queue, - @NonNull BroadcastReceiverBatch batch, @NonNull BroadcastRecord r, int index) { + @Nullable BroadcastReceiverBatch batch, @NonNull BroadcastRecord r, int index) { + final String reason = shouldSkipReceiver(queue, r, index); + if (reason != null) { + if (batch == null) { + enqueueFinishReceiver(queue, r, index, BroadcastRecord.DELIVERY_SKIPPED, reason); + } else { + batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, reason); + } + return true; + } + return false; + } + + /** + * Consults {@link BroadcastSkipPolicy} and the receiver process state to decide whether or + * not the broadcast to a receiver can be skipped. + */ + private String shouldSkipReceiver(@NonNull BroadcastProcessQueue queue, + @NonNull BroadcastRecord r, int index) { final int oldDeliveryState = getDeliveryState(r, index); final ProcessRecord app = queue.app; final Object receiver = r.receivers.get(index); // If someone already finished this broadcast, finish immediately if (isDeliveryStateTerminal(oldDeliveryState)) { - batch.finish(r, index, oldDeliveryState, "already terminal state"); - return true; + return "already terminal state"; } // Consider additional cases where we'd want to finish immediately - if (app.isInFullBackup()) { - batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup"); - return true; + if (app != null && app.isInFullBackup()) { + return "isInFullBackup"; } if (mSkipPolicy.shouldSkip(r, receiver)) { - batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy"); - return true; + return "mSkipPolicy"; } final Intent receiverIntent = r.getReceiverIntent(receiver); if (receiverIntent == null) { - batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent"); - return true; + return "getReceiverIntent"; } // Ignore registered receivers from a previous PID if ((receiver instanceof BroadcastFilter) && ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) { - batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, - "BroadcastFilter for mismatched PID"); - return true; + return "BroadcastFilter for mismatched PID"; } // The receiver was not handled in this method. - return false; + return null; } /** diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index b6a53ac1846fa..8d9dda0863e92 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -75,6 +75,8 @@ import android.os.PowerExemptionManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -156,6 +158,7 @@ public class BroadcastQueueTest { private ActivityManagerService mAms; private BroadcastQueue mQueue; BroadcastConstants mConstants; + private TestBroadcastSkipPolicy mSkipPolicy; /** * Desired behavior of the next @@ -282,16 +285,8 @@ public class BroadcastQueueTest { mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); mConstants.TIMEOUT = 100; mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0; - final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) { - public boolean shouldSkip(BroadcastRecord r, Object o) { - // Ignored - return false; - } - public String shouldSkipMessage(BroadcastRecord r, Object o) { - // Ignored - return null; - } - }; + mSkipPolicy = new TestBroadcastSkipPolicy(mAms); + final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) { public void addBroadcastToHistoryLocked(BroadcastRecord original) { // Ignored @@ -300,13 +295,13 @@ public class BroadcastQueueTest { if (mImpl == Impl.DEFAULT) { var q = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG, - mConstants, emptySkipPolicy, emptyHistory, false, + mConstants, mSkipPolicy, emptyHistory, false, ProcessList.SCHED_GROUP_DEFAULT); q.mReceiverBatch.mDeepReceiverCopy = true; mQueue = q; } else if (mImpl == Impl.MODERN) { var q = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(), - mConstants, mConstants, emptySkipPolicy, emptyHistory); + mConstants, mConstants, mSkipPolicy, emptyHistory); q.mReceiverBatch.mDeepReceiverCopy = true; mQueue = q; } else { @@ -327,6 +322,43 @@ public class BroadcastQueueTest { } } + private static class TestBroadcastSkipPolicy extends BroadcastSkipPolicy { + private final ArrayMap mReceiversToSkip = new ArrayMap<>(); + + TestBroadcastSkipPolicy(ActivityManagerService service) { + super(service); + } + + public String shouldSkipMessage(BroadcastRecord r, Object o) { + if (shouldSkipReceiver(r.intent.getAction(), o)) { + return "test skipped receiver"; + } + return null; + } + + private boolean shouldSkipReceiver(String action, Object o) { + final ArraySet receiversToSkip = mReceiversToSkip.get(action); + if (receiversToSkip == null) { + return false; + } + for (int i = 0; i < receiversToSkip.size(); ++i) { + if (BroadcastRecord.isReceiverEquals(o, receiversToSkip.valueAt(i))) { + return true; + } + } + return false; + } + + public void setSkipReceiver(String action, Object o) { + ArraySet receiversToSkip = mReceiversToSkip.get(action); + if (receiversToSkip == null) { + receiversToSkip = new ArraySet<>(); + mReceiversToSkip.put(action, receiversToSkip); + } + receiversToSkip.add(o); + } + } + private class TestInjector extends Injector { TestInjector(Context context) { super(context); @@ -835,6 +867,7 @@ public class BroadcastQueueTest { static final String PACKAGE_GREEN = "com.example.green"; static final String PACKAGE_BLUE = "com.example.blue"; static final String PACKAGE_YELLOW = "com.example.yellow"; + static final String PACKAGE_ORANGE = "com.example.orange"; static final String PROCESS_SYSTEM = "system"; @@ -842,6 +875,7 @@ public class BroadcastQueueTest { static final String CLASS_GREEN = "com.example.green.Green"; static final String CLASS_BLUE = "com.example.blue.Blue"; static final String CLASS_YELLOW = "com.example.yellow.Yellow"; + static final String CLASS_ORANGE = "com.example.orange.Orange"; static int getUidForPackage(@NonNull String packageName) { switch (packageName) { @@ -851,6 +885,7 @@ public class BroadcastQueueTest { case PACKAGE_GREEN: return android.os.Process.FIRST_APPLICATION_UID + 2; case PACKAGE_BLUE: return android.os.Process.FIRST_APPLICATION_UID + 3; case PACKAGE_YELLOW: return android.os.Process.FIRST_APPLICATION_UID + 4; + case PACKAGE_ORANGE: return android.os.Process.FIRST_APPLICATION_UID + 5; default: throw new IllegalArgumentException(); } } @@ -1873,4 +1908,38 @@ public class BroadcastQueueTest { verify(mAms).addBroadcastStatLocked(eq(Intent.ACTION_TIMEZONE_CHANGED), eq(PACKAGE_RED), eq(1), eq(0), anyLong()); } + + /** + * Verify that we skip broadcasts if {@link BroadcastSkipPolicy} decides it should be skipped. + */ + @Test + public void testSkipPolicy() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN); + final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + try (SyncBarrier b = new SyncBarrier()) { + final Object greenReceiver = makeRegisteredReceiver(receiverGreenApp); + final Object blueReceiver = makeRegisteredReceiver(receiverBlueApp); + final Object yellowReceiver = makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW); + final Object orangeReceiver = makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + List.of(greenReceiver, blueReceiver, yellowReceiver, orangeReceiver))); + + mSkipPolicy.setSkipReceiver(airplane.getAction(), greenReceiver); + mSkipPolicy.setSkipReceiver(airplane.getAction(), orangeReceiver); + } + + waitForIdle(); + // Verify that only blue and yellow receiver apps received the broadcast. + verifyScheduleRegisteredReceiver(never(), receiverGreenApp, USER_SYSTEM); + verifyScheduleRegisteredReceiver(receiverBlueApp, airplane); + final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW, + getUidForPackage(PACKAGE_YELLOW)); + verifyScheduleReceiver(receiverYellowApp, airplane); + final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE, + getUidForPackage(PACKAGE_ORANGE)); + assertNull(receiverOrangeApp); + } }