1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.usage;
18 
19 import android.annotation.ElapsedRealtimeLong;
20 import android.annotation.IntDef;
21 import android.annotation.IntRange;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.UserIdInt;
25 import android.app.ActivityManager.ProcessState;
26 import android.app.role.OnRoleHoldersChangedListener;
27 import android.app.role.RoleManager;
28 import android.app.usage.BroadcastResponseStats;
29 import android.content.Context;
30 import android.content.pm.PackageManager;
31 import android.os.SystemClock;
32 import android.os.UserHandle;
33 import android.permission.PermissionManager;
34 import android.util.ArrayMap;
35 import android.util.ArraySet;
36 import android.util.LongArrayQueue;
37 import android.util.Slog;
38 import android.util.SparseArray;
39 
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.os.BackgroundThread;
42 import com.android.internal.util.CollectionUtils;
43 import com.android.internal.util.IndentingPrintWriter;
44 
45 import java.lang.annotation.Retention;
46 import java.lang.annotation.RetentionPolicy;
47 import java.util.ArrayList;
48 import java.util.List;
49 
50 class BroadcastResponseStatsTracker {
51     static final String TAG = "ResponseStatsTracker";
52 
53     @Retention(RetentionPolicy.SOURCE)
54     @IntDef(prefix = {"NOTIFICATION_EVENT_TYPE_"}, value = {
55             NOTIFICATION_EVENT_TYPE_POSTED,
56             NOTIFICATION_EVENT_TYPE_UPDATED,
57             NOTIFICATION_EVENT_TYPE_CANCELLED
58     })
59     public @interface NotificationEventType {}
60 
61     static final int NOTIFICATION_EVENT_TYPE_POSTED = 0;
62     static final int NOTIFICATION_EVENT_TYPE_UPDATED = 1;
63     static final int NOTIFICATION_EVENT_TYPE_CANCELLED = 2;
64 
65     private final Object mLock = new Object();
66 
67     /**
68      * Contains the mapping of user -> UserBroadcastEvents data.
69      */
70     @GuardedBy("mLock")
71     private SparseArray<UserBroadcastEvents> mUserBroadcastEvents = new SparseArray<>();
72 
73     /**
74      * Contains the mapping of sourceUid -> {targetUser -> UserBroadcastResponseStats} data.
75      * Here sourceUid refers to the uid that sent a broadcast and targetUser is the user that the
76      * broadcast was directed to.
77      */
78     @GuardedBy("mLock")
79     private SparseArray<SparseArray<UserBroadcastResponseStats>> mUserResponseStats =
80             new SparseArray<>();
81 
82     /**
83      * Cache of package names holding exempted roles.
84      *
85      * Contains the mapping of userId -> {roleName -> <packages>} data.
86      */
87     // TODO: Use SparseArrayMap to simplify the logic.
88     @GuardedBy("mLock")
89     private SparseArray<ArrayMap<String, List<String>>> mExemptedRoleHoldersCache =
90             new SparseArray<>();
91     private final OnRoleHoldersChangedListener mRoleHoldersChangedListener =
92             this::onRoleHoldersChanged;
93 
94     private AppStandbyInternal mAppStandby;
95     private BroadcastResponseStatsLogger mLogger;
96     private RoleManager mRoleManager;
97 
BroadcastResponseStatsTracker(@onNull AppStandbyInternal appStandby)98     BroadcastResponseStatsTracker(@NonNull AppStandbyInternal appStandby) {
99         mAppStandby = appStandby;
100         mLogger = new BroadcastResponseStatsLogger();
101     }
102 
onSystemServicesReady(Context context)103     void onSystemServicesReady(Context context) {
104         mRoleManager = context.getSystemService(RoleManager.class);
105         mRoleManager.addOnRoleHoldersChangedListenerAsUser(BackgroundThread.getExecutor(),
106                 mRoleHoldersChangedListener, UserHandle.ALL);
107     }
108 
109     // TODO (206518114): Move all callbacks handling to a handler thread.
reportBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage, UserHandle targetUser, long idForResponseEvent, @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState)110     void reportBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
111             UserHandle targetUser, long idForResponseEvent,
112             @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState) {
113         mLogger.logBroadcastDispatchEvent(sourceUid, targetPackage, targetUser,
114                 idForResponseEvent, timestampMs, targetUidProcState);
115         if (targetUidProcState <= mAppStandby.getBroadcastResponseFgThresholdState()) {
116             // No need to track the broadcast response stats while the target app is
117             // in the foreground.
118             return;
119         }
120         if (doesPackageHoldExemptedRole(targetPackage, targetUser)) {
121             // Package holds an exempted role, so no need to track the broadcast response stats.
122             return;
123         }
124         if (doesPackageHoldExemptedPermission(targetPackage, targetUser)) {
125             // Package holds an exempted permission, so no need to track the broadcast response
126             // stats
127             return;
128         }
129         synchronized (mLock) {
130             final ArraySet<BroadcastEvent> broadcastEvents =
131                     getOrCreateBroadcastEventsLocked(targetPackage, targetUser);
132             final BroadcastEvent broadcastEvent = getOrCreateBroadcastEvent(broadcastEvents,
133                     sourceUid, targetPackage, targetUser.getIdentifier(), idForResponseEvent);
134             broadcastEvent.addTimestampMs(timestampMs);
135 
136             // Delete any old broadcast event related data so that we don't keep accumulating them.
137             recordAndPruneOldBroadcastDispatchTimestamps(broadcastEvent);
138         }
139     }
140 
reportNotificationPosted(@onNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs)141     void reportNotificationPosted(@NonNull String packageName, UserHandle user,
142             @ElapsedRealtimeLong long timestampMs) {
143         reportNotificationEvent(NOTIFICATION_EVENT_TYPE_POSTED, packageName, user, timestampMs);
144     }
145 
reportNotificationUpdated(@onNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs)146     void reportNotificationUpdated(@NonNull String packageName, UserHandle user,
147             @ElapsedRealtimeLong long timestampMs) {
148         reportNotificationEvent(NOTIFICATION_EVENT_TYPE_UPDATED, packageName, user, timestampMs);
149 
150     }
151 
reportNotificationCancelled(@onNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs)152     void reportNotificationCancelled(@NonNull String packageName, UserHandle user,
153             @ElapsedRealtimeLong long timestampMs) {
154         reportNotificationEvent(NOTIFICATION_EVENT_TYPE_CANCELLED, packageName, user, timestampMs);
155     }
156 
reportNotificationEvent(@otificationEventType int event, @NonNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs)157     private void reportNotificationEvent(@NotificationEventType int event,
158             @NonNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs) {
159         mLogger.logNotificationEvent(event, packageName, user, timestampMs);
160         synchronized (mLock) {
161             final ArraySet<BroadcastEvent> broadcastEvents =
162                     getBroadcastEventsLocked(packageName, user);
163             if (broadcastEvents == null) {
164                 return;
165             }
166             final long broadcastResponseWindowDurationMs =
167                     mAppStandby.getBroadcastResponseWindowDurationMs();
168             final long broadcastsSessionWithResponseDurationMs =
169                     mAppStandby.getBroadcastSessionsWithResponseDurationMs();
170             final boolean recordAllBroadcastsSessionsWithinResponseWindow =
171                     mAppStandby.shouldNoteResponseEventForAllBroadcastSessions();
172             for (int i = broadcastEvents.size() - 1; i >= 0; --i) {
173                 final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i);
174                 recordAndPruneOldBroadcastDispatchTimestamps(broadcastEvent);
175 
176                 final LongArrayQueue dispatchTimestampsMs = broadcastEvent.getTimestampsMs();
177                 long broadcastsSessionEndTimestampMs = 0;
178                 // We only need to look at the broadcast events that occurred before
179                 // this notification related event.
180                 while (dispatchTimestampsMs.size() > 0
181                         && dispatchTimestampsMs.peekFirst() < timestampMs) {
182                     final long dispatchTimestampMs = dispatchTimestampsMs.peekFirst();
183                     final long elapsedDurationMs = timestampMs - dispatchTimestampMs;
184                     // Only increment the counts if the broadcast was sent not too long ago, as
185                     // decided by 'broadcastResponseWindowDurationMs' and is part of a new session.
186                     // That is, it occurred 'broadcastsSessionWithResponseDurationMs' after the
187                     // previously handled broadcast event which is represented by
188                     // 'broadcastsSessionEndTimestampMs'.
189                     if (elapsedDurationMs <= broadcastResponseWindowDurationMs
190                             && dispatchTimestampMs >= broadcastsSessionEndTimestampMs) {
191                         if (broadcastsSessionEndTimestampMs != 0
192                                 && !recordAllBroadcastsSessionsWithinResponseWindow) {
193                             break;
194                         }
195                         final BroadcastResponseStats responseStats =
196                                 getOrCreateBroadcastResponseStats(broadcastEvent);
197                         responseStats.incrementBroadcastsDispatchedCount(1);
198                         broadcastsSessionEndTimestampMs = dispatchTimestampMs
199                                 + broadcastsSessionWithResponseDurationMs;
200                         switch (event) {
201                             case NOTIFICATION_EVENT_TYPE_POSTED:
202                                 responseStats.incrementNotificationsPostedCount(1);
203                                 break;
204                             case NOTIFICATION_EVENT_TYPE_UPDATED:
205                                 responseStats.incrementNotificationsUpdatedCount(1);
206                                 break;
207                             case NOTIFICATION_EVENT_TYPE_CANCELLED:
208                                 responseStats.incrementNotificationsCancelledCount(1);
209                                 break;
210                             default:
211                                 Slog.wtf(TAG, "Unknown event: " + event);
212                         }
213                     }
214                     dispatchTimestampsMs.removeFirst();
215                 }
216                 if (dispatchTimestampsMs.size() == 0) {
217                     broadcastEvents.removeAt(i);
218                 }
219             }
220         }
221     }
222 
223     @GuardedBy("mLock")
recordAndPruneOldBroadcastDispatchTimestamps(BroadcastEvent broadcastEvent)224     private void recordAndPruneOldBroadcastDispatchTimestamps(BroadcastEvent broadcastEvent) {
225         final LongArrayQueue timestampsMs = broadcastEvent.getTimestampsMs();
226         final long broadcastResponseWindowDurationMs =
227                 mAppStandby.getBroadcastResponseWindowDurationMs();
228         final long broadcastsSessionDurationMs =
229                 mAppStandby.getBroadcastSessionsDurationMs();
230         final long nowElapsedMs = SystemClock.elapsedRealtime();
231         long broadcastsSessionEndTimestampMs = 0;
232         while (timestampsMs.size() > 0
233                 && timestampsMs.peekFirst() < (nowElapsedMs - broadcastResponseWindowDurationMs)) {
234             final long eventTimestampMs = timestampsMs.peekFirst();
235             if (eventTimestampMs >= broadcastsSessionEndTimestampMs) {
236                 final BroadcastResponseStats responseStats =
237                         getOrCreateBroadcastResponseStats(broadcastEvent);
238                 responseStats.incrementBroadcastsDispatchedCount(1);
239                 broadcastsSessionEndTimestampMs = eventTimestampMs + broadcastsSessionDurationMs;
240             }
241             timestampsMs.removeFirst();
242         }
243     }
244 
queryBroadcastResponseStats(int callingUid, @Nullable String packageName, @IntRange(from = 0) long id, @UserIdInt int userId)245     @NonNull List<BroadcastResponseStats> queryBroadcastResponseStats(int callingUid,
246             @Nullable String packageName, @IntRange(from = 0) long id, @UserIdInt int userId) {
247         final List<BroadcastResponseStats> broadcastResponseStatsList = new ArrayList<>();
248         synchronized (mLock) {
249             final SparseArray<UserBroadcastResponseStats> responseStatsForCaller =
250                     mUserResponseStats.get(callingUid);
251             if (responseStatsForCaller == null) {
252                 return broadcastResponseStatsList;
253             }
254             final UserBroadcastResponseStats responseStatsForUser =
255                     responseStatsForCaller.get(userId);
256             if (responseStatsForUser == null) {
257                 return broadcastResponseStatsList;
258             }
259             responseStatsForUser.populateAllBroadcastResponseStats(
260                     broadcastResponseStatsList, packageName, id);
261         }
262         return broadcastResponseStatsList;
263     }
264 
clearBroadcastResponseStats(int callingUid, @Nullable String packageName, long id, @UserIdInt int userId)265     void clearBroadcastResponseStats(int callingUid, @Nullable String packageName, long id,
266             @UserIdInt int userId) {
267         synchronized (mLock) {
268             final SparseArray<UserBroadcastResponseStats> responseStatsForCaller =
269                     mUserResponseStats.get(callingUid);
270             if (responseStatsForCaller == null) {
271                 return;
272             }
273             final UserBroadcastResponseStats responseStatsForUser =
274                     responseStatsForCaller.get(userId);
275             if (responseStatsForUser == null) {
276                 return;
277             }
278             responseStatsForUser.clearBroadcastResponseStats(packageName, id);
279         }
280     }
281 
clearBroadcastEvents(int callingUid, @UserIdInt int userId)282     void clearBroadcastEvents(int callingUid, @UserIdInt int userId) {
283         synchronized (mLock) {
284             final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(userId);
285             if (userBroadcastEvents == null) {
286                 return;
287             }
288             userBroadcastEvents.clear(callingUid);
289         }
290     }
291 
doesPackageHoldExemptedRole(@onNull String packageName, @NonNull UserHandle user)292     boolean doesPackageHoldExemptedRole(@NonNull String packageName, @NonNull UserHandle user) {
293         final List<String> exemptedRoles = mAppStandby.getBroadcastResponseExemptedRoles();
294         synchronized (mLock) {
295             for (int i = exemptedRoles.size() - 1; i >= 0; --i) {
296                 final String roleName = exemptedRoles.get(i);
297                 final List<String> roleHolders = getRoleHoldersLocked(roleName, user);
298                 if (CollectionUtils.contains(roleHolders, packageName)) {
299                     return true;
300                 }
301             }
302         }
303         return false;
304     }
305 
doesPackageHoldExemptedPermission(@onNull String packageName, @NonNull UserHandle user)306     boolean doesPackageHoldExemptedPermission(@NonNull String packageName,
307             @NonNull UserHandle user) {
308         final List<String> exemptedPermissions = mAppStandby
309                 .getBroadcastResponseExemptedPermissions();
310         for (int i = exemptedPermissions.size() - 1; i >= 0; --i) {
311             final String permissionName = exemptedPermissions.get(i);
312             if (PermissionManager.checkPackageNamePermission(permissionName, packageName,
313                     user.getIdentifier()) == PackageManager.PERMISSION_GRANTED) {
314                 return true;
315             }
316         }
317         return false;
318     }
319 
320     @GuardedBy("mLock")
321     @Nullable
getRoleHoldersLocked(@onNull String roleName, @NonNull UserHandle user)322     private List<String> getRoleHoldersLocked(@NonNull String roleName, @NonNull UserHandle user) {
323         ArrayMap<String, List<String>> roleHoldersForUser = mExemptedRoleHoldersCache.get(
324                 user.getIdentifier());
325         if (roleHoldersForUser == null) {
326             roleHoldersForUser = new ArrayMap<>();
327             mExemptedRoleHoldersCache.put(user.getIdentifier(), roleHoldersForUser);
328         }
329         List<String> roleHolders = roleHoldersForUser.get(roleName);
330         if (roleHolders == null && mRoleManager != null) {
331             roleHolders = mRoleManager.getRoleHoldersAsUser(roleName, user);
332             roleHoldersForUser.put(roleName, roleHolders);
333         }
334         return roleHolders;
335     }
336 
onRoleHoldersChanged(@onNull String roleName, @NonNull UserHandle user)337     private void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
338         synchronized (mLock) {
339             final ArrayMap<String, List<String>> roleHoldersForUser =
340                     mExemptedRoleHoldersCache.get(user.getIdentifier());
341             if (roleHoldersForUser == null) {
342                 return;
343             }
344             roleHoldersForUser.remove(roleName);
345         }
346     }
347 
onUserRemoved(@serIdInt int userId)348     void onUserRemoved(@UserIdInt int userId) {
349         synchronized (mLock) {
350             mUserBroadcastEvents.remove(userId);
351 
352             for (int i = mUserResponseStats.size() - 1; i >= 0; --i) {
353                 mUserResponseStats.valueAt(i).remove(userId);
354             }
355             mExemptedRoleHoldersCache.remove(userId);
356         }
357     }
358 
onPackageRemoved(@onNull String packageName, @UserIdInt int userId)359     void onPackageRemoved(@NonNull String packageName, @UserIdInt int userId) {
360         synchronized (mLock) {
361             final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(userId);
362             if (userBroadcastEvents != null) {
363                 userBroadcastEvents.onPackageRemoved(packageName);
364             }
365 
366             for (int i = mUserResponseStats.size() - 1; i >= 0; --i) {
367                 final UserBroadcastResponseStats userResponseStats =
368                         mUserResponseStats.valueAt(i).get(userId);
369                 if (userResponseStats != null) {
370                     userResponseStats.onPackageRemoved(packageName);
371                 }
372             }
373         }
374     }
375 
onUidRemoved(int uid)376     void onUidRemoved(int uid) {
377         synchronized (mLock) {
378             for (int i = mUserBroadcastEvents.size() - 1; i >= 0; --i) {
379                 mUserBroadcastEvents.valueAt(i).onUidRemoved(uid);
380             }
381 
382             mUserResponseStats.remove(uid);
383         }
384     }
385 
386     @GuardedBy("mLock")
387     @Nullable
getBroadcastEventsLocked( @onNull String packageName, UserHandle user)388     private ArraySet<BroadcastEvent> getBroadcastEventsLocked(
389             @NonNull String packageName, UserHandle user) {
390         final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(
391                 user.getIdentifier());
392         if (userBroadcastEvents == null) {
393             return null;
394         }
395         return userBroadcastEvents.getBroadcastEvents(packageName);
396     }
397 
398     @GuardedBy("mLock")
399     @NonNull
getOrCreateBroadcastEventsLocked( @onNull String packageName, UserHandle user)400     private ArraySet<BroadcastEvent> getOrCreateBroadcastEventsLocked(
401             @NonNull String packageName, UserHandle user) {
402         UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(user.getIdentifier());
403         if (userBroadcastEvents == null) {
404             userBroadcastEvents = new UserBroadcastEvents();
405             mUserBroadcastEvents.put(user.getIdentifier(), userBroadcastEvents);
406         }
407         return userBroadcastEvents.getOrCreateBroadcastEvents(packageName);
408     }
409 
410     @GuardedBy("mLock")
411     @Nullable
getBroadcastResponseStats( @ullable SparseArray<UserBroadcastResponseStats> responseStatsForUid, @NonNull BroadcastEvent broadcastEvent)412     private BroadcastResponseStats getBroadcastResponseStats(
413             @Nullable SparseArray<UserBroadcastResponseStats> responseStatsForUid,
414             @NonNull BroadcastEvent broadcastEvent) {
415         if (responseStatsForUid == null) {
416             return null;
417         }
418         final UserBroadcastResponseStats userResponseStats = responseStatsForUid.get(
419                 broadcastEvent.getTargetUserId());
420         if (userResponseStats == null) {
421             return null;
422         }
423         return userResponseStats.getBroadcastResponseStats(broadcastEvent);
424     }
425 
426     @GuardedBy("mLock")
427     @NonNull
getOrCreateBroadcastResponseStats( @onNull BroadcastEvent broadcastEvent)428     private BroadcastResponseStats getOrCreateBroadcastResponseStats(
429             @NonNull BroadcastEvent broadcastEvent) {
430         final int sourceUid = broadcastEvent.getSourceUid();
431         SparseArray<UserBroadcastResponseStats> userResponseStatsForUid =
432                 mUserResponseStats.get(sourceUid);
433         if (userResponseStatsForUid == null) {
434             userResponseStatsForUid = new SparseArray<>();
435             mUserResponseStats.put(sourceUid, userResponseStatsForUid);
436         }
437         UserBroadcastResponseStats userResponseStats = userResponseStatsForUid.get(
438                 broadcastEvent.getTargetUserId());
439         if (userResponseStats == null) {
440             userResponseStats = new UserBroadcastResponseStats();
441             userResponseStatsForUid.put(broadcastEvent.getTargetUserId(), userResponseStats);
442         }
443         return userResponseStats.getOrCreateBroadcastResponseStats(broadcastEvent);
444     }
445 
getOrCreateBroadcastEvent( ArraySet<BroadcastEvent> broadcastEvents, int sourceUid, String targetPackage, int targetUserId, long idForResponseEvent)446     private static BroadcastEvent getOrCreateBroadcastEvent(
447             ArraySet<BroadcastEvent> broadcastEvents,
448             int sourceUid, String targetPackage, int targetUserId, long idForResponseEvent) {
449         final BroadcastEvent broadcastEvent = new BroadcastEvent(
450                 sourceUid, targetPackage, targetUserId, idForResponseEvent);
451         final int index = broadcastEvents.indexOf(broadcastEvent);
452         if (index >= 0) {
453             return broadcastEvents.valueAt(index);
454         } else {
455             broadcastEvents.add(broadcastEvent);
456             return broadcastEvent;
457         }
458     }
459 
dump(@onNull IndentingPrintWriter ipw)460     void dump(@NonNull IndentingPrintWriter ipw) {
461         ipw.println("Broadcast response stats:");
462         ipw.increaseIndent();
463 
464         synchronized (mLock) {
465             dumpBroadcastEventsLocked(ipw);
466             ipw.println();
467             dumpResponseStatsLocked(ipw);
468             ipw.println();
469             dumpRoleHoldersLocked(ipw);
470             ipw.println();
471             mLogger.dumpLogs(ipw);
472         }
473 
474         ipw.decreaseIndent();
475     }
476 
477     @GuardedBy("mLock")
dumpBroadcastEventsLocked(@onNull IndentingPrintWriter ipw)478     private void dumpBroadcastEventsLocked(@NonNull IndentingPrintWriter ipw) {
479         ipw.println("Broadcast events:");
480         ipw.increaseIndent();
481         for (int i = 0; i < mUserBroadcastEvents.size(); ++i) {
482             final int userId = mUserBroadcastEvents.keyAt(i);
483             final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.valueAt(i);
484             ipw.println("User " + userId + ":");
485             ipw.increaseIndent();
486             userBroadcastEvents.dump(ipw);
487             ipw.decreaseIndent();
488         }
489         ipw.decreaseIndent();
490     }
491 
492     @GuardedBy("mLock")
dumpResponseStatsLocked(@onNull IndentingPrintWriter ipw)493     private void dumpResponseStatsLocked(@NonNull IndentingPrintWriter ipw) {
494         ipw.println("Response stats:");
495         ipw.increaseIndent();
496         for (int i = 0; i < mUserResponseStats.size(); ++i) {
497             final int sourceUid = mUserResponseStats.keyAt(i);
498             final SparseArray<UserBroadcastResponseStats> userBroadcastResponseStats =
499                     mUserResponseStats.valueAt(i);
500             ipw.println("Uid " + sourceUid + ":");
501             ipw.increaseIndent();
502             for (int j = 0; j < userBroadcastResponseStats.size(); ++j) {
503                 final int userId = userBroadcastResponseStats.keyAt(j);
504                 final UserBroadcastResponseStats broadcastResponseStats =
505                         userBroadcastResponseStats.valueAt(j);
506                 ipw.println("User " + userId + ":");
507                 ipw.increaseIndent();
508                 broadcastResponseStats.dump(ipw);
509                 ipw.decreaseIndent();
510             }
511             ipw.decreaseIndent();
512         }
513         ipw.decreaseIndent();
514     }
515 
516     @GuardedBy("mLock")
dumpRoleHoldersLocked(@onNull IndentingPrintWriter ipw)517     private void dumpRoleHoldersLocked(@NonNull IndentingPrintWriter ipw) {
518         ipw.println("Role holders:");
519         ipw.increaseIndent();
520         for (int userIdx = 0; userIdx < mExemptedRoleHoldersCache.size(); ++userIdx) {
521             final int userId = mExemptedRoleHoldersCache.keyAt(userIdx);
522             final ArrayMap<String, List<String>> roleHoldersForUser =
523                     mExemptedRoleHoldersCache.valueAt(userIdx);
524             ipw.println("User " + userId + ":");
525             ipw.increaseIndent();
526             for (int roleIdx = 0; roleIdx < roleHoldersForUser.size(); ++roleIdx) {
527                 final String roleName = roleHoldersForUser.keyAt(roleIdx);
528                 final List<String> holders = roleHoldersForUser.valueAt(roleIdx);
529                 ipw.print(roleName + ": ");
530                 for (int holderIdx = 0; holderIdx < holders.size(); ++holderIdx) {
531                     if (holderIdx > 0) {
532                         ipw.print(", ");
533                     }
534                     ipw.print(holders.get(holderIdx));
535                 }
536                 ipw.println();
537             }
538             ipw.decreaseIndent();
539         }
540         ipw.decreaseIndent();
541     }
542 }
543 
544