1 /*
2  * Copyright (C) 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  */
16 
17 package android.util;
18 
19 import static android.Manifest.permission.DUMP;
20 import static android.Manifest.permission.PACKAGE_USAGE_STATS;
21 
22 import android.Manifest;
23 import android.annotation.NonNull;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SystemApi;
26 import android.content.Context;
27 import android.os.IStatsManager;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 
31 /**
32  * StatsLog provides an API for developers to send events to statsd. The events can be used to
33  * define custom metrics inside statsd.
34  */
35 public final class StatsLog extends StatsLogInternal {
36     private static final String TAG = "StatsLog";
37     private static final boolean DEBUG = false;
38 
39     private static IStatsManager sService;
40 
41     private static Object sLogLock = new Object();
42 
StatsLog()43     private StatsLog() {
44     }
45 
46     /**
47      * Logs a start event.
48      *
49      * @param label developer-chosen label.
50      * @return True if the log request was sent to statsd.
51      */
logStart(int label)52     public static boolean logStart(int label) {
53         synchronized (sLogLock) {
54             try {
55                 IStatsManager service = getIStatsManagerLocked();
56                 if (service == null) {
57                     if (DEBUG) {
58                         Slog.d(TAG, "Failed to find statsd when logging start");
59                     }
60                     return false;
61                 }
62                 service.sendAppBreadcrumbAtom(label,
63                         StatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
64                 return true;
65             } catch (RemoteException e) {
66                 sService = null;
67                 if (DEBUG) {
68                     Slog.d(TAG, "Failed to connect to statsd when logging start");
69                 }
70                 return false;
71             }
72         }
73     }
74 
75     /**
76      * Logs a stop event.
77      *
78      * @param label developer-chosen label.
79      * @return True if the log request was sent to statsd.
80      */
logStop(int label)81     public static boolean logStop(int label) {
82         synchronized (sLogLock) {
83             try {
84                 IStatsManager service = getIStatsManagerLocked();
85                 if (service == null) {
86                     if (DEBUG) {
87                         Slog.d(TAG, "Failed to find statsd when logging stop");
88                     }
89                     return false;
90                 }
91                 service.sendAppBreadcrumbAtom(label, StatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
92                 return true;
93             } catch (RemoteException e) {
94                 sService = null;
95                 if (DEBUG) {
96                     Slog.d(TAG, "Failed to connect to statsd when logging stop");
97                 }
98                 return false;
99             }
100         }
101     }
102 
103     /**
104      * Logs an event that does not represent a start or stop boundary.
105      *
106      * @param label developer-chosen label.
107      * @return True if the log request was sent to statsd.
108      */
logEvent(int label)109     public static boolean logEvent(int label) {
110         synchronized (sLogLock) {
111             try {
112                 IStatsManager service = getIStatsManagerLocked();
113                 if (service == null) {
114                     if (DEBUG) {
115                         Slog.d(TAG, "Failed to find statsd when logging event");
116                     }
117                     return false;
118                 }
119                 service.sendAppBreadcrumbAtom(
120                         label, StatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
121                 return true;
122             } catch (RemoteException e) {
123                 sService = null;
124                 if (DEBUG) {
125                     Slog.d(TAG, "Failed to connect to statsd when logging event");
126                 }
127                 return false;
128             }
129         }
130     }
131 
132     /**
133      * Logs an event for binary push for module updates.
134      *
135      * @param trainName        name of install train.
136      * @param trainVersionCode version code of the train.
137      * @param options          optional flags about this install.
138      *                         The last 3 bits indicate options:
139      *                             0x01: FLAG_REQUIRE_STAGING
140      *                             0x02: FLAG_ROLLBACK_ENABLED
141      *                             0x04: FLAG_REQUIRE_LOW_LATENCY_MONITOR
142      * @param state            current install state. Defined as State enums in
143      *                         BinaryPushStateChanged atom in
144      *                         frameworks/base/cmds/statsd/src/atoms.proto
145      * @param experimentIds    experiment ids.
146      * @return True if the log request was sent to statsd.
147      */
148     @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
logBinaryPushStateChanged(@onNull String trainName, long trainVersionCode, int options, int state, @NonNull long[] experimentIds)149     public static boolean logBinaryPushStateChanged(@NonNull String trainName,
150             long trainVersionCode, int options, int state,
151             @NonNull long[] experimentIds) {
152         synchronized (sLogLock) {
153             try {
154                 IStatsManager service = getIStatsManagerLocked();
155                 if (service == null) {
156                     if (DEBUG) {
157                         Slog.d(TAG, "Failed to find statsd when logging event");
158                     }
159                     return false;
160                 }
161                 service.sendBinaryPushStateChangedAtom(
162                         trainName, trainVersionCode, options, state, experimentIds);
163                 return true;
164             } catch (RemoteException e) {
165                 sService = null;
166                 if (DEBUG) {
167                     Slog.d(TAG,
168                             "Failed to connect to StatsCompanionService when logging "
169                                     + "BinaryPushStateChanged");
170                 }
171                 return false;
172             }
173         }
174     }
175 
176     /**
177      * Logs an event for watchdog rollbacks.
178      *
179      * @param rollbackType          state of the rollback.
180      * @param packageName           package name being rolled back.
181      * @param packageVersionCode    version of the package being rolled back.
182      * @param rollbackReason        reason the package is being rolled back.
183      * @param failingPackageName    the package name causing the failure.
184      *
185      * @return True if the log request was sent to statsd.
186      *
187      * @hide
188      */
189     @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
logWatchdogRollbackOccurred(int rollbackType, String packageName, long packageVersionCode, int rollbackReason, String failingPackageName)190     public static boolean logWatchdogRollbackOccurred(int rollbackType, String packageName,
191             long packageVersionCode, int rollbackReason, String failingPackageName) {
192         synchronized (sLogLock) {
193             try {
194                 IStatsManager service = getIStatsManagerLocked();
195                 if (service == null) {
196                     if (DEBUG) {
197                         Slog.d(TAG, "Failed to find statsd when logging event");
198                     }
199                     return false;
200                 }
201 
202                 service.sendWatchdogRollbackOccurredAtom(rollbackType, packageName,
203                         packageVersionCode, rollbackReason, failingPackageName);
204                 return true;
205             } catch (RemoteException e) {
206                 sService = null;
207                 if (DEBUG) {
208                     Slog.d(TAG,
209                             "Failed to connect to StatsCompanionService when logging "
210                                     + "WatchdogRollbackOccurred");
211                 }
212                 return false;
213             }
214         }
215     }
216 
217 
getIStatsManagerLocked()218     private static IStatsManager getIStatsManagerLocked() throws RemoteException {
219         if (sService != null) {
220             return sService;
221         }
222         sService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
223         return sService;
224     }
225 
226     /**
227      * Write an event to stats log using the raw format.
228      *
229      * @param buffer    The encoded buffer of data to write.
230      * @param size      The number of bytes from the buffer to write.
231      * @hide
232      */
233     // TODO(b/144935988): Mark deprecated.
234     @SystemApi
writeRaw(@onNull byte[] buffer, int size)235     public static void writeRaw(@NonNull byte[] buffer, int size) {
236         // TODO(b/144935988): make this no-op once clients have migrated to StatsEvent.
237         writeImpl(buffer, size, 0);
238     }
239 
240     /**
241      * Write an event to stats log using the raw format.
242      *
243      * @param buffer    The encoded buffer of data to write.
244      * @param size      The number of bytes from the buffer to write.
245      * @param atomId    The id of the atom to which the event belongs.
246      */
writeImpl(@onNull byte[] buffer, int size, int atomId)247     private static native void writeImpl(@NonNull byte[] buffer, int size, int atomId);
248 
249     /**
250      * Write an event to stats log using the raw format encapsulated in StatsEvent.
251      * After writing to stats log, release() is called on the StatsEvent object.
252      * No further action should be taken on the StatsEvent object following this call.
253      *
254      * @param statsEvent    The StatsEvent object containing the encoded buffer of data to write.
255      * @hide
256      */
write(@onNull final StatsEvent statsEvent)257     public static void write(@NonNull final StatsEvent statsEvent) {
258         writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId());
259         statsEvent.release();
260     }
261 
enforceDumpCallingPermission(Context context)262     private static void enforceDumpCallingPermission(Context context) {
263         context.enforceCallingPermission(android.Manifest.permission.DUMP, "Need DUMP permission.");
264     }
265 
enforcesageStatsCallingPermission(Context context)266     private static void enforcesageStatsCallingPermission(Context context) {
267         context.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS,
268                 "Need PACKAGE_USAGE_STATS permission.");
269     }
270 }
271