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

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:9527eea90391
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/. */
4
5 package org.mozilla.gecko.background.healthreport;
6
7 import java.io.File;
8 import java.util.ArrayList;
9 import java.util.List;
10 import java.util.Map.Entry;
11
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;
16
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;
23
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);
53
54 public static final String HEALTH_AUTHORITY = HealthReportConstants.HEALTH_AUTHORITY;
55
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;
62
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;
66
67 private static final int ENVIRONMENT_DETAILS = 30;
68 private static final int FIELDS_MEASUREMENT = 31;
69
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);
76
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);
80
81 uriMatcher.addURI(HEALTH_AUTHORITY, "environments/#", ENVIRONMENT_DETAILS);
82 uriMatcher.addURI(HEALTH_AUTHORITY, "fields/*/#", FIELDS_MEASUREMENT);
83 }
84
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 }
94
95 private HealthReportDatabaseStorage getProfileStorageForUri(Uri uri) {
96 final String profilePath = uri.getQueryParameter("profilePath");
97 return getProfileStorage(profilePath);
98 }
99
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 }
108
109 @Override
110 public String getType(Uri uri) {
111 return null;
112 }
113
114 @Override
115 public boolean onCreate() {
116 databases = new HealthReportDatabases(getContext());
117 return true;
118 }
119
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;
132
133 case ENVIRONMENTS_ROOT:
134 DatabaseEnvironment environment = storage.getEnvironment();
135 environment.init(values);
136 return ContentUris.withAppendedId(uri, environment.register());
137
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);
143
144 if (!values.containsKey("value")) {
145 throw new IllegalArgumentException("Must provide ContentValues including 'value' key.");
146 }
147
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 }
157
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 }
165
166 @Override
167 public int update(Uri uri, ContentValues values, String selection,
168 String[] selectionArgs) {
169
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 }
175
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);
181
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;
187
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;
199
200 default:
201 // javac's flow control analysis sucks.
202 return 0;
203 }
204 }
205
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 }
220
221 // TODO: more
222 }
223
224 @Override
225 public Cursor query(Uri uri, String[] projection, String selection,
226 String[] selectionArgs, String sortOrder) {
227 int match = uriMatcher.match(uri);
228
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 }
252
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 }
261
262 private static int getEnvironmentFromUri(final Uri uri) {
263 return Integer.parseInt(uri.getPathSegments().get(1), 10);
264 }
265
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;
278
279 List<String> pathSegments = uri.getPathSegments();
280 measurement = pathSegments.get(2);
281 measurementVersion = Integer.parseInt(pathSegments.get(3), 10);
282 field = pathSegments.get(4);
283
284 return storage.getField(measurement, measurementVersion, field);
285 }
286
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 }
292
293 return new MeasurementFields() {
294 @Override
295 public Iterable<FieldSpec> getFields() {
296 return specs;
297 }
298 };
299 }
300
301 }

mercurial