• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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.provider;
18 
19 import org.apache.commons.codec.binary.Base64;
20 
21 import android.content.ContentResolver;
22 import android.content.ContentValues;
23 import android.database.SQLException;
24 import android.net.Uri;
25 import android.os.SystemClock;
26 import android.server.data.CrashData;
27 import android.util.Log;
28 
29 import java.io.ByteArrayOutputStream;
30 import java.io.DataOutputStream;
31 
32 /**
33  * Contract class for the checkin provider, used to store events and
34  * statistics that will be uploaded to a checkin server eventually.
35  * Describes the exposed database schema, and offers methods to add
36  * events and statistics to be uploaded.
37  *
38  * TODO: Move this to vendor/google when we have a home for it.
39  *
40  * @hide
41  */
42 public final class Checkin {
43     public static final String AUTHORITY = "android.server.checkin";
44 
45     /**
46      * The events table is a log of important timestamped occurrences.
47      * Each event has a type tag and an optional string value.
48      * If too many events are added before they can be reported, the
49      * content provider will erase older events to limit the table size.
50      */
51     public interface Events extends BaseColumns {
52         public static final String TABLE_NAME = "events";
53         public static final Uri CONTENT_URI =
54             Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
55 
56         public static final String TAG = "tag";     // TEXT
57         public static final String VALUE = "value"; // TEXT
58         public static final String DATE = "date";   // INTEGER
59 
60         /** Valid tag values.  Extend as necessary for your needs. */
61         public enum Tag {
62             APANIC_CONSOLE,
63             APANIC_THREADS,
64             AUTOTEST_FAILURE,
65             AUTOTEST_SEQUENCE_BEGIN,
66             AUTOTEST_SUITE_BEGIN,
67             AUTOTEST_TCPDUMP_BEGIN,
68             AUTOTEST_TCPDUMP_DATA,
69             AUTOTEST_TCPDUMP_END,
70             AUTOTEST_TEST_BEGIN,
71             AUTOTEST_TEST_FAILURE,
72             AUTOTEST_TEST_SUCCESS,
73             BROWSER_BUG_REPORT,
74             CARRIER_BUG_REPORT,
75             CHECKIN_FAILURE,
76             CHECKIN_SUCCESS,
77             FOTA_BEGIN,
78             FOTA_FAILURE,
79             FOTA_INSTALL,
80             FOTA_PROMPT,
81             FOTA_PROMPT_ACCEPT,
82             FOTA_PROMPT_REJECT,
83             FOTA_PROMPT_SKIPPED,
84             GSERVICES_ERROR,
85             GSERVICES_UPDATE,
86             LOGIN_SERVICE_ACCOUNT_TRIED,
87             LOGIN_SERVICE_ACCOUNT_SAVED,
88             LOGIN_SERVICE_AUTHENTICATE,
89             LOGIN_SERVICE_CAPTCHA_ANSWERED,
90             LOGIN_SERVICE_CAPTCHA_SHOWN,
91             LOGIN_SERVICE_PASSWORD_ENTERED,
92             LOGIN_SERVICE_SWITCH_GOOGLE_MAIL,
93             NETWORK_DOWN,
94             NETWORK_UP,
95             PHONE_UI,
96             RADIO_BUG_REPORT,
97             SETUP_COMPLETED,
98             SETUP_INITIATED,
99             SETUP_IO_ERROR,
100             SETUP_NETWORK_ERROR,
101             SETUP_REQUIRED_CAPTCHA,
102             SETUP_RETRIES_EXHAUSTED,
103             SETUP_SERVER_ERROR,
104             SETUP_SERVER_TIMEOUT,
105             SETUP_NO_DATA_NETWORK,
106             SYSTEM_BOOT,
107             SYSTEM_LAST_KMSG,
108             SYSTEM_RECOVERY_LOG,
109             SYSTEM_RESTART,
110             SYSTEM_SERVICE_LOOPING,
111             SYSTEM_TOMBSTONE,
112             TEST,
113             BATTERY_DISCHARGE_INFO,
114             MARKET_DOWNLOAD,
115             MARKET_INSTALL,
116             MARKET_REMOVE,
117             MARKET_REFUND,
118             MARKET_UNINSTALL,
119         }
120     }
121 
122     /**
123      * The stats table is a list of counter values indexed by a tag name.
124      * Each statistic has a count and sum fields, so it can track averages.
125      * When multiple statistics are inserted with the same tag, the count
126      * and sum fields are added together into a single entry in the database.
127      */
128     public interface Stats extends BaseColumns {
129         public static final String TABLE_NAME = "stats";
130         public static final Uri CONTENT_URI =
131             Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
132 
133         public static final String TAG = "tag";      // TEXT UNIQUE
134         public static final String COUNT = "count";  // INTEGER
135         public static final String SUM = "sum";      // REAL
136 
137         /** Valid tag values.  Extend as necessary for your needs. */
138         public enum Tag {
139             BROWSER_SNAP_CENTER,
140             BROWSER_TEXT_SIZE_CHANGE,
141             BROWSER_ZOOM_OVERVIEW,
142 
143             CRASHES_REPORTED,
144             CRASHES_TRUNCATED,
145             ELAPSED_REALTIME_SEC,
146             ELAPSED_UPTIME_SEC,
147             HTTP_REQUEST,
148             HTTP_REUSED,
149             HTTP_STATUS,
150             PHONE_GSM_REGISTERED,
151             PHONE_GPRS_ATTEMPTED,
152             PHONE_GPRS_CONNECTED,
153             PHONE_RADIO_RESETS,
154             TEST,
155             NETWORK_RX_MOBILE,
156             NETWORK_TX_MOBILE,
157             PHONE_CDMA_REGISTERED,
158             PHONE_CDMA_DATA_ATTEMPTED,
159             PHONE_CDMA_DATA_CONNECTED,
160         }
161     }
162 
163     /**
164      * The properties table is a set of tagged values sent with every checkin.
165      * Unlike statistics or events, they are not cleared after being uploaded.
166      * Multiple properties inserted with the same tag overwrite each other.
167      */
168     public interface Properties extends BaseColumns {
169         public static final String TABLE_NAME = "properties";
170         public static final Uri CONTENT_URI =
171             Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
172 
173         public static final String TAG = "tag";      // TEXT UNIQUE
174         public static final String VALUE = "value";  // TEXT
175 
176         /** Valid tag values, to be extended as necessary. */
177         public enum Tag {
178             DESIRED_BUILD,
179             MARKET_CHECKIN,
180         }
181     }
182 
183     /**
184      * The crashes table is a log of crash reports, kept separate from the
185      * general event log because crashes are large, important, and bursty.
186      * Like the events table, the crashes table is pruned on insert.
187      */
188     public interface Crashes extends BaseColumns {
189         public static final String TABLE_NAME = "crashes";
190         public static final Uri CONTENT_URI =
191             Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
192 
193         // TODO: one or both of these should be a file attachment, not a column
194         public static final String DATA = "data";    // TEXT
195         public static final String LOGS = "logs";    // TEXT
196     }
197 
198     /**
199      * Intents with this action cause a checkin attempt.  Normally triggered by
200      * a periodic alarm, these may be sent directly to force immediate checkin.
201      */
202     public interface TriggerIntent {
203         public static final String ACTION = "android.server.checkin.CHECKIN";
204 
205         // The category is used for GTalk service messages
206         public static final String CATEGORY = "android.server.checkin.CHECKIN";
207 
208         // If true indicates that the checkin should only transfer market related data
209         public static final String EXTRA_MARKET_ONLY = "market_only";
210     }
211 
212     private static final String TAG = "Checkin";
213 
214     /**
215      * Helper function to log an event to the database.
216      *
217      * @param resolver from {@link android.content.Context#getContentResolver}
218      * @param tag identifying the type of event being recorded
219      * @param value associated with event, if any
220      * @return URI of the event that was added
221      */
logEvent(ContentResolver resolver, Events.Tag tag, String value)222     static public Uri logEvent(ContentResolver resolver,
223             Events.Tag tag, String value) {
224         try {
225             // Don't specify the date column; the content provider will add that.
226             ContentValues values = new ContentValues();
227             values.put(Events.TAG, tag.toString());
228             if (value != null) values.put(Events.VALUE, value);
229             return resolver.insert(Events.CONTENT_URI, values);
230         } catch (IllegalArgumentException e) {  // thrown when provider is unavailable.
231             Log.w(TAG, "Can't log event " + tag + ": " + e);
232             return null;
233         } catch (SQLException e) {
234             Log.e(TAG, "Can't log event " + tag, e);  // Database errors are not fatal.
235             return null;
236         }
237     }
238 
239     /**
240      * Helper function to update statistics in the database.
241      * Note that multiple updates to the same tag will be combined.
242      *
243      * @param tag identifying what is being observed
244      * @param count of occurrences
245      * @param sum of some value over these occurrences
246      * @return URI of the statistic that was returned
247      */
updateStats(ContentResolver resolver, Stats.Tag tag, int count, double sum)248     static public Uri updateStats(ContentResolver resolver,
249             Stats.Tag tag, int count, double sum) {
250         try {
251             ContentValues values = new ContentValues();
252             values.put(Stats.TAG, tag.toString());
253             if (count != 0) values.put(Stats.COUNT, count);
254             if (sum != 0.0) values.put(Stats.SUM, sum);
255             return resolver.insert(Stats.CONTENT_URI, values);
256         } catch (IllegalArgumentException e) {  // thrown when provider is unavailable.
257             Log.w(TAG, "Can't update stat " + tag + ": " + e);
258             return null;
259         } catch (SQLException e) {
260             Log.e(TAG, "Can't update stat " + tag, e);  // Database errors are not fatal.
261             return null;
262         }
263     }
264 
265     /** Minimum time to wait after a crash failure before trying again. */
266     static private final long MIN_CRASH_FAILURE_RETRY = 10000;  // 10 seconds
267 
268     /** {@link SystemClock#elapsedRealtime} of the last time a crash report failed. */
269     static private volatile long sLastCrashFailureRealtime = -MIN_CRASH_FAILURE_RETRY;
270 
271     /**
272      * Helper function to report a crash.
273      *
274      * @param resolver from {@link android.content.Context#getContentResolver}
275      * @param crash data from {@link android.server.data.CrashData}
276      * @return URI of the crash report that was added
277      */
reportCrash(ContentResolver resolver, byte[] crash)278     static public Uri reportCrash(ContentResolver resolver, byte[] crash) {
279         try {
280             // If we are in a situation where crash reports fail (such as a full disk),
281             // it's important that we don't get into a loop trying to report failures.
282             // So discard all crash reports for a few seconds after reporting fails.
283             long realtime = SystemClock.elapsedRealtime();
284             if (realtime - sLastCrashFailureRealtime < MIN_CRASH_FAILURE_RETRY) {
285                 Log.e(TAG, "Crash logging skipped, too soon after logging failure");
286                 return null;
287             }
288 
289             // HACK: we don't support BLOB values, so base64 encode it.
290             byte[] encoded = Base64.encodeBase64(crash);
291             ContentValues values = new ContentValues();
292             values.put(Crashes.DATA, new String(encoded));
293             Uri uri = resolver.insert(Crashes.CONTENT_URI, values);
294             if (uri == null) {
295                 Log.e(TAG, "Error reporting crash");
296                 sLastCrashFailureRealtime = SystemClock.elapsedRealtime();
297             }
298             return uri;
299         } catch (Throwable t) {
300             // To avoid an infinite crash-reporting loop, swallow all errors and exceptions.
301             Log.e(TAG, "Error reporting crash: " + t);
302             sLastCrashFailureRealtime = SystemClock.elapsedRealtime();
303             return null;
304         }
305     }
306 
307     /**
308      * Report a crash in CrashData format.
309      *
310      * @param resolver from {@link android.content.Context#getContentResolver}
311      * @param crash data to report
312      * @return URI of the crash report that was added
313      */
reportCrash(ContentResolver resolver, CrashData crash)314     static public Uri reportCrash(ContentResolver resolver, CrashData crash) {
315         try {
316             ByteArrayOutputStream data = new ByteArrayOutputStream();
317             crash.write(new DataOutputStream(data));
318             return reportCrash(resolver, data.toByteArray());
319         } catch (Throwable t) {
320             // Swallow all errors and exceptions when writing crash report
321             Log.e(TAG, "Error writing crash: " + t);
322             return null;
323         }
324     }
325 }
326