Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      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
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     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.
     15  */
     16 package com.android.photos.data;
     17 
     18 import android.content.ContentResolver;
     19 import android.content.ContentUris;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.content.UriMatcher;
     23 import android.database.Cursor;
     24 import android.database.DatabaseUtils;
     25 import android.database.sqlite.SQLiteDatabase;
     26 import android.database.sqlite.SQLiteOpenHelper;
     27 import android.database.sqlite.SQLiteQueryBuilder;
     28 import android.media.ExifInterface;
     29 import android.net.Uri;
     30 import android.os.CancellationSignal;
     31 import android.provider.BaseColumns;
     32 
     33 import com.android.gallery3d.common.ApiHelper;
     34 
     35 import java.util.List;
     36 
     37 /**
     38  * A provider that gives access to photo and video information for media stored
     39  * on the server. Only media that is or will be put on the server will be
     40  * accessed by this provider. Use Photos.CONTENT_URI to query all photos and
     41  * videos. Use Albums.CONTENT_URI to query all albums. Use Metadata.CONTENT_URI
     42  * to query metadata about a photo or video, based on the ID of the media. Use
     43  * ImageCache.THUMBNAIL_CONTENT_URI, ImageCache.PREVIEW_CONTENT_URI, or
     44  * ImageCache.ORIGINAL_CONTENT_URI to query the path of the thumbnail, preview,
     45  * or original-sized image respectfully. <br/>
     46  * To add or update metadata, use the update function rather than insert. All
     47  * values for the metadata must be in the ContentValues, even if they are also
     48  * in the selection. The selection and selectionArgs are not used when updating
     49  * metadata. If the metadata values are null, the row will be deleted.
     50  */
     51 public class PhotoProvider extends SQLiteContentProvider {
     52     @SuppressWarnings("unused")
     53     private static final String TAG = PhotoProvider.class.getSimpleName();
     54 
     55     protected static final String DB_NAME = "photo.db";
     56     public static final String AUTHORITY = PhotoProviderAuthority.AUTHORITY;
     57     static final Uri BASE_CONTENT_URI = new Uri.Builder().scheme("content").authority(AUTHORITY)
     58             .build();
     59 
     60     // Used to allow mocking out the change notification because
     61     // MockContextResolver disallows system-wide notification.
     62     public static interface ChangeNotification {
     63         void notifyChange(Uri uri, boolean syncToNetwork);
     64     }
     65 
     66     /**
     67      * Contains columns that can be accessed via Accounts.CONTENT_URI
     68      */
     69     public static interface Accounts extends BaseColumns {
     70         /**
     71          * Internal database table used for account information
     72          */
     73         public static final String TABLE = "accounts";
     74         /**
     75          * Content URI for account information
     76          */
     77         public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
     78         /**
     79          * User name for this account.
     80          */
     81         public static final String ACCOUNT_NAME = "name";
     82     }
     83 
     84     /**
     85      * Contains columns that can be accessed via Photos.CONTENT_URI.
     86      */
     87     public static interface Photos extends BaseColumns {
     88         /**
     89          * The image_type query parameter required for requesting a specific
     90          * size of image.
     91          */
     92         public static final String MEDIA_SIZE_QUERY_PARAMETER = "media_size";
     93 
     94         /** Internal database table used for basic photo information. */
     95         public static final String TABLE = "photos";
     96         /** Content URI for basic photo and video information. */
     97         public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
     98 
     99         /** Long foreign key to Accounts._ID */
    100         public static final String ACCOUNT_ID = "account_id";
    101         /** Column name for the width of the original image. Integer value. */
    102         public static final String WIDTH = "width";
    103         /** Column name for the height of the original image. Integer value. */
    104         public static final String HEIGHT = "height";
    105         /**
    106          * Column name for the date that the original image was taken. Long
    107          * value indicating the milliseconds since epoch in the GMT time zone.
    108          */
    109         public static final String DATE_TAKEN = "date_taken";
    110         /**
    111          * Column name indicating the long value of the album id that this image
    112          * resides in. Will be NULL if it it has not been uploaded to the
    113          * server.
    114          */
    115         public static final String ALBUM_ID = "album_id";
    116         /** The column name for the mime-type String. */
    117         public static final String MIME_TYPE = "mime_type";
    118         /** The title of the photo. String value. */
    119         public static final String TITLE = "title";
    120         /** The date the photo entry was last updated. Long value. */
    121         public static final String DATE_MODIFIED = "date_modified";
    122         /**
    123          * The rotation of the photo in degrees, if rotation has not already
    124          * been applied. Integer value.
    125          */
    126         public static final String ROTATION = "rotation";
    127     }
    128 
    129     /**
    130      * Contains columns and Uri for accessing album information.
    131      */
    132     public static interface Albums extends BaseColumns {
    133         /** Internal database table used album information. */
    134         public static final String TABLE = "albums";
    135         /** Content URI for album information. */
    136         public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
    137 
    138         /** Long foreign key to Accounts._ID */
    139         public static final String ACCOUNT_ID = "account_id";
    140         /** Parent directory or null if this is in the root. */
    141         public static final String PARENT_ID = "parent_id";
    142         /** The type of album. Non-null, if album is auto-generated. String value. */
    143         public static final String ALBUM_TYPE = "album_type";
    144         /**
    145          * Column name for the visibility level of the album. Can be any of the
    146          * VISIBILITY_* values.
    147          */
    148         public static final String VISIBILITY = "visibility";
    149         /** The user-specified location associated with the album. String value. */
    150         public static final String LOCATION_STRING = "location_string";
    151         /** The title of the album. String value. */
    152         public static final String TITLE = "title";
    153         /** A short summary of the contents of the album. String value. */
    154         public static final String SUMMARY = "summary";
    155         /** The date the album was created. Long value */
    156         public static final String DATE_PUBLISHED = "date_published";
    157         /** The date the album entry was last updated. Long value. */
    158         public static final String DATE_MODIFIED = "date_modified";
    159 
    160         // Privacy values for Albums.VISIBILITY
    161         public static final int VISIBILITY_PRIVATE = 1;
    162         public static final int VISIBILITY_SHARED = 2;
    163         public static final int VISIBILITY_PUBLIC = 3;
    164     }
    165 
    166     /**
    167      * Contains columns and Uri for accessing photo and video metadata
    168      */
    169     public static interface Metadata extends BaseColumns {
    170         /** Internal database table used metadata information. */
    171         public static final String TABLE = "metadata";
    172         /** Content URI for photo and video metadata. */
    173         public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
    174         /** Foreign key to photo_id. Long value. */
    175         public static final String PHOTO_ID = "photo_id";
    176         /** Metadata key. String value */
    177         public static final String KEY = "key";
    178         /**
    179          * Metadata value. Type is based on key.
    180          */
    181         public static final String VALUE = "value";
    182 
    183         /** A short summary of the photo. String value. */
    184         public static final String KEY_SUMMARY = "summary";
    185         /** The date the photo was added. Long value. */
    186         public static final String KEY_PUBLISHED = "date_published";
    187         /** The date the photo was last updated. Long value. */
    188         public static final String KEY_DATE_UPDATED = "date_updated";
    189         /** The size of the photo is bytes. Integer value. */
    190         public static final String KEY_SIZE_IN_BTYES = "size";
    191         /** The latitude associated with the photo. Double value. */
    192         public static final String KEY_LATITUDE = "latitude";
    193         /** The longitude associated with the photo. Double value. */
    194         public static final String KEY_LONGITUDE = "longitude";
    195 
    196         /** The make of the camera used. String value. */
    197         public static final String KEY_EXIF_MAKE = ExifInterface.TAG_MAKE;
    198         /** The model of the camera used. String value. */
    199         public static final String KEY_EXIF_MODEL = ExifInterface.TAG_MODEL;;
    200         /** The exposure time used. Float value. */
    201         public static final String KEY_EXIF_EXPOSURE = ExifInterface.TAG_EXPOSURE_TIME;
    202         /** Whether the flash was used. Boolean value. */
    203         public static final String KEY_EXIF_FLASH = ExifInterface.TAG_FLASH;
    204         /** The focal length used. Float value. */
    205         public static final String KEY_EXIF_FOCAL_LENGTH = ExifInterface.TAG_FOCAL_LENGTH;
    206         /** The fstop value used. Float value. */
    207         public static final String KEY_EXIF_FSTOP = ExifInterface.TAG_APERTURE;
    208         /** The ISO equivalent value used. Integer value. */
    209         public static final String KEY_EXIF_ISO = ExifInterface.TAG_ISO;
    210     }
    211 
    212     // SQL used within this class.
    213     protected static final String WHERE_ID = BaseColumns._ID + " = ?";
    214     protected static final String WHERE_METADATA_ID = Metadata.PHOTO_ID + " = ? AND "
    215             + Metadata.KEY + " = ?";
    216 
    217     protected static final String SELECT_ALBUM_ID = "SELECT " + Albums._ID + " FROM "
    218             + Albums.TABLE;
    219     protected static final String SELECT_PHOTO_ID = "SELECT " + Photos._ID + " FROM "
    220             + Photos.TABLE;
    221     protected static final String SELECT_PHOTO_COUNT = "SELECT COUNT(*) FROM " + Photos.TABLE;
    222     protected static final String DELETE_PHOTOS = "DELETE FROM " + Photos.TABLE;
    223     protected static final String DELETE_METADATA = "DELETE FROM " + Metadata.TABLE;
    224     protected static final String SELECT_METADATA_COUNT = "SELECT COUNT(*) FROM " + Metadata.TABLE;
    225     protected static final String WHERE = " WHERE ";
    226     protected static final String IN = " IN ";
    227     protected static final String NESTED_SELECT_START = "(";
    228     protected static final String NESTED_SELECT_END = ")";
    229     protected static final String[] PROJECTION_COUNT = {
    230         "COUNT(*)"
    231     };
    232 
    233     /**
    234      * For selecting the mime-type for an image.
    235      */
    236     private static final String[] PROJECTION_MIME_TYPE = {
    237         Photos.MIME_TYPE,
    238     };
    239 
    240     protected static final String[] BASE_COLUMNS_ID = {
    241         BaseColumns._ID,
    242     };
    243 
    244     protected ChangeNotification mNotifier = null;
    245     protected static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    246 
    247     protected static final int MATCH_PHOTO = 1;
    248     protected static final int MATCH_PHOTO_ID = 2;
    249     protected static final int MATCH_ALBUM = 3;
    250     protected static final int MATCH_ALBUM_ID = 4;
    251     protected static final int MATCH_METADATA = 5;
    252     protected static final int MATCH_METADATA_ID = 6;
    253     protected static final int MATCH_ACCOUNT = 7;
    254     protected static final int MATCH_ACCOUNT_ID = 8;
    255 
    256     static {
    257         sUriMatcher.addURI(AUTHORITY, Photos.TABLE, MATCH_PHOTO);
    258         // match against Photos._ID
    259         sUriMatcher.addURI(AUTHORITY, Photos.TABLE + "/#", MATCH_PHOTO_ID);
    260         sUriMatcher.addURI(AUTHORITY, Albums.TABLE, MATCH_ALBUM);
    261         // match against Albums._ID
    262         sUriMatcher.addURI(AUTHORITY, Albums.TABLE + "/#", MATCH_ALBUM_ID);
    263         sUriMatcher.addURI(AUTHORITY, Metadata.TABLE, MATCH_METADATA);
    264         // match against metadata/<Metadata._ID>
    265         sUriMatcher.addURI(AUTHORITY, Metadata.TABLE + "/#", MATCH_METADATA_ID);
    266         sUriMatcher.addURI(AUTHORITY, Accounts.TABLE, MATCH_ACCOUNT);
    267         // match against Accounts._ID
    268         sUriMatcher.addURI(AUTHORITY, Accounts.TABLE + "/#", MATCH_ACCOUNT_ID);
    269     }
    270 
    271     @Override
    272     public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
    273             boolean callerIsSyncAdapter) {
    274         int match = matchUri(uri);
    275         selection = addIdToSelection(match, selection);
    276         selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
    277         return deleteCascade(uri, match, selection, selectionArgs);
    278     }
    279 
    280     @Override
    281     public String getType(Uri uri) {
    282         Cursor cursor = query(uri, PROJECTION_MIME_TYPE, null, null, null);
    283         String mimeType = null;
    284         if (cursor.moveToNext()) {
    285             mimeType = cursor.getString(0);
    286         }
    287         cursor.close();
    288         return mimeType;
    289     }
    290 
    291     @Override
    292     public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
    293         int match = matchUri(uri);
    294         validateMatchTable(match);
    295         String table = getTableFromMatch(match, uri);
    296         SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
    297         Uri insertedUri = null;
    298         long id = db.insert(table, null, values);
    299         if (id != -1) {
    300             // uri already matches the table.
    301             insertedUri = ContentUris.withAppendedId(uri, id);
    302             postNotifyUri(insertedUri);
    303         }
    304         return insertedUri;
    305     }
    306 
    307     @Override
    308     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    309             String sortOrder) {
    310         return query(uri, projection, selection, selectionArgs, sortOrder, null);
    311     }
    312 
    313     @Override
    314     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    315             String sortOrder, CancellationSignal cancellationSignal) {
    316         projection = replaceCount(projection);
    317         int match = matchUri(uri);
    318         selection = addIdToSelection(match, selection);
    319         selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
    320         String table = getTableFromMatch(match, uri);
    321         Cursor c = query(table, projection, selection, selectionArgs, sortOrder, cancellationSignal);
    322         if (c != null) {
    323             c.setNotificationUri(getContext().getContentResolver(), uri);
    324         }
    325         return c;
    326     }
    327 
    328     @Override
    329     public int updateInTransaction(Uri uri, ContentValues values, String selection,
    330             String[] selectionArgs, boolean callerIsSyncAdapter) {
    331         int match = matchUri(uri);
    332         int rowsUpdated = 0;
    333         SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
    334         if (match == MATCH_METADATA) {
    335             rowsUpdated = modifyMetadata(db, values);
    336         } else {
    337             selection = addIdToSelection(match, selection);
    338             selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
    339             String table = getTableFromMatch(match, uri);
    340             rowsUpdated = db.update(table, values, selection, selectionArgs);
    341         }
    342         postNotifyUri(uri);
    343         return rowsUpdated;
    344     }
    345 
    346     public void setMockNotification(ChangeNotification notification) {
    347         mNotifier = notification;
    348     }
    349 
    350     protected static String addIdToSelection(int match, String selection) {
    351         String where;
    352         switch (match) {
    353             case MATCH_PHOTO_ID:
    354             case MATCH_ALBUM_ID:
    355             case MATCH_METADATA_ID:
    356                 where = WHERE_ID;
    357                 break;
    358             default:
    359                 return selection;
    360         }
    361         return DatabaseUtils.concatenateWhere(selection, where);
    362     }
    363 
    364     protected static String[] addIdToSelectionArgs(int match, Uri uri, String[] selectionArgs) {
    365         String[] whereArgs;
    366         switch (match) {
    367             case MATCH_PHOTO_ID:
    368             case MATCH_ALBUM_ID:
    369             case MATCH_METADATA_ID:
    370                 whereArgs = new String[] {
    371                     uri.getPathSegments().get(1),
    372                 };
    373                 break;
    374             default:
    375                 return selectionArgs;
    376         }
    377         return DatabaseUtils.appendSelectionArgs(selectionArgs, whereArgs);
    378     }
    379 
    380     protected static String[] addMetadataKeysToSelectionArgs(String[] selectionArgs, Uri uri) {
    381         List<String> segments = uri.getPathSegments();
    382         String[] additionalArgs = {
    383                 segments.get(1),
    384                 segments.get(2),
    385         };
    386 
    387         return DatabaseUtils.appendSelectionArgs(selectionArgs, additionalArgs);
    388     }
    389 
    390     protected static String getTableFromMatch(int match, Uri uri) {
    391         String table;
    392         switch (match) {
    393             case MATCH_PHOTO:
    394             case MATCH_PHOTO_ID:
    395                 table = Photos.TABLE;
    396                 break;
    397             case MATCH_ALBUM:
    398             case MATCH_ALBUM_ID:
    399                 table = Albums.TABLE;
    400                 break;
    401             case MATCH_METADATA:
    402             case MATCH_METADATA_ID:
    403                 table = Metadata.TABLE;
    404                 break;
    405             case MATCH_ACCOUNT:
    406             case MATCH_ACCOUNT_ID:
    407                 table = Accounts.TABLE;
    408                 break;
    409             default:
    410                 throw unknownUri(uri);
    411         }
    412         return table;
    413     }
    414 
    415     @Override
    416     public SQLiteOpenHelper getDatabaseHelper(Context context) {
    417         return new PhotoDatabase(context, DB_NAME);
    418     }
    419 
    420     private int modifyMetadata(SQLiteDatabase db, ContentValues values) {
    421         int rowCount;
    422         if (values.get(Metadata.VALUE) == null) {
    423             String[] selectionArgs = {
    424                     values.getAsString(Metadata.PHOTO_ID), values.getAsString(Metadata.KEY),
    425             };
    426             rowCount = db.delete(Metadata.TABLE, WHERE_METADATA_ID, selectionArgs);
    427         } else {
    428             long rowId = db.replace(Metadata.TABLE, null, values);
    429             rowCount = (rowId == -1) ? 0 : 1;
    430         }
    431         return rowCount;
    432     }
    433 
    434     private int matchUri(Uri uri) {
    435         int match = sUriMatcher.match(uri);
    436         if (match == UriMatcher.NO_MATCH) {
    437             throw unknownUri(uri);
    438         }
    439         return match;
    440     }
    441 
    442     @Override
    443     protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) {
    444         if (mNotifier != null) {
    445             mNotifier.notifyChange(uri, syncToNetwork);
    446         } else {
    447             super.notifyChange(resolver, uri, syncToNetwork);
    448         }
    449     }
    450 
    451     protected static IllegalArgumentException unknownUri(Uri uri) {
    452         return new IllegalArgumentException("Unknown Uri format: " + uri);
    453     }
    454 
    455     protected static String nestWhere(String matchColumn, String table, String nestedWhere) {
    456         String query = SQLiteQueryBuilder.buildQueryString(false, table, BASE_COLUMNS_ID,
    457                 nestedWhere, null, null, null, null);
    458         return matchColumn + IN + NESTED_SELECT_START + query + NESTED_SELECT_END;
    459     }
    460 
    461     protected static String metadataSelectionFromPhotos(String where) {
    462         return nestWhere(Metadata.PHOTO_ID, Photos.TABLE, where);
    463     }
    464 
    465     protected static String photoSelectionFromAlbums(String where) {
    466         return nestWhere(Photos.ALBUM_ID, Albums.TABLE, where);
    467     }
    468 
    469     protected static String photoSelectionFromAccounts(String where) {
    470         return nestWhere(Photos.ACCOUNT_ID, Accounts.TABLE, where);
    471     }
    472 
    473     protected static String albumSelectionFromAccounts(String where) {
    474         return nestWhere(Albums.ACCOUNT_ID, Accounts.TABLE, where);
    475     }
    476 
    477     protected int deleteCascade(Uri uri, int match, String selection, String[] selectionArgs) {
    478         switch (match) {
    479             case MATCH_PHOTO:
    480             case MATCH_PHOTO_ID:
    481                 deleteCascade(Metadata.CONTENT_URI, MATCH_METADATA,
    482                         metadataSelectionFromPhotos(selection), selectionArgs);
    483                 break;
    484             case MATCH_ALBUM:
    485             case MATCH_ALBUM_ID:
    486                 deleteCascade(Photos.CONTENT_URI, MATCH_PHOTO,
    487                         photoSelectionFromAlbums(selection), selectionArgs);
    488                 break;
    489             case MATCH_ACCOUNT:
    490             case MATCH_ACCOUNT_ID:
    491                 deleteCascade(Photos.CONTENT_URI, MATCH_PHOTO,
    492                         photoSelectionFromAccounts(selection), selectionArgs);
    493                 deleteCascade(Albums.CONTENT_URI, MATCH_ALBUM,
    494                         albumSelectionFromAccounts(selection), selectionArgs);
    495                 break;
    496         }
    497         SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
    498         String table = getTableFromMatch(match, uri);
    499         int deleted = db.delete(table, selection, selectionArgs);
    500         if (deleted > 0) {
    501             postNotifyUri(uri);
    502         }
    503         return deleted;
    504     }
    505 
    506     private static void validateMatchTable(int match) {
    507         switch (match) {
    508             case MATCH_PHOTO:
    509             case MATCH_ALBUM:
    510             case MATCH_METADATA:
    511             case MATCH_ACCOUNT:
    512                 break;
    513             default:
    514                 throw new IllegalArgumentException("Operation not allowed on an existing row.");
    515         }
    516     }
    517 
    518     protected Cursor query(String table, String[] columns, String selection,
    519             String[] selectionArgs, String orderBy, CancellationSignal cancellationSignal) {
    520         SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
    521         if (ApiHelper.HAS_CANCELLATION_SIGNAL) {
    522             return db.query(false, table, columns, selection, selectionArgs, null, null,
    523                     orderBy, null, cancellationSignal);
    524         } else {
    525             return db.query(table, columns, selection, selectionArgs, null, null, orderBy);
    526         }
    527     }
    528 
    529     protected static String[] replaceCount(String[] projection) {
    530         if (projection != null && projection.length == 1
    531                 && BaseColumns._COUNT.equals(projection[0])) {
    532             return PROJECTION_COUNT;
    533         }
    534         return projection;
    535     }
    536 }
    537