| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.provider; |
| |
| import org.apache.commons.codec.binary.Base64; |
| |
| import android.content.ContentResolver; |
| import android.content.ContentValues; |
| import android.database.SQLException; |
| import android.net.Uri; |
| import android.os.SystemClock; |
| import android.server.data.CrashData; |
| import android.util.Log; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataOutputStream; |
| |
| /** |
| * Contract class for the checkin provider, used to store events and |
| * statistics that will be uploaded to a checkin server eventually. |
| * Describes the exposed database schema, and offers methods to add |
| * events and statistics to be uploaded. |
| * |
| * TODO: Move this to vendor/google when we have a home for it. |
| * |
| * @hide |
| */ |
| public final class Checkin { |
| public static final String AUTHORITY = "android.server.checkin"; |
| |
| /** |
| * The events table is a log of important timestamped occurrences. |
| * Each event has a type tag and an optional string value. |
| * If too many events are added before they can be reported, the |
| * content provider will erase older events to limit the table size. |
| */ |
| public interface Events extends BaseColumns { |
| public static final String TABLE_NAME = "events"; |
| public static final Uri CONTENT_URI = |
| Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME); |
| |
| public static final String TAG = "tag"; // TEXT |
| public static final String VALUE = "value"; // TEXT |
| public static final String DATE = "date"; // INTEGER |
| |
| /** Valid tag values. Extend as necessary for your needs. */ |
| public enum Tag { |
| APANIC_CONSOLE, |
| APANIC_THREADS, |
| AUTOTEST_FAILURE, |
| AUTOTEST_SEQUENCE_BEGIN, |
| AUTOTEST_SUITE_BEGIN, |
| AUTOTEST_TCPDUMP_BEGIN, |
| AUTOTEST_TCPDUMP_DATA, |
| AUTOTEST_TCPDUMP_END, |
| AUTOTEST_TEST_BEGIN, |
| AUTOTEST_TEST_FAILURE, |
| AUTOTEST_TEST_SUCCESS, |
| BROWSER_BUG_REPORT, |
| CARRIER_BUG_REPORT, |
| CHECKIN_FAILURE, |
| CHECKIN_SUCCESS, |
| CPUFREQ_STATS, |
| FOTA_BEGIN, |
| FOTA_FAILURE, |
| FOTA_INSTALL, |
| FOTA_PROMPT, |
| FOTA_PROMPT_ACCEPT, |
| FOTA_PROMPT_REJECT, |
| FOTA_PROMPT_SKIPPED, |
| GSERVICES_ERROR, |
| GSERVICES_UPDATE, |
| LOGIN_SERVICE_ACCOUNT_TRIED, |
| LOGIN_SERVICE_ACCOUNT_SAVED, |
| LOGIN_SERVICE_AUTHENTICATE, |
| LOGIN_SERVICE_CAPTCHA_ANSWERED, |
| LOGIN_SERVICE_CAPTCHA_SHOWN, |
| LOGIN_SERVICE_PASSWORD_ENTERED, |
| LOGIN_SERVICE_SWITCH_GOOGLE_MAIL, |
| NETWORK_DOWN, |
| NETWORK_UP, |
| PHONE_UI, |
| RADIO_BUG_REPORT, |
| SETUP_COMPLETED, |
| SETUP_INITIATED, |
| SETUP_IO_ERROR, |
| SETUP_NETWORK_ERROR, |
| SETUP_REQUIRED_CAPTCHA, |
| SETUP_RETRIES_EXHAUSTED, |
| SETUP_SERVER_ERROR, |
| SETUP_SERVER_TIMEOUT, |
| SETUP_NO_DATA_NETWORK, |
| SYSTEM_BOOT, |
| SYSTEM_LAST_KMSG, |
| SYSTEM_RECOVERY_LOG, |
| SYSTEM_RESTART, |
| SYSTEM_SERVICE_LOOPING, |
| SYSTEM_TOMBSTONE, |
| TEST, |
| BATTERY_DISCHARGE_INFO, |
| MARKET_DOWNLOAD, |
| MARKET_INSTALL, |
| MARKET_REMOVE, |
| MARKET_REFUND, |
| MARKET_UNINSTALL, |
| } |
| } |
| |
| /** |
| * The stats table is a list of counter values indexed by a tag name. |
| * Each statistic has a count and sum fields, so it can track averages. |
| * When multiple statistics are inserted with the same tag, the count |
| * and sum fields are added together into a single entry in the database. |
| */ |
| public interface Stats extends BaseColumns { |
| public static final String TABLE_NAME = "stats"; |
| public static final Uri CONTENT_URI = |
| Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME); |
| |
| public static final String TAG = "tag"; // TEXT UNIQUE |
| public static final String COUNT = "count"; // INTEGER |
| public static final String SUM = "sum"; // REAL |
| |
| /** Valid tag values. Extend as necessary for your needs. */ |
| public enum Tag { |
| BROWSER_SNAP_CENTER, |
| BROWSER_TEXT_SIZE_CHANGE, |
| BROWSER_ZOOM_OVERVIEW, |
| |
| CRASHES_REPORTED, |
| CRASHES_TRUNCATED, |
| ELAPSED_REALTIME_SEC, |
| ELAPSED_UPTIME_SEC, |
| HTTP_REQUEST, |
| HTTP_REUSED, |
| HTTP_STATUS, |
| PHONE_GSM_REGISTERED, |
| PHONE_GPRS_ATTEMPTED, |
| PHONE_GPRS_CONNECTED, |
| PHONE_RADIO_RESETS, |
| TEST, |
| NETWORK_RX_MOBILE, |
| NETWORK_TX_MOBILE, |
| PHONE_CDMA_REGISTERED, |
| PHONE_CDMA_DATA_ATTEMPTED, |
| PHONE_CDMA_DATA_CONNECTED, |
| } |
| } |
| |
| /** |
| * The properties table is a set of tagged values sent with every checkin. |
| * Unlike statistics or events, they are not cleared after being uploaded. |
| * Multiple properties inserted with the same tag overwrite each other. |
| */ |
| public interface Properties extends BaseColumns { |
| public static final String TABLE_NAME = "properties"; |
| public static final Uri CONTENT_URI = |
| Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME); |
| |
| public static final String TAG = "tag"; // TEXT UNIQUE |
| public static final String VALUE = "value"; // TEXT |
| |
| /** Valid tag values, to be extended as necessary. */ |
| public enum Tag { |
| DESIRED_BUILD, |
| MARKET_CHECKIN, |
| } |
| } |
| |
| /** |
| * The crashes table is a log of crash reports, kept separate from the |
| * general event log because crashes are large, important, and bursty. |
| * Like the events table, the crashes table is pruned on insert. |
| */ |
| public interface Crashes extends BaseColumns { |
| public static final String TABLE_NAME = "crashes"; |
| public static final Uri CONTENT_URI = |
| Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME); |
| |
| // TODO: one or both of these should be a file attachment, not a column |
| public static final String DATA = "data"; // TEXT |
| public static final String LOGS = "logs"; // TEXT |
| } |
| |
| /** |
| * Intents with this action cause a checkin attempt. Normally triggered by |
| * a periodic alarm, these may be sent directly to force immediate checkin. |
| */ |
| public interface TriggerIntent { |
| public static final String ACTION = "android.server.checkin.CHECKIN"; |
| |
| // The category is used for GTalk service messages |
| public static final String CATEGORY = "android.server.checkin.CHECKIN"; |
| |
| // If true indicates that the checkin should only transfer market related data |
| public static final String EXTRA_MARKET_ONLY = "market_only"; |
| } |
| |
| private static final String TAG = "Checkin"; |
| |
| /** |
| * Helper function to log an event to the database. |
| * |
| * @param resolver from {@link android.content.Context#getContentResolver} |
| * @param tag identifying the type of event being recorded |
| * @param value associated with event, if any |
| * @return URI of the event that was added |
| */ |
| static public Uri logEvent(ContentResolver resolver, |
| Events.Tag tag, String value) { |
| try { |
| // Don't specify the date column; the content provider will add that. |
| ContentValues values = new ContentValues(); |
| values.put(Events.TAG, tag.toString()); |
| if (value != null) values.put(Events.VALUE, value); |
| return resolver.insert(Events.CONTENT_URI, values); |
| } catch (IllegalArgumentException e) { // thrown when provider is unavailable. |
| Log.w(TAG, "Can't log event " + tag + ": " + e); |
| return null; |
| } catch (SQLException e) { |
| Log.e(TAG, "Can't log event " + tag, e); // Database errors are not fatal. |
| return null; |
| } |
| } |
| |
| /** |
| * Helper function to update statistics in the database. |
| * Note that multiple updates to the same tag will be combined. |
| * |
| * @param tag identifying what is being observed |
| * @param count of occurrences |
| * @param sum of some value over these occurrences |
| * @return URI of the statistic that was returned |
| */ |
| static public Uri updateStats(ContentResolver resolver, |
| Stats.Tag tag, int count, double sum) { |
| try { |
| ContentValues values = new ContentValues(); |
| values.put(Stats.TAG, tag.toString()); |
| if (count != 0) values.put(Stats.COUNT, count); |
| if (sum != 0.0) values.put(Stats.SUM, sum); |
| return resolver.insert(Stats.CONTENT_URI, values); |
| } catch (IllegalArgumentException e) { // thrown when provider is unavailable. |
| Log.w(TAG, "Can't update stat " + tag + ": " + e); |
| return null; |
| } catch (SQLException e) { |
| Log.e(TAG, "Can't update stat " + tag, e); // Database errors are not fatal. |
| return null; |
| } |
| } |
| |
| /** Minimum time to wait after a crash failure before trying again. */ |
| static private final long MIN_CRASH_FAILURE_RETRY = 10000; // 10 seconds |
| |
| /** {@link SystemClock#elapsedRealtime} of the last time a crash report failed. */ |
| static private volatile long sLastCrashFailureRealtime = -MIN_CRASH_FAILURE_RETRY; |
| |
| /** |
| * Helper function to report a crash. |
| * |
| * @param resolver from {@link android.content.Context#getContentResolver} |
| * @param crash data from {@link android.server.data.CrashData} |
| * @return URI of the crash report that was added |
| */ |
| static public Uri reportCrash(ContentResolver resolver, byte[] crash) { |
| try { |
| // If we are in a situation where crash reports fail (such as a full disk), |
| // it's important that we don't get into a loop trying to report failures. |
| // So discard all crash reports for a few seconds after reporting fails. |
| long realtime = SystemClock.elapsedRealtime(); |
| if (realtime - sLastCrashFailureRealtime < MIN_CRASH_FAILURE_RETRY) { |
| Log.e(TAG, "Crash logging skipped, too soon after logging failure"); |
| return null; |
| } |
| |
| // HACK: we don't support BLOB values, so base64 encode it. |
| byte[] encoded = Base64.encodeBase64(crash); |
| ContentValues values = new ContentValues(); |
| values.put(Crashes.DATA, new String(encoded)); |
| Uri uri = resolver.insert(Crashes.CONTENT_URI, values); |
| if (uri == null) { |
| Log.e(TAG, "Error reporting crash"); |
| sLastCrashFailureRealtime = SystemClock.elapsedRealtime(); |
| } |
| return uri; |
| } catch (Throwable t) { |
| // To avoid an infinite crash-reporting loop, swallow all errors and exceptions. |
| Log.e(TAG, "Error reporting crash: " + t); |
| sLastCrashFailureRealtime = SystemClock.elapsedRealtime(); |
| return null; |
| } |
| } |
| |
| /** |
| * Report a crash in CrashData format. |
| * |
| * @param resolver from {@link android.content.Context#getContentResolver} |
| * @param crash data to report |
| * @return URI of the crash report that was added |
| */ |
| static public Uri reportCrash(ContentResolver resolver, CrashData crash) { |
| try { |
| ByteArrayOutputStream data = new ByteArrayOutputStream(); |
| crash.write(new DataOutputStream(data)); |
| return reportCrash(resolver, data.toByteArray()); |
| } catch (Throwable t) { |
| // Swallow all errors and exceptions when writing crash report |
| Log.e(TAG, "Error writing crash: " + t); |
| return null; |
| } |
| } |
| } |