Loading services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +31 −14 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading @@ -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; } /** Loading services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +81 −12 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -156,6 +158,7 @@ public class BroadcastQueueTest { private ActivityManagerService mAms; private BroadcastQueue mQueue; BroadcastConstants mConstants; private TestBroadcastSkipPolicy mSkipPolicy; /** * Desired behavior of the next Loading Loading @@ -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 Loading @@ -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 { Loading @@ -327,6 +322,43 @@ public class BroadcastQueueTest { } } private static class TestBroadcastSkipPolicy extends BroadcastSkipPolicy { private final ArrayMap<String, ArraySet> 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<Object> 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<Object> 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); Loading Loading @@ -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"; Loading @@ -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) { Loading @@ -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(); } } Loading Loading @@ -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); } } Loading
services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +31 −14 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading @@ -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; } /** Loading
services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +81 −12 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -156,6 +158,7 @@ public class BroadcastQueueTest { private ActivityManagerService mAms; private BroadcastQueue mQueue; BroadcastConstants mConstants; private TestBroadcastSkipPolicy mSkipPolicy; /** * Desired behavior of the next Loading Loading @@ -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 Loading @@ -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 { Loading @@ -327,6 +322,43 @@ public class BroadcastQueueTest { } } private static class TestBroadcastSkipPolicy extends BroadcastSkipPolicy { private final ArrayMap<String, ArraySet> 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<Object> 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<Object> 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); Loading Loading @@ -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"; Loading @@ -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) { Loading @@ -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(); } } Loading Loading @@ -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); } }