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

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial