blob: 99b91ffb1c2fcd4faea9918fcc270a784efac301 [file] [log] [blame]
/*
* Copyright (C) 2006 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_BACKGROUND_RESTRICTED;
import static android.app.AppProtoEnums.BROADCAST_TYPE_ALARM;
import static android.app.AppProtoEnums.BROADCAST_TYPE_BACKGROUND;
import static android.app.AppProtoEnums.BROADCAST_TYPE_DEFERRABLE_UNTIL_ACTIVE;
import static android.app.AppProtoEnums.BROADCAST_TYPE_FOREGROUND;
import static android.app.AppProtoEnums.BROADCAST_TYPE_INITIAL_STICKY;
import static android.app.AppProtoEnums.BROADCAST_TYPE_INTERACTIVE;
import static android.app.AppProtoEnums.BROADCAST_TYPE_NONE;
import static android.app.AppProtoEnums.BROADCAST_TYPE_ORDERED;
import static android.app.AppProtoEnums.BROADCAST_TYPE_PRIORITIZED;
import static android.app.AppProtoEnums.BROADCAST_TYPE_PUSH_MESSAGE;
import static android.app.AppProtoEnums.BROADCAST_TYPE_PUSH_MESSAGE_OVER_QUOTA;
import static android.app.AppProtoEnums.BROADCAST_TYPE_RESULT_TO;
import static android.app.AppProtoEnums.BROADCAST_TYPE_STICKY;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_CHANGE_ID;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
import android.annotation.CheckResult;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
import android.app.ActivityManager.ProcessState;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.BroadcastOptions.DeliveryGroupPolicy;
import android.app.compat.CompatChanges;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.PrintWriterPrinter;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import dalvik.annotation.optimization.NeverCompile;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
/**
* An active intent broadcast.
*/
final class BroadcastRecord extends Binder {
final @NonNull Intent intent; // the original intent that generated us
final @Nullable ComponentName targetComp; // original component name set on the intent
final @Nullable ProcessRecord callerApp; // process that sent this
final @Nullable String callerPackage; // who sent this
final @Nullable String callerFeatureId; // which feature in the package sent this
final int callingPid; // the pid of who sent this
final int callingUid; // the uid of who sent this
final @ProcessState int callerProcState; // Procstate of the caller process at enqueue time.
final int originalStickyCallingUid;
// if this is a sticky broadcast, the Uid of the original sender
final boolean callerInstantApp; // caller is an Instant App?
final boolean callerInstrumented; // caller is being instrumented?
final boolean ordered; // serialize the send to receivers?
final boolean sticky; // originated from existing sticky data?
final boolean alarm; // originated from an alarm triggering?
final boolean pushMessage; // originated from a push message?
final boolean pushMessageOverQuota; // originated from a push message which was over quota?
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 boolean shareIdentity; // whether the broadcaster's identity should be shared
final boolean urgent; // has been classified as "urgent"
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
final @Nullable String[] excludedPermissions; // permissions to exclude
final @Nullable String[] excludedPackages; // packages to exclude
final int appOp; // an app op that is associated with this broadcast
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 @NonNull String[] deliveryReasons; // reasons for delivery state of each receiver
final int[] blockedUntilBeyondCount; // 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
boolean deferred;
int splitCount; // refcount for result callback, when split
int splitToken; // identifier for cross-BroadcastRecord refcount
@UptimeMillisLong long enqueueTime; // when broadcast enqueued
@ElapsedRealtimeLong long enqueueRealTime; // when broadcast enqueued
@CurrentTimeMillisLong long enqueueClockTime; // when broadcast enqueued
// When broadcast is originally enqueued. Only used in case of replacing broadcasts
// with FLAG_RECEIVER_REPLACE_PENDING. If it is 0, then 'enqueueClockTime' is the original
// enqueue time.
@UptimeMillisLong long originalEnqueueClockTime;
@UptimeMillisLong long dispatchTime; // when broadcast dispatch started
@ElapsedRealtimeLong long dispatchRealTime; // when broadcast dispatch started
@CurrentTimeMillisLong long dispatchClockTime; // when broadcast dispatch started
@UptimeMillisLong long receiverTime; // when receiver started for timeouts
@UptimeMillisLong long finishTime; // when broadcast finished
final @UptimeMillisLong long[] scheduledTime; // when each receiver was scheduled
final @UptimeMillisLong long[] terminalTime; // when each receiver was terminal
final boolean timeoutExempt; // true if this broadcast is not subject to receiver timeouts
int resultCode; // current result code value.
@Nullable String resultData; // current result data value.
@Nullable Bundle resultExtras; // current result extra data values.
boolean resultAbort; // current result abortBroadcast value.
int nextReceiver; // next receiver to be executed.
int state;
int anrCount; // has this broadcast record hit any ANRs?
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.
int beyondCount; // high-water number of receivers we've moved beyond.
@Nullable BroadcastQueue queue; // the outbound queue handling this broadcast
// Determines the privileges the app's process has in regard to background starts.
final BackgroundStartPrivileges mBackgroundStartPrivileges;
// Filter the intent extras by using the rules of the package visibility before broadcasting
// the intent to the receiver.
@Nullable
final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
// Cache of records that are "matching" this. Only used at the time of enqueuing this record
// into the queue.
@Nullable
private ArrayMap<BroadcastRecord, Boolean> mMatchingRecordsCache;
private @Nullable String mCachedToString;
private @Nullable String mCachedToShortString;
/**
* When enabled, assume that {@link UserHandle#isCore(int)} apps should
* treat {@link BroadcastOptions#DEFERRAL_POLICY_DEFAULT} as
* {@link BroadcastOptions#DEFERRAL_POLICY_UNTIL_ACTIVE}.
*/
static boolean CORE_DEFER_UNTIL_ACTIVE = false;
/** Empty immutable list of receivers */
static final List<Object> EMPTY_RECEIVERS = List.of();
static final int IDLE = 0;
static final int APP_RECEIVE = 1;
static final int CALL_IN_RECEIVE = 2;
static final int CALL_DONE_RECEIVE = 3;
static final int WAITING_SERVICES = 4;
/** Initial state: waiting to run in future */
static final int DELIVERY_PENDING = 0;
/** Terminal state: finished successfully */
static final int DELIVERY_DELIVERED = 1;
/** Terminal state: skipped due to internal policy */
static final int DELIVERY_SKIPPED = 2;
/** Terminal state: timed out during attempted delivery */
static final int DELIVERY_TIMEOUT = 3;
/** Intermediate state: currently executing */
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,
DELIVERY_DELIVERED,
DELIVERY_SKIPPED,
DELIVERY_TIMEOUT,
DELIVERY_SCHEDULED,
DELIVERY_FAILURE,
DELIVERY_DEFERRED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeliveryState {}
static @NonNull String deliveryStateToString(@DeliveryState int deliveryState) {
switch (deliveryState) {
case DELIVERY_PENDING: return "PENDING";
case DELIVERY_DELIVERED: return "DELIVERED";
case DELIVERY_SKIPPED: return "SKIPPED";
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);
}
}
/**
* Return if the given delivery state is "terminal", where no additional
* delivery state changes will be made.
*/
static boolean isDeliveryStateTerminal(@DeliveryState int deliveryState) {
switch (deliveryState) {
case DELIVERY_DELIVERED:
case DELIVERY_SKIPPED:
case DELIVERY_TIMEOUT:
case DELIVERY_FAILURE:
return true;
default:
return false;
}
}
/**
* Return if the given delivery state is "beyond", which means that we've
* moved beyond this receiver, and future receivers are now unblocked.
*/
static boolean isDeliveryStateBeyond(@DeliveryState int deliveryState) {
switch (deliveryState) {
case DELIVERY_DELIVERED:
case DELIVERY_SKIPPED:
case DELIVERY_TIMEOUT:
case DELIVERY_FAILURE:
case DELIVERY_DEFERRED:
return true;
default:
return false;
}
}
/**
* Return true if this receiver should be assumed to have been delivered.
*/
boolean isAssumedDelivered(int index) {
return (receivers.get(index) instanceof BroadcastFilter) && !ordered
&& (resultTo == null);
}
ProcessRecord curApp; // hosting application of current receiver.
ComponentName curComponent; // the receiver class that is currently running.
ActivityInfo curReceiver; // the manifest receiver that is currently running.
BroadcastFilter curFilter; // the registered receiver currently running.
Bundle curFilteredExtras; // the bundle that has been filtered by the package visibility rules
int curAppLastProcessState; // The last process state of the current receiver before receiving
boolean mIsReceiverAppRunning; // Was the receiver's app already running.
boolean mWasReceiverAppStopped; // Was the receiver app stopped prior to starting
// Private refcount-management bookkeeping; start > 0
static AtomicInteger sNextToken = new AtomicInteger(1);
@NeverCompile
void dump(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
final long now = SystemClock.uptimeMillis();
pw.print(prefix); pw.print(this); pw.print(" to user "); pw.println(userId);
pw.print(prefix); pw.println(intent.toInsecureString());
if (targetComp != null && targetComp != intent.getComponent()) {
pw.print(prefix); pw.print(" targetComp: "); pw.println(targetComp.toShortString());
}
Bundle bundle = intent.getExtras();
if (bundle != null) {
pw.print(prefix); pw.print(" extras: "); pw.println(bundle.toString());
}
pw.print(prefix); pw.print("caller="); pw.print(callerPackage); pw.print(" ");
pw.print(callerApp != null ? callerApp.toShortString() : "null");
pw.print(" pid="); pw.print(callingPid);
pw.print(" uid="); pw.println(callingUid);
if ((requiredPermissions != null && requiredPermissions.length > 0)
|| appOp != AppOpsManager.OP_NONE) {
pw.print(prefix); pw.print("requiredPermissions=");
pw.print(Arrays.toString(requiredPermissions));
pw.print(" appOp="); pw.println(appOp);
}
if (excludedPermissions != null && excludedPermissions.length > 0) {
pw.print(prefix); pw.print("excludedPermissions=");
pw.print(Arrays.toString(excludedPermissions));
}
if (excludedPackages != null && excludedPackages.length > 0) {
pw.print(prefix); pw.print("excludedPackages=");
pw.print(Arrays.toString(excludedPackages));
}
if (options != null) {
pw.print(prefix); pw.print("options="); pw.println(options.toBundle());
}
pw.print(prefix); pw.print("enqueueClockTime=");
pw.print(sdf.format(new Date(enqueueClockTime)));
pw.print(" dispatchClockTime=");
pw.print(sdf.format(new Date(dispatchClockTime)));
if (originalEnqueueClockTime > 0) {
pw.print(" originalEnqueueClockTime=");
pw.print(sdf.format(new Date(originalEnqueueClockTime)));
}
pw.println();
pw.print(prefix); pw.print("dispatchTime=");
TimeUtils.formatDuration(dispatchTime, now, pw);
pw.print(" (");
TimeUtils.formatDuration(dispatchTime - enqueueTime, pw);
pw.print(" since enq)");
if (finishTime != 0) {
pw.print(" finishTime="); TimeUtils.formatDuration(finishTime, now, pw);
pw.print(" (");
TimeUtils.formatDuration(finishTime-dispatchTime, pw);
pw.print(" since disp)");
} else {
pw.print(" receiverTime="); TimeUtils.formatDuration(receiverTime, now, pw);
}
pw.println("");
if (anrCount != 0) {
pw.print(prefix); pw.print("anrCount="); pw.println(anrCount);
}
if (resultTo != null || resultCode != -1 || resultData != null) {
pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
pw.print(" resultCode="); pw.print(resultCode);
pw.print(" resultData="); pw.println(resultData);
}
if (resultExtras != null) {
pw.print(prefix); pw.print("resultExtras="); pw.println(resultExtras);
}
if (resultAbort || ordered || sticky || initialSticky) {
pw.print(prefix); pw.print("resultAbort="); pw.print(resultAbort);
pw.print(" ordered="); pw.print(ordered);
pw.print(" sticky="); pw.print(sticky);
pw.print(" initialSticky="); pw.print(initialSticky);
pw.print(" originalStickyCallingUid="); pw.println(originalStickyCallingUid);
}
if (nextReceiver != 0) {
pw.print(prefix); pw.print("nextReceiver="); pw.println(nextReceiver);
}
if (curFilter != null) {
pw.print(prefix); pw.print("curFilter="); pw.println(curFilter);
}
if (curReceiver != null) {
pw.print(prefix); pw.print("curReceiver="); pw.println(curReceiver);
}
if (curApp != null) {
pw.print(prefix); pw.print("curApp="); pw.println(curApp);
pw.print(prefix); pw.print("curComponent=");
pw.println((curComponent != null ? curComponent.toShortString() : "--"));
if (curReceiver != null && curReceiver.applicationInfo != null) {
pw.print(prefix); pw.print("curSourceDir=");
pw.println(curReceiver.applicationInfo.sourceDir);
}
}
if (curFilteredExtras != null) {
pw.print(" filtered extras: "); pw.println(curFilteredExtras);
}
if (state != IDLE) {
String stateStr = " (?)";
switch (state) {
case APP_RECEIVE: stateStr=" (APP_RECEIVE)"; break;
case CALL_IN_RECEIVE: stateStr=" (CALL_IN_RECEIVE)"; break;
case CALL_DONE_RECEIVE: stateStr=" (CALL_DONE_RECEIVE)"; break;
case WAITING_SERVICES: stateStr=" (WAITING_SERVICES)"; break;
}
pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr);
}
pw.print(prefix); pw.print("terminalCount="); pw.println(terminalCount);
final int N = receivers != null ? receivers.size() : 0;
String p2 = prefix + " ";
PrintWriterPrinter printer = new PrintWriterPrinter(pw);
for (int i = 0; i < N; i++) {
Object o = receivers.get(i);
pw.print(prefix);
pw.print(deliveryStateToString(delivery[i]));
pw.print(' ');
if (scheduledTime[i] != 0) {
pw.print("scheduled ");
TimeUtils.formatDuration(scheduledTime[i] - enqueueTime, pw);
pw.print(' ');
}
if (terminalTime[i] != 0) {
pw.print("terminal ");
TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw);
pw.print(' ');
}
pw.print("("); pw.print(blockedUntilBeyondCount[i]); pw.print(") ");
pw.print("#"); pw.print(i); pw.print(": ");
if (o instanceof BroadcastFilter) {
pw.println(o);
((BroadcastFilter) o).dumpBrief(pw, p2);
} else if (o instanceof ResolveInfo) {
pw.println("(manifest)");
((ResolveInfo) o).dump(printer, p2, 0);
} else {
pw.println(o);
}
if (deliveryReasons[i] != null) {
pw.print(p2); pw.print("reason: "); pw.println(deliveryReasons[i]);
}
}
}
BroadcastRecord(BroadcastQueue queue,
Intent intent, ProcessRecord callerApp, String callerPackage,
@Nullable String callerFeatureId, int callingPid, int callingUid,
boolean callerInstantApp, String resolvedType,
String[] requiredPermissions, String[] excludedPermissions,
String[] excludedPackages, int appOp,
BroadcastOptions options, List receivers,
ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
String resultData, Bundle resultExtras, boolean serialized, boolean sticky,
boolean initialSticky, int userId,
@NonNull BackgroundStartPrivileges backgroundStartPrivileges,
boolean timeoutExempt,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
int callerAppProcessState) {
this(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid,
callingUid, callerInstantApp, resolvedType, requiredPermissions,
excludedPermissions, excludedPackages, appOp, options, receivers, resultToApp,
resultTo, resultCode, resultData, resultExtras, serialized, sticky,
initialSticky, userId, -1, backgroundStartPrivileges, timeoutExempt,
filterExtrasForReceiver, callerAppProcessState);
}
BroadcastRecord(BroadcastQueue _queue,
Intent _intent, ProcessRecord _callerApp, String _callerPackage,
@Nullable String _callerFeatureId, int _callingPid, int _callingUid,
boolean _callerInstantApp, String _resolvedType,
String[] _requiredPermissions, String[] _excludedPermissions,
String[] _excludedPackages, int _appOp,
BroadcastOptions _options, List _receivers,
ProcessRecord _resultToApp, IIntentReceiver _resultTo, int _resultCode,
String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky,
boolean _initialSticky, int _userId, int originalStickyCallingUid,
@NonNull BackgroundStartPrivileges backgroundStartPrivileges,
boolean timeoutExempt,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
int callerAppProcessState) {
if (_intent == null) {
throw new NullPointerException("Can't construct with a null intent");
}
queue = _queue;
intent = Objects.requireNonNull(_intent);
targetComp = _intent.getComponent();
callerApp = _callerApp;
callerPackage = _callerPackage;
callerFeatureId = _callerFeatureId;
callingPid = _callingPid;
callingUid = _callingUid;
callerProcState = callerAppProcessState;
callerInstantApp = _callerInstantApp;
callerInstrumented = isCallerInstrumented(_callerApp, _callingUid);
resolvedType = _resolvedType;
requiredPermissions = _requiredPermissions;
excludedPermissions = _excludedPermissions;
excludedPackages = _excludedPackages;
appOp = _appOp;
options = _options;
receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
delivery = new int[_receivers != null ? _receivers.size() : 0];
deliveryReasons = new String[delivery.length];
urgent = calculateUrgent(_intent, _options);
deferUntilActive = calculateDeferUntilActive(_callingUid,
_options, _resultTo, _serialized, urgent);
blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized);
scheduledTime = new long[delivery.length];
terminalTime = new long[delivery.length];
resultToApp = _resultToApp;
resultTo = _resultTo;
resultCode = _resultCode;
resultData = _resultData;
resultExtras = _resultExtras;
ordered = _serialized;
sticky = _sticky;
initialSticky = _initialSticky;
prioritized = isPrioritized(blockedUntilBeyondCount, _serialized);
userId = _userId;
nextReceiver = 0;
state = IDLE;
mBackgroundStartPrivileges = backgroundStartPrivileges;
this.timeoutExempt = timeoutExempt;
alarm = options != null && options.isAlarmBroadcast();
pushMessage = options != null && options.isPushMessagingBroadcast();
pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
interactive = options != null && options.isInteractive();
shareIdentity = options != null && options.isShareIdentityEnabled();
this.filterExtrasForReceiver = filterExtrasForReceiver;
this.originalStickyCallingUid = originalStickyCallingUid;
}
/**
* Copy constructor which takes a different intent.
* Only used by {@link #maybeStripForHistory}.
*/
private BroadcastRecord(BroadcastRecord from, Intent newIntent) {
intent = Objects.requireNonNull(newIntent);
targetComp = newIntent.getComponent();
callerApp = from.callerApp;
callerPackage = from.callerPackage;
callerFeatureId = from.callerFeatureId;
callingPid = from.callingPid;
callingUid = from.callingUid;
callerProcState = from.callerProcState;
callerInstantApp = from.callerInstantApp;
callerInstrumented = from.callerInstrumented;
ordered = from.ordered;
sticky = from.sticky;
initialSticky = from.initialSticky;
prioritized = from.prioritized;
userId = from.userId;
resolvedType = from.resolvedType;
requiredPermissions = from.requiredPermissions;
excludedPermissions = from.excludedPermissions;
excludedPackages = from.excludedPackages;
appOp = from.appOp;
options = from.options;
receivers = from.receivers;
delivery = from.delivery;
deliveryReasons = from.deliveryReasons;
deferUntilActive = from.deferUntilActive;
blockedUntilBeyondCount = from.blockedUntilBeyondCount;
scheduledTime = from.scheduledTime;
terminalTime = from.terminalTime;
resultToApp = from.resultToApp;
resultTo = from.resultTo;
enqueueTime = from.enqueueTime;
enqueueRealTime = from.enqueueRealTime;
enqueueClockTime = from.enqueueClockTime;
dispatchTime = from.dispatchTime;
dispatchRealTime = from.dispatchRealTime;
dispatchClockTime = from.dispatchClockTime;
receiverTime = from.receiverTime;
finishTime = from.finishTime;
resultCode = from.resultCode;
resultData = from.resultData;
resultExtras = from.resultExtras;
resultAbort = from.resultAbort;
nextReceiver = from.nextReceiver;
state = from.state;
anrCount = from.anrCount;
manifestCount = from.manifestCount;
manifestSkipCount = from.manifestSkipCount;
queue = from.queue;
mBackgroundStartPrivileges = from.mBackgroundStartPrivileges;
timeoutExempt = from.timeoutExempt;
alarm = from.alarm;
pushMessage = from.pushMessage;
pushMessageOverQuota = from.pushMessageOverQuota;
interactive = from.interactive;
shareIdentity = from.shareIdentity;
urgent = from.urgent;
filterExtrasForReceiver = from.filterExtrasForReceiver;
originalStickyCallingUid = from.originalStickyCallingUid;
}
/**
* Split off a new BroadcastRecord that clones this one, but contains only the
* recipient records for the current (just-finished) receiver's app, starting
* after the just-finished receiver [i.e. at r.nextReceiver]. Returns null
* if there are no matching subsequent receivers in this BroadcastRecord.
*/
BroadcastRecord splitRecipientsLocked(int slowAppUid, int startingAt) {
// Do we actually have any matching receivers down the line...?
ArrayList splitReceivers = null;
for (int i = startingAt; i < receivers.size(); ) {
Object o = receivers.get(i);
if (getReceiverUid(o) == slowAppUid) {
if (splitReceivers == null) {
splitReceivers = new ArrayList<>();
}
splitReceivers.add(o);
receivers.remove(i);
} else {
i++;
}
}
// No later receivers in the same app, so we have no more to do
if (splitReceivers == null) {
return null;
}
// build a new BroadcastRecord around that single-target list
BroadcastRecord split = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
splitReceivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
ordered, sticky, initialSticky, userId,
mBackgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
callerProcState);
split.enqueueTime = this.enqueueTime;
split.enqueueRealTime = this.enqueueRealTime;
split.enqueueClockTime = this.enqueueClockTime;
split.splitToken = this.splitToken;
return split;
}
/**
* Split a BroadcastRecord to a map of deferred receiver UID to deferred BroadcastRecord.
*
* The receivers that are deferred are removed from original BroadcastRecord's receivers list.
* The receivers that are not deferred are kept in original BroadcastRecord's receivers list.
*
* Only used to split LOCKED_BOOT_COMPLETED or BOOT_COMPLETED BroadcastRecord.
* LOCKED_BOOT_COMPLETED or BOOT_COMPLETED broadcast can be deferred until the first time
* the receiver's UID has a process started.
*
* @param ams The ActivityManagerService object.
* @param deferType Defer what UID?
* @return the deferred UID to BroadcastRecord map, the BroadcastRecord has the list of
* receivers in that UID.
*/
@NonNull SparseArray<BroadcastRecord> splitDeferredBootCompletedBroadcastLocked(
ActivityManagerInternal activityManagerInternal,
@BroadcastConstants.DeferBootCompletedBroadcastType int deferType) {
final SparseArray<BroadcastRecord> ret = new SparseArray<>();
if (deferType == DEFER_BOOT_COMPLETED_BROADCAST_NONE) {
return ret;
}
if (receivers == null) {
return ret;
}
final String action = intent.getAction();
if (!Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)
&& !Intent.ACTION_BOOT_COMPLETED.equals(action)) {
return ret;
}
final SparseArray<List<Object>> uid2receiverList = new SparseArray<>();
for (int i = receivers.size() - 1; i >= 0; i--) {
final Object receiver = receivers.get(i);
final int uid = getReceiverUid(receiver);
if (deferType != DEFER_BOOT_COMPLETED_BROADCAST_ALL) {
if ((deferType & DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY) != 0) {
if (activityManagerInternal.getRestrictionLevel(uid)
< RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
// skip if the UID is not background restricted.
continue;
}
}
if ((deferType & DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY) != 0) {
if (!CompatChanges.isChangeEnabled(DEFER_BOOT_COMPLETED_BROADCAST_CHANGE_ID,
uid)) {
// skip if the UID is not targetSdkVersion T+.
continue;
}
}
}
// Remove receiver from original BroadcastRecord's receivers list.
receivers.remove(i);
final List<Object> receiverList = uid2receiverList.get(uid);
if (receiverList != null) {
receiverList.add(0, receiver);
} else {
ArrayList<Object> splitReceivers = new ArrayList<>();
splitReceivers.add(0, receiver);
uid2receiverList.put(uid, splitReceivers);
}
}
final int uidSize = uid2receiverList.size();
for (int i = 0; i < uidSize; i++) {
final BroadcastRecord br = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
uid2receiverList.valueAt(i), null /* _resultToApp */, null /* _resultTo */,
resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
mBackgroundStartPrivileges, timeoutExempt,
filterExtrasForReceiver, callerProcState);
br.enqueueTime = this.enqueueTime;
br.enqueueRealTime = this.enqueueRealTime;
br.enqueueClockTime = this.enqueueClockTime;
ret.put(uid2receiverList.keyAt(i), br);
}
return ret;
}
/**
* Update the delivery state of the given {@link #receivers} index.
* Automatically updates any time measurements related to state changes.
*
* @return if {@link #beyondCount} changed due to this state transition,
* indicating that other events may be unblocked.
*/
@CheckResult
boolean setDeliveryState(int index, @DeliveryState int newDeliveryState,
@NonNull String reason) {
final int oldDeliveryState = delivery[index];
if (isDeliveryStateTerminal(oldDeliveryState)
|| newDeliveryState == oldDeliveryState) {
// We've already arrived in terminal or requested state, so leave
// any statistics and reasons intact from the first transition
return false;
}
switch (oldDeliveryState) {
case DELIVERY_DEFERRED:
deferredCount--;
break;
}
switch (newDeliveryState) {
case DELIVERY_PENDING:
scheduledTime[index] = 0;
break;
case DELIVERY_SCHEDULED:
scheduledTime[index] = SystemClock.uptimeMillis();
break;
case DELIVERY_DEFERRED:
deferredCount++;
break;
case DELIVERY_DELIVERED:
case DELIVERY_SKIPPED:
case DELIVERY_TIMEOUT:
case DELIVERY_FAILURE:
terminalTime[index] = SystemClock.uptimeMillis();
terminalCount++;
break;
}
delivery[index] = newDeliveryState;
deliveryReasons[index] = reason;
// If this state change might bring us to a new high-water mark, bring
// ourselves as high as we possibly can
final int oldBeyondCount = beyondCount;
if (index >= beyondCount) {
for (int i = beyondCount; i < delivery.length; i++) {
if (isDeliveryStateBeyond(getDeliveryState(i))) {
beyondCount = i + 1;
} else {
break;
}
}
}
return (beyondCount != oldBeyondCount);
}
@DeliveryState int getDeliveryState(int index) {
return delivery[index];
}
/**
* @return if the given {@link #receivers} index should be considered
* blocked based on the current status of the overall broadcast.
*/
boolean isBlocked(int index) {
return (beyondCount < blockedUntilBeyondCount[index]);
}
boolean wasDeliveryAttempted(int index) {
final int deliveryState = getDeliveryState(index);
switch (deliveryState) {
case DELIVERY_DELIVERED:
case DELIVERY_TIMEOUT:
case DELIVERY_FAILURE:
return true;
default:
return false;
}
}
void copyEnqueueTimeFrom(@NonNull BroadcastRecord replacedBroadcast) {
originalEnqueueClockTime = enqueueClockTime;
enqueueTime = replacedBroadcast.enqueueTime;
enqueueRealTime = replacedBroadcast.enqueueRealTime;
enqueueClockTime = replacedBroadcast.enqueueClockTime;
}
boolean isForeground() {
return (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
}
boolean isReplacePending() {
return (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
}
boolean isNoAbort() {
return (intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0;
}
boolean isOffload() {
return (intent.getFlags() & Intent.FLAG_RECEIVER_OFFLOAD) != 0;
}
boolean isDeferUntilActive() {
return deferUntilActive;
}
boolean isUrgent() {
return urgent;
}
@NonNull String getHostingRecordTriggerType() {
if (alarm) {
return HostingRecord.TRIGGER_TYPE_ALARM;
} else if (pushMessage) {
return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
} else if (pushMessageOverQuota) {
return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
}
return HostingRecord.TRIGGER_TYPE_UNKNOWN;
}
/**
* Return an instance of {@link #intent} specialized for the given receiver.
* For example, this returns a new specialized instance if the extras need
* to be filtered, or a {@link ResolveInfo} needs to be configured.
*
* @return a specialized intent, otherwise {@code null} to indicate that the
* broadcast should not be delivered to this receiver, typically due
* to it being filtered away by {@link #filterExtrasForReceiver}.
*/
@Nullable Intent getReceiverIntent(@NonNull Object receiver) {
Intent newIntent = null;
if (filterExtrasForReceiver != null) {
final Bundle extras = intent.getExtras();
if (extras != null) {
final int receiverUid = getReceiverUid(receiver);
final Bundle filteredExtras = filterExtrasForReceiver.apply(receiverUid, extras);
if (filteredExtras == null) {
// Completely filtered; skip the broadcast!
return null;
} else {
newIntent = new Intent(intent);
newIntent.replaceExtras(filteredExtras);
}
}
}
if (receiver instanceof ResolveInfo) {
if (newIntent == null) {
newIntent = new Intent(intent);
}
newIntent.setComponent(((ResolveInfo) receiver).activityInfo.getComponentName());
}
return (newIntent != null) ? newIntent : intent;
}
static boolean isCallerInstrumented(@Nullable ProcessRecord callerApp, int callingUid) {
switch (UserHandle.getAppId(callingUid)) {
case android.os.Process.ROOT_UID:
case android.os.Process.SHELL_UID:
// Broadcasts sent via "shell" are typically invoked by test
// suites, so we treat them as if the caller was instrumented
return true;
}
return (callerApp != null) ? (callerApp.getActiveInstrumentation() != null) : false;
}
/**
* Determine if the result of {@link #calculateBlockedUntilTerminalCount}
* has prioritized tranches of receivers.
*/
@VisibleForTesting
static boolean isPrioritized(@NonNull int[] blockedUntilBeyondCount,
boolean ordered) {
return !ordered && (blockedUntilBeyondCount.length > 0)
&& (blockedUntilBeyondCount[0] != -1);
}
/**
* Calculate the {@link #beyondCount} that each receiver should be
* considered blocked until.
* <p>
* For example, in an ordered broadcast, receiver {@code N} is blocked until
* receiver {@code N-1} reaches a terminal or deferred state. Similarly, in
* a prioritized broadcast, receiver {@code N} is blocked until all
* receivers of a higher priority reach a terminal or deferred state.
* <p>
* When there are no beyond count constraints, the blocked value for each
* receiver is {@code -1}.
*/
@VisibleForTesting
static @NonNull int[] calculateBlockedUntilBeyondCount(
@NonNull List<Object> receivers, boolean ordered) {
final int N = receivers.size();
final int[] blockedUntilBeyondCount = new int[N];
int lastPriority = 0;
int lastPriorityIndex = 0;
for (int i = 0; i < N; i++) {
if (ordered) {
// When sending an ordered broadcast, we need to block this
// receiver until all previous receivers have terminated
blockedUntilBeyondCount[i] = i;
} else {
// When sending a prioritized broadcast, we only need to wait
// for the previous tranche of receivers to be terminated
final int thisPriority = getReceiverPriority(receivers.get(i));
if ((i == 0) || (thisPriority != lastPriority)) {
lastPriority = thisPriority;
lastPriorityIndex = i;
blockedUntilBeyondCount[i] = i;
} else {
blockedUntilBeyondCount[i] = lastPriorityIndex;
}
}
}
// If the entire list is in the same priority tranche, mark as -1 to
// indicate that none of them need to wait
if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) {
Arrays.fill(blockedUntilBeyondCount, -1);
}
return blockedUntilBeyondCount;
}
static int getReceiverUid(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).owningUid;
} else /* if (receiver instanceof ResolveInfo) */ {
return ((ResolveInfo) receiver).activityInfo.applicationInfo.uid;
}
}
static @NonNull String getReceiverProcessName(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).receiverList.app.processName;
} else /* if (receiver instanceof ResolveInfo) */ {
return ((ResolveInfo) receiver).activityInfo.processName;
}
}
static @NonNull String getReceiverPackageName(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).receiverList.app.info.packageName;
} else /* if (receiver instanceof ResolveInfo) */ {
return ((ResolveInfo) receiver).activityInfo.packageName;
}
}
static @Nullable String getReceiverClassName(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).getReceiverClassName();
} else /* if (receiver instanceof ResolveInfo) */ {
return ((ResolveInfo) receiver).activityInfo.name;
}
}
static int getReceiverPriority(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).getPriority();
} else /* if (receiver instanceof ResolveInfo) */ {
return ((ResolveInfo) receiver).priority;
}
}
static boolean isReceiverEquals(@NonNull Object a, @NonNull Object b) {
if (a == b) {
return true;
} else if (a instanceof ResolveInfo && b instanceof ResolveInfo) {
final ResolveInfo infoA = (ResolveInfo) a;
final ResolveInfo infoB = (ResolveInfo) b;
return Objects.equals(infoA.activityInfo.packageName, infoB.activityInfo.packageName)
&& Objects.equals(infoA.activityInfo.name, infoB.activityInfo.name);
} else {
return false;
}
}
/**
* Core policy determination about this broadcast's delivery prioritization
*/
@VisibleForTesting
static boolean calculateUrgent(@NonNull Intent intent, @Nullable BroadcastOptions options) {
// TODO: flags for controlling policy
// TODO: migrate alarm-prioritization flag to BroadcastConstants
if ((intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0) {
return true;
}
if (options != null) {
if (options.isInteractive()) {
return true;
}
if (options.isAlarmBroadcast()) {
return true;
}
}
return false;
}
/**
* Resolve the requested {@link BroadcastOptions#setDeferralPolicy(int)}
* against this broadcast state to determine if it should be marked as
* "defer until active".
*/
@VisibleForTesting
static boolean calculateDeferUntilActive(int callingUid, @Nullable BroadcastOptions options,
@Nullable IIntentReceiver resultTo, boolean ordered, boolean urgent) {
// Ordered broadcasts can never be deferred until active
if (ordered) {
return false;
}
// Unordered resultTo broadcasts are always deferred until active
if (!ordered && resultTo != null) {
return true;
}
// Determine if a strong preference in either direction was expressed;
// a preference here overrides all remaining policies
if (options != null) {
switch (options.getDeferralPolicy()) {
case BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE:
return true;
case BroadcastOptions.DEFERRAL_POLICY_NONE:
return false;
}
}
// Urgent broadcasts aren't deferred until active
if (urgent) {
return false;
}
// Otherwise, choose a reasonable default
if (CORE_DEFER_UNTIL_ACTIVE && UserHandle.isCore(callingUid)) {
return true;
} else {
return false;
}
}
int calculateTypeForLogging() {
int type = BROADCAST_TYPE_NONE;
if (isForeground()) {
type |= BROADCAST_TYPE_FOREGROUND;
} else {
type |= BROADCAST_TYPE_BACKGROUND;
}
if (alarm) {
type |= BROADCAST_TYPE_ALARM;
}
if (interactive) {
type |= BROADCAST_TYPE_INTERACTIVE;
}
if (ordered) {
type |= BROADCAST_TYPE_ORDERED;
}
if (prioritized) {
type |= BROADCAST_TYPE_PRIORITIZED;
}
if (resultTo != null) {
type |= BROADCAST_TYPE_RESULT_TO;
}
if (deferUntilActive) {
type |= BROADCAST_TYPE_DEFERRABLE_UNTIL_ACTIVE;
}
if (pushMessage) {
type |= BROADCAST_TYPE_PUSH_MESSAGE;
}
if (pushMessageOverQuota) {
type |= BROADCAST_TYPE_PUSH_MESSAGE_OVER_QUOTA;
}
if (sticky) {
type |= BROADCAST_TYPE_STICKY;
}
if (initialSticky) {
type |= BROADCAST_TYPE_INITIAL_STICKY;
}
return type;
}
public BroadcastRecord maybeStripForHistory() {
if (!intent.canStripForHistory()) {
return this;
}
return new BroadcastRecord(this, intent.maybeStripForHistory());
}
@VisibleForTesting
boolean cleanupDisabledPackageReceiversLocked(
String packageName, Set<String> filterByClasses, int userId, boolean doit) {
if (receivers == null) {
return false;
}
final boolean cleanupAllUsers = userId == UserHandle.USER_ALL;
final boolean sendToAllUsers = this.userId == UserHandle.USER_ALL;
if (this.userId != userId && !cleanupAllUsers && !sendToAllUsers) {
return false;
}
boolean didSomething = false;
Object o;
for (int i = receivers.size() - 1; i >= 0; i--) {
o = receivers.get(i);
if (!(o instanceof ResolveInfo)) {
continue;
}
ActivityInfo info = ((ResolveInfo)o).activityInfo;
final boolean sameComponent = packageName == null
|| (info.applicationInfo.packageName.equals(packageName)
&& (filterByClasses == null || filterByClasses.contains(info.name)));
if (sameComponent && (cleanupAllUsers
|| UserHandle.getUserId(info.applicationInfo.uid) == userId)) {
if (!doit) {
return true;
}
didSomething = true;
receivers.remove(i);
if (i < nextReceiver) {
nextReceiver--;
}
}
}
nextReceiver = Math.min(nextReceiver, receivers.size());
return didSomething;
}
/**
* Apply special treatment to manifest receivers hosted by a singleton
* process, by re-targeting them at {@link UserHandle#USER_SYSTEM}.
*/
void applySingletonPolicy(@NonNull ActivityManagerService service) {
if (receivers == null) return;
for (int i = 0; i < receivers.size(); i++) {
final Object receiver = receivers.get(i);
if (receiver instanceof ResolveInfo) {
final ResolveInfo info = (ResolveInfo) receiver;
boolean isSingleton = false;
try {
isSingleton = service.isSingleton(info.activityInfo.processName,
info.activityInfo.applicationInfo,
info.activityInfo.name, info.activityInfo.flags);
} catch (SecurityException e) {
BroadcastQueue.logw(e.getMessage());
}
final int receiverUid = info.activityInfo.applicationInfo.uid;
if (callingUid != android.os.Process.SYSTEM_UID && isSingleton
&& service.isValidSingletonCall(callingUid, receiverUid)) {
info.activityInfo = service.getActivityInfoForUser(info.activityInfo,
UserHandle.USER_SYSTEM);
}
}
}
}
boolean containsReceiver(@NonNull Object receiver) {
for (int i = receivers.size() - 1; i >= 0; --i) {
if (isReceiverEquals(receiver, receivers.get(i))) {
return true;
}
}
return false;
}
boolean containsAllReceivers(@NonNull List<Object> otherReceivers) {
for (int i = otherReceivers.size() - 1; i >= 0; --i) {
if (!containsReceiver(otherReceivers.get(i))) {
return false;
}
}
return true;
}
@DeliveryGroupPolicy
int getDeliveryGroupPolicy() {
return (options != null) ? options.getDeliveryGroupPolicy()
: BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
}
boolean matchesDeliveryGroup(@NonNull BroadcastRecord other) {
return matchesDeliveryGroup(this, other);
}
private static boolean matchesDeliveryGroup(@NonNull BroadcastRecord newRecord,
@NonNull BroadcastRecord oldRecord) {
final IntentFilter newMatchingFilter = getDeliveryGroupMatchingFilter(newRecord);
// If neither delivery group key nor matching filter is specified, then use
// Intent.filterEquals() to identify the delivery group.
if (isMatchingKeyNull(newRecord) && isMatchingKeyNull(oldRecord)
&& newMatchingFilter == null) {
return newRecord.intent.filterEquals(oldRecord.intent);
}
if (newMatchingFilter != null && !newMatchingFilter.asPredicate().test(oldRecord.intent)) {
return false;
}
return areMatchingKeysEqual(newRecord, oldRecord);
}
private static boolean isMatchingKeyNull(@NonNull BroadcastRecord record) {
final String namespace = getDeliveryGroupMatchingNamespaceFragment(record);
final String key = getDeliveryGroupMatchingKeyFragment(record);
// If either namespace or key part is null, then treat the entire matching key as null.
return namespace == null || key == null;
}
private static boolean areMatchingKeysEqual(@NonNull BroadcastRecord newRecord,
@NonNull BroadcastRecord oldRecord) {
final String newNamespaceFragment = getDeliveryGroupMatchingNamespaceFragment(newRecord);
final String oldNamespaceFragment = getDeliveryGroupMatchingNamespaceFragment(oldRecord);
if (!Objects.equals(newNamespaceFragment, oldNamespaceFragment)) {
return false;
}
final String newKeyFragment = getDeliveryGroupMatchingKeyFragment(newRecord);
final String oldKeyFragment = getDeliveryGroupMatchingKeyFragment(oldRecord);
return Objects.equals(newKeyFragment, oldKeyFragment);
}
@Nullable
private static String getDeliveryGroupMatchingNamespaceFragment(
@NonNull BroadcastRecord record) {
return record.options == null
? null : record.options.getDeliveryGroupMatchingNamespaceFragment();
}
@Nullable
private static String getDeliveryGroupMatchingKeyFragment(@NonNull BroadcastRecord record) {
return record.options == null
? null : record.options.getDeliveryGroupMatchingKeyFragment();
}
@Nullable
private static IntentFilter getDeliveryGroupMatchingFilter(@NonNull BroadcastRecord record) {
return record.options == null ? null : record.options.getDeliveryGroupMatchingFilter();
}
/**
* Returns {@code true} if all the receivers are still waiting to receive the broadcast.
* Otherwise {@code false}.
*/
boolean allReceiversPending() {
// We could also count the number of receivers with deliver state DELIVERY_PENDING, but
// checking how many receivers have finished (either skipped or cancelled) and whether or
// not the dispatch has been started should be sufficient.
return (terminalCount == 0 && dispatchTime <= 0);
}
boolean isMatchingRecord(@NonNull BroadcastRecord record) {
final int idx = mMatchingRecordsCache.indexOfKey(record);
if (idx > 0) {
return mMatchingRecordsCache.valueAt(idx);
}
// Consider a record to be matching if has the same receivers in the same order.
boolean matches = (receivers.size() == record.receivers.size());
if (matches) {
for (int i = receivers.size() - 1; i >= 0; --i) {
if (!isReceiverEquals(receivers.get(i), record.receivers.get(i))) {
matches = false;
break;
}
}
}
mMatchingRecordsCache.put(record, matches);
return matches;
}
void setMatchingRecordsCache(@NonNull ArrayMap<BroadcastRecord, Boolean> matchingRecordsCache) {
mMatchingRecordsCache = matchingRecordsCache;
}
void clearMatchingRecordsCache() {
mMatchingRecordsCache = null;
}
@Override
public String toString() {
if (mCachedToString == null) {
String label = intent.getAction();
if (label == null) {
label = intent.toString();
}
mCachedToString = "BroadcastRecord{" + toShortString() + "}";
}
return mCachedToString;
}
public String toShortString() {
if (mCachedToShortString == null) {
String label = intent.getAction();
if (label == null) {
label = intent.toString();
}
mCachedToShortString = Integer.toHexString(System.identityHashCode(this))
+ " " + label + "/u" + userId;
}
return mCachedToShortString;
}
@NeverCompile
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(BroadcastRecordProto.USER_ID, userId);
proto.write(BroadcastRecordProto.INTENT_ACTION, intent.getAction());
proto.end(token);
}
}