blob: 86949cab08d4bab672fd25c5464e6124eb275be8 [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";
36
David Chen661f7912018-01-22 17:46:24 -080037 /**
38 * Long extra of uid that added the relevant stats config.
39 */
40 public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
41 /**
42 * Long extra of the relevant stats config's configKey.
43 */
44 public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
45 /**
46 * Long extra of the relevant statsd_config.proto's Subscription.id.
47 */
Bookatzc6977972018-01-16 16:55:05 -080048 public static final String EXTRA_STATS_SUBSCRIPTION_ID =
49 "android.app.extra.STATS_SUBSCRIPTION_ID";
David Chen661f7912018-01-22 17:46:24 -080050 /**
51 * Long extra of the relevant statsd_config.proto's Subscription.rule_id.
52 */
Bookatzc6977972018-01-16 16:55:05 -080053 public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID =
54 "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
55 /**
56 * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value
57 * information.
58 */
59 public static final String EXTRA_STATS_DIMENSIONS_VALUE =
60 "android.app.extra.STATS_DIMENSIONS_VALUE";
61
62 /**
Bookatz5c800e32018-01-24 14:59:52 -080063 * Broadcast Action: Statsd has started.
64 * Configurations and PendingIntents can now be sent to it.
65 */
66 public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
67
68 /**
Bookatzc6977972018-01-16 16:55:05 -080069 * Constructor for StatsManagerClient.
70 *
71 * @hide
72 */
73 public StatsManager() {
74 }
75
76 /**
David Cheneac4e642018-02-08 14:12:20 -080077 * Temporary. Will be deleted.
78 */
79 @RequiresPermission(Manifest.permission.DUMP)
80 public boolean addConfiguration(long configKey, byte[] config, String a, String b) {
81 return addConfiguration(configKey, config);
82 }
83
84 /**
Bookatzc6977972018-01-16 16:55:05 -080085 * Clients can send a configuration and simultaneously registers the name of a broadcast
86 * receiver that listens for when it should request data.
87 *
88 * @param configKey An arbitrary integer that allows clients to track the configuration.
89 * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all
90 * dependencies eg, conditions and matchers).
Bookatzc6977972018-01-16 16:55:05 -080091 * @return true if successful
92 */
93 @RequiresPermission(Manifest.permission.DUMP)
David Chen661f7912018-01-22 17:46:24 -080094 public boolean addConfiguration(long configKey, byte[] config) {
Bookatzc6977972018-01-16 16:55:05 -080095 synchronized (this) {
96 try {
97 IStatsManager service = getIStatsManagerLocked();
98 if (service == null) {
99 Slog.d(TAG, "Failed to find statsd when adding configuration");
100 return false;
101 }
David Chen661f7912018-01-22 17:46:24 -0800102 return service.addConfiguration(configKey, config);
Bookatzc6977972018-01-16 16:55:05 -0800103 } catch (RemoteException e) {
104 Slog.d(TAG, "Failed to connect to statsd when adding configuration");
105 return false;
106 }
107 }
108 }
109
110 /**
111 * Remove a configuration from logging.
112 *
113 * @param configKey Configuration key to remove.
114 * @return true if successful
115 */
116 @RequiresPermission(Manifest.permission.DUMP)
117 public boolean removeConfiguration(long configKey) {
118 synchronized (this) {
119 try {
120 IStatsManager service = getIStatsManagerLocked();
121 if (service == null) {
122 Slog.d(TAG, "Failed to find statsd when removing configuration");
123 return false;
124 }
125 return service.removeConfiguration(configKey);
126 } catch (RemoteException e) {
127 Slog.d(TAG, "Failed to connect to statsd when removing configuration");
128 return false;
129 }
130 }
131 }
132
133 /**
134 * Set the PendingIntent to be used when broadcasting subscriber information to the given
135 * subscriberId within the given config.
Bookatzc6977972018-01-16 16:55:05 -0800136 * <p>
137 * Suppose that the calling uid has added a config with key configKey, and that in this config
138 * it is specified that when a particular anomaly is detected, a broadcast should be sent to
139 * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
140 * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
141 * when the anomaly is detected.
Bookatzc6977972018-01-16 16:55:05 -0800142 * <p>
143 * When statsd sends the broadcast, the PendingIntent will used to send an intent with
144 * information of
David Chen661f7912018-01-22 17:46:24 -0800145 * {@link #EXTRA_STATS_CONFIG_UID},
146 * {@link #EXTRA_STATS_CONFIG_KEY},
147 * {@link #EXTRA_STATS_SUBSCRIPTION_ID},
148 * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, and
149 * {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
Bookatzc6977972018-01-16 16:55:05 -0800150 * <p>
151 * This function can only be called by the owner (uid) of the config. It must be called each
152 * time statsd starts. The config must have been added first (via addConfiguration()).
153 *
David Chen661f7912018-01-22 17:46:24 -0800154 * @param configKey The integer naming the config to which this subscriber is attached.
155 * @param subscriberId ID of the subscriber, as used in the config.
Bookatzc6977972018-01-16 16:55:05 -0800156 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
157 * associated with the given subscriberId. May be null, in which case
158 * it undoes any previous setting of this subscriberId.
159 * @return true if successful
160 */
161 @RequiresPermission(Manifest.permission.DUMP)
David Chen661f7912018-01-22 17:46:24 -0800162 public boolean setBroadcastSubscriber(
163 long configKey, long subscriberId, PendingIntent pendingIntent) {
Bookatzc6977972018-01-16 16:55:05 -0800164 synchronized (this) {
165 try {
166 IStatsManager service = getIStatsManagerLocked();
167 if (service == null) {
168 Slog.w(TAG, "Failed to find statsd when adding broadcast subscriber");
169 return false;
170 }
171 if (pendingIntent != null) {
172 // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
173 IBinder intentSender = pendingIntent.getTarget().asBinder();
174 return service.setBroadcastSubscriber(configKey, subscriberId, intentSender);
175 } else {
176 return service.unsetBroadcastSubscriber(configKey, subscriberId);
177 }
178 } catch (RemoteException e) {
179 Slog.w(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
180 return false;
181 }
182 }
183 }
184
185 /**
David Chen661f7912018-01-22 17:46:24 -0800186 * Registers the operation that is called to retrieve the metrics data. This must be called
187 * each time statsd starts. The config must have been added first (via addConfiguration(),
188 * although addConfiguration could have been called on a previous boot). This operation allows
189 * statsd to send metrics data whenever statsd determines that the metrics in memory are
190 * approaching the memory limits. The fetch operation should call {@link #getData} to fetch the
191 * data, which also deletes the retrieved metrics from statsd's memory.
192 *
193 * @param configKey The integer naming the config to which this operation is attached.
194 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
195 * associated with the given subscriberId. May be null, in which case
196 * it removes any associated pending intent with this configKey.
197 * @return true if successful
198 */
199 @RequiresPermission(Manifest.permission.DUMP)
200 public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) {
201 synchronized (this) {
202 try {
203 IStatsManager service = getIStatsManagerLocked();
204 if (service == null) {
205 Slog.d(TAG, "Failed to find statsd when registering data listener.");
206 return false;
207 }
208 if (pendingIntent == null) {
209 return service.removeDataFetchOperation(configKey);
210 } else {
211 // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
212 IBinder intentSender = pendingIntent.getTarget().asBinder();
213 return service.setDataFetchOperation(configKey, intentSender);
214 }
215
216 } catch (RemoteException e) {
217 Slog.d(TAG, "Failed to connect to statsd when registering data listener.");
218 return false;
219 }
220 }
221 }
222
223 /**
Bookatzc6977972018-01-16 16:55:05 -0800224 * Clients can request data with a binder call. This getter is destructive and also clears
225 * the retrieved metrics from statsd memory.
226 *
227 * @param configKey Configuration key to retrieve data from.
228 * @return Serialized ConfigMetricsReportList proto. Returns null on failure.
229 */
230 @RequiresPermission(Manifest.permission.DUMP)
231 public byte[] getData(long configKey) {
232 synchronized (this) {
233 try {
234 IStatsManager service = getIStatsManagerLocked();
235 if (service == null) {
236 Slog.d(TAG, "Failed to find statsd when getting data");
237 return null;
238 }
239 return service.getData(configKey);
240 } catch (RemoteException e) {
241 Slog.d(TAG, "Failed to connecto statsd when getting data");
242 return null;
243 }
244 }
245 }
246
247 /**
248 * Clients can request metadata for statsd. Will contain stats across all configurations but not
249 * the actual metrics themselves (metrics must be collected via {@link #getData(String)}.
250 * This getter is not destructive and will not reset any metrics/counters.
251 *
252 * @return Serialized StatsdStatsReport proto. Returns null on failure.
253 */
254 @RequiresPermission(Manifest.permission.DUMP)
255 public byte[] getMetadata() {
256 synchronized (this) {
257 try {
258 IStatsManager service = getIStatsManagerLocked();
259 if (service == null) {
260 Slog.d(TAG, "Failed to find statsd when getting metadata");
261 return null;
262 }
263 return service.getMetadata();
264 } catch (RemoteException e) {
265 Slog.d(TAG, "Failed to connecto statsd when getting metadata");
266 return null;
267 }
268 }
269 }
270
271 private class StatsdDeathRecipient implements IBinder.DeathRecipient {
272 @Override
273 public void binderDied() {
274 synchronized (this) {
275 mService = null;
276 }
277 }
278 }
279
280 private IStatsManager getIStatsManagerLocked() throws RemoteException {
281 if (mService != null) {
282 return mService;
283 }
284 mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
285 if (mService != null) {
286 mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
287 }
288 return mService;
289 }
290}