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