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