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