• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Process;
32 import android.os.SystemClock;
33 import android.os.UserHandle;
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     private final Context mContext;
98 
BroadcastResponseStatsTracker(@onNull AppStandbyInternal appStandby, @NonNull Context context)99     BroadcastResponseStatsTracker(@NonNull AppStandbyInternal appStandby,
100             @NonNull Context context) {
101         mAppStandby = appStandby;
102         mContext = context;
103         mLogger = new BroadcastResponseStatsLogger();
104     }
105 
onSystemServicesReady(Context context)106     void onSystemServicesReady(Context context) {
107         mRoleManager = context.getSystemService(RoleManager.class);
108         mRoleManager.addOnRoleHoldersChangedListenerAsUser(BackgroundThread.getExecutor(),
109                 mRoleHoldersChangedListener, UserHandle.ALL);
110     }
111 
112     // 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)113     void reportBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
114             UserHandle targetUser, long idForResponseEvent,
115             @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState) {
116         mLogger.logBroadcastDispatchEvent(sourceUid, targetPackage, targetUser,
117                 idForResponseEvent, timestampMs, targetUidProcState);
118         if (targetUidProcState <= mAppStandby.getBroadcastResponseFgThresholdState()) {
119             // No need to track the broadcast response stats while the target app is
120             // in the foreground.
121             return;
122         }
123         if (doesPackageHoldExemptedRole(targetPackage, targetUser)) {
124             // Package holds an exempted role, so no need to track the broadcast response stats.
125             return;
126         }
127         if (doesPackageHoldExemptedPermission(targetPackage, targetUser)) {
128             // Package holds an exempted permission, so no need to track the broadcast response
129             // stats
130             return;
131         }
132         synchronized (mLock) {
133             final ArraySet<BroadcastEvent> broadcastEvents =
134                     getOrCreateBroadcastEventsLocked(targetPackage, targetUser);
135             final BroadcastEvent broadcastEvent = getOrCreateBroadcastEvent(broadcastEvents,
136                     sourceUid, targetPackage, targetUser.getIdentifier(), idForResponseEvent);
137             broadcastEvent.addTimestampMs(timestampMs);
138 
139             // Delete any old broadcast event related data so that we don't keep accumulating them.
140             recordAndPruneOldBroadcastDispatchTimestamps(broadcastEvent);
141         }
142     }
143 
reportNotificationPosted(@onNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs)144     void reportNotificationPosted(@NonNull String packageName, UserHandle user,
145             @ElapsedRealtimeLong long timestampMs) {
146         reportNotificationEvent(NOTIFICATION_EVENT_TYPE_POSTED, packageName, user, timestampMs);
147     }
148 
reportNotificationUpdated(@onNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs)149     void reportNotificationUpdated(@NonNull String packageName, UserHandle user,
150             @ElapsedRealtimeLong long timestampMs) {
151         reportNotificationEvent(NOTIFICATION_EVENT_TYPE_UPDATED, packageName, user, timestampMs);
152 
153     }
154 
reportNotificationCancelled(@onNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs)155     void reportNotificationCancelled(@NonNull String packageName, UserHandle user,
156             @ElapsedRealtimeLong long timestampMs) {
157         reportNotificationEvent(NOTIFICATION_EVENT_TYPE_CANCELLED, packageName, user, timestampMs);
158     }
159 
reportNotificationEvent(@otificationEventType int event, @NonNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs)160     private void reportNotificationEvent(@NotificationEventType int event,
161             @NonNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs) {
162         mLogger.logNotificationEvent(event, packageName, user, timestampMs);
163         synchronized (mLock) {
164             final ArraySet<BroadcastEvent> broadcastEvents =
165                     getBroadcastEventsLocked(packageName, user);
166             if (broadcastEvents == null) {
167                 return;
168             }
169             final long broadcastResponseWindowDurationMs =
170                     mAppStandby.getBroadcastResponseWindowDurationMs();
171             final long broadcastsSessionWithResponseDurationMs =
172                     mAppStandby.getBroadcastSessionsWithResponseDurationMs();
173             final boolean recordAllBroadcastsSessionsWithinResponseWindow =
174                     mAppStandby.shouldNoteResponseEventForAllBroadcastSessions();
175             for (int i = broadcastEvents.size() - 1; i >= 0; --i) {
176                 final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i);
177                 recordAndPruneOldBroadcastDispatchTimestamps(broadcastEvent);
178 
179                 final LongArrayQueue dispatchTimestampsMs = broadcastEvent.getTimestampsMs();
180                 long broadcastsSessionEndTimestampMs = 0;
181                 // We only need to look at the broadcast events that occurred before
182                 // this notification related event.
183                 while (dispatchTimestampsMs.size() > 0
184                         && dispatchTimestampsMs.peekFirst() <= timestampMs) {
185                     final long dispatchTimestampMs = dispatchTimestampsMs.peekFirst();
186                     final long elapsedDurationMs = timestampMs - dispatchTimestampMs;
187                     // Only increment the counts if the broadcast was sent not too long ago, as
188                     // decided by 'broadcastResponseWindowDurationMs' and is part of a new session.
189                     // That is, it occurred 'broadcastsSessionWithResponseDurationMs' after the
190                     // previously handled broadcast event which is represented by
191                     // 'broadcastsSessionEndTimestampMs'.
192                     if (elapsedDurationMs <= broadcastResponseWindowDurationMs
193                             && dispatchTimestampMs >= broadcastsSessionEndTimestampMs) {
194                         if (broadcastsSessionEndTimestampMs != 0
195                                 && !recordAllBroadcastsSessionsWithinResponseWindow) {
196                             break;
197                         }
198                         final BroadcastResponseStats responseStats =
199                                 getOrCreateBroadcastResponseStats(broadcastEvent);
200                         responseStats.incrementBroadcastsDispatchedCount(1);
201                         broadcastsSessionEndTimestampMs = dispatchTimestampMs
202                                 + broadcastsSessionWithResponseDurationMs;
203                         switch (event) {
204                             case NOTIFICATION_EVENT_TYPE_POSTED:
205                                 responseStats.incrementNotificationsPostedCount(1);
206                                 break;
207                             case NOTIFICATION_EVENT_TYPE_UPDATED:
208                                 responseStats.incrementNotificationsUpdatedCount(1);
209                                 break;
210                             case NOTIFICATION_EVENT_TYPE_CANCELLED:
211                                 responseStats.incrementNotificationsCancelledCount(1);
212                                 break;
213                             default:
214                                 Slog.wtf(TAG, "Unknown event: " + event);
215                         }
216                     }
217                     dispatchTimestampsMs.removeFirst();
218                 }
219                 if (dispatchTimestampsMs.size() == 0) {
220                     broadcastEvents.removeAt(i);
221                 }
222             }
223         }
224     }
225 
226     @GuardedBy("mLock")
recordAndPruneOldBroadcastDispatchTimestamps(BroadcastEvent broadcastEvent)227     private void recordAndPruneOldBroadcastDispatchTimestamps(BroadcastEvent broadcastEvent) {
228         final LongArrayQueue timestampsMs = broadcastEvent.getTimestampsMs();
229         final long broadcastResponseWindowDurationMs =
230                 mAppStandby.getBroadcastResponseWindowDurationMs();
231         final long broadcastsSessionDurationMs =
232                 mAppStandby.getBroadcastSessionsDurationMs();
233         final long nowElapsedMs = SystemClock.elapsedRealtime();
234         long broadcastsSessionEndTimestampMs = 0;
235         while (timestampsMs.size() > 0
236                 && timestampsMs.peekFirst() < (nowElapsedMs - broadcastResponseWindowDurationMs)) {
237             final long eventTimestampMs = timestampsMs.peekFirst();
238             if (eventTimestampMs >= broadcastsSessionEndTimestampMs) {
239                 final BroadcastResponseStats responseStats =
240                         getOrCreateBroadcastResponseStats(broadcastEvent);
241                 responseStats.incrementBroadcastsDispatchedCount(1);
242                 broadcastsSessionEndTimestampMs = eventTimestampMs + broadcastsSessionDurationMs;
243             }
244             timestampsMs.removeFirst();
245         }
246     }
247 
queryBroadcastResponseStats(int callingUid, @Nullable String packageName, @IntRange(from = 0) long id, @UserIdInt int userId)248     @NonNull List<BroadcastResponseStats> queryBroadcastResponseStats(int callingUid,
249             @Nullable String packageName, @IntRange(from = 0) long id, @UserIdInt int userId) {
250         final List<BroadcastResponseStats> broadcastResponseStatsList = new ArrayList<>();
251         synchronized (mLock) {
252             final SparseArray<UserBroadcastResponseStats> responseStatsForCaller =
253                     mUserResponseStats.get(callingUid);
254             if (responseStatsForCaller == null) {
255                 return broadcastResponseStatsList;
256             }
257             final UserBroadcastResponseStats responseStatsForUser =
258                     responseStatsForCaller.get(userId);
259             if (responseStatsForUser == null) {
260                 return broadcastResponseStatsList;
261             }
262             responseStatsForUser.populateAllBroadcastResponseStats(
263                     broadcastResponseStatsList, packageName, id);
264         }
265         return broadcastResponseStatsList;
266     }
267 
clearBroadcastResponseStats(int callingUid, @Nullable String packageName, long id, @UserIdInt int userId)268     void clearBroadcastResponseStats(int callingUid, @Nullable String packageName, long id,
269             @UserIdInt int userId) {
270         synchronized (mLock) {
271             final SparseArray<UserBroadcastResponseStats> responseStatsForCaller =
272                     mUserResponseStats.get(callingUid);
273             if (responseStatsForCaller == null) {
274                 return;
275             }
276             final UserBroadcastResponseStats responseStatsForUser =
277                     responseStatsForCaller.get(userId);
278             if (responseStatsForUser == null) {
279                 return;
280             }
281             responseStatsForUser.clearBroadcastResponseStats(packageName, id);
282         }
283     }
284 
clearBroadcastEvents(int callingUid, @UserIdInt int userId)285     void clearBroadcastEvents(int callingUid, @UserIdInt int userId) {
286         synchronized (mLock) {
287             final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(userId);
288             if (userBroadcastEvents == null) {
289                 return;
290             }
291             userBroadcastEvents.clear(callingUid);
292         }
293     }
294 
isPackageExemptedFromBroadcastResponseStats(@onNull String packageName, @NonNull UserHandle user)295     boolean isPackageExemptedFromBroadcastResponseStats(@NonNull String packageName,
296             @NonNull UserHandle user) {
297         synchronized (mLock) {
298             if (doesPackageHoldExemptedPermission(packageName, user)) {
299                 return true;
300             }
301             if (doesPackageHoldExemptedRole(packageName, user)) {
302                 return true;
303             }
304             return false;
305         }
306     }
307 
doesPackageHoldExemptedRole(@onNull String packageName, @NonNull UserHandle user)308     boolean doesPackageHoldExemptedRole(@NonNull String packageName, @NonNull UserHandle user) {
309         final List<String> exemptedRoles = mAppStandby.getBroadcastResponseExemptedRoles();
310         synchronized (mLock) {
311             for (int i = exemptedRoles.size() - 1; i >= 0; --i) {
312                 final String roleName = exemptedRoles.get(i);
313                 final List<String> roleHolders = getRoleHoldersLocked(roleName, user);
314                 if (CollectionUtils.contains(roleHolders, packageName)) {
315                     return true;
316                 }
317             }
318         }
319         return false;
320     }
321 
doesPackageHoldExemptedPermission(@onNull String packageName, @NonNull UserHandle user)322     boolean doesPackageHoldExemptedPermission(@NonNull String packageName,
323             @NonNull UserHandle user) {
324         int uid;
325         try {
326             uid = mContext.getPackageManager().getPackageUidAsUser(
327                     packageName, user.getIdentifier());
328         } catch (PackageManager.NameNotFoundException e) {
329             return false;
330         }
331         final List<String> exemptedPermissions =
332                 mAppStandby.getBroadcastResponseExemptedPermissions();
333         for (int i = exemptedPermissions.size() - 1; i >= 0; --i) {
334             final String permissionName = exemptedPermissions.get(i);
335             if (mContext.checkPermission(permissionName, Process.INVALID_PID, uid)
336                     == PackageManager.PERMISSION_GRANTED) {
337                 return true;
338             }
339         }
340         return false;
341     }
342 
343     @GuardedBy("mLock")
344     @Nullable
getRoleHoldersLocked(@onNull String roleName, @NonNull UserHandle user)345     private List<String> getRoleHoldersLocked(@NonNull String roleName, @NonNull UserHandle user) {
346         ArrayMap<String, List<String>> roleHoldersForUser = mExemptedRoleHoldersCache.get(
347                 user.getIdentifier());
348         if (roleHoldersForUser == null) {
349             roleHoldersForUser = new ArrayMap<>();
350             mExemptedRoleHoldersCache.put(user.getIdentifier(), roleHoldersForUser);
351         }
352         List<String> roleHolders = roleHoldersForUser.get(roleName);
353         if (roleHolders == null && mRoleManager != null) {
354             roleHolders = mRoleManager.getRoleHoldersAsUser(roleName, user);
355             roleHoldersForUser.put(roleName, roleHolders);
356         }
357         return roleHolders;
358     }
359 
onRoleHoldersChanged(@onNull String roleName, @NonNull UserHandle user)360     private void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
361         synchronized (mLock) {
362             final ArrayMap<String, List<String>> roleHoldersForUser =
363                     mExemptedRoleHoldersCache.get(user.getIdentifier());
364             if (roleHoldersForUser == null) {
365                 return;
366             }
367             roleHoldersForUser.remove(roleName);
368         }
369     }
370 
onUserRemoved(@serIdInt int userId)371     void onUserRemoved(@UserIdInt int userId) {
372         synchronized (mLock) {
373             mUserBroadcastEvents.remove(userId);
374 
375             for (int i = mUserResponseStats.size() - 1; i >= 0; --i) {
376                 mUserResponseStats.valueAt(i).remove(userId);
377             }
378             mExemptedRoleHoldersCache.remove(userId);
379         }
380     }
381 
onPackageRemoved(@onNull String packageName, @UserIdInt int userId)382     void onPackageRemoved(@NonNull String packageName, @UserIdInt int userId) {
383         synchronized (mLock) {
384             final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(userId);
385             if (userBroadcastEvents != null) {
386                 userBroadcastEvents.onPackageRemoved(packageName);
387             }
388 
389             for (int i = mUserResponseStats.size() - 1; i >= 0; --i) {
390                 final UserBroadcastResponseStats userResponseStats =
391                         mUserResponseStats.valueAt(i).get(userId);
392                 if (userResponseStats != null) {
393                     userResponseStats.onPackageRemoved(packageName);
394                 }
395             }
396         }
397     }
398 
onUidRemoved(int uid)399     void onUidRemoved(int uid) {
400         synchronized (mLock) {
401             for (int i = mUserBroadcastEvents.size() - 1; i >= 0; --i) {
402                 mUserBroadcastEvents.valueAt(i).onUidRemoved(uid);
403             }
404 
405             mUserResponseStats.remove(uid);
406         }
407     }
408 
409     @GuardedBy("mLock")
410     @Nullable
getBroadcastEventsLocked( @onNull String packageName, UserHandle user)411     private ArraySet<BroadcastEvent> getBroadcastEventsLocked(
412             @NonNull String packageName, UserHandle user) {
413         final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(
414                 user.getIdentifier());
415         if (userBroadcastEvents == null) {
416             return null;
417         }
418         return userBroadcastEvents.getBroadcastEvents(packageName);
419     }
420 
421     @GuardedBy("mLock")
422     @NonNull
getOrCreateBroadcastEventsLocked( @onNull String packageName, UserHandle user)423     private ArraySet<BroadcastEvent> getOrCreateBroadcastEventsLocked(
424             @NonNull String packageName, UserHandle user) {
425         UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(user.getIdentifier());
426         if (userBroadcastEvents == null) {
427             userBroadcastEvents = new UserBroadcastEvents();
428             mUserBroadcastEvents.put(user.getIdentifier(), userBroadcastEvents);
429         }
430         return userBroadcastEvents.getOrCreateBroadcastEvents(packageName);
431     }
432 
433     @GuardedBy("mLock")
434     @Nullable
getBroadcastResponseStats( @ullable SparseArray<UserBroadcastResponseStats> responseStatsForUid, @NonNull BroadcastEvent broadcastEvent)435     private BroadcastResponseStats getBroadcastResponseStats(
436             @Nullable SparseArray<UserBroadcastResponseStats> responseStatsForUid,
437             @NonNull BroadcastEvent broadcastEvent) {
438         if (responseStatsForUid == null) {
439             return null;
440         }
441         final UserBroadcastResponseStats userResponseStats = responseStatsForUid.get(
442                 broadcastEvent.getTargetUserId());
443         if (userResponseStats == null) {
444             return null;
445         }
446         return userResponseStats.getBroadcastResponseStats(broadcastEvent);
447     }
448 
449     @GuardedBy("mLock")
450     @NonNull
getOrCreateBroadcastResponseStats( @onNull BroadcastEvent broadcastEvent)451     private BroadcastResponseStats getOrCreateBroadcastResponseStats(
452             @NonNull BroadcastEvent broadcastEvent) {
453         final int sourceUid = broadcastEvent.getSourceUid();
454         SparseArray<UserBroadcastResponseStats> userResponseStatsForUid =
455                 mUserResponseStats.get(sourceUid);
456         if (userResponseStatsForUid == null) {
457             userResponseStatsForUid = new SparseArray<>();
458             mUserResponseStats.put(sourceUid, userResponseStatsForUid);
459         }
460         UserBroadcastResponseStats userResponseStats = userResponseStatsForUid.get(
461                 broadcastEvent.getTargetUserId());
462         if (userResponseStats == null) {
463             userResponseStats = new UserBroadcastResponseStats();
464             userResponseStatsForUid.put(broadcastEvent.getTargetUserId(), userResponseStats);
465         }
466         return userResponseStats.getOrCreateBroadcastResponseStats(broadcastEvent);
467     }
468 
getOrCreateBroadcastEvent( ArraySet<BroadcastEvent> broadcastEvents, int sourceUid, String targetPackage, int targetUserId, long idForResponseEvent)469     private static BroadcastEvent getOrCreateBroadcastEvent(
470             ArraySet<BroadcastEvent> broadcastEvents,
471             int sourceUid, String targetPackage, int targetUserId, long idForResponseEvent) {
472         final BroadcastEvent broadcastEvent = new BroadcastEvent(
473                 sourceUid, targetPackage, targetUserId, idForResponseEvent);
474         final int index = broadcastEvents.indexOf(broadcastEvent);
475         if (index >= 0) {
476             return broadcastEvents.valueAt(index);
477         } else {
478             broadcastEvents.add(broadcastEvent);
479             return broadcastEvent;
480         }
481     }
482 
dump(@onNull IndentingPrintWriter ipw)483     void dump(@NonNull IndentingPrintWriter ipw) {
484         ipw.println("Broadcast response stats:");
485         ipw.increaseIndent();
486 
487         synchronized (mLock) {
488             dumpBroadcastEventsLocked(ipw);
489             ipw.println();
490             dumpResponseStatsLocked(ipw);
491             ipw.println();
492             dumpRoleHoldersLocked(ipw);
493             ipw.println();
494             mLogger.dumpLogs(ipw);
495         }
496 
497         ipw.decreaseIndent();
498     }
499 
500     @GuardedBy("mLock")
dumpBroadcastEventsLocked(@onNull IndentingPrintWriter ipw)501     private void dumpBroadcastEventsLocked(@NonNull IndentingPrintWriter ipw) {
502         ipw.println("Broadcast events:");
503         ipw.increaseIndent();
504         for (int i = 0; i < mUserBroadcastEvents.size(); ++i) {
505             final int userId = mUserBroadcastEvents.keyAt(i);
506             final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.valueAt(i);
507             ipw.println("User " + userId + ":");
508             ipw.increaseIndent();
509             userBroadcastEvents.dump(ipw);
510             ipw.decreaseIndent();
511         }
512         ipw.decreaseIndent();
513     }
514 
515     @GuardedBy("mLock")
dumpResponseStatsLocked(@onNull IndentingPrintWriter ipw)516     private void dumpResponseStatsLocked(@NonNull IndentingPrintWriter ipw) {
517         ipw.println("Response stats:");
518         ipw.increaseIndent();
519         for (int i = 0; i < mUserResponseStats.size(); ++i) {
520             final int sourceUid = mUserResponseStats.keyAt(i);
521             final SparseArray<UserBroadcastResponseStats> userBroadcastResponseStats =
522                     mUserResponseStats.valueAt(i);
523             ipw.println("Uid " + sourceUid + ":");
524             ipw.increaseIndent();
525             for (int j = 0; j < userBroadcastResponseStats.size(); ++j) {
526                 final int userId = userBroadcastResponseStats.keyAt(j);
527                 final UserBroadcastResponseStats broadcastResponseStats =
528                         userBroadcastResponseStats.valueAt(j);
529                 ipw.println("User " + userId + ":");
530                 ipw.increaseIndent();
531                 broadcastResponseStats.dump(ipw);
532                 ipw.decreaseIndent();
533             }
534             ipw.decreaseIndent();
535         }
536         ipw.decreaseIndent();
537     }
538 
539     @GuardedBy("mLock")
dumpRoleHoldersLocked(@onNull IndentingPrintWriter ipw)540     private void dumpRoleHoldersLocked(@NonNull IndentingPrintWriter ipw) {
541         ipw.println("Role holders:");
542         ipw.increaseIndent();
543         for (int userIdx = 0; userIdx < mExemptedRoleHoldersCache.size(); ++userIdx) {
544             final int userId = mExemptedRoleHoldersCache.keyAt(userIdx);
545             final ArrayMap<String, List<String>> roleHoldersForUser =
546                     mExemptedRoleHoldersCache.valueAt(userIdx);
547             ipw.println("User " + userId + ":");
548             ipw.increaseIndent();
549             for (int roleIdx = 0; roleIdx < roleHoldersForUser.size(); ++roleIdx) {
550                 final String roleName = roleHoldersForUser.keyAt(roleIdx);
551                 final List<String> holders = roleHoldersForUser.valueAt(roleIdx);
552                 ipw.print(roleName + ": ");
553                 for (int holderIdx = 0; holderIdx < holders.size(); ++holderIdx) {
554                     if (holderIdx > 0) {
555                         ipw.print(", ");
556                     }
557                     ipw.print(holders.get(holderIdx));
558                 }
559                 ipw.println();
560             }
561             ipw.decreaseIndent();
562         }
563         ipw.decreaseIndent();
564     }
565 }
566 
567