1 /* 2 * Copyright (C) 2019 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.stats; 18 19 import static com.android.server.stats.StatsCompanion.PendingIntentRef; 20 21 import android.Manifest; 22 import android.annotation.Nullable; 23 import android.app.AppOpsManager; 24 import android.app.PendingIntent; 25 import android.content.Context; 26 import android.os.Binder; 27 import android.os.IPullAtomCallback; 28 import android.os.IStatsManagerService; 29 import android.os.IStatsd; 30 import android.os.PowerManager; 31 import android.os.Process; 32 import android.os.RemoteException; 33 import android.util.ArrayMap; 34 import android.util.Log; 35 36 import com.android.internal.annotations.GuardedBy; 37 38 import java.util.Map; 39 import java.util.Objects; 40 41 /** 42 * Service for {@link android.app.StatsManager}. 43 * 44 * @hide 45 */ 46 public class StatsManagerService extends IStatsManagerService.Stub { 47 48 private static final String TAG = "StatsManagerService"; 49 private static final boolean DEBUG = false; 50 51 private static final int STATSD_TIMEOUT_MILLIS = 5000; 52 53 private static final String USAGE_STATS_PERMISSION_OPS = "android:get_usage_stats"; 54 55 @GuardedBy("mLock") 56 private IStatsd mStatsd; 57 private final Object mLock = new Object(); 58 59 private StatsCompanionService mStatsCompanionService; 60 private Context mContext; 61 62 @GuardedBy("mLock") 63 private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>(); 64 @GuardedBy("mLock") 65 private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>(); 66 @GuardedBy("mLock") 67 private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap = 68 new ArrayMap<>(); 69 StatsManagerService(Context context)70 public StatsManagerService(Context context) { 71 super(); 72 mContext = context; 73 } 74 75 private static class ConfigKey { 76 private final int mUid; 77 private final long mConfigId; 78 ConfigKey(int uid, long configId)79 ConfigKey(int uid, long configId) { 80 mUid = uid; 81 mConfigId = configId; 82 } 83 getUid()84 public int getUid() { 85 return mUid; 86 } 87 getConfigId()88 public long getConfigId() { 89 return mConfigId; 90 } 91 92 @Override hashCode()93 public int hashCode() { 94 return Objects.hash(mUid, mConfigId); 95 } 96 97 @Override equals(Object obj)98 public boolean equals(Object obj) { 99 if (obj instanceof ConfigKey) { 100 ConfigKey other = (ConfigKey) obj; 101 return this.mUid == other.getUid() && this.mConfigId == other.getConfigId(); 102 } 103 return false; 104 } 105 } 106 107 private static class PullerKey { 108 private final int mUid; 109 private final int mAtomTag; 110 PullerKey(int uid, int atom)111 PullerKey(int uid, int atom) { 112 mUid = uid; 113 mAtomTag = atom; 114 } 115 getUid()116 public int getUid() { 117 return mUid; 118 } 119 getAtom()120 public int getAtom() { 121 return mAtomTag; 122 } 123 124 @Override hashCode()125 public int hashCode() { 126 return Objects.hash(mUid, mAtomTag); 127 } 128 129 @Override equals(Object obj)130 public boolean equals(Object obj) { 131 if (obj instanceof PullerKey) { 132 PullerKey other = (PullerKey) obj; 133 return this.mUid == other.getUid() && this.mAtomTag == other.getAtom(); 134 } 135 return false; 136 } 137 } 138 139 private static class PullerValue { 140 private final long mCoolDownMillis; 141 private final long mTimeoutMillis; 142 private final int[] mAdditiveFields; 143 private final IPullAtomCallback mCallback; 144 PullerValue(long coolDownMillis, long timeoutMillis, int[] additiveFields, IPullAtomCallback callback)145 PullerValue(long coolDownMillis, long timeoutMillis, int[] additiveFields, 146 IPullAtomCallback callback) { 147 mCoolDownMillis = coolDownMillis; 148 mTimeoutMillis = timeoutMillis; 149 mAdditiveFields = additiveFields; 150 mCallback = callback; 151 } 152 getCoolDownMillis()153 public long getCoolDownMillis() { 154 return mCoolDownMillis; 155 } 156 getTimeoutMillis()157 public long getTimeoutMillis() { 158 return mTimeoutMillis; 159 } 160 getAdditiveFields()161 public int[] getAdditiveFields() { 162 return mAdditiveFields; 163 } 164 getCallback()165 public IPullAtomCallback getCallback() { 166 return mCallback; 167 } 168 } 169 170 private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>(); 171 172 @Override registerPullAtomCallback(int atomTag, long coolDownMillis, long timeoutMillis, int[] additiveFields, IPullAtomCallback pullerCallback)173 public void registerPullAtomCallback(int atomTag, long coolDownMillis, long timeoutMillis, 174 int[] additiveFields, IPullAtomCallback pullerCallback) { 175 enforceRegisterStatsPullAtomPermission(); 176 if (pullerCallback == null) { 177 Log.w(TAG, "Puller callback is null for atom " + atomTag); 178 return; 179 } 180 int callingUid = Binder.getCallingUid(); 181 PullerKey key = new PullerKey(callingUid, atomTag); 182 PullerValue val = 183 new PullerValue(coolDownMillis, timeoutMillis, additiveFields, pullerCallback); 184 185 // Always cache the puller in StatsManagerService. If statsd is down, we will register the 186 // puller when statsd comes back up. 187 synchronized (mLock) { 188 mPullers.put(key, val); 189 } 190 191 IStatsd statsd = getStatsdNonblocking(); 192 if (statsd == null) { 193 return; 194 } 195 196 final long token = Binder.clearCallingIdentity(); 197 try { 198 statsd.registerPullAtomCallback(callingUid, atomTag, coolDownMillis, timeoutMillis, 199 additiveFields, pullerCallback); 200 } catch (RemoteException e) { 201 Log.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); 202 } finally { 203 Binder.restoreCallingIdentity(token); 204 } 205 } 206 207 @Override unregisterPullAtomCallback(int atomTag)208 public void unregisterPullAtomCallback(int atomTag) { 209 enforceRegisterStatsPullAtomPermission(); 210 int callingUid = Binder.getCallingUid(); 211 PullerKey key = new PullerKey(callingUid, atomTag); 212 213 // Always remove the puller from StatsManagerService even if statsd is down. When statsd 214 // comes back up, we will not re-register the removed puller. 215 synchronized (mLock) { 216 mPullers.remove(key); 217 } 218 219 IStatsd statsd = getStatsdNonblocking(); 220 if (statsd == null) { 221 return; 222 } 223 224 final long token = Binder.clearCallingIdentity(); 225 try { 226 statsd.unregisterPullAtomCallback(callingUid, atomTag); 227 } catch (RemoteException e) { 228 Log.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag); 229 } finally { 230 Binder.restoreCallingIdentity(token); 231 } 232 } 233 234 @Override setDataFetchOperation(long configId, PendingIntent pendingIntent, String packageName)235 public void setDataFetchOperation(long configId, PendingIntent pendingIntent, 236 String packageName) { 237 enforceDumpAndUsageStatsPermission(packageName); 238 int callingUid = Binder.getCallingUid(); 239 final long token = Binder.clearCallingIdentity(); 240 PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext); 241 ConfigKey key = new ConfigKey(callingUid, configId); 242 // We add the PIR to a map so we can reregister if statsd is unavailable. 243 synchronized (mLock) { 244 mDataFetchPirMap.put(key, pir); 245 } 246 try { 247 IStatsd statsd = getStatsdNonblocking(); 248 if (statsd != null) { 249 statsd.setDataFetchOperation(configId, pir, callingUid); 250 } 251 } catch (RemoteException e) { 252 Log.e(TAG, "Failed to setDataFetchOperation with statsd"); 253 } finally { 254 Binder.restoreCallingIdentity(token); 255 } 256 } 257 258 @Override removeDataFetchOperation(long configId, String packageName)259 public void removeDataFetchOperation(long configId, String packageName) { 260 enforceDumpAndUsageStatsPermission(packageName); 261 int callingUid = Binder.getCallingUid(); 262 final long token = Binder.clearCallingIdentity(); 263 ConfigKey key = new ConfigKey(callingUid, configId); 264 synchronized (mLock) { 265 mDataFetchPirMap.remove(key); 266 } 267 try { 268 IStatsd statsd = getStatsdNonblocking(); 269 if (statsd != null) { 270 statsd.removeDataFetchOperation(configId, callingUid); 271 } 272 } catch (RemoteException e) { 273 Log.e(TAG, "Failed to removeDataFetchOperation with statsd"); 274 } finally { 275 Binder.restoreCallingIdentity(token); 276 } 277 } 278 279 @Override setActiveConfigsChangedOperation(PendingIntent pendingIntent, String packageName)280 public long[] setActiveConfigsChangedOperation(PendingIntent pendingIntent, 281 String packageName) { 282 enforceDumpAndUsageStatsPermission(packageName); 283 int callingUid = Binder.getCallingUid(); 284 final long token = Binder.clearCallingIdentity(); 285 PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext); 286 // We add the PIR to a map so we can reregister if statsd is unavailable. 287 synchronized (mLock) { 288 mActiveConfigsPirMap.put(callingUid, pir); 289 } 290 try { 291 IStatsd statsd = getStatsdNonblocking(); 292 if (statsd != null) { 293 return statsd.setActiveConfigsChangedOperation(pir, callingUid); 294 } 295 } catch (RemoteException e) { 296 Log.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd"); 297 } finally { 298 Binder.restoreCallingIdentity(token); 299 } 300 return new long[] {}; 301 } 302 303 @Override removeActiveConfigsChangedOperation(String packageName)304 public void removeActiveConfigsChangedOperation(String packageName) { 305 enforceDumpAndUsageStatsPermission(packageName); 306 int callingUid = Binder.getCallingUid(); 307 final long token = Binder.clearCallingIdentity(); 308 synchronized (mLock) { 309 mActiveConfigsPirMap.remove(callingUid); 310 } 311 try { 312 IStatsd statsd = getStatsdNonblocking(); 313 if (statsd != null) { 314 statsd.removeActiveConfigsChangedOperation(callingUid); 315 } 316 } catch (RemoteException e) { 317 Log.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd"); 318 } finally { 319 Binder.restoreCallingIdentity(token); 320 } 321 } 322 323 @Override setBroadcastSubscriber(long configId, long subscriberId, PendingIntent pendingIntent, String packageName)324 public void setBroadcastSubscriber(long configId, long subscriberId, 325 PendingIntent pendingIntent, String packageName) { 326 enforceDumpAndUsageStatsPermission(packageName); 327 int callingUid = Binder.getCallingUid(); 328 final long token = Binder.clearCallingIdentity(); 329 PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext); 330 ConfigKey key = new ConfigKey(callingUid, configId); 331 // We add the PIR to a map so we can reregister if statsd is unavailable. 332 synchronized (mLock) { 333 ArrayMap<Long, PendingIntentRef> innerMap = mBroadcastSubscriberPirMap 334 .getOrDefault(key, new ArrayMap<>()); 335 innerMap.put(subscriberId, pir); 336 mBroadcastSubscriberPirMap.put(key, innerMap); 337 } 338 try { 339 IStatsd statsd = getStatsdNonblocking(); 340 if (statsd != null) { 341 statsd.setBroadcastSubscriber( 342 configId, subscriberId, pir, callingUid); 343 } 344 } catch (RemoteException e) { 345 Log.e(TAG, "Failed to setBroadcastSubscriber with statsd"); 346 } finally { 347 Binder.restoreCallingIdentity(token); 348 } 349 } 350 351 @Override unsetBroadcastSubscriber(long configId, long subscriberId, String packageName)352 public void unsetBroadcastSubscriber(long configId, long subscriberId, String packageName) { 353 enforceDumpAndUsageStatsPermission(packageName); 354 int callingUid = Binder.getCallingUid(); 355 final long token = Binder.clearCallingIdentity(); 356 ConfigKey key = new ConfigKey(callingUid, configId); 357 synchronized (mLock) { 358 ArrayMap<Long, PendingIntentRef> innerMap = mBroadcastSubscriberPirMap 359 .getOrDefault(key, new ArrayMap<>()); 360 innerMap.remove(subscriberId); 361 if (innerMap.isEmpty()) { 362 mBroadcastSubscriberPirMap.remove(key); 363 } 364 } 365 try { 366 IStatsd statsd = getStatsdNonblocking(); 367 if (statsd != null) { 368 statsd.unsetBroadcastSubscriber(configId, subscriberId, callingUid); 369 } 370 } catch (RemoteException e) { 371 Log.e(TAG, "Failed to unsetBroadcastSubscriber with statsd"); 372 } finally { 373 Binder.restoreCallingIdentity(token); 374 } 375 } 376 377 @Override getRegisteredExperimentIds()378 public long[] getRegisteredExperimentIds() throws IllegalStateException { 379 enforceDumpAndUsageStatsPermission(null); 380 final long token = Binder.clearCallingIdentity(); 381 try { 382 IStatsd statsd = waitForStatsd(); 383 if (statsd != null) { 384 return statsd.getRegisteredExperimentIds(); 385 } 386 } catch (RemoteException e) { 387 Log.e(TAG, "Failed to getRegisteredExperimentIds with statsd"); 388 throw new IllegalStateException(e.getMessage(), e); 389 } finally { 390 Binder.restoreCallingIdentity(token); 391 } 392 throw new IllegalStateException("Failed to connect to statsd to registerExperimentIds"); 393 } 394 395 @Override getMetadata(String packageName)396 public byte[] getMetadata(String packageName) throws IllegalStateException { 397 enforceDumpAndUsageStatsPermission(packageName); 398 final long token = Binder.clearCallingIdentity(); 399 try { 400 IStatsd statsd = waitForStatsd(); 401 if (statsd != null) { 402 return statsd.getMetadata(); 403 } 404 } catch (RemoteException e) { 405 Log.e(TAG, "Failed to getMetadata with statsd"); 406 throw new IllegalStateException(e.getMessage(), e); 407 } finally { 408 Binder.restoreCallingIdentity(token); 409 } 410 throw new IllegalStateException("Failed to connect to statsd to getMetadata"); 411 } 412 413 @Override getData(long key, String packageName)414 public byte[] getData(long key, String packageName) throws IllegalStateException { 415 enforceDumpAndUsageStatsPermission(packageName); 416 PowerManager powerManager = (PowerManager) 417 mContext.getSystemService(Context.POWER_SERVICE); 418 PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 419 /*tag=*/ StatsManagerService.class.getCanonicalName()); 420 int callingUid = Binder.getCallingUid(); 421 final long token = Binder.clearCallingIdentity(); 422 wl.acquire(); 423 try { 424 IStatsd statsd = waitForStatsd(); 425 if (statsd != null) { 426 return statsd.getData(key, callingUid); 427 } 428 } catch (RemoteException e) { 429 Log.e(TAG, "Failed to getData with statsd"); 430 throw new IllegalStateException(e.getMessage(), e); 431 } finally { 432 wl.release(); 433 Binder.restoreCallingIdentity(token); 434 } 435 throw new IllegalStateException("Failed to connect to statsd to getData"); 436 } 437 438 @Override addConfiguration(long configId, byte[] config, String packageName)439 public void addConfiguration(long configId, byte[] config, String packageName) 440 throws IllegalStateException { 441 enforceDumpAndUsageStatsPermission(packageName); 442 int callingUid = Binder.getCallingUid(); 443 final long token = Binder.clearCallingIdentity(); 444 try { 445 IStatsd statsd = waitForStatsd(); 446 if (statsd != null) { 447 statsd.addConfiguration(configId, config, callingUid); 448 return; 449 } 450 } catch (RemoteException e) { 451 Log.e(TAG, "Failed to addConfiguration with statsd"); 452 throw new IllegalStateException(e.getMessage(), e); 453 } finally { 454 Binder.restoreCallingIdentity(token); 455 } 456 throw new IllegalStateException("Failed to connect to statsd to addConfig"); 457 } 458 459 @Override removeConfiguration(long configId, String packageName)460 public void removeConfiguration(long configId, String packageName) 461 throws IllegalStateException { 462 enforceDumpAndUsageStatsPermission(packageName); 463 int callingUid = Binder.getCallingUid(); 464 final long token = Binder.clearCallingIdentity(); 465 try { 466 IStatsd statsd = waitForStatsd(); 467 if (statsd != null) { 468 statsd.removeConfiguration(configId, callingUid); 469 return; 470 } 471 } catch (RemoteException e) { 472 Log.e(TAG, "Failed to removeConfiguration with statsd"); 473 throw new IllegalStateException(e.getMessage(), e); 474 } finally { 475 Binder.restoreCallingIdentity(token); 476 } 477 throw new IllegalStateException("Failed to connect to statsd to removeConfig"); 478 } 479 setStatsCompanionService(StatsCompanionService statsCompanionService)480 void setStatsCompanionService(StatsCompanionService statsCompanionService) { 481 mStatsCompanionService = statsCompanionService; 482 } 483 484 /** 485 * Checks that the caller has both DUMP and PACKAGE_USAGE_STATS permissions. Also checks that 486 * the caller has USAGE_STATS_PERMISSION_OPS for the specified packageName if it is not null. 487 * 488 * @param packageName The packageName to check USAGE_STATS_PERMISSION_OPS. 489 */ enforceDumpAndUsageStatsPermission(@ullable String packageName)490 private void enforceDumpAndUsageStatsPermission(@Nullable String packageName) { 491 int callingUid = Binder.getCallingUid(); 492 int callingPid = Binder.getCallingPid(); 493 494 if (callingPid == Process.myPid()) { 495 return; 496 } 497 498 mContext.enforceCallingPermission(Manifest.permission.DUMP, null); 499 mContext.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS, null); 500 501 if (packageName == null) { 502 return; 503 } 504 AppOpsManager appOpsManager = (AppOpsManager) mContext 505 .getSystemService(Context.APP_OPS_SERVICE); 506 switch (appOpsManager.noteOp(USAGE_STATS_PERMISSION_OPS, 507 Binder.getCallingUid(), packageName, null, null)) { 508 case AppOpsManager.MODE_ALLOWED: 509 case AppOpsManager.MODE_DEFAULT: 510 break; 511 default: 512 throw new SecurityException( 513 String.format("UID %d / PID %d lacks app-op %s", 514 callingUid, callingPid, USAGE_STATS_PERMISSION_OPS) 515 ); 516 } 517 } 518 enforceRegisterStatsPullAtomPermission()519 private void enforceRegisterStatsPullAtomPermission() { 520 mContext.enforceCallingOrSelfPermission( 521 android.Manifest.permission.REGISTER_STATS_PULL_ATOM, 522 "Need REGISTER_STATS_PULL_ATOM permission."); 523 } 524 525 526 /** 527 * Clients should call this if blocking until statsd to be ready is desired 528 * 529 * @return IStatsd object if statsd becomes ready within the timeout, null otherwise. 530 */ waitForStatsd()531 private IStatsd waitForStatsd() { 532 synchronized (mLock) { 533 if (mStatsd == null) { 534 try { 535 mLock.wait(STATSD_TIMEOUT_MILLIS); 536 } catch (InterruptedException e) { 537 Log.e(TAG, "wait for statsd interrupted"); 538 } 539 } 540 return mStatsd; 541 } 542 } 543 544 /** 545 * Clients should call this to receive a reference to statsd. 546 * 547 * @return IStatsd object if statsd is ready, null otherwise. 548 */ getStatsdNonblocking()549 private IStatsd getStatsdNonblocking() { 550 synchronized (mLock) { 551 return mStatsd; 552 } 553 } 554 555 /** 556 * Called from {@link StatsCompanionService}. 557 * 558 * Tells StatsManagerService that Statsd is ready and updates 559 * Statsd with the contents of our local cache. 560 */ statsdReady(IStatsd statsd)561 void statsdReady(IStatsd statsd) { 562 synchronized (mLock) { 563 mStatsd = statsd; 564 mLock.notify(); 565 } 566 sayHiToStatsd(statsd); 567 } 568 569 /** 570 * Called from {@link StatsCompanionService}. 571 * 572 * Tells StatsManagerService that Statsd is no longer ready 573 * and we should no longer make binder calls with statsd. 574 */ statsdNotReady()575 void statsdNotReady() { 576 synchronized (mLock) { 577 mStatsd = null; 578 } 579 } 580 sayHiToStatsd(IStatsd statsd)581 private void sayHiToStatsd(IStatsd statsd) { 582 if (statsd == null) { 583 return; 584 } 585 586 final long token = Binder.clearCallingIdentity(); 587 try { 588 registerAllPullers(statsd); 589 registerAllDataFetchOperations(statsd); 590 registerAllActiveConfigsChangedOperations(statsd); 591 registerAllBroadcastSubscribers(statsd); 592 } catch (RemoteException e) { 593 Log.e(TAG, "StatsManager failed to (re-)register data with statsd"); 594 } finally { 595 Binder.restoreCallingIdentity(token); 596 } 597 } 598 599 // Pre-condition: the Binder calling identity has already been cleared registerAllPullers(IStatsd statsd)600 private void registerAllPullers(IStatsd statsd) throws RemoteException { 601 // Since we do not want to make an IPC with the lock held, we first create a copy of the 602 // data with the lock held before iterating through the map. 603 ArrayMap<PullerKey, PullerValue> pullersCopy; 604 synchronized (mLock) { 605 pullersCopy = new ArrayMap<>(mPullers); 606 } 607 608 for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) { 609 PullerKey key = entry.getKey(); 610 PullerValue value = entry.getValue(); 611 statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownMillis(), 612 value.getTimeoutMillis(), value.getAdditiveFields(), value.getCallback()); 613 } 614 statsd.allPullersFromBootRegistered(); 615 } 616 617 // Pre-condition: the Binder calling identity has already been cleared registerAllDataFetchOperations(IStatsd statsd)618 private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException { 619 // Since we do not want to make an IPC with the lock held, we first create a copy of the 620 // data with the lock held before iterating through the map. 621 ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy; 622 synchronized (mLock) { 623 dataFetchCopy = new ArrayMap<>(mDataFetchPirMap); 624 } 625 626 for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) { 627 ConfigKey key = entry.getKey(); 628 statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid()); 629 } 630 } 631 632 // Pre-condition: the Binder calling identity has already been cleared registerAllActiveConfigsChangedOperations(IStatsd statsd)633 private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException { 634 // Since we do not want to make an IPC with the lock held, we first create a copy of the 635 // data with the lock held before iterating through the map. 636 ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy; 637 synchronized (mLock) { 638 activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap); 639 } 640 641 for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) { 642 statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey()); 643 } 644 } 645 646 // Pre-condition: the Binder calling identity has already been cleared registerAllBroadcastSubscribers(IStatsd statsd)647 private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException { 648 // Since we do not want to make an IPC with the lock held, we first create a deep copy of 649 // the data with the lock held before iterating through the map. 650 ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy = 651 new ArrayMap<>(); 652 synchronized (mLock) { 653 for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry : 654 mBroadcastSubscriberPirMap.entrySet()) { 655 broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue())); 656 } 657 } 658 659 for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry : 660 mBroadcastSubscriberPirMap.entrySet()) { 661 ConfigKey configKey = entry.getKey(); 662 for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) { 663 statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(), 664 subscriberEntry.getValue(), configKey.getUid()); 665 } 666 } 667 } 668 } 669