2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.photos.data;
18 import android.content.ContentProvider;
19 import android.content.ContentResolver;
20 import android.content.ContentValues;
21 import android.content.UriMatcher;
22 import android.database.Cursor;
23 import android.database.DatabaseUtils;
24 import android.database.sqlite.SQLiteDatabase;
25 import android.database.sqlite.SQLiteOpenHelper;
26 import android.database.sqlite.SQLiteStatement;
27 import android.net.Uri;
28 import android.os.CancellationSignal;
29 import android.provider.BaseColumns;
31 import java.util.ArrayList;
32 import java.util.List;
35 * A provider that gives access to photo and video information for media stored
36 * on the server. Only media that is or will be put on the server will be
37 * accessed by this provider. Use Photos.CONTENT_URI to query all photos and
38 * videos. Use Albums.CONTENT_URI to query all albums. Use Metadata.CONTENT_URI
39 * to query metadata about a photo or video, based on the ID of the media. Use
40 * ImageCache.THUMBNAIL_CONTENT_URI, ImageCache.PREVIEW_CONTENT_URI, or
41 * ImageCache.ORIGINAL_CONTENT_URI to query the path of the thumbnail, preview,
42 * or original-sized image respectfully. <br/>
43 * To add or update metadata, use the update function rather than insert. All
44 * values for the metadata must be in the ContentValues, even if they are also
45 * in the selection. The selection and selectionArgs are not used when updating
46 * metadata. If the metadata values are null, the row will be deleted.
48 public class PhotoProvider extends ContentProvider {
49 @SuppressWarnings("unused")
50 private static final String TAG = PhotoProvider.class.getSimpleName();
51 static final String AUTHORITY = "com.android.gallery3d.photoprovider";
52 static final Uri BASE_CONTENT_URI = new Uri.Builder().scheme("content").authority(AUTHORITY)
56 * Contains columns that can be accessed via PHOTOS_CONTENT_URI.
58 public static interface Photos extends BaseColumns {
60 * Internal database table used for basic photo information.
62 public static final String TABLE = "photo";
64 * Content URI for basic photo and video information.
66 public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
68 * Identifier used on the server. Long value.
70 public static final String SERVER_ID = "server_id";
72 * Column name for the width of the original image. Integer value.
74 public static final String WIDTH = "width";
76 * Column name for the height of the original image. Integer value.
78 public static final String HEIGHT = "height";
80 * Column name for the date that the original image was taken. Long
81 * value indicating the milliseconds since epoch in the GMT time zone.
83 public static final String DATE_TAKEN = "date_taken";
85 * Column name indicating the long value of the album id that this image
86 * resides in. Will be NULL if it it has not been uploaded to the
89 public static final String ALBUM_ID = "album_id";
91 * The column name for the mime-type String.
93 public static final String MIME_TYPE = "mime_type";
97 * Contains columns and Uri for accessing album information.
99 public static interface Albums extends BaseColumns {
101 * Internal database table used album information.
103 public static final String TABLE = "album";
105 * Content URI for album information.
107 public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
109 * Parent directory or null if this is in the root.
111 public static final String PARENT_ID = "parent";
113 * Column name for the name of the album. String value.
115 public static final String NAME = "name";
117 * Column name for the visibility level of the album. Can be any of the
118 * VISIBILITY_* values.
120 public static final String VISIBILITY = "visibility";
122 * Column name for the server identifier for this album. NULL if the
123 * server doesn't have this album yet.
125 public static final String SERVER_ID = "server_id";
127 // Privacy values for Albums.VISIBILITY
128 public static final int VISIBILITY_PRIVATE = 1;
129 public static final int VISIBILITY_SHARED = 2;
130 public static final int VISIBILITY_PUBLIC = 3;
134 * Contains columns and Uri for accessing photo and video metadata
136 public static interface Metadata extends BaseColumns {
138 * Internal database table used metadata information.
140 public static final String TABLE = "metadata";
142 * Content URI for photo and video metadata.
144 public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
146 * Foreign key to photo_id. Long value.
148 public static final String PHOTO_ID = "photo_id";
150 * Metadata key. String value
152 public static final String KEY = "key";
154 * Metadata value. Type is based on key.
156 public static final String VALUE = "value";
160 * Contains columns and Uri for maintaining the image cache.
162 public static interface ImageCache extends BaseColumns {
164 * Internal database table used for the image cache
166 public static final String TABLE = "image_cache";
169 * The image_type query parameter required for accessing a specific
172 public static final String IMAGE_TYPE_QUERY_PARAMETER = "image_type";
174 // ImageCache.IMAGE_TYPE values
175 public static final int IMAGE_TYPE_THUMBNAIL = 1;
176 public static final int IMAGE_TYPE_PREVIEW = 2;
177 public static final int IMAGE_TYPE_ORIGINAL = 3;
180 * Content URI for retrieving image paths. The
181 * IMAGE_TYPE_QUERY_PARAMETER must be used in queries.
183 public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
186 * Foreign key to the photos._id. Long value.
188 public static final String PHOTO_ID = "photos_id";
190 * One of IMAGE_TYPE_* values.
192 public static final String IMAGE_TYPE = "image_type";
194 * The String path to the image.
196 public static final String PATH = "path";
199 // SQL used within this class.
200 protected static final String WHERE_ID = BaseColumns._ID + " = ?";
201 protected static final String WHERE_METADATA_ID = Metadata.PHOTO_ID + " = ? AND "
202 + Metadata.KEY + " = ?";
204 protected static final String SELECT_ALBUM_ID = "SELECT " + Albums._ID + " FROM "
206 protected static final String SELECT_PHOTO_ID = "SELECT " + Photos._ID + " FROM "
208 protected static final String SELECT_PHOTO_COUNT = "SELECT COUNT(*) FROM " + Photos.TABLE;
209 protected static final String DELETE_PHOTOS = "DELETE FROM " + Photos.TABLE;
210 protected static final String DELETE_METADATA = "DELETE FROM " + Metadata.TABLE;
211 protected static final String SELECT_METADATA_COUNT = "SELECT COUNT(*) FROM " + Metadata.TABLE;
212 protected static final String WHERE = " WHERE ";
213 protected static final String IN = " IN ";
214 protected static final String NESTED_SELECT_START = "(";
215 protected static final String NESTED_SELECT_END = ")";
218 * For selecting the mime-type for an image.
220 private static final String[] PROJECTION_MIME_TYPE = {
224 private SQLiteOpenHelper mOpenHelper;
225 protected static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
227 protected static final int MATCH_PHOTO = 1;
228 protected static final int MATCH_PHOTO_ID = 2;
229 protected static final int MATCH_ALBUM = 3;
230 protected static final int MATCH_ALBUM_ID = 4;
231 protected static final int MATCH_METADATA = 5;
232 protected static final int MATCH_METADATA_ID = 6;
233 protected static final int MATCH_IMAGE = 7;
236 sUriMatcher.addURI(AUTHORITY, Photos.TABLE, MATCH_PHOTO);
237 // match against Photos._ID
238 sUriMatcher.addURI(AUTHORITY, Photos.TABLE + "/#", MATCH_PHOTO_ID);
239 sUriMatcher.addURI(AUTHORITY, Albums.TABLE, MATCH_ALBUM);
240 // match against Albums._ID
241 sUriMatcher.addURI(AUTHORITY, Albums.TABLE + "/#", MATCH_ALBUM_ID);
242 sUriMatcher.addURI(AUTHORITY, Metadata.TABLE, MATCH_METADATA);
243 // match against metadata/<Metadata._ID>
244 sUriMatcher.addURI(AUTHORITY, Metadata.TABLE + "/#", MATCH_METADATA_ID);
245 // match against image_cache/<ImageCache.PHOTO_ID>
246 sUriMatcher.addURI(AUTHORITY, ImageCache.TABLE + "/#", MATCH_IMAGE);
250 public int delete(Uri uri, String selection, String[] selectionArgs) {
251 int match = matchUri(uri);
252 if (match == MATCH_IMAGE) {
253 throw new IllegalArgumentException("Cannot delete from image cache");
255 selection = addIdToSelection(match, selection);
256 selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
257 List<Uri> changeUris = new ArrayList<Uri>();
259 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
260 db.beginTransaction();
262 deleted = deleteCascade(db, match, selection, selectionArgs, changeUris, uri);
263 db.setTransactionSuccessful();
267 for (Uri changeUri : changeUris) {
268 notifyChanges(changeUri);
274 public String getType(Uri uri) {
275 Cursor cursor = query(uri, PROJECTION_MIME_TYPE, null, null, null);
276 String mimeType = null;
277 if (cursor.moveToNext()) {
278 mimeType = cursor.getString(0);
285 public Uri insert(Uri uri, ContentValues values) {
286 // Cannot insert into this ContentProvider
291 public boolean onCreate() {
292 mOpenHelper = createDatabaseHelper();
297 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
299 return query(uri, projection, selection, selectionArgs, sortOrder, null);
303 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
304 String sortOrder, CancellationSignal cancellationSignal) {
305 int match = matchUri(uri);
306 selection = addIdToSelection(match, selection);
307 selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
308 String table = getTableFromMatch(match, uri);
309 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
310 return db.query(false, table, projection, selection, selectionArgs, null, null, sortOrder,
311 null, cancellationSignal);
315 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
316 int match = matchUri(uri);
318 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
319 db.beginTransaction();
321 if (match == MATCH_METADATA) {
322 rowsUpdated = modifyMetadata(db, values);
324 selection = addIdToSelection(match, selection);
325 selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
326 String table = getTableFromMatch(match, uri);
327 rowsUpdated = db.update(table, values, selection, selectionArgs);
329 db.setTransactionSuccessful();
337 protected static String addIdToSelection(int match, String selection) {
342 case MATCH_METADATA_ID:
348 return DatabaseUtils.concatenateWhere(selection, where);
351 protected static String[] addIdToSelectionArgs(int match, Uri uri, String[] selectionArgs) {
356 case MATCH_METADATA_ID:
357 whereArgs = new String[] {
358 uri.getPathSegments().get(1),
362 return selectionArgs;
364 return DatabaseUtils.appendSelectionArgs(selectionArgs, whereArgs);
367 protected static String[] addMetadataKeysToSelectionArgs(String[] selectionArgs, Uri uri) {
368 List<String> segments = uri.getPathSegments();
369 String[] additionalArgs = {
374 return DatabaseUtils.appendSelectionArgs(selectionArgs, additionalArgs);
377 protected static String getTableFromMatch(int match, Uri uri) {
382 table = Photos.TABLE;
386 table = Albums.TABLE;
389 case MATCH_METADATA_ID:
390 table = Metadata.TABLE;
393 throw unknownUri(uri);
398 protected final SQLiteOpenHelper getDatabaseHelper() {
402 protected SQLiteOpenHelper createDatabaseHelper() {
403 return new PhotoDatabase(getContext());
406 private int modifyMetadata(SQLiteDatabase db, ContentValues values) {
407 String[] selectionArgs = {
408 values.getAsString(Metadata.PHOTO_ID),
409 values.getAsString(Metadata.KEY),
412 if (values.get(Metadata.VALUE) == null) {
413 rowCount = db.delete(Metadata.TABLE, WHERE_METADATA_ID, selectionArgs);
415 rowCount = (int) DatabaseUtils.queryNumEntries(db, Metadata.TABLE, WHERE_METADATA_ID,
418 db.update(Metadata.TABLE, values, WHERE_METADATA_ID, selectionArgs);
420 db.insert(Metadata.TABLE, null, values);
427 private int matchUri(Uri uri) {
428 int match = sUriMatcher.match(uri);
429 if (match == UriMatcher.NO_MATCH) {
430 throw unknownUri(uri);
435 protected void notifyChanges(Uri uri) {
436 ContentResolver resolver = getContext().getContentResolver();
437 resolver.notifyChange(uri, null, false);
440 protected static IllegalArgumentException unknownUri(Uri uri) {
441 return new IllegalArgumentException("Unknown Uri format: " + uri);
444 protected static String nestSql(String base, String columnMatch, String nested) {
445 StringBuilder sql = new StringBuilder(base);
447 sql.append(columnMatch);
449 sql.append(NESTED_SELECT_START);
451 sql.append(NESTED_SELECT_END);
452 return sql.toString();
455 protected static String addWhere(String base, String where) {
456 if (where == null || where.isEmpty()) {
459 return base + WHERE + where;
462 protected static int deleteCascade(SQLiteDatabase db, int match, String selection,
463 String[] selectionArgs, List<Uri> changeUris, Uri uri) {
466 case MATCH_PHOTO_ID: {
467 String selectPhotoIdsSql = addWhere(SELECT_PHOTO_ID, selection);
468 deleteCascadeMetadata(db, selectPhotoIdsSql, selectionArgs, changeUris);
472 case MATCH_ALBUM_ID: {
473 String selectAlbumIdSql = addWhere(SELECT_ALBUM_ID, selection);
474 deleteCascadePhotos(db, selectAlbumIdSql, selectionArgs, changeUris);
478 String table = getTableFromMatch(match, uri);
480 return db.delete(table, selection, selectionArgs);
483 protected static void execSql(SQLiteDatabase db, String sql, String[] args) {
487 db.execSQL(sql, args);
491 private static void deleteCascadePhotos(SQLiteDatabase db, String albumSelect,
492 String[] selectArgs, List<Uri> changeUris) {
493 String selectPhotoIdSql = nestSql(SELECT_PHOTO_ID, Photos.ALBUM_ID, albumSelect);
494 deleteCascadeMetadata(db, selectPhotoIdSql, selectArgs, changeUris);
495 String deletePhotoSql = nestSql(DELETE_PHOTOS, Photos.ALBUM_ID, albumSelect);
496 SQLiteStatement statement = db.compileStatement(deletePhotoSql);
497 statement.bindAllArgsAsStrings(selectArgs);
498 int deleted = statement.executeUpdateDelete();
500 changeUris.add(Photos.CONTENT_URI);
504 private static void deleteCascadeMetadata(SQLiteDatabase db, String photosSelect,
505 String[] selectArgs, List<Uri> changeUris) {
506 String deleteMetadataSql = nestSql(DELETE_METADATA, Metadata.PHOTO_ID, photosSelect);
507 SQLiteStatement statement = db.compileStatement(deleteMetadataSql);
508 statement.bindAllArgsAsStrings(selectArgs);
509 int deleted = statement.executeUpdateDelete();
511 changeUris.add(Metadata.CONTENT_URI);