mobile/android/base/background/healthreport/HealthReportProvider.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko.background.healthreport;
     7 import java.io.File;
     8 import java.util.ArrayList;
     9 import java.util.List;
    10 import java.util.Map.Entry;
    12 import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage.DatabaseEnvironment;
    13 import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field;
    14 import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields;
    15 import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields.FieldSpec;
    17 import android.content.ContentProvider;
    18 import android.content.ContentUris;
    19 import android.content.ContentValues;
    20 import android.content.UriMatcher;
    21 import android.database.Cursor;
    22 import android.net.Uri;
    24 /**
    25  * This is a {@link ContentProvider} wrapper around a database-backed Health
    26  * Report storage layer.
    27  *
    28  * It stores environments, fields, and measurements, and events which refer to
    29  * each of these by integer ID.
    30  *
    31  * Insert = daily discrete.
    32  * content://org.mozilla.gecko.health/events/env/measurement/v/field
    33  *
    34  * Update = daily last or daily counter
    35  * content://org.mozilla.gecko.health/events/env/measurement/v/field/counter
    36  * content://org.mozilla.gecko.health/events/env/measurement/v/field/last
    37  *
    38  * Delete = drop today's row
    39  * content://org.mozilla.gecko.health/events/env/measurement/v/field/
    40  *
    41  * Query, of course: content://org.mozilla.gecko.health/events/?since
    42  *
    43  * Each operation accepts an optional `time` query parameter, formatted as
    44  * milliseconds since epoch. If omitted, it defaults to the current time.
    45  *
    46  * Each operation also accepts mandatory `profilePath` and `env` arguments.
    47  *
    48  * TODO: document measurements.
    49  */
    50 public class HealthReportProvider extends ContentProvider {
    51   private HealthReportDatabases databases;
    52   private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    54   public static final String HEALTH_AUTHORITY = HealthReportConstants.HEALTH_AUTHORITY;
    56   // URI matches.
    57   private static final int ENVIRONMENTS_ROOT    = 10;
    58   private static final int EVENTS_ROOT          = 11;
    59   private static final int EVENTS_RAW_ROOT      = 12;
    60   private static final int FIELDS_ROOT          = 13;
    61   private static final int MEASUREMENTS_ROOT    = 14;
    63   private static final int EVENTS_FIELD_GENERIC = 20;
    64   private static final int EVENTS_FIELD_COUNTER = 21;
    65   private static final int EVENTS_FIELD_LAST    = 22;
    67   private static final int ENVIRONMENT_DETAILS  = 30;
    68   private static final int FIELDS_MEASUREMENT   = 31;
    70   static {
    71     uriMatcher.addURI(HEALTH_AUTHORITY, "environments/", ENVIRONMENTS_ROOT);
    72     uriMatcher.addURI(HEALTH_AUTHORITY, "events/", EVENTS_ROOT);
    73     uriMatcher.addURI(HEALTH_AUTHORITY, "rawevents/", EVENTS_RAW_ROOT);
    74     uriMatcher.addURI(HEALTH_AUTHORITY, "fields/", FIELDS_ROOT);
    75     uriMatcher.addURI(HEALTH_AUTHORITY, "measurements/", MEASUREMENTS_ROOT);
    77     uriMatcher.addURI(HEALTH_AUTHORITY, "events/#/*/#/*", EVENTS_FIELD_GENERIC);
    78     uriMatcher.addURI(HEALTH_AUTHORITY, "events/#/*/#/*/counter", EVENTS_FIELD_COUNTER);
    79     uriMatcher.addURI(HEALTH_AUTHORITY, "events/#/*/#/*/last", EVENTS_FIELD_LAST);
    81     uriMatcher.addURI(HEALTH_AUTHORITY, "environments/#", ENVIRONMENT_DETAILS);
    82     uriMatcher.addURI(HEALTH_AUTHORITY, "fields/*/#", FIELDS_MEASUREMENT);
    83   }
    85   /**
    86    * So we can bypass the ContentProvider layer.
    87    */
    88   public HealthReportDatabaseStorage getProfileStorage(final String profilePath) {
    89     if (profilePath == null) {
    90       throw new IllegalArgumentException("profilePath must be provided.");
    91     }
    92     return databases.getDatabaseHelperForProfile(new File(profilePath));
    93   }
    95   private HealthReportDatabaseStorage getProfileStorageForUri(Uri uri) {
    96     final String profilePath = uri.getQueryParameter("profilePath");
    97     return getProfileStorage(profilePath);
    98   }
   100   @Override
   101   public void onLowMemory() {
   102     // While we could prune the database here, it wouldn't help - it would restore disk space
   103     // rather then lower our RAM usage. Additionally, pruning the database may use even more
   104     // memory and take too long to run in this method.
   105     super.onLowMemory();
   106     databases.closeDatabaseHelpers();
   107   }
   109   @Override
   110   public String getType(Uri uri) {
   111     return null;
   112   }
   114   @Override
   115   public boolean onCreate() {
   116     databases = new HealthReportDatabases(getContext());
   117     return true;
   118   }
   120   @Override
   121   public Uri insert(Uri uri, ContentValues values) {
   122     int match = uriMatcher.match(uri);
   123     HealthReportDatabaseStorage storage = getProfileStorageForUri(uri);
   124     switch (match) {
   125     case FIELDS_MEASUREMENT:
   126       // The keys of this ContentValues are field names.
   127       List<String> pathSegments = uri.getPathSegments();
   128       String measurement = pathSegments.get(1);
   129       int v = Integer.parseInt(pathSegments.get(2));
   130       storage.ensureMeasurementInitialized(measurement, v, getFieldSpecs(values));
   131       return uri;
   133     case ENVIRONMENTS_ROOT:
   134       DatabaseEnvironment environment = storage.getEnvironment();
   135       environment.init(values);
   136       return ContentUris.withAppendedId(uri, environment.register());
   138     case EVENTS_FIELD_GENERIC:
   139       long time = getTimeFromUri(uri);
   140       int day = storage.getDay(time);
   141       int env = getEnvironmentFromUri(uri);
   142       Field field = getFieldFromUri(storage, uri);
   144       if (!values.containsKey("value")) {
   145         throw new IllegalArgumentException("Must provide ContentValues including 'value' key.");
   146       }
   148       Object object = values.get("value");
   149       if (object instanceof Integer ||
   150           object instanceof Long) {
   151         storage.recordDailyDiscrete(env, day, field.getID(), ((Integer) object).intValue());
   152       } else if (object instanceof String) {
   153         storage.recordDailyDiscrete(env, day, field.getID(), (String) object);
   154       } else {
   155         storage.recordDailyDiscrete(env, day, field.getID(), object.toString());
   156       }
   158       // TODO: eventually we might want to return something more useful than
   159       // the input URI.
   160       return uri;
   161     default:
   162       throw new IllegalArgumentException("Unknown insert URI");
   163     }
   164   }
   166   @Override
   167   public int update(Uri uri, ContentValues values, String selection,
   168                     String[] selectionArgs) {
   170     int match = uriMatcher.match(uri);
   171     if (match != EVENTS_FIELD_COUNTER &&
   172         match != EVENTS_FIELD_LAST) {
   173       throw new IllegalArgumentException("Must provide operation for update.");
   174     }
   176     HealthReportStorage storage = getProfileStorageForUri(uri);
   177     long time = getTimeFromUri(uri);
   178     int day = storage.getDay(time);
   179     int env = getEnvironmentFromUri(uri);
   180     Field field = getFieldFromUri(storage, uri);
   182     switch (match) {
   183     case EVENTS_FIELD_COUNTER:
   184       int by = values.containsKey("value") ? values.getAsInteger("value") : 1;
   185       storage.incrementDailyCount(env, day, field.getID(), by);
   186       return 1;
   188     case EVENTS_FIELD_LAST:
   189       Object object = values.get("value");
   190       if (object instanceof Integer ||
   191           object instanceof Long) {
   192         storage.recordDailyLast(env, day, field.getID(), ((Integer) object).intValue());
   193       } else if (object instanceof String) {
   194         storage.recordDailyLast(env, day, field.getID(), (String) object);
   195       } else {
   196         storage.recordDailyLast(env, day, field.getID(), object.toString());
   197       }
   198       return 1;
   200     default:
   201         // javac's flow control analysis sucks.
   202         return 0;
   203     }
   204   }
   206   @Override
   207   public int delete(Uri uri, String selection, String[] selectionArgs) {
   208     int match = uriMatcher.match(uri);
   209     HealthReportStorage storage = getProfileStorageForUri(uri);
   210     switch (match) {
   211     case MEASUREMENTS_ROOT:
   212       storage.deleteMeasurements();
   213       return 1;
   214     case ENVIRONMENTS_ROOT:
   215       storage.deleteEnvironments();
   216       return 1;
   217     default:
   218       throw new IllegalArgumentException();
   219     }
   221     // TODO: more
   222   }
   224   @Override
   225   public Cursor query(Uri uri, String[] projection, String selection,
   226                       String[] selectionArgs, String sortOrder) {
   227     int match = uriMatcher.match(uri);
   229     HealthReportStorage storage = getProfileStorageForUri(uri);
   230     switch (match) {
   231     case EVENTS_ROOT:
   232       return storage.getEventsSince(getTimeFromUri(uri));
   233     case EVENTS_RAW_ROOT:
   234       return storage.getRawEventsSince(getTimeFromUri(uri));
   235     case MEASUREMENTS_ROOT:
   236       return storage.getMeasurementVersions();
   237     case FIELDS_ROOT:
   238       return storage.getFieldVersions();
   239     }
   240     List<String> pathSegments = uri.getPathSegments();
   241     switch (match) {
   242     case ENVIRONMENT_DETAILS:
   243       return storage.getEnvironmentRecordForID(Integer.parseInt(pathSegments.get(1), 10));
   244     case FIELDS_MEASUREMENT:
   245       String measurement = pathSegments.get(1);
   246       int v = Integer.parseInt(pathSegments.get(2));
   247       return storage.getFieldVersions(measurement, v);
   248     default:
   249     return null;
   250     }
   251   }
   253   private static long getTimeFromUri(final Uri uri) {
   254     String t = uri.getQueryParameter("time");
   255     if (t == null) {
   256       return System.currentTimeMillis();
   257     } else {
   258       return Long.parseLong(t, 10);
   259     }
   260   }
   262   private static int getEnvironmentFromUri(final Uri uri) {
   263     return Integer.parseInt(uri.getPathSegments().get(1), 10);
   264   }
   266   /**
   267    * Assumes a URI structured like:
   268    *
   269    * <code>content://org.mozilla.gecko.health/events/env/measurement/v/field</code>
   270    *
   271    * @param uri a URI formatted as expected.
   272    * @return a {@link Field} instance.
   273    */
   274   private static Field getFieldFromUri(HealthReportStorage storage, final Uri uri) {
   275     String measurement;
   276     String field;
   277     int measurementVersion;
   279     List<String> pathSegments = uri.getPathSegments();
   280     measurement = pathSegments.get(2);
   281     measurementVersion = Integer.parseInt(pathSegments.get(3), 10);
   282     field = pathSegments.get(4);
   284     return storage.getField(measurement, measurementVersion, field);
   285   }
   287   private MeasurementFields getFieldSpecs(ContentValues values) {
   288     final ArrayList<FieldSpec> specs = new ArrayList<FieldSpec>(values.size());
   289     for (Entry<String, Object> entry : values.valueSet()) {
   290       specs.add(new FieldSpec(entry.getKey(), ((Integer) entry.getValue()).intValue()));
   291     }
   293     return new MeasurementFields() {
   294       @Override
   295       public Iterable<FieldSpec> getFields() {
   296         return specs;
   297       }
   298     };
   299   }
   301 }

mercurial