blob: 8783d945defa4d6d27a18999dd7414eda9bead87 [file] [log] [blame]
Jeff Davidsona192cc22018-02-08 15:30:06 -08001/*
2 * Copyright 2017 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 */
16package android.app;
17
18import android.Manifest;
Justin Klaassen4d01eea2018-04-03 23:21:57 -040019import android.annotation.Nullable;
Jeff Davidsona192cc22018-02-08 15:30:06 -080020import android.annotation.RequiresPermission;
21import android.annotation.SystemApi;
22import android.os.IBinder;
23import android.os.IStatsManager;
24import android.os.RemoteException;
25import android.os.ServiceManager;
Justin Klaassenb8042fc2018-04-15 00:41:15 -040026import android.util.AndroidException;
Jeff Davidsona192cc22018-02-08 15:30:06 -080027import android.util.Slog;
28
29/**
30 * API for statsd clients to send configurations and retrieve data.
31 *
32 * @hide
33 */
34@SystemApi
Justin Klaassen4d01eea2018-04-03 23:21:57 -040035public final class StatsManager {
Jeff Davidsona192cc22018-02-08 15:30:06 -080036 IStatsManager mService;
37 private static final String TAG = "StatsManager";
Justin Klaassen4d01eea2018-04-03 23:21:57 -040038 private static final boolean DEBUG = false;
Jeff Davidsona192cc22018-02-08 15:30:06 -080039
Justin Klaassen4d01eea2018-04-03 23:21:57 -040040 /**
41 * Long extra of uid that added the relevant stats config.
42 */
43 public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
44 /**
45 * Long extra of the relevant stats config's configKey.
46 */
47 public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
48 /**
49 * Long extra of the relevant statsd_config.proto's Subscription.id.
50 */
Jeff Davidsona192cc22018-02-08 15:30:06 -080051 public static final String EXTRA_STATS_SUBSCRIPTION_ID =
52 "android.app.extra.STATS_SUBSCRIPTION_ID";
Justin Klaassen4d01eea2018-04-03 23:21:57 -040053 /**
54 * Long extra of the relevant statsd_config.proto's Subscription.rule_id.
55 */
Jeff Davidsona192cc22018-02-08 15:30:06 -080056 public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID =
57 "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
58 /**
Justin Klaassen4d01eea2018-04-03 23:21:57 -040059 * List<String> of the relevant statsd_config.proto's BroadcastSubscriberDetails.cookie.
60 * Obtain using {@link android.content.Intent#getStringArrayListExtra(String)}.
61 */
62 public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES =
63 "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
64 /**
Jeff Davidsona192cc22018-02-08 15:30:06 -080065 * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value
66 * information.
67 */
68 public static final String EXTRA_STATS_DIMENSIONS_VALUE =
69 "android.app.extra.STATS_DIMENSIONS_VALUE";
70
71 /**
Justin Klaassen4d01eea2018-04-03 23:21:57 -040072 * Broadcast Action: Statsd has started.
73 * Configurations and PendingIntents can now be sent to it.
74 */
75 public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
76
77 /**
Jeff Davidsona192cc22018-02-08 15:30:06 -080078 * Constructor for StatsManagerClient.
79 *
80 * @hide
81 */
82 public StatsManager() {
83 }
84
85 /**
Justin Klaassenb8042fc2018-04-15 00:41:15 -040086 * Adds the given configuration and associates it with the given configKey. If a config with the
87 * given configKey already exists for the caller's uid, it is replaced with the new one.
Jeff Davidsona192cc22018-02-08 15:30:06 -080088 *
89 * @param configKey An arbitrary integer that allows clients to track the configuration.
Justin Klaassenb8042fc2018-04-15 00:41:15 -040090 * @param config Wire-encoded StatsdConfig proto that specifies metrics (and all
Jeff Davidsona192cc22018-02-08 15:30:06 -080091 * dependencies eg, conditions and matchers).
Justin Klaassenb8042fc2018-04-15 00:41:15 -040092 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
93 * @throws IllegalArgumentException if config is not a wire-encoded StatsdConfig proto
Jeff Davidsona192cc22018-02-08 15:30:06 -080094 */
95 @RequiresPermission(Manifest.permission.DUMP)
Justin Klaassenb8042fc2018-04-15 00:41:15 -040096 public void addConfig(long configKey, byte[] config) throws StatsUnavailableException {
Jeff Davidsona192cc22018-02-08 15:30:06 -080097 synchronized (this) {
98 try {
99 IStatsManager service = getIStatsManagerLocked();
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400100 service.addConfiguration(configKey, config); // can throw IllegalArgumentException
Jeff Davidsona192cc22018-02-08 15:30:06 -0800101 } catch (RemoteException e) {
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400102 Slog.e(TAG, "Failed to connect to statsd when adding configuration");
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400103 throw new StatsUnavailableException("could not connect", e);
Jeff Davidsona192cc22018-02-08 15:30:06 -0800104 }
105 }
106 }
107
108 /**
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400109 * TODO: Temporary for backwards compatibility. Remove.
110 */
111 @RequiresPermission(Manifest.permission.DUMP)
112 public boolean addConfiguration(long configKey, byte[] config) {
113 try {
114 addConfig(configKey, config);
115 return true;
116 } catch (StatsUnavailableException | IllegalArgumentException e) {
117 return false;
118 }
119 }
120
121 /**
Jeff Davidsona192cc22018-02-08 15:30:06 -0800122 * Remove a configuration from logging.
123 *
124 * @param configKey Configuration key to remove.
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400125 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
Jeff Davidsona192cc22018-02-08 15:30:06 -0800126 */
127 @RequiresPermission(Manifest.permission.DUMP)
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400128 public void removeConfig(long configKey) throws StatsUnavailableException {
Jeff Davidsona192cc22018-02-08 15:30:06 -0800129 synchronized (this) {
130 try {
131 IStatsManager service = getIStatsManagerLocked();
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400132 service.removeConfiguration(configKey);
Jeff Davidsona192cc22018-02-08 15:30:06 -0800133 } catch (RemoteException e) {
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400134 Slog.e(TAG, "Failed to connect to statsd when removing configuration");
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400135 throw new StatsUnavailableException("could not connect", e);
Jeff Davidsona192cc22018-02-08 15:30:06 -0800136 }
137 }
138 }
139
140 /**
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400141 * TODO: Temporary for backwards compatibility. Remove.
142 */
143 @RequiresPermission(Manifest.permission.DUMP)
144 public boolean removeConfiguration(long configKey) {
145 try {
146 removeConfig(configKey);
147 return true;
148 } catch (StatsUnavailableException e) {
149 return false;
150 }
151 }
152
153 /**
Jeff Davidsona192cc22018-02-08 15:30:06 -0800154 * Set the PendingIntent to be used when broadcasting subscriber information to the given
155 * subscriberId within the given config.
Jeff Davidsona192cc22018-02-08 15:30:06 -0800156 * <p>
157 * Suppose that the calling uid has added a config with key configKey, and that in this config
158 * it is specified that when a particular anomaly is detected, a broadcast should be sent to
159 * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
160 * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
161 * when the anomaly is detected.
Jeff Davidsona192cc22018-02-08 15:30:06 -0800162 * <p>
163 * When statsd sends the broadcast, the PendingIntent will used to send an intent with
164 * information of
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400165 * {@link #EXTRA_STATS_CONFIG_UID},
166 * {@link #EXTRA_STATS_CONFIG_KEY},
167 * {@link #EXTRA_STATS_SUBSCRIPTION_ID},
168 * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID},
169 * {@link #EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES}, and
170 * {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
Jeff Davidsona192cc22018-02-08 15:30:06 -0800171 * <p>
172 * This function can only be called by the owner (uid) of the config. It must be called each
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400173 * time statsd starts. The config must have been added first (via {@link #addConfig}).
Jeff Davidsona192cc22018-02-08 15:30:06 -0800174 *
Jeff Davidsona192cc22018-02-08 15:30:06 -0800175 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
176 * associated with the given subscriberId. May be null, in which case
177 * it undoes any previous setting of this subscriberId.
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400178 * @param configKey The integer naming the config to which this subscriber is attached.
179 * @param subscriberId ID of the subscriber, as used in the config.
180 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
181 */
182 @RequiresPermission(Manifest.permission.DUMP)
183 public void setBroadcastSubscriber(
184 PendingIntent pendingIntent, long configKey, long subscriberId)
185 throws StatsUnavailableException {
186 synchronized (this) {
187 try {
188 IStatsManager service = getIStatsManagerLocked();
189 if (pendingIntent != null) {
190 // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
191 IBinder intentSender = pendingIntent.getTarget().asBinder();
192 service.setBroadcastSubscriber(configKey, subscriberId, intentSender);
193 } else {
194 service.unsetBroadcastSubscriber(configKey, subscriberId);
195 }
196 } catch (RemoteException e) {
197 Slog.e(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
198 throw new StatsUnavailableException("could not connect", e);
199 }
200 }
201 }
202
203 /**
204 * TODO: Temporary for backwards compatibility. Remove.
Jeff Davidsona192cc22018-02-08 15:30:06 -0800205 */
206 @RequiresPermission(Manifest.permission.DUMP)
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400207 public boolean setBroadcastSubscriber(
208 long configKey, long subscriberId, PendingIntent pendingIntent) {
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400209 try {
210 setBroadcastSubscriber(pendingIntent, configKey, subscriberId);
211 return true;
212 } catch (StatsUnavailableException e) {
213 return false;
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400214 }
215 }
216
217 /**
218 * Registers the operation that is called to retrieve the metrics data. This must be called
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400219 * each time statsd starts. The config must have been added first (via {@link #addConfig},
220 * although addConfig could have been called on a previous boot). This operation allows
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400221 * statsd to send metrics data whenever statsd determines that the metrics in memory are
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400222 * approaching the memory limits. The fetch operation should call {@link #getReports} to fetch
223 * the data, which also deletes the retrieved metrics from statsd's memory.
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400224 *
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400225 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
226 * associated with the given subscriberId. May be null, in which case
227 * it removes any associated pending intent with this configKey.
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400228 * @param configKey The integer naming the config to which this operation is attached.
229 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400230 */
231 @RequiresPermission(Manifest.permission.DUMP)
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400232 public void setFetchReportsOperation(PendingIntent pendingIntent, long configKey)
233 throws StatsUnavailableException {
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400234 synchronized (this) {
235 try {
236 IStatsManager service = getIStatsManagerLocked();
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400237 if (pendingIntent == null) {
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400238 service.removeDataFetchOperation(configKey);
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400239 } else {
240 // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
241 IBinder intentSender = pendingIntent.getTarget().asBinder();
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400242 service.setDataFetchOperation(configKey, intentSender);
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400243 }
244
245 } catch (RemoteException e) {
246 Slog.e(TAG, "Failed to connect to statsd when registering data listener.");
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400247 throw new StatsUnavailableException("could not connect", e);
Jeff Davidsona192cc22018-02-08 15:30:06 -0800248 }
249 }
250 }
251
252 /**
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400253 * TODO: Temporary for backwards compatibility. Remove.
Jeff Davidsona192cc22018-02-08 15:30:06 -0800254 */
255 @RequiresPermission(Manifest.permission.DUMP)
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400256 public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) {
257 try {
258 setFetchReportsOperation(pendingIntent, configKey);
259 return true;
260 } catch (StatsUnavailableException e) {
261 return false;
262 }
263 }
264
265 /**
266 * Request the data collected for the given configKey.
267 * This getter is destructive - it also clears the retrieved metrics from statsd's memory.
268 *
269 * @param configKey Configuration key to retrieve data from.
270 * @return Serialized ConfigMetricsReportList proto.
271 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
272 */
273 @RequiresPermission(Manifest.permission.DUMP)
274 public byte[] getReports(long configKey) throws StatsUnavailableException {
Jeff Davidsona192cc22018-02-08 15:30:06 -0800275 synchronized (this) {
276 try {
277 IStatsManager service = getIStatsManagerLocked();
Jeff Davidsona192cc22018-02-08 15:30:06 -0800278 return service.getData(configKey);
279 } catch (RemoteException e) {
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400280 Slog.e(TAG, "Failed to connect to statsd when getting data");
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400281 throw new StatsUnavailableException("could not connect", e);
282 }
283 }
284 }
285
286 /**
287 * TODO: Temporary for backwards compatibility. Remove.
288 */
289 @RequiresPermission(Manifest.permission.DUMP)
290 public @Nullable byte[] getData(long configKey) {
291 try {
292 return getReports(configKey);
293 } catch (StatsUnavailableException e) {
294 return null;
295 }
296 }
297
298 /**
299 * Clients can request metadata for statsd. Will contain stats across all configurations but not
300 * the actual metrics themselves (metrics must be collected via {@link #getReports(long)}.
301 * This getter is not destructive and will not reset any metrics/counters.
302 *
303 * @return Serialized StatsdStatsReport proto.
304 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
305 */
306 @RequiresPermission(Manifest.permission.DUMP)
307 public byte[] getStatsMetadata() throws StatsUnavailableException {
308 synchronized (this) {
309 try {
310 IStatsManager service = getIStatsManagerLocked();
311 return service.getMetadata();
312 } catch (RemoteException e) {
313 Slog.e(TAG, "Failed to connect to statsd when getting metadata");
314 throw new StatsUnavailableException("could not connect", e);
Jeff Davidsona192cc22018-02-08 15:30:06 -0800315 }
316 }
317 }
318
319 /**
320 * Clients can request metadata for statsd. Will contain stats across all configurations but not
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400321 * the actual metrics themselves (metrics must be collected via {@link #getReports(long)}.
Jeff Davidsona192cc22018-02-08 15:30:06 -0800322 * This getter is not destructive and will not reset any metrics/counters.
323 *
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400324 * @return Serialized StatsdStatsReport proto. Returns null on failure (eg, if statsd crashed).
Jeff Davidsona192cc22018-02-08 15:30:06 -0800325 */
326 @RequiresPermission(Manifest.permission.DUMP)
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400327 public @Nullable byte[] getMetadata() {
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400328 try {
329 return getStatsMetadata();
330 } catch (StatsUnavailableException e) {
331 return null;
Jeff Davidsona192cc22018-02-08 15:30:06 -0800332 }
333 }
334
335 private class StatsdDeathRecipient implements IBinder.DeathRecipient {
336 @Override
337 public void binderDied() {
338 synchronized (this) {
339 mService = null;
340 }
341 }
342 }
343
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400344 private IStatsManager getIStatsManagerLocked() throws StatsUnavailableException {
Jeff Davidsona192cc22018-02-08 15:30:06 -0800345 if (mService != null) {
346 return mService;
347 }
348 mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400349 if (mService == null) {
350 throw new StatsUnavailableException("could not be found");
351 }
352 try {
Jeff Davidsona192cc22018-02-08 15:30:06 -0800353 mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400354 } catch (RemoteException e) {
355 throw new StatsUnavailableException("could not connect when linkToDeath", e);
Jeff Davidsona192cc22018-02-08 15:30:06 -0800356 }
357 return mService;
358 }
Justin Klaassenb8042fc2018-04-15 00:41:15 -0400359
360 /**
361 * Exception thrown when communication with the stats service fails (eg if it is not available).
362 * This might be thrown early during boot before the stats service has started or if it crashed.
363 */
364 public static class StatsUnavailableException extends AndroidException {
365 public StatsUnavailableException(String reason) {
366 super("Failed to connect to statsd: " + reason);
367 }
368
369 public StatsUnavailableException(String reason, Throwable e) {
370 super("Failed to connect to statsd: " + reason, e);
371 }
372 }
Jeff Davidsona192cc22018-02-08 15:30:06 -0800373}