blob: c2c91c2bcbd6f9f7769ac7a68ecc1d35867e99a9 [file] [log] [blame]
Bookatzc6977972018-01-16 16:55:05 -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;
19import android.annotation.RequiresPermission;
20import android.annotation.SystemApi;
21import android.os.IBinder;
22import android.os.IStatsManager;
23import android.os.RemoteException;
24import android.os.ServiceManager;
25import android.util.Slog;
26
27/**
28 * API for statsd clients to send configurations and retrieve data.
29 *
30 * @hide
31 */
32@SystemApi
David Chen661f7912018-01-22 17:46:24 -080033public final class StatsManager {
Bookatzc6977972018-01-16 16:55:05 -080034 IStatsManager mService;
35 private static final String TAG = "StatsManager";
Tej Singh484524a2018-02-01 15:10:05 -080036 private static final boolean DEBUG = false;
Bookatzc6977972018-01-16 16:55:05 -080037
David Chen661f7912018-01-22 17:46:24 -080038 /**
39 * Long extra of uid that added the relevant stats config.
40 */
41 public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
42 /**
43 * Long extra of the relevant stats config's configKey.
44 */
45 public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
46 /**
47 * Long extra of the relevant statsd_config.proto's Subscription.id.
48 */
Bookatzc6977972018-01-16 16:55:05 -080049 public static final String EXTRA_STATS_SUBSCRIPTION_ID =
50 "android.app.extra.STATS_SUBSCRIPTION_ID";
David Chen661f7912018-01-22 17:46:24 -080051 /**
52 * Long extra of the relevant statsd_config.proto's Subscription.rule_id.
53 */
Bookatzc6977972018-01-16 16:55:05 -080054 public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID =
55 "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
56 /**
57 * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value
58 * information.
59 */
60 public static final String EXTRA_STATS_DIMENSIONS_VALUE =
61 "android.app.extra.STATS_DIMENSIONS_VALUE";
62
63 /**
Bookatz5c800e32018-01-24 14:59:52 -080064 * Broadcast Action: Statsd has started.
65 * Configurations and PendingIntents can now be sent to it.
66 */
67 public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
68
69 /**
Bookatzc6977972018-01-16 16:55:05 -080070 * Constructor for StatsManagerClient.
71 *
72 * @hide
73 */
74 public StatsManager() {
75 }
76
77 /**
David Cheneac4e642018-02-08 14:12:20 -080078 * Temporary. Will be deleted.
79 */
80 @RequiresPermission(Manifest.permission.DUMP)
81 public boolean addConfiguration(long configKey, byte[] config, String a, String b) {
82 return addConfiguration(configKey, config);
83 }
84
85 /**
Bookatzc6977972018-01-16 16:55:05 -080086 * Clients can send a configuration and simultaneously registers the name of a broadcast
87 * receiver that listens for when it should request data.
88 *
89 * @param configKey An arbitrary integer that allows clients to track the configuration.
90 * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all
91 * dependencies eg, conditions and matchers).
Bookatzc6977972018-01-16 16:55:05 -080092 * @return true if successful
93 */
94 @RequiresPermission(Manifest.permission.DUMP)
David Chen661f7912018-01-22 17:46:24 -080095 public boolean addConfiguration(long configKey, byte[] config) {
Bookatzc6977972018-01-16 16:55:05 -080096 synchronized (this) {
97 try {
98 IStatsManager service = getIStatsManagerLocked();
99 if (service == null) {
Tej Singh484524a2018-02-01 15:10:05 -0800100 if (DEBUG) Slog.d(TAG, "Failed to find statsd when adding configuration");
Bookatzc6977972018-01-16 16:55:05 -0800101 return false;
102 }
David Chen661f7912018-01-22 17:46:24 -0800103 return service.addConfiguration(configKey, config);
Bookatzc6977972018-01-16 16:55:05 -0800104 } catch (RemoteException e) {
Tej Singh484524a2018-02-01 15:10:05 -0800105 if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when adding configuration");
Bookatzc6977972018-01-16 16:55:05 -0800106 return false;
107 }
108 }
109 }
110
111 /**
112 * Remove a configuration from logging.
113 *
114 * @param configKey Configuration key to remove.
115 * @return true if successful
116 */
117 @RequiresPermission(Manifest.permission.DUMP)
118 public boolean removeConfiguration(long configKey) {
119 synchronized (this) {
120 try {
121 IStatsManager service = getIStatsManagerLocked();
122 if (service == null) {
Tej Singh484524a2018-02-01 15:10:05 -0800123 if (DEBUG) Slog.d(TAG, "Failed to find statsd when removing configuration");
Bookatzc6977972018-01-16 16:55:05 -0800124 return false;
125 }
126 return service.removeConfiguration(configKey);
127 } catch (RemoteException e) {
Tej Singh484524a2018-02-01 15:10:05 -0800128 if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when removing configuration");
Bookatzc6977972018-01-16 16:55:05 -0800129 return false;
130 }
131 }
132 }
133
134 /**
135 * Set the PendingIntent to be used when broadcasting subscriber information to the given
136 * subscriberId within the given config.
Bookatzc6977972018-01-16 16:55:05 -0800137 * <p>
138 * Suppose that the calling uid has added a config with key configKey, and that in this config
139 * it is specified that when a particular anomaly is detected, a broadcast should be sent to
140 * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
141 * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
142 * when the anomaly is detected.
Bookatzc6977972018-01-16 16:55:05 -0800143 * <p>
144 * When statsd sends the broadcast, the PendingIntent will used to send an intent with
145 * information of
David Chen661f7912018-01-22 17:46:24 -0800146 * {@link #EXTRA_STATS_CONFIG_UID},
147 * {@link #EXTRA_STATS_CONFIG_KEY},
148 * {@link #EXTRA_STATS_SUBSCRIPTION_ID},
149 * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, and
150 * {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
Bookatzc6977972018-01-16 16:55:05 -0800151 * <p>
152 * This function can only be called by the owner (uid) of the config. It must be called each
153 * time statsd starts. The config must have been added first (via addConfiguration()).
154 *
David Chen661f7912018-01-22 17:46:24 -0800155 * @param configKey The integer naming the config to which this subscriber is attached.
156 * @param subscriberId ID of the subscriber, as used in the config.
Bookatzc6977972018-01-16 16:55:05 -0800157 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
158 * associated with the given subscriberId. May be null, in which case
159 * it undoes any previous setting of this subscriberId.
160 * @return true if successful
161 */
162 @RequiresPermission(Manifest.permission.DUMP)
David Chen661f7912018-01-22 17:46:24 -0800163 public boolean setBroadcastSubscriber(
164 long configKey, long subscriberId, PendingIntent pendingIntent) {
Bookatzc6977972018-01-16 16:55:05 -0800165 synchronized (this) {
166 try {
167 IStatsManager service = getIStatsManagerLocked();
168 if (service == null) {
169 Slog.w(TAG, "Failed to find statsd when adding broadcast subscriber");
170 return false;
171 }
172 if (pendingIntent != null) {
173 // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
174 IBinder intentSender = pendingIntent.getTarget().asBinder();
175 return service.setBroadcastSubscriber(configKey, subscriberId, intentSender);
176 } else {
177 return service.unsetBroadcastSubscriber(configKey, subscriberId);
178 }
179 } catch (RemoteException e) {
180 Slog.w(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
181 return false;
182 }
183 }
184 }
185
186 /**
David Chen661f7912018-01-22 17:46:24 -0800187 * Registers the operation that is called to retrieve the metrics data. This must be called
188 * each time statsd starts. The config must have been added first (via addConfiguration(),
189 * although addConfiguration could have been called on a previous boot). This operation allows
190 * statsd to send metrics data whenever statsd determines that the metrics in memory are
191 * approaching the memory limits. The fetch operation should call {@link #getData} to fetch the
192 * data, which also deletes the retrieved metrics from statsd's memory.
193 *
194 * @param configKey The integer naming the config to which this operation is attached.
195 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
196 * associated with the given subscriberId. May be null, in which case
197 * it removes any associated pending intent with this configKey.
198 * @return true if successful
199 */
200 @RequiresPermission(Manifest.permission.DUMP)
201 public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) {
202 synchronized (this) {
203 try {
204 IStatsManager service = getIStatsManagerLocked();
205 if (service == null) {
206 Slog.d(TAG, "Failed to find statsd when registering data listener.");
207 return false;
208 }
209 if (pendingIntent == null) {
210 return service.removeDataFetchOperation(configKey);
211 } else {
212 // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
213 IBinder intentSender = pendingIntent.getTarget().asBinder();
214 return service.setDataFetchOperation(configKey, intentSender);
215 }
216
217 } catch (RemoteException e) {
218 Slog.d(TAG, "Failed to connect to statsd when registering data listener.");
219 return false;
220 }
221 }
222 }
223
224 /**
Bookatzc6977972018-01-16 16:55:05 -0800225 * Clients can request data with a binder call. This getter is destructive and also clears
226 * the retrieved metrics from statsd memory.
227 *
228 * @param configKey Configuration key to retrieve data from.
229 * @return Serialized ConfigMetricsReportList proto. Returns null on failure.
230 */
231 @RequiresPermission(Manifest.permission.DUMP)
232 public byte[] getData(long configKey) {
233 synchronized (this) {
234 try {
235 IStatsManager service = getIStatsManagerLocked();
236 if (service == null) {
Tej Singh484524a2018-02-01 15:10:05 -0800237 if (DEBUG) Slog.d(TAG, "Failed to find statsd when getting data");
Bookatzc6977972018-01-16 16:55:05 -0800238 return null;
239 }
240 return service.getData(configKey);
241 } catch (RemoteException e) {
Tej Singh484524a2018-02-01 15:10:05 -0800242 if (DEBUG) Slog.d(TAG, "Failed to connecto statsd when getting data");
Bookatzc6977972018-01-16 16:55:05 -0800243 return null;
244 }
245 }
246 }
247
248 /**
249 * Clients can request metadata for statsd. Will contain stats across all configurations but not
250 * the actual metrics themselves (metrics must be collected via {@link #getData(String)}.
251 * This getter is not destructive and will not reset any metrics/counters.
252 *
253 * @return Serialized StatsdStatsReport proto. Returns null on failure.
254 */
255 @RequiresPermission(Manifest.permission.DUMP)
256 public byte[] getMetadata() {
257 synchronized (this) {
258 try {
259 IStatsManager service = getIStatsManagerLocked();
260 if (service == null) {
Tej Singh484524a2018-02-01 15:10:05 -0800261 if (DEBUG) Slog.d(TAG, "Failed to find statsd when getting metadata");
Bookatzc6977972018-01-16 16:55:05 -0800262 return null;
263 }
264 return service.getMetadata();
265 } catch (RemoteException e) {
Tej Singh484524a2018-02-01 15:10:05 -0800266 if (DEBUG) Slog.d(TAG, "Failed to connecto statsd when getting metadata");
Bookatzc6977972018-01-16 16:55:05 -0800267 return null;
268 }
269 }
270 }
271
272 private class StatsdDeathRecipient implements IBinder.DeathRecipient {
273 @Override
274 public void binderDied() {
275 synchronized (this) {
276 mService = null;
277 }
278 }
279 }
280
281 private IStatsManager getIStatsManagerLocked() throws RemoteException {
282 if (mService != null) {
283 return mService;
284 }
285 mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
286 if (mService != null) {
287 mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
288 }
289 return mService;
290 }
291}