diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 7591057f414a5..1f6573076d512 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -385,9 +385,11 @@ class BroadcastProcessQueue { public void setProcess(@Nullable ProcessRecord app) { this.app = app; if (app != null) { + setProcessCached(app.isCached()); setProcessInstrumented(app.getActiveInstrumentation() != null); setProcessPersistent(app.isPersistent()); } else { + setProcessCached(false); setProcessInstrumented(false); setProcessPersistent(false); } diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index bef16b6f081c2..bc331588a9874 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -1328,7 +1328,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { synchronized (mService) { BroadcastProcessQueue leaf = mProcessQueues.get(uid); while (leaf != null) { - leaf.setProcessCached(cached); + // Update internal state by refreshing values previously + // read from any known running process + leaf.setProcess(leaf.app); updateQueueDeferred(leaf); updateRunnableList(leaf); leaf = leaf.processNameNext; diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index fa3f684b034e5..50d00b471f657 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1039,7 +1039,13 @@ class ProcessRecord implements WindowProcessListener { mInFullBackup = inFullBackup; } + @GuardedBy("mService") + public void setCached(boolean cached) { + mState.setCached(cached); + } + @Override + @GuardedBy("mService") public boolean isCached() { return mState.isCached(); } 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 cf8460bca1a6a..2d4f5ca2f71da 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -53,6 +53,7 @@ import android.app.AppOpsManager; import android.app.BackgroundStartPrivileges; import android.app.BroadcastOptions; import android.app.IApplicationThread; +import android.app.UidObserver; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; @@ -83,6 +84,7 @@ import android.util.proto.ProtoOutputStream; import androidx.test.filters.MediumTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.server.AlarmManagerInternal; import com.android.server.DropBoxManagerInternal; import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService.Injector; @@ -153,11 +155,14 @@ public class BroadcastQueueTest { private PackageManagerInternal mPackageManagerInt; @Mock private UsageStatsManagerInternal mUsageStatsManagerInt; + @Mock + private AlarmManagerInternal mAlarmManagerInt; private ActivityManagerService mAms; private BroadcastQueue mQueue; BroadcastConstants mConstants; private BroadcastSkipPolicy mSkipPolicy; + private UidObserver mUidObserver; /** * Desired behavior of the next @@ -204,6 +209,8 @@ public class BroadcastQueueTest { LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); + LocalServices.removeServiceForTest(AlarmManagerInternal.class); + LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt); doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt()); doAnswer((invocation) -> { @@ -281,6 +288,11 @@ public class BroadcastQueueTest { }).when(mAms).getProcessRecordLocked(any(), anyInt()); doNothing().when(mAms).appNotResponding(any(), any()); + doAnswer((invocation) -> { + mUidObserver = invocation.getArgument(0); + return null; + }).when(mAms).registerUidObserver(any(), anyInt(), anyInt(), any()); + mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); mConstants.TIMEOUT = 100; mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0; @@ -305,6 +317,8 @@ public class BroadcastQueueTest { } else { throw new UnsupportedOperationException(); } + + mQueue.start(mContext.getContentResolver()); } @After @@ -683,12 +697,22 @@ public class BroadcastQueueTest { anyInt(), anyInt(), any()); } - private void verifyScheduleRegisteredReceiver(ProcessRecord app, + private void verifyScheduleRegisteredReceiver(ProcessRecord app, Intent intent) + throws Exception { + verifyScheduleRegisteredReceiver(times(1), app, intent, UserHandle.USER_SYSTEM); + } + + private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app, Intent intent) throws Exception { - verify(app.getThread()).scheduleRegisteredReceiver( + verifyScheduleRegisteredReceiver(mode, app, intent, UserHandle.USER_SYSTEM); + } + + private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app, + Intent intent, int userId) throws Exception { + verify(app.getThread(), mode).scheduleRegisteredReceiver( any(), argThat(filterEqualsIgnoringComponent(intent)), anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), - eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any()); + eq(userId), anyInt(), anyInt(), any()); } private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app, @@ -1934,4 +1958,49 @@ public class BroadcastQueueTest { getUidForPackage(PACKAGE_ORANGE)); assertNull(receiverOrangeApp); } + + /** + * Verify broadcasts to runtime receivers in cached processes are deferred + * until that process leaves the cached state. + */ + @Test + public void testDeferralPolicy_UntilActive() throws Exception { + // Legacy stack doesn't support deferral + Assume.assumeTrue(mImpl == Impl.MODERN); + + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN); + final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); + final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); + + receiverGreenApp.setCached(true); + receiverBlueApp.setCached(true); + receiverYellowApp.setCached(false); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + final BroadcastOptions opts = BroadcastOptions.makeBasic() + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, opts, + List.of(makeRegisteredReceiver(receiverGreenApp), + makeRegisteredReceiver(receiverBlueApp), + makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), + makeRegisteredReceiver(receiverYellowApp)))); + waitForIdle(); + + // Green ignored since it's in cached state + verifyScheduleRegisteredReceiver(never(), receiverGreenApp, airplane); + + // Blue delivered both since it has a manifest receiver + verifyScheduleReceiver(times(1), receiverBlueApp, airplane); + verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane); + + // Yellow delivered since it's not cached + verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane); + + // Shift green to be active and confirm that deferred broadcast is delivered + receiverGreenApp.setCached(false); + mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), false); + waitForIdle(); + verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane); + } }