Add infinitely deferred broadcasts.

- Adds a flag that prevents a broadcast from being delivered to a
  cached app until that app is no longer cached.

- As of this CL, this flag is only honored for unordered broadcasts to
  runtime receivers. Ordered broadcasts, alarm/interactive broadcasts,
  and manifest broadcasts are never deferred.

- This CL prevents the modern queue from cold starting processes for
  manifest receivers that would not be eligible to receive a
  particular broadcast, as in the case of USER_PRESENT.

- Unordered broadcasts with a completion callback (ie, resultTo
  broadcasts) are always marked as infinitely-deferred. resultTo
  broadcasts must still be dispatched only to runtime receivers or
  manifest receivers that disallow process starts in order to be
  deferred.

- This CL marks various BATTERY_CHANGED-esque broadcasts as infinitely
  deferred.

- This CL marks SCREEN_ON and SCREEN_OFF broadcasts as infinitely
  deferred.

- This CL marks USER_PRESENT broadcasts are infinitely deferred.

Test: atest BroadcastDeliveryGroupTest
Test: atest BroadcastQueueModernImplTest
Test: atest BroadcastQueueTest
Bug: 261065790
Bug: 263141882
Bug: 263143790

Change-Id: I9a356c8701d73f5687831fbe5b699e4485f07dd5
This commit is contained in:
Tim Murray
2022-12-07 16:25:13 -08:00
parent a9c38b7bfd
commit 3bb8afe601
11 changed files with 357 additions and 68 deletions

View File

@@ -806,10 +806,12 @@ package android.app {
method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
method @Nullable public String getDeliveryGroupMatchingKey();
method public int getDeliveryGroupPolicy();
method public boolean isDeferUntilActive();
method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
method public static android.app.BroadcastOptions makeBasic();
method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
method @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);

View File

@@ -63,6 +63,7 @@ public class BroadcastOptions extends ComponentOptions {
private long mRequireCompatChangeId = CHANGE_INVALID;
private boolean mRequireCompatChangeEnabled = true;
private boolean mIsAlarmBroadcast = false;
private boolean mIsDeferUntilActive = false;
private long mIdForResponseEvent;
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
@@ -200,6 +201,12 @@ public class BroadcastOptions extends ComponentOptions {
private static final String KEY_REMOVE_MATCHING_FILTER =
"android:broadcast.removeMatchingFilter";
/**
* Corresponds to {@link #setDeferUntilActive(boolean)}.
*/
private static final String KEY_DEFER_UNTIL_ACTIVE =
"android:broadcast.deferuntilactive";
/**
* Corresponds to {@link #setDeliveryGroupPolicy(int)}.
*/
@@ -320,6 +327,7 @@ public class BroadcastOptions extends ComponentOptions {
BundleMerger.class);
mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
IntentFilter.class);
mIsDeferUntilActive = opts.getBoolean(KEY_DEFER_UNTIL_ACTIVE, false);
}
/**
@@ -699,6 +707,41 @@ public class BroadcastOptions extends ComponentOptions {
return mIdForResponseEvent;
}
/**
* Sets whether the broadcast should not run until the process is in an active process state
* (ie, a process exists for the app and the app is not in a cached process state).
*
* Whether an app's process state is considered active is independent of its standby bucket.
*
* A broadcast that is deferred until the process is active will not execute until the process
* is brought to an active state by some other action, like a job, alarm, or service binding. As
* a result, the broadcast may be delayed indefinitely. This deferral only applies to runtime
* registered receivers of a broadcast. Any manifest receivers will run immediately, similar to
* how a manifest receiver would start a new process in order to run a broadcast receiver.
*
* Ordered broadcasts, alarm broadcasts, interactive broadcasts, and manifest broadcasts are
* never deferred.
*
* Unordered broadcasts and unordered broadcasts with completion callbacks may be
* deferred. Completion callbacks for broadcasts deferred until active are
* best-effort. Completion callbacks will run when all eligible processes have finished
* executing the broadcast. Processes in inactive process states that defer the broadcast are
* not considered eligible and may not execute the broadcast prior to the completion callback.
*
* @hide
*/
@SystemApi
public @NonNull BroadcastOptions setDeferUntilActive(boolean shouldDefer) {
mIsDeferUntilActive = shouldDefer;
return this;
}
/** @hide */
@SystemApi
public boolean isDeferUntilActive() {
return mIsDeferUntilActive;
}
/**
* When enqueuing this broadcast, remove all pending broadcasts previously
* sent by this app which match the given filter.
@@ -963,6 +1006,9 @@ public class BroadcastOptions extends ComponentOptions {
if (mDeliveryGroupMatchingFilter != null) {
b.putParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER, mDeliveryGroupMatchingFilter);
}
if (mIsDeferUntilActive) {
b.putBoolean(KEY_DEFER_UNTIL_ACTIVE, mIsDeferUntilActive);
}
return b.isEmpty() ? null : b;
}
}

View File

@@ -40,6 +40,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.WindowConfiguration;
@@ -388,6 +389,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
private static final Bundle USER_PRESENT_INTENT_OPTIONS =
BroadcastOptions.makeBasic()
.setDeferUntilActive(true)
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
.toBundle();
/**
* {@link #setKeyguardEnabled} waits on this condition when it re-enables
* the keyguard.
@@ -2300,7 +2307,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
Context.USER_SERVICE);
mUiBgExecutor.execute(() -> {
for (int profileId : um.getProfileIdsWithDisabled(currentUser.getIdentifier())) {
mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, UserHandle.of(profileId));
mContext.sendBroadcastAsUser(USER_PRESENT_INTENT,
UserHandle.of(profileId),
null,
USER_PRESENT_INTENT_OPTIONS);
}
mLockPatternUtils.userPresent(currentUserId);
});

View File

@@ -189,15 +189,20 @@ public final class BatteryService extends SystemService {
private long mLastBatteryLevelChangedSentMs;
private Bundle mBatteryChangedOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).toBundle();
new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).setDeferUntilActive(true)
.toBundle();
private Bundle mPowerConnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).toBundle();
new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).setDeferUntilActive(true)
.toBundle();
private Bundle mPowerDisconnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_POWER_CONNECTED)).toBundle();
new IntentFilter(Intent.ACTION_POWER_CONNECTED)).setDeferUntilActive(true)
.toBundle();
private Bundle mBatteryLowOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_BATTERY_OKAY)).toBundle();
new IntentFilter(Intent.ACTION_BATTERY_OKAY)).setDeferUntilActive(true)
.toBundle();
private Bundle mBatteryOkayOptions = BroadcastOptions.makeRemovingMatchingFilter(
new IntentFilter(Intent.ACTION_BATTERY_LOW)).toBundle();
new IntentFilter(Intent.ACTION_BATTERY_LOW)).setDeferUntilActive(true)
.toBundle();
private MetricsLogger mMetricsLogger;

View File

@@ -14108,8 +14108,16 @@ public class ActivityManagerService extends IActivityManager.Stub
if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
(sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
+ " ordered=" + ordered + " userid=" + userId);
if ((resultTo != null) && !ordered && !mEnableModernQueue) {
Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
if ((resultTo != null) && !ordered) {
if (!mEnableModernQueue) {
Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
}
if (!UserHandle.isCore(callingUid)) {
String msg = "Unauthorized unordered resultTo broadcast "
+ intent + " sent from uid " + callingUid;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
}
userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
@@ -14190,6 +14198,18 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
// resultTo broadcasts are always infinitely deferrable.
if ((resultTo != null) && !ordered && mEnableModernQueue) {
if (brOptions == null) {
brOptions = BroadcastOptions.makeBasic();
}
brOptions.setDeferUntilActive(true);
}
if (ordered && brOptions != null && brOptions.isDeferUntilActive()) {
throw new IllegalArgumentException("Ordered broadcasts can't be deferred until active");
}
// Verify that protected broadcasts are only being sent by system code,
// and that system code is only sending protected broadcasts.
final boolean isProtectedBroadcast;

View File

@@ -62,6 +62,7 @@ import java.util.Objects;
*/
// @NotThreadSafe
class BroadcastProcessQueue {
static final boolean VERBOSE = false;
final @NonNull BroadcastConstants constants;
final @NonNull String processName;
final int uid;
@@ -168,10 +169,14 @@ class BroadcastProcessQueue {
/**
* Count of pending broadcasts of these various flavors.
*/
private int mCountEnqueued;
private int mCountDeferred;
private int mCountForeground;
private int mCountForegroundDeferred;
private int mCountOrdered;
private int mCountAlarm;
private int mCountPrioritized;
private int mCountPrioritizedDeferred;
private int mCountInteractive;
private int mCountResultTo;
private int mCountInstrumented;
@@ -226,10 +231,10 @@ class BroadcastProcessQueue {
* used for ordered broadcasts and priority traunches.
*/
public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
@NonNull BroadcastConsumer replacedBroadcastConsumer) {
@NonNull BroadcastConsumer replacedBroadcastConsumer, boolean wouldBeSkipped) {
if (record.isReplacePending()) {
final boolean didReplace = replaceBroadcast(record, recordIndex,
replacedBroadcastConsumer);
replacedBroadcastConsumer, wouldBeSkipped);
if (didReplace) {
return;
}
@@ -240,13 +245,14 @@ class BroadcastProcessQueue {
SomeArgs newBroadcastArgs = SomeArgs.obtain();
newBroadcastArgs.arg1 = record;
newBroadcastArgs.argi1 = recordIndex;
newBroadcastArgs.argi2 = (wouldBeSkipped ? 1 : 0);
// Cross-broadcast prioritization policy: some broadcasts might warrant being
// issued ahead of others that are already pending, for example if this new
// broadcast is in a different delivery class or is tied to a direct user interaction
// with implicit responsiveness expectations.
getQueueForBroadcast(record).addLast(newBroadcastArgs);
onBroadcastEnqueued(record, recordIndex);
onBroadcastEnqueued(record, recordIndex, wouldBeSkipped);
}
/**
@@ -258,11 +264,12 @@ class BroadcastProcessQueue {
* {@code false} otherwise.
*/
private boolean replaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
@NonNull BroadcastConsumer replacedBroadcastConsumer) {
@NonNull BroadcastConsumer replacedBroadcastConsumer, boolean wouldBeSkipped) {
final int count = mPendingQueues.size();
for (int i = 0; i < count; ++i) {
final ArrayDeque<SomeArgs> queue = mPendingQueues.get(i);
if (replaceBroadcastInQueue(queue, record, recordIndex, replacedBroadcastConsumer)) {
if (replaceBroadcastInQueue(queue, record, recordIndex,
replacedBroadcastConsumer, wouldBeSkipped)) {
return true;
}
}
@@ -279,13 +286,15 @@ class BroadcastProcessQueue {
*/
private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
@NonNull BroadcastRecord record, int recordIndex,
@NonNull BroadcastConsumer replacedBroadcastConsumer) {
@NonNull BroadcastConsumer replacedBroadcastConsumer,
boolean wouldBeSkipped) {
final Iterator<SomeArgs> it = queue.descendingIterator();
final Object receiver = record.receivers.get(recordIndex);
while (it.hasNext()) {
final SomeArgs args = it.next();
final BroadcastRecord testRecord = (BroadcastRecord) args.arg1;
final int testRecordIndex = args.argi1;
final boolean testWouldBeSkipped = (args.argi2 == 1);
final Object testReceiver = testRecord.receivers.get(testRecordIndex);
if ((record.callingUid == testRecord.callingUid)
&& (record.userId == testRecord.userId)
@@ -295,9 +304,10 @@ class BroadcastProcessQueue {
// Exact match found; perform in-place swap
args.arg1 = record;
args.argi1 = recordIndex;
args.argi2 = (wouldBeSkipped ? 1 : 0);
record.copyEnqueueTimeFrom(testRecord);
onBroadcastDequeued(testRecord, testRecordIndex);
onBroadcastEnqueued(record, recordIndex);
onBroadcastDequeued(testRecord, testRecordIndex, testWouldBeSkipped);
onBroadcastEnqueued(record, recordIndex, wouldBeSkipped);
replacedBroadcastConsumer.accept(testRecord, testRecordIndex);
return true;
}
@@ -352,12 +362,13 @@ class BroadcastProcessQueue {
final SomeArgs args = it.next();
final BroadcastRecord record = (BroadcastRecord) args.arg1;
final int recordIndex = args.argi1;
final boolean recordWouldBeSkipped = (args.argi2 == 1);
if (predicate.test(record, recordIndex)) {
consumer.accept(record, recordIndex);
if (andRemove) {
args.recycle();
it.remove();
onBroadcastDequeued(record, recordIndex);
onBroadcastDequeued(record, recordIndex, recordWouldBeSkipped);
}
didSomething = true;
}
@@ -423,7 +434,7 @@ class BroadcastProcessQueue {
}
public int getPreferredSchedulingGroupLocked() {
if (mCountForeground > 0) {
if (mCountForeground > mCountForegroundDeferred) {
// We have a foreground broadcast somewhere down the queue, so
// boost priority until we drain them all
return ProcessList.SCHED_GROUP_DEFAULT;
@@ -469,10 +480,11 @@ class BroadcastProcessQueue {
final SomeArgs next = removeNextBroadcast();
mActive = (BroadcastRecord) next.arg1;
mActiveIndex = next.argi1;
final boolean wouldBeSkipped = (next.argi2 == 1);
mActiveCountSinceIdle++;
mActiveViaColdStart = false;
next.recycle();
onBroadcastDequeued(mActive, mActiveIndex);
onBroadcastDequeued(mActive, mActiveIndex, wouldBeSkipped);
}
/**
@@ -489,8 +501,16 @@ class BroadcastProcessQueue {
/**
* Update summary statistics when the given record has been enqueued.
*/
private void onBroadcastEnqueued(@NonNull BroadcastRecord record, int recordIndex) {
private void onBroadcastEnqueued(@NonNull BroadcastRecord record, int recordIndex,
boolean wouldBeSkipped) {
mCountEnqueued++;
if (record.deferUntilActive) {
mCountDeferred++;
}
if (record.isForeground()) {
if (record.deferUntilActive) {
mCountForegroundDeferred++;
}
mCountForeground++;
}
if (record.ordered) {
@@ -500,6 +520,9 @@ class BroadcastProcessQueue {
mCountAlarm++;
}
if (record.prioritized) {
if (record.deferUntilActive) {
mCountPrioritizedDeferred++;
}
mCountPrioritized++;
}
if (record.interactive) {
@@ -511,7 +534,8 @@ class BroadcastProcessQueue {
if (record.callerInstrumented) {
mCountInstrumented++;
}
if (record.receivers.get(recordIndex) instanceof ResolveInfo) {
if (!wouldBeSkipped
&& (record.receivers.get(recordIndex) instanceof ResolveInfo)) {
mCountManifest++;
}
invalidateRunnableAt();
@@ -520,8 +544,16 @@ class BroadcastProcessQueue {
/**
* Update summary statistics when the given record has been dequeued.
*/
private void onBroadcastDequeued(@NonNull BroadcastRecord record, int recordIndex) {
private void onBroadcastDequeued(@NonNull BroadcastRecord record, int recordIndex,
boolean wouldBeSkipped) {
mCountEnqueued--;
if (record.deferUntilActive) {
mCountDeferred--;
}
if (record.isForeground()) {
if (record.deferUntilActive) {
mCountForegroundDeferred--;
}
mCountForeground--;
}
if (record.ordered) {
@@ -531,6 +563,9 @@ class BroadcastProcessQueue {
mCountAlarm--;
}
if (record.prioritized) {
if (record.deferUntilActive) {
mCountPrioritizedDeferred--;
}
mCountPrioritized--;
}
if (record.interactive) {
@@ -542,7 +577,8 @@ class BroadcastProcessQueue {
if (record.callerInstrumented) {
mCountInstrumented--;
}
if (record.receivers.get(recordIndex) instanceof ResolveInfo) {
if (!wouldBeSkipped
&& (record.receivers.get(recordIndex) instanceof ResolveInfo)) {
mCountManifest--;
}
invalidateRunnableAt();
@@ -741,6 +777,15 @@ class BroadcastProcessQueue {
return mRunnableAt != Long.MAX_VALUE;
}
public boolean isDeferredUntilActive() {
if (mRunnableAtInvalidated) updateRunnableAt();
return mRunnableAtReason == BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER;
}
public boolean hasDeferredBroadcasts() {
return (mCountDeferred > 0);
}
/**
* Return time at which this process is considered runnable. This is
* typically the time at which the next pending broadcast was first
@@ -776,6 +821,7 @@ class BroadcastProcessQueue {
static final int REASON_INSTRUMENTED = 5;
static final int REASON_PERSISTENT = 6;
static final int REASON_FORCE_DELAYED = 7;
static final int REASON_CACHED_INFINITE_DEFER = 8;
static final int REASON_CONTAINS_FOREGROUND = 10;
static final int REASON_CONTAINS_ORDERED = 11;
static final int REASON_CONTAINS_ALARM = 12;
@@ -794,6 +840,7 @@ class BroadcastProcessQueue {
REASON_INSTRUMENTED,
REASON_PERSISTENT,
REASON_FORCE_DELAYED,
REASON_CACHED_INFINITE_DEFER,
REASON_CONTAINS_FOREGROUND,
REASON_CONTAINS_ORDERED,
REASON_CONTAINS_ALARM,
@@ -816,6 +863,7 @@ class BroadcastProcessQueue {
case REASON_INSTRUMENTED: return "INSTRUMENTED";
case REASON_PERSISTENT: return "PERSISTENT";
case REASON_FORCE_DELAYED: return "FORCE_DELAYED";
case REASON_CACHED_INFINITE_DEFER: return "INFINITE_DEFER";
case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
@@ -831,9 +879,16 @@ class BroadcastProcessQueue {
private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
int existingDeferredCount = 0;
if (r.deferUntilActive) {
for (int i = 0; i < index; i++) {
if (r.deferredUntilActive[i]) existingDeferredCount++;
}
}
// We might be blocked waiting for other receivers to finish,
// typically for an ordered broadcast or priority traunches
if (r.terminalCount < blockedUntilTerminalCount
if ((r.terminalCount + existingDeferredCount) < blockedUntilTerminalCount
&& !isDeliveryStateTerminal(r.getDeliveryState(index))) {
return true;
}
@@ -862,7 +917,7 @@ class BroadcastProcessQueue {
if (mForcedDelayedDurationMs > 0) {
mRunnableAt = runnableAt + mForcedDelayedDurationMs;
mRunnableAtReason = REASON_FORCE_DELAYED;
} else if (mCountForeground > 0) {
} else if (mCountForeground > mCountForegroundDeferred) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_CONTAINS_FOREGROUND;
} else if (mCountInteractive > 0) {
@@ -880,12 +935,9 @@ class BroadcastProcessQueue {
} else if (mCountAlarm > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_ALARM;
} else if (mCountPrioritized > 0) {
} else if (mCountPrioritized > mCountPrioritizedDeferred) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_PRIORITIZED;
} else if (mCountResultTo > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
} else if (mCountManifest > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_MANIFEST;
@@ -893,8 +945,39 @@ class BroadcastProcessQueue {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_PERSISTENT;
} else if (mProcessCached) {
mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
mRunnableAtReason = REASON_CACHED;
if (r.deferUntilActive) {
// All enqueued broadcasts are deferrable, defer
if (mCountDeferred == mCountEnqueued) {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_CACHED_INFINITE_DEFER;
} else {
// At least one enqueued broadcast isn't deferrable, repick time and reason
// for this record. If a later record is not deferrable and is one of these
// special cases, one of the cases above would have already caught that.
if (r.isForeground()) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_CONTAINS_FOREGROUND;
} else if (r.prioritized) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_PRIORITIZED;
} else if (r.resultTo != null) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
} else {
mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
mRunnableAtReason = REASON_CACHED;
}
}
} else {
// This record isn't deferrable
mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
mRunnableAtReason = REASON_CACHED;
}
} else if (mCountResultTo > 0) {
// All resultTo broadcasts are infinitely deferrable, so if the app
// is already cached, they'll be deferred on the line above
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
} else {
mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS;
mRunnableAtReason = REASON_NORMAL;
@@ -907,6 +990,15 @@ class BroadcastProcessQueue {
mRunnableAt = Math.min(mRunnableAt, runnableAt);
mRunnableAtReason = REASON_MAX_PENDING;
}
if (VERBOSE) {
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, "BroadcastQueue",
((app != null) ? app.processName : "(null)")
+ ":" + r.intent.toString() + ":"
+ r.deferUntilActive
+ ":" + mRunnableAt + " " + reasonToString(mRunnableAtReason)
+ ":" + ((app != null) ? app.isCached() : "false"));
}
} else {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_EMPTY;

View File

@@ -385,6 +385,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// If app isn't running, and there's nothing in the queue, clean up
if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
removeProcessQueue(queue.processName, queue.uid);
} else {
updateQueueDeferred(queue);
}
}
@@ -637,11 +639,34 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final ArraySet<BroadcastRecord> replacedBroadcasts = new ArraySet<>();
final BroadcastConsumer replacedBroadcastConsumer =
(record, i) -> replacedBroadcasts.add(record);
boolean enqueuedBroadcast = false;
for (int i = 0; i < r.receivers.size(); i++) {
final Object receiver = r.receivers.get(i);
final BroadcastProcessQueue queue = getOrCreateProcessQueue(
getReceiverProcessName(receiver), getReceiverUid(receiver));
queue.enqueueOrReplaceBroadcast(r, i, replacedBroadcastConsumer);
boolean wouldBeSkipped = false;
if (receiver instanceof ResolveInfo) {
// If the app is running but would not have been started if the process weren't
// running, we're going to deliver the broadcast but mark that it's not a manifest
// broadcast that would have started the app. This allows BroadcastProcessQueue to
// defer the broadcast as though it were a normal runtime receiver.
wouldBeSkipped = (mSkipPolicy.shouldSkipMessage(r, receiver) != null);
if (wouldBeSkipped && queue.app == null && !queue.getActiveViaColdStart()) {
// Skip receiver if there's no running app, the app is not being started, and
// the app wouldn't be launched for this broadcast
setDeliveryState(null, null, r, i, receiver, BroadcastRecord.DELIVERY_SKIPPED,
"skipped by policy to avoid cold start");
continue;
}
}
enqueuedBroadcast = true;
queue.enqueueOrReplaceBroadcast(r, i, replacedBroadcastConsumer, wouldBeSkipped);
if (r.isDeferUntilActive() && queue.isDeferredUntilActive()) {
setDeliveryState(queue, null, r, i, receiver, BroadcastRecord.DELIVERY_DEFERRED,
"deferred at enqueue time");
}
updateRunnableList(queue);
enqueueUpdateRunningList();
}
@@ -651,7 +676,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
skipAndCancelReplacedBroadcasts(replacedBroadcasts);
// If nothing to dispatch, send any pending result immediately
if (r.receivers.isEmpty()) {
if (r.receivers.isEmpty() || !enqueuedBroadcast) {
scheduleResultTo(r);
notifyFinishBroadcast(r);
}
@@ -1195,11 +1220,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
@NonNull Object receiver, @DeliveryState int newDeliveryState, String reason) {
final int cookie = traceBegin("setDeliveryState");
final int oldDeliveryState = getDeliveryState(r, index);
boolean checkFinished = false;
// Only apply state when we haven't already reached a terminal state;
// this is how we ignore racing timeout messages
if (!isDeliveryStateTerminal(oldDeliveryState)) {
r.setDeliveryState(index, newDeliveryState);
if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
r.deferredCount--;
} else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
// If we're deferring a broadcast, maybe that's enough to unblock the final callback
r.deferredCount++;
checkFinished = true;
}
}
// Emit any relevant tracing results when we're changing the delivery
@@ -1217,7 +1250,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// bookkeeping to update for ordered broadcasts
if (!isDeliveryStateTerminal(oldDeliveryState)
&& isDeliveryStateTerminal(newDeliveryState)) {
if (newDeliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
if (DEBUG_BROADCAST
&& newDeliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
logw("Delivery state of " + r + " to " + receiver
+ " via " + app + " changed from "
+ deliveryStateToString(oldDeliveryState) + " to "
@@ -1226,9 +1260,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
r.terminalCount++;
notifyFinishReceiver(queue, r, index, receiver);
// When entire ordered broadcast finished, deliver final result
final boolean recordFinished = (r.terminalCount == r.receivers.size());
checkFinished = true;
}
// When entire ordered broadcast finished, deliver final result
if (checkFinished) {
final boolean recordFinished =
((r.terminalCount + r.deferredCount) == r.receivers.size());
if (recordFinished) {
scheduleResultTo(r);
}
@@ -1329,6 +1366,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
r.resultExtras = null;
};
private final BroadcastConsumer mBroadcastConsumerDefer = (r, i) -> {
setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_DEFERRED,
"mBroadcastConsumerDefer");
};
private final BroadcastConsumer mBroadcastConsumerUndoDefer = (r, i) -> {
setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_PENDING,
"mBroadcastConsumerUndoDefer");
};
/**
* Verify that all known {@link #mProcessQueues} are in the state tested by
* the given {@link Predicate}.
@@ -1392,6 +1439,21 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
}
private void updateQueueDeferred(
@NonNull BroadcastProcessQueue leaf) {
if (leaf.isDeferredUntilActive()) {
leaf.forEachMatchingBroadcast((r, i) -> {
return r.deferUntilActive && (r.getDeliveryState(i)
== BroadcastRecord.DELIVERY_PENDING);
}, mBroadcastConsumerDefer, false);
} else if (leaf.hasDeferredBroadcasts()) {
leaf.forEachMatchingBroadcast((r, i) -> {
return r.deferUntilActive && (r.getDeliveryState(i)
== BroadcastRecord.DELIVERY_DEFERRED);
}, mBroadcastConsumerUndoDefer, false);
}
}
@Override
public void start(@NonNull ContentResolver resolver) {
mFgConstants.startObserving(mHandler, resolver);
@@ -1404,6 +1466,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
BroadcastProcessQueue leaf = mProcessQueues.get(uid);
while (leaf != null) {
leaf.setProcessCached(cached);
updateQueueDeferred(leaf);
updateRunnableList(leaf);
leaf = leaf.processNameNext;
}

View File

@@ -88,6 +88,7 @@ final class BroadcastRecord extends Binder {
final boolean interactive; // originated from user interaction?
final boolean initialSticky; // initial broadcast from register to sticky?
final boolean prioritized; // contains more than one priority tranche
final boolean deferUntilActive; // infinitely deferrable broadcast
final int userId; // user id this broadcast was for
final @Nullable String resolvedType; // the resolved data type
final @Nullable String[] requiredPermissions; // permissions the caller has required
@@ -97,6 +98,7 @@ final class BroadcastRecord extends Binder {
final @Nullable BroadcastOptions options; // BroadcastOptions supplied by caller
final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo
final @DeliveryState int[] delivery; // delivery state of each receiver
final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred
final int[] blockedUntilTerminalCount; // blocked until count of each receiver
@Nullable ProcessRecord resultToApp; // who receives final result if non-null
@Nullable IIntentReceiver resultTo; // who receives final result if non-null
@@ -130,6 +132,7 @@ final class BroadcastRecord extends Binder {
int manifestCount; // number of manifest receivers dispatched.
int manifestSkipCount; // number of manifest receivers skipped.
int terminalCount; // number of receivers in terminal state.
int deferredCount; // number of receivers in deferred state.
@Nullable BroadcastQueue queue; // the outbound queue handling this broadcast
// if set to true, app's process will be temporarily allowed to start activities from background
@@ -168,6 +171,8 @@ final class BroadcastRecord extends Binder {
static final int DELIVERY_SCHEDULED = 4;
/** Terminal state: failure to dispatch */
static final int DELIVERY_FAILURE = 5;
/** Intermediate state: currently deferred while app is cached */
static final int DELIVERY_DEFERRED = 6;
@IntDef(flag = false, prefix = { "DELIVERY_" }, value = {
DELIVERY_PENDING,
@@ -176,6 +181,7 @@ final class BroadcastRecord extends Binder {
DELIVERY_TIMEOUT,
DELIVERY_SCHEDULED,
DELIVERY_FAILURE,
DELIVERY_DEFERRED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeliveryState {}
@@ -188,6 +194,7 @@ final class BroadcastRecord extends Binder {
case DELIVERY_TIMEOUT: return "TIMEOUT";
case DELIVERY_SCHEDULED: return "SCHEDULED";
case DELIVERY_FAILURE: return "FAILURE";
case DELIVERY_DEFERRED: return "DEFERRED";
default: return Integer.toString(deliveryState);
}
}
@@ -388,6 +395,8 @@ final class BroadcastRecord extends Binder {
options = _options;
receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
delivery = new int[_receivers != null ? _receivers.size() : 0];
deferUntilActive = options != null ? options.isDeferUntilActive() : false;
deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
scheduledTime = new long[delivery.length];
terminalTime = new long[delivery.length];
@@ -443,6 +452,8 @@ final class BroadcastRecord extends Binder {
options = from.options;
receivers = from.receivers;
delivery = from.delivery;
deferUntilActive = from.deferUntilActive;
deferredUntilActive = from.deferredUntilActive;
blockedUntilTerminalCount = from.blockedUntilTerminalCount;
scheduledTime = from.scheduledTime;
terminalTime = from.terminalTime;
@@ -606,7 +617,7 @@ final class BroadcastRecord extends Binder {
*/
void setDeliveryState(int index, @DeliveryState int deliveryState) {
delivery[index] = deliveryState;
if (deferUntilActive) deferredUntilActive[index] = false;
switch (deliveryState) {
case DELIVERY_DELIVERED:
case DELIVERY_SKIPPED:
@@ -617,6 +628,9 @@ final class BroadcastRecord extends Binder {
case DELIVERY_SCHEDULED:
scheduledTime[index] = SystemClock.uptimeMillis();
break;
case DELIVERY_DEFERRED:
if (deferUntilActive) deferredUntilActive[index] = true;
break;
}
}
@@ -647,6 +661,10 @@ final class BroadcastRecord extends Binder {
return (intent.getFlags() & Intent.FLAG_RECEIVER_OFFLOAD) != 0;
}
boolean isDeferUntilActive() {
return deferUntilActive;
}
/**
* Core policy determination about this broadcast's delivery prioritization
*/

View File

@@ -229,12 +229,7 @@ public class BroadcastSkipPolicy {
return "Background execution disabled: receiving "
+ r.intent + " to "
+ component.flattenToShortString();
} 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))) {
} else if (disallowBackgroundStart(r)) {
mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
component.getPackageName());
return "Background execution not allowed: receiving "
@@ -340,6 +335,18 @@ public class BroadcastSkipPolicy {
return null;
}
/**
* Determine if the given {@link BroadcastRecord} is eligible to launch processes.
*/
public boolean disallowBackgroundStart(@NonNull BroadcastRecord r) {
return ((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));
}
/**
* Determine if the given {@link BroadcastRecord} is eligible to be sent to
* the given {@link BroadcastFilter}.
@@ -624,7 +631,7 @@ public class BroadcastSkipPolicy {
/**
* Return true if all given permissions are signature-only perms.
*/
private boolean isSignaturePerm(String[] perms) {
private static boolean isSignaturePerm(String[] perms) {
if (perms == null) {
return false;
}

View File

@@ -22,7 +22,6 @@ import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_INTERA
import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_MANIFEST;
import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_ORDERED;
import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_PRIORITIZED;
import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_RESULT_TO;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
import static com.android.server.am.BroadcastQueueTest.CLASS_BLUE;
@@ -115,8 +114,29 @@ public class BroadcastQueueModernImplTest {
mConstants.DELAY_NORMAL_MILLIS = 10_000;
mConstants.DELAY_CACHED_MILLIS = 120_000;
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;
}
public boolean disallowBackgroundStart(BroadcastRecord r) {
// Ignored
return false;
}
};
final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
public void addBroadcastToHistoryLocked(BroadcastRecord original) {
// Ignored
}
};
mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
mConstants, mConstants);
mConstants, mConstants, emptySkipPolicy, emptyHistory);
doReturn(1L).when(mQueue1).getRunnableAt();
doReturn(2L).when(mQueue2).getRunnableAt();
@@ -200,7 +220,8 @@ public class BroadcastQueueModernImplTest {
private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
BroadcastRecord record, int recordIndex, long enqueueTime) {
queue.enqueueOrReplaceBroadcast(record, recordIndex, null /* replacedBroadcastConsumer */);
queue.enqueueOrReplaceBroadcast(record, recordIndex,
null /* replacedBroadcastConsumer */, false);
record.enqueueTime = enqueueTime;
}
@@ -330,7 +351,8 @@ public class BroadcastQueueModernImplTest {
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
List.of(makeMockRegisteredReceiver()));
queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, null /* replacedBroadcastConsumer */);
queue.enqueueOrReplaceBroadcast(airplaneRecord, 0,
null /* replacedBroadcastConsumer */, false);
queue.setProcessCached(false);
final long notCachedRunnableAt = queue.getRunnableAt();
@@ -352,12 +374,14 @@ public class BroadcastQueueModernImplTest {
// enqueue a bg-priority broadcast then a fg-priority one
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, null /* replacedBroadcastConsumer */);
queue.enqueueOrReplaceBroadcast(timezoneRecord, 0,
null /* replacedBroadcastConsumer */, false);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, null /* replacedBroadcastConsumer */);
queue.enqueueOrReplaceBroadcast(airplaneRecord, 0,
null /* replacedBroadcastConsumer */, false);
// verify that:
// (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -388,7 +412,8 @@ public class BroadcastQueueModernImplTest {
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, null /* replacedBroadcastConsumer */);
queue.enqueueOrReplaceBroadcast(airplaneRecord, 1,
null /* replacedBroadcastConsumer */, false);
assertFalse(queue.isRunnable());
assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -411,7 +436,8 @@ public class BroadcastQueueModernImplTest {
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
List.of(makeMockRegisteredReceiver()));
queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, null /* replacedBroadcastConsumer */);
queue.enqueueOrReplaceBroadcast(airplaneRecord, 0,
null /* replacedBroadcastConsumer */, false);
mConstants.MAX_PENDING_BROADCASTS = 128;
queue.invalidateRunnableAt();
@@ -437,11 +463,13 @@ public class BroadcastQueueModernImplTest {
new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
List.of(makeMockRegisteredReceiver()));
queue.enqueueOrReplaceBroadcast(lazyRecord, 0, null /* replacedBroadcastConsumer */);
queue.enqueueOrReplaceBroadcast(lazyRecord, 0,
null /* replacedBroadcastConsumer */, false);
assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime);
assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason);
queue.enqueueOrReplaceBroadcast(testRecord, 0, null /* replacedBroadcastConsumer */);
queue.enqueueOrReplaceBroadcast(testRecord, 0,
null /* replacedBroadcastConsumer */, false);
assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime);
assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason);
}
@@ -458,13 +486,6 @@ public class BroadcastQueueModernImplTest {
List.of(makeMockRegisteredReceiver()), null, true), REASON_CONTAINS_ORDERED);
}
@Test
public void testRunnableAt_Cached_ResultTo() {
final IIntentReceiver resultTo = mock(IIntentReceiver.class);
doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
List.of(makeMockRegisteredReceiver()), resultTo, false), REASON_CONTAINS_RESULT_TO);
}
@Test
public void testRunnableAt_Cached_Foreground() {
final Intent foregroundIntent = new Intent();
@@ -511,25 +532,25 @@ public class BroadcastQueueModernImplTest {
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
.addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0,
null /* replacedBroadcastConsumer */);
null /* replacedBroadcastConsumer */, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0,
null /* replacedBroadcastConsumer */);
null /* replacedBroadcastConsumer */, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0,
null /* replacedBroadcastConsumer */);
null /* replacedBroadcastConsumer */, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
.addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0,
null /* replacedBroadcastConsumer */);
null /* replacedBroadcastConsumer */, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0,
null /* replacedBroadcastConsumer */);
null /* replacedBroadcastConsumer */, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0,
null /* replacedBroadcastConsumer */);
null /* replacedBroadcastConsumer */, false);
queue.makeActiveNextPending();
assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());

View File

@@ -357,6 +357,11 @@ public class BroadcastQueueTest {
}
receiversToSkip.add(o);
}
public boolean disallowBackgroundStart(BroadcastRecord r) {
// Ignored
return false;
}
}
private class TestInjector extends Injector {