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