• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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