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 android.app.PendingIntent;
20 import android.app.StatsManager;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.Binder;
24 import android.os.IPendingIntentRef;
25 import android.os.Process;
26 import android.os.StatsDimensionsValue;
27 import android.os.StatsDimensionsValueParcel;
28 import android.util.Log;
29 
30 import com.android.server.SystemService;
31 
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 
35 /**
36  * @hide
37  */
38 public class StatsCompanion {
39     private static final String TAG = "StatsCompanion";
40     private static final boolean DEBUG = false;
41 
42     private static final int AID_STATSD = 1066;
43 
44     private static final String STATS_COMPANION_SERVICE = "statscompanion";
45     private static final String STATS_MANAGER_SERVICE = "statsmanager";
46 
enforceStatsdCallingUid()47     static void enforceStatsdCallingUid() {
48         if (Binder.getCallingPid() == Process.myPid()) {
49             return;
50         }
51         if (Binder.getCallingUid() != AID_STATSD) {
52             throw new SecurityException("Not allowed to access StatsCompanion");
53         }
54     }
55 
56     /**
57      * Lifecycle class for both {@link StatsCompanionService} and {@link StatsManagerService}.
58      */
59     public static final class Lifecycle extends SystemService {
60         private StatsCompanionService mStatsCompanionService;
61         private StatsManagerService mStatsManagerService;
62 
Lifecycle(Context context)63         public Lifecycle(Context context) {
64             super(context);
65         }
66 
67         @Override
onStart()68         public void onStart() {
69             mStatsCompanionService = new StatsCompanionService(getContext());
70             mStatsManagerService = new StatsManagerService(getContext());
71             mStatsCompanionService.setStatsManagerService(mStatsManagerService);
72             mStatsManagerService.setStatsCompanionService(mStatsCompanionService);
73 
74             try {
75                 publishBinderService(STATS_COMPANION_SERVICE, mStatsCompanionService);
76                 if (DEBUG) Log.d(TAG, "Published " + STATS_COMPANION_SERVICE);
77                 publishBinderService(STATS_MANAGER_SERVICE, mStatsManagerService);
78                 if (DEBUG) Log.d(TAG, "Published " + STATS_MANAGER_SERVICE);
79             } catch (Exception e) {
80                 Log.e(TAG, "Failed to publishBinderService", e);
81             }
82         }
83 
84         @Override
onBootPhase(int phase)85         public void onBootPhase(int phase) {
86             super.onBootPhase(phase);
87             if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
88                 mStatsCompanionService.systemReady();
89             }
90             if (phase == PHASE_BOOT_COMPLETED) {
91                 mStatsCompanionService.bootCompleted();
92             }
93         }
94     }
95 
96     /**
97      * Wrapper for {@link PendingIntent}. Allows Statsd to send PendingIntents.
98      */
99     public static class PendingIntentRef extends IPendingIntentRef.Stub {
100 
101         private static final String TAG = "PendingIntentRef";
102 
103         /**
104          * The last report time is provided with each intent registered to
105          * StatsManager#setFetchReportsOperation. This allows easy de-duping in the receiver if
106          * statsd is requesting the client to retrieve the same statsd data. The last report time
107          * corresponds to the last_report_elapsed_nanos that will provided in the current
108          * ConfigMetricsReport, and this timestamp also corresponds to the
109          * current_report_elapsed_nanos of the most recently obtained ConfigMetricsReport.
110          */
111         private static final String EXTRA_LAST_REPORT_TIME = "android.app.extra.LAST_REPORT_TIME";
112         private static final int CODE_DATA_BROADCAST = 1;
113         private static final int CODE_ACTIVE_CONFIGS_BROADCAST = 1;
114         private static final int CODE_SUBSCRIBER_BROADCAST = 1;
115 
116         private final PendingIntent mPendingIntent;
117         private final Context mContext;
118 
PendingIntentRef(PendingIntent pendingIntent, Context context)119         public PendingIntentRef(PendingIntent pendingIntent, Context context) {
120             mPendingIntent = pendingIntent;
121             mContext = context;
122         }
123 
124         @Override
sendDataBroadcast(long lastReportTimeNs)125         public void sendDataBroadcast(long lastReportTimeNs) {
126             enforceStatsdCallingUid();
127             Intent intent = new Intent();
128             intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs);
129             try {
130                 mPendingIntent.send(mContext, CODE_DATA_BROADCAST, intent, null, null);
131             } catch (PendingIntent.CanceledException e) {
132                 Log.w(TAG, "Unable to send PendingIntent");
133             }
134         }
135 
136         @Override
sendActiveConfigsChangedBroadcast(long[] configIds)137         public void sendActiveConfigsChangedBroadcast(long[] configIds) {
138             enforceStatsdCallingUid();
139             Intent intent = new Intent();
140             intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds);
141             try {
142                 mPendingIntent.send(mContext, CODE_ACTIVE_CONFIGS_BROADCAST, intent, null, null);
143                 if (DEBUG) {
144                     Log.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds));
145                 }
146             } catch (PendingIntent.CanceledException e) {
147                 Log.w(TAG, "Unable to send active configs changed broadcast using PendingIntent");
148             }
149         }
150 
151         @Override
sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, long subscriptionRuleId, String[] cookies, StatsDimensionsValueParcel dimensionsValueParcel)152         public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId,
153                 long subscriptionRuleId, String[] cookies,
154                 StatsDimensionsValueParcel dimensionsValueParcel) {
155             enforceStatsdCallingUid();
156             StatsDimensionsValue dimensionsValue = new StatsDimensionsValue(dimensionsValueParcel);
157             Intent intent =
158                     new Intent()
159                             .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid)
160                             .putExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, configId)
161                             .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, subscriptionId)
162                             .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_RULE_ID,
163                                     subscriptionRuleId)
164                             .putExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, dimensionsValue);
165 
166             ArrayList<String> cookieList = new ArrayList<>(cookies.length);
167             cookieList.addAll(Arrays.asList(cookies));
168             intent.putStringArrayListExtra(
169                     StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookieList);
170 
171             if (DEBUG) {
172                 Log.d(TAG,
173                         String.format(
174                                 "Statsd sendSubscriberBroadcast with params {%d %d %d %d %s %s}",
175                                 configUid, configId, subscriptionId, subscriptionRuleId,
176                                 Arrays.toString(cookies),
177                                 dimensionsValue));
178             }
179             try {
180                 mPendingIntent.send(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null);
181             } catch (PendingIntent.CanceledException e) {
182                 Log.w(TAG,
183                         "Unable to send using PendingIntent from uid " + configUid
184                                 + "; presumably it had been cancelled.");
185             }
186         }
187     }
188 }
189