Add an API to bulk query broadcast response stats for all pkgs.

Bug: 217567456
Test: atest tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
Change-Id: I12853fdfb8a7b70eac935cf6aab23a388d2b7005
This commit is contained in:
Sudheer Shanka
2022-02-14 13:44:28 -08:00
parent f13ace95ab
commit d807ffe41f
10 changed files with 289 additions and 55 deletions

View File

@@ -2507,9 +2507,10 @@ package android.app.time {
package android.app.usage {
public final class BroadcastResponseStats implements android.os.Parcelable {
ctor public BroadcastResponseStats(@NonNull String);
ctor public BroadcastResponseStats(@NonNull String, @IntRange(from=1) long);
method public int describeContents();
method @IntRange(from=0) public int getBroadcastsDispatchedCount();
method @IntRange(from=1) public long getId();
method @IntRange(from=0) public int getNotificationsCancelledCount();
method @IntRange(from=0) public int getNotificationsPostedCount();
method @IntRange(from=0) public int getNotificationsUpdatedCount();
@@ -2566,13 +2567,13 @@ package android.app.usage {
}
public final class UsageStatsManager {
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void clearBroadcastResponseStats(@NonNull String, @IntRange(from=1) long);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void clearBroadcastResponseStats(@Nullable String, @IntRange(from=0) long);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
method @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long getLastTimeAnyComponentUsed(@NonNull String);
method public int getUsageSource();
method @RequiresPermission(android.Manifest.permission.BIND_CARRIER_SERVICES) public void onCarrierPrivilegedAppsChanged();
method @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.BroadcastResponseStats queryBroadcastResponseStats(@NonNull String, @IntRange(from=1) long);
method @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.List<android.app.usage.BroadcastResponseStats> queryBroadcastResponseStats(@Nullable String, @IntRange(from=0) long);
method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @Nullable android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);

View File

@@ -23,6 +23,8 @@ import android.app.BroadcastOptions;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/**
* Class containing a collection of stats related to response events started from an app
* after receiving a broadcast.
@@ -32,17 +34,30 @@ import android.os.Parcelable;
@SystemApi
public final class BroadcastResponseStats implements Parcelable {
private final String mPackageName;
private final long mId;
private int mBroadcastsDispatchedCount;
private int mNotificationsPostedCount;
private int mNotificationsUpdatedCount;
private int mNotificationsCancelledCount;
public BroadcastResponseStats(@NonNull String packageName) {
/**
* Creates a new {@link BroadcastResponseStats} object that contain the stats for broadcasts
* with {@code id} (specified using
* {@link BroadcastOptions#recordResponseEventWhileInBackground(long)} by the sender) that
* were sent to {@code packageName}.
*
* @param packageName the name of the package that broadcasts were sent to.
* @param id the ID specified by the sender using
* {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
*/
public BroadcastResponseStats(@NonNull String packageName, @IntRange(from = 1) long id) {
mPackageName = packageName;
mId = id;
}
private BroadcastResponseStats(@NonNull Parcel in) {
mPackageName = in.readString8();
mId = in.readLong();
mBroadcastsDispatchedCount = in.readInt();
mNotificationsPostedCount = in.readInt();
mNotificationsUpdatedCount = in.readInt();
@@ -57,6 +72,14 @@ public final class BroadcastResponseStats implements Parcelable {
return mPackageName;
}
/**
* @return the ID of the broadcasts that the stats in this object correspond to.
*/
@IntRange(from = 1)
public long getId() {
return mId;
}
/**
* Returns the total number of broadcasts that were dispatched to the app by the caller.
*
@@ -147,10 +170,36 @@ public final class BroadcastResponseStats implements Parcelable {
incrementNotificationsCancelledCount(stats.getNotificationsCancelledCount());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof BroadcastResponseStats)) {
return false;
}
final BroadcastResponseStats other = (BroadcastResponseStats) obj;
return this.mBroadcastsDispatchedCount == other.mBroadcastsDispatchedCount
&& this.mNotificationsPostedCount == other.mNotificationsPostedCount
&& this.mNotificationsUpdatedCount == other.mNotificationsUpdatedCount
&& this.mNotificationsCancelledCount == other.mNotificationsCancelledCount
&& this.mId == other.mId
&& this.mPackageName.equals(other.mPackageName);
}
@Override
public int hashCode() {
return Objects.hash(mPackageName, mId, mBroadcastsDispatchedCount,
mNotificationsPostedCount, mNotificationsUpdatedCount,
mNotificationsCancelledCount);
}
@Override
public @NonNull String toString() {
return "stats {"
+ "broadcastsSent=" + mBroadcastsDispatchedCount
+ "package=" + mPackageName
+ ",id=" + mId
+ ",broadcastsSent=" + mBroadcastsDispatchedCount
+ ",notificationsPosted=" + mNotificationsPostedCount
+ ",notificationsUpdated=" + mNotificationsUpdatedCount
+ ",notificationsCancelled=" + mNotificationsCancelledCount
@@ -165,6 +214,7 @@ public final class BroadcastResponseStats implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) {
dest.writeString8(mPackageName);
dest.writeLong(mId);
dest.writeInt(mBroadcastsDispatchedCount);
dest.writeInt(mNotificationsPostedCount);
dest.writeInt(mNotificationsUpdatedCount);

View File

@@ -0,0 +1,20 @@
/*
* Copyright (C) 2022 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 android.app.usage;
/** {@hide} */
parcelable BroadcastResponseStatsList;

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2022 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 android.app.usage;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** @hide */
public final class BroadcastResponseStatsList implements Parcelable {
private List<BroadcastResponseStats> mBroadcastResponseStats;
public BroadcastResponseStatsList(
@NonNull List<BroadcastResponseStats> broadcastResponseStats) {
mBroadcastResponseStats = broadcastResponseStats;
}
private BroadcastResponseStatsList(@NonNull Parcel in) {
mBroadcastResponseStats = new ArrayList<>();
final byte[] bytes = in.readBlob();
final Parcel data = Parcel.obtain();
try {
data.unmarshall(bytes, 0, bytes.length);
data.setDataPosition(0);
data.readTypedList(mBroadcastResponseStats, BroadcastResponseStats.CREATOR);
} finally {
data.recycle();
}
}
@NonNull
public List<BroadcastResponseStats> getList() {
return mBroadcastResponseStats == null ? Collections.emptyList() : mBroadcastResponseStats;
}
@Override
public @ContentsFlags int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) {
final Parcel data = Parcel.obtain();
try {
data.writeTypedList(mBroadcastResponseStats);
dest.writeBlob(data.marshall());
} finally {
data.recycle();
}
}
public static final @NonNull Creator<BroadcastResponseStatsList> CREATOR =
new Creator<BroadcastResponseStatsList>() {
@Override
public @NonNull BroadcastResponseStatsList createFromParcel(
@NonNull Parcel source) {
return new BroadcastResponseStatsList(source);
}
@Override
public @NonNull BroadcastResponseStatsList[] newArray(int size) {
return new BroadcastResponseStatsList[size];
}
};
}

View File

@@ -18,6 +18,7 @@ package android.app.usage;
import android.app.PendingIntent;
import android.app.usage.BroadcastResponseStats;
import android.app.usage.BroadcastResponseStatsList;
import android.app.usage.UsageEvents;
import android.content.pm.ParceledListSlice;
@@ -73,9 +74,11 @@ interface IUsageStatsManager {
void forceUsageSourceSettingRead();
long getLastTimeAnyComponentUsed(String packageName, String callingPackage);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
BroadcastResponseStats queryBroadcastResponseStats(
BroadcastResponseStatsList queryBroadcastResponseStats(
String packageName, long id, String callingPackage, int userId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
void clearBroadcastResponseStats(String packageName, long id, String callingPackage,
int userId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
void clearBroadcastEvents(String callingPackage, int userId);
}

View File

@@ -1397,29 +1397,46 @@ public final class UsageStatsManager {
* Returns the broadcast response stats since the last boot corresponding to
* {@code packageName} and {@code id}.
*
* <p>Broadcast response stats will include the aggregated data of what actions an app took upon
* receiving a broadcast. This data will consider the broadcasts that the caller sent to
* <p> Broadcast response stats will include the aggregated data of what actions an app took
* upon receiving a broadcast. This data will consider the broadcasts that the caller sent to
* {@code packageName} and explicitly requested to record the response events using
* {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
*
* @param packageName The name of the package that the caller wants to query for.
* @param id The ID corresponding to the broadcasts that the caller wants to query for. This is
* the ID the caller specifies when requesting a broadcast response event to be
* recorded using {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
* <p> The returned list could one or more {@link BroadcastResponseStats} objects or be empty
* depending on the {@code packageName} and {@code id} and whether there is any data
* corresponding to these. If the {@code packageName} is not {@code null} and {@code id} is
* {@code > 0}, then the returned list would contain at most one {@link BroadcastResponseStats}
* object. Otherwise, the returned list could contain more than one
* {@link BroadcastResponseStats} object in no particular order.
*
* @return the broadcast response stats corresponding to {@code packageName} and {@code id}.
* <p> Note: It is possible that same {@code id} was used for broadcasts sent to different
* packages. So, callers can query the data corresponding to
* all broadcasts with a particular {@code id} by passing {@code packageName} as {@code null}.
*
* @param packageName The name of the package that the caller wants to query for
* or {@code null} to indicate that data corresponding to all packages
* should be returned.
* @param id The ID corresponding to the broadcasts that the caller wants to query for, or
* {@code 0} to indicate that data corresponding to all IDs should be returned.
* This is the ID the caller specifies when requesting a broadcast response event
* to be recorded using
* {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
*
* @return the list of broadcast response stats corresponding to {@code packageName}
* and {@code id}.
*
* @see #clearBroadcastResponseStats(String, long)
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
@UserHandleAware
@NonNull
public BroadcastResponseStats queryBroadcastResponseStats(
@NonNull String packageName, @IntRange(from = 1) long id) {
public List<BroadcastResponseStats> queryBroadcastResponseStats(
@Nullable String packageName, @IntRange(from = 0) long id) {
try {
return mService.queryBroadcastResponseStats(packageName, id,
mContext.getOpPackageName(), mContext.getUserId());
mContext.getOpPackageName(), mContext.getUserId()).getList();
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -1428,12 +1445,15 @@ public final class UsageStatsManager {
/**
* Clears the broadcast response stats corresponding to {@code packageName} and {@code id}.
*
* When a caller uses this API, stats related to the events occurring till that point will be
* cleared and subsequent calls to {@link #queryBroadcastResponseStats(String, long)} will
* <p> When a caller uses this API, stats related to the events occurring till that point will
* be cleared and subsequent calls to {@link #queryBroadcastResponseStats(String, long)} will
* return stats related to events occurring after this.
*
* @param packageName The name of the package that the caller wants to clear the data for.
* @param id The ID corresponding to the broadcasts that the caller wants to clear the data for.
* @param packageName The name of the package that the caller wants to clear the data for or
* {@code null} to indicate that data corresponding to all packages should
* be cleared.
* @param id The ID corresponding to the broadcasts that the caller wants to clear the data
* for, or {code 0} to indicate that data corresponding to all IDs should be deleted.
* This is the ID the caller specifies when requesting a broadcast response event
* to be recorded using
* {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
@@ -1444,8 +1464,8 @@ public final class UsageStatsManager {
@SystemApi
@RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
@UserHandleAware
public void clearBroadcastResponseStats(@NonNull String packageName,
@IntRange(from = 1) long id) {
public void clearBroadcastResponseStats(@Nullable String packageName,
@IntRange(from = 0) long id) {
try {
mService.clearBroadcastResponseStats(packageName, id,
mContext.getOpPackageName(), mContext.getUserId());
@@ -1453,4 +1473,19 @@ public final class UsageStatsManager {
throw re.rethrowFromSystemServer();
}
}
/**
* Clears the broadcast events that were sent by the caller uid.
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
@UserHandleAware
public void clearBroadcastEvents() {
try {
mService.clearBroadcastEvents(mContext.getOpPackageName(), mContext.getUserId());
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
}

View File

@@ -22,6 +22,7 @@ import static com.android.server.usage.UsageStatsService.DEBUG_RESPONSE_STATS;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -39,6 +40,8 @@ import com.android.internal.util.IndentingPrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
class BroadcastResponseStatsTracker {
private static final String TAG = "ResponseStatsTracker";
@@ -172,28 +175,27 @@ class BroadcastResponseStatsTracker {
}
}
@NonNull BroadcastResponseStats queryBroadcastResponseStats(int callingUid,
@NonNull String packageName, long id, @UserIdInt int userId) {
final BroadcastResponseStats aggregatedResponseStats =
new BroadcastResponseStats(packageName);
@NonNull List<BroadcastResponseStats> queryBroadcastResponseStats(int callingUid,
@Nullable String packageName, @IntRange(from = 0) long id, @UserIdInt int userId) {
final List<BroadcastResponseStats> broadcastResponseStatsList = new ArrayList<>();
synchronized (mLock) {
final SparseArray<UserBroadcastResponseStats> responseStatsForCaller =
mUserResponseStats.get(callingUid);
if (responseStatsForCaller == null) {
return aggregatedResponseStats;
return broadcastResponseStatsList;
}
final UserBroadcastResponseStats responseStatsForUser =
responseStatsForCaller.get(userId);
if (responseStatsForUser == null) {
return aggregatedResponseStats;
return broadcastResponseStatsList;
}
responseStatsForUser.aggregateBroadcastResponseStats(aggregatedResponseStats,
packageName, id);
responseStatsForUser.populateAllBroadcastResponseStats(
broadcastResponseStatsList, packageName, id);
}
return aggregatedResponseStats;
return broadcastResponseStatsList;
}
void clearBroadcastResponseStats(int callingUid, @NonNull String packageName, long id,
void clearBroadcastResponseStats(int callingUid, @Nullable String packageName, long id,
@UserIdInt int userId) {
synchronized (mLock) {
final SparseArray<UserBroadcastResponseStats> responseStatsForCaller =
@@ -210,6 +212,16 @@ class BroadcastResponseStatsTracker {
}
}
void clearBroadcastEvents(int callingUid, @UserIdInt int userId) {
synchronized (mLock) {
final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(userId);
if (userBroadcastEvents == null) {
return;
}
userBroadcastEvents.clear(callingUid);
}
}
void onUserRemoved(@UserIdInt int userId) {
synchronized (mLock) {
mUserBroadcastEvents.remove(userId);

View File

@@ -49,7 +49,7 @@ import android.app.PendingIntent;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.AppLaunchEstimateInfo;
import android.app.usage.AppStandbyInfo;
import android.app.usage.BroadcastResponseStats;
import android.app.usage.BroadcastResponseStatsList;
import android.app.usage.ConfigurationStats;
import android.app.usage.EventStats;
import android.app.usage.IUsageStatsManager;
@@ -2686,16 +2686,15 @@ public class UsageStatsService extends SystemService implements
@Override
@NonNull
public BroadcastResponseStats queryBroadcastResponseStats(
@NonNull String packageName,
@IntRange(from = 1) long id,
public BroadcastResponseStatsList queryBroadcastResponseStats(
@Nullable String packageName,
@IntRange(from = 0) long id,
@NonNull String callingPackage,
@UserIdInt int userId) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callingPackage);
// TODO: Move to Preconditions utility class
if (id <= 0) {
throw new IllegalArgumentException("id needs to be >0");
if (id < 0) {
throw new IllegalArgumentException("id needs to be >=0");
}
final int callingUid = Binder.getCallingUid();
@@ -2708,8 +2707,9 @@ public class UsageStatsService extends SystemService implements
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid,
userId, false /* allowAll */, false /* requireFull */,
"queryBroadcastResponseStats" /* name */, callingPackage);
return mResponseStatsTracker.queryBroadcastResponseStats(
callingUid, packageName, id, userId);
return new BroadcastResponseStatsList(
mResponseStatsTracker.queryBroadcastResponseStats(
callingUid, packageName, id, userId));
}
@Override
@@ -2718,10 +2718,9 @@ public class UsageStatsService extends SystemService implements
@IntRange(from = 1) long id,
@NonNull String callingPackage,
@UserIdInt int userId) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callingPackage);
if (id <= 0) {
throw new IllegalArgumentException("id needs to be >0");
if (id < 0) {
throw new IllegalArgumentException("id needs to be >=0");
}
final int callingUid = Binder.getCallingUid();
@@ -2737,6 +2736,23 @@ public class UsageStatsService extends SystemService implements
mResponseStatsTracker.clearBroadcastResponseStats(callingUid,
packageName, id, userId);
}
@Override
public void clearBroadcastEvents(@NonNull String callingPackage, @UserIdInt int userId) {
Objects.requireNonNull(callingPackage);
final int callingUid = Binder.getCallingUid();
if (!hasPermission(callingPackage)) {
throw new SecurityException(
"Caller does not have the permission needed to call this API; "
+ "callingPackage=" + callingPackage
+ ", callingUid=" + callingUid);
}
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid,
userId, false /* allowAll */, false /* requireFull */,
"clearBroadcastResponseStats" /* name */, callingPackage);
mResponseStatsTracker.clearBroadcastEvents(callingUid, userId);
}
}
void registerAppUsageObserver(int callingUid, int observerId, String[] packages,

View File

@@ -51,6 +51,10 @@ class UserBroadcastEvents {
}
void onUidRemoved(int uid) {
clear(uid);
}
void clear(int uid) {
for (int i = mBroadcastEvents.size() - 1; i >= 0; --i) {
final LongSparseArray<BroadcastEvent> broadcastEvents = mBroadcastEvents.valueAt(i);
for (int j = broadcastEvents.size() - 1; j >= 0; --j) {

View File

@@ -16,6 +16,7 @@
package com.android.server.usage;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.usage.BroadcastResponseStats;
@@ -23,6 +24,8 @@ import android.util.ArrayMap;
import com.android.internal.util.IndentingPrintWriter;
import java.util.List;
class UserBroadcastResponseStats {
/**
* Contains the mapping of a BroadcastEvent type to it's aggregated stats.
@@ -39,31 +42,38 @@ class UserBroadcastResponseStats {
BroadcastEvent broadcastEvent) {
BroadcastResponseStats responseStats = mResponseStats.get(broadcastEvent);
if (responseStats == null) {
responseStats = new BroadcastResponseStats(broadcastEvent.getTargetPackage());
responseStats = new BroadcastResponseStats(broadcastEvent.getTargetPackage(),
broadcastEvent.getIdForResponseEvent());
mResponseStats.put(broadcastEvent, responseStats);
}
return responseStats;
}
void aggregateBroadcastResponseStats(
@NonNull BroadcastResponseStats responseStats,
@NonNull String packageName, long id) {
void populateAllBroadcastResponseStats(
@NonNull List<BroadcastResponseStats> broadcastResponseStatsList,
@Nullable String packageName, @IntRange(from = 0) long id) {
for (int i = mResponseStats.size() - 1; i >= 0; --i) {
final BroadcastEvent broadcastEvent = mResponseStats.keyAt(i);
if (broadcastEvent.getIdForResponseEvent() == id
&& broadcastEvent.getTargetPackage().equals(packageName)) {
responseStats.addCounts(mResponseStats.valueAt(i));
if (id != 0 && id != broadcastEvent.getIdForResponseEvent()) {
continue;
}
if (packageName != null && !packageName.equals(broadcastEvent.getTargetPackage())) {
continue;
}
broadcastResponseStatsList.add(mResponseStats.valueAt(i));
}
}
void clearBroadcastResponseStats(@NonNull String packageName, long id) {
void clearBroadcastResponseStats(@Nullable String packageName, @IntRange(from = 0) long id) {
for (int i = mResponseStats.size() - 1; i >= 0; --i) {
final BroadcastEvent broadcastEvent = mResponseStats.keyAt(i);
if (broadcastEvent.getIdForResponseEvent() == id
&& broadcastEvent.getTargetPackage().equals(packageName)) {
mResponseStats.removeAt(i);
if (id != 0 && id != broadcastEvent.getIdForResponseEvent()) {
continue;
}
if (packageName != null && !packageName.equals(broadcastEvent.getTargetPackage())) {
continue;
}
mResponseStats.removeAt(i);
}
}