OSDN Git Service

Import translations. DO NOT MERGE
[android-x86/packages-apps-Gallery2.git] / src / com / android / photos / data / PhotoProvider.java
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.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;
30
31 import java.util.ArrayList;
32 import java.util.List;
33
34 /**
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.
47  */
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)
53             .build();
54
55     /**
56      * Contains columns that can be accessed via PHOTOS_CONTENT_URI.
57      */
58     public static interface Photos extends BaseColumns {
59         /**
60          * Internal database table used for basic photo information.
61          */
62         public static final String TABLE = "photo";
63         /**
64          * Content URI for basic photo and video information.
65          */
66         public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
67         /**
68          * Identifier used on the server. Long value.
69          */
70         public static final String SERVER_ID = "server_id";
71         /**
72          * Column name for the width of the original image. Integer value.
73          */
74         public static final String WIDTH = "width";
75         /**
76          * Column name for the height of the original image. Integer value.
77          */
78         public static final String HEIGHT = "height";
79         /**
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.
82          */
83         public static final String DATE_TAKEN = "date_taken";
84         /**
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
87          * server.
88          */
89         public static final String ALBUM_ID = "album_id";
90         /**
91          * The column name for the mime-type String.
92          */
93         public static final String MIME_TYPE = "mime_type";
94     }
95
96     /**
97      * Contains columns and Uri for accessing album information.
98      */
99     public static interface Albums extends BaseColumns {
100         /**
101          * Internal database table used album information.
102          */
103         public static final String TABLE = "album";
104         /**
105          * Content URI for album information.
106          */
107         public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
108         /**
109          * Parent directory or null if this is in the root.
110          */
111         public static final String PARENT_ID = "parent";
112         /**
113          * Column name for the name of the album. String value.
114          */
115         public static final String NAME = "name";
116         /**
117          * Column name for the visibility level of the album. Can be any of the
118          * VISIBILITY_* values.
119          */
120         public static final String VISIBILITY = "visibility";
121         /**
122          * Column name for the server identifier for this album. NULL if the
123          * server doesn't have this album yet.
124          */
125         public static final String SERVER_ID = "server_id";
126
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;
131     }
132
133     /**
134      * Contains columns and Uri for accessing photo and video metadata
135      */
136     public static interface Metadata extends BaseColumns {
137         /**
138          * Internal database table used metadata information.
139          */
140         public static final String TABLE = "metadata";
141         /**
142          * Content URI for photo and video metadata.
143          */
144         public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
145         /**
146          * Foreign key to photo_id. Long value.
147          */
148         public static final String PHOTO_ID = "photo_id";
149         /**
150          * Metadata key. String value
151          */
152         public static final String KEY = "key";
153         /**
154          * Metadata value. Type is based on key.
155          */
156         public static final String VALUE = "value";
157     }
158
159     /**
160      * Contains columns and Uri for maintaining the image cache.
161      */
162     public static interface ImageCache extends BaseColumns {
163         /**
164          * Internal database table used for the image cache
165          */
166         public static final String TABLE = "image_cache";
167
168         /**
169          * The image_type query parameter required for accessing a specific
170          * image
171          */
172         public static final String IMAGE_TYPE_QUERY_PARAMETER = "image_type";
173
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;
178
179         /**
180          * Content URI for retrieving image paths. The
181          * IMAGE_TYPE_QUERY_PARAMETER must be used in queries.
182          */
183         public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
184
185         /**
186          * Foreign key to the photos._id. Long value.
187          */
188         public static final String PHOTO_ID = "photos_id";
189         /**
190          * One of IMAGE_TYPE_* values.
191          */
192         public static final String IMAGE_TYPE = "image_type";
193         /**
194          * The String path to the image.
195          */
196         public static final String PATH = "path";
197     };
198
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 + " = ?";
203
204     protected static final String SELECT_ALBUM_ID = "SELECT " + Albums._ID + " FROM "
205             + Albums.TABLE;
206     protected static final String SELECT_PHOTO_ID = "SELECT " + Photos._ID + " FROM "
207             + Photos.TABLE;
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 = ")";
216
217     /**
218      * For selecting the mime-type for an image.
219      */
220     private static final String[] PROJECTION_MIME_TYPE = {
221         Photos.MIME_TYPE,
222     };
223
224     private SQLiteOpenHelper mOpenHelper;
225     protected static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
226
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;
234
235     static {
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);
247     }
248
249     @Override
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");
254         }
255         selection = addIdToSelection(match, selection);
256         selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
257         List<Uri> changeUris = new ArrayList<Uri>();
258         int deleted = 0;
259         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
260         db.beginTransaction();
261         try {
262             deleted = deleteCascade(db, match, selection, selectionArgs, changeUris, uri);
263             db.setTransactionSuccessful();
264         } finally {
265             db.endTransaction();
266         }
267         for (Uri changeUri : changeUris) {
268             notifyChanges(changeUri);
269         }
270         return deleted;
271     }
272
273     @Override
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);
279         }
280         cursor.close();
281         return mimeType;
282     }
283
284     @Override
285     public Uri insert(Uri uri, ContentValues values) {
286         // Cannot insert into this ContentProvider
287         return null;
288     }
289
290     @Override
291     public boolean onCreate() {
292         mOpenHelper = createDatabaseHelper();
293         return true;
294     }
295
296     @Override
297     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
298             String sortOrder) {
299         return query(uri, projection, selection, selectionArgs, sortOrder, null);
300     }
301
302     @Override
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);
312     }
313
314     @Override
315     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
316         int match = matchUri(uri);
317         int rowsUpdated = 0;
318         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
319         db.beginTransaction();
320         try {
321             if (match == MATCH_METADATA) {
322                 rowsUpdated = modifyMetadata(db, values);
323             } else {
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);
328             }
329             db.setTransactionSuccessful();
330         } finally {
331             db.endTransaction();
332         }
333         notifyChanges(uri);
334         return rowsUpdated;
335     }
336
337     protected static String addIdToSelection(int match, String selection) {
338         String where;
339         switch (match) {
340             case MATCH_PHOTO_ID:
341             case MATCH_ALBUM_ID:
342             case MATCH_METADATA_ID:
343                 where = WHERE_ID;
344                 break;
345             default:
346                 return selection;
347         }
348         return DatabaseUtils.concatenateWhere(selection, where);
349     }
350
351     protected static String[] addIdToSelectionArgs(int match, Uri uri, String[] selectionArgs) {
352         String[] whereArgs;
353         switch (match) {
354             case MATCH_PHOTO_ID:
355             case MATCH_ALBUM_ID:
356             case MATCH_METADATA_ID:
357                 whereArgs = new String[] {
358                     uri.getPathSegments().get(1),
359                 };
360                 break;
361             default:
362                 return selectionArgs;
363         }
364         return DatabaseUtils.appendSelectionArgs(selectionArgs, whereArgs);
365     }
366
367     protected static String[] addMetadataKeysToSelectionArgs(String[] selectionArgs, Uri uri) {
368         List<String> segments = uri.getPathSegments();
369         String[] additionalArgs = {
370                 segments.get(1),
371                 segments.get(2),
372         };
373
374         return DatabaseUtils.appendSelectionArgs(selectionArgs, additionalArgs);
375     }
376
377     protected static String getTableFromMatch(int match, Uri uri) {
378         String table;
379         switch (match) {
380             case MATCH_PHOTO:
381             case MATCH_PHOTO_ID:
382                 table = Photos.TABLE;
383                 break;
384             case MATCH_ALBUM:
385             case MATCH_ALBUM_ID:
386                 table = Albums.TABLE;
387                 break;
388             case MATCH_METADATA:
389             case MATCH_METADATA_ID:
390                 table = Metadata.TABLE;
391                 break;
392             default:
393                 throw unknownUri(uri);
394         }
395         return table;
396     }
397
398     protected final SQLiteOpenHelper getDatabaseHelper() {
399         return mOpenHelper;
400     }
401
402     protected SQLiteOpenHelper createDatabaseHelper() {
403         return new PhotoDatabase(getContext());
404     }
405
406     private int modifyMetadata(SQLiteDatabase db, ContentValues values) {
407         String[] selectionArgs = {
408             values.getAsString(Metadata.PHOTO_ID),
409             values.getAsString(Metadata.KEY),
410         };
411         int rowCount;
412         if (values.get(Metadata.VALUE) == null) {
413             rowCount = db.delete(Metadata.TABLE, WHERE_METADATA_ID, selectionArgs);
414         } else {
415             rowCount = (int) DatabaseUtils.queryNumEntries(db, Metadata.TABLE, WHERE_METADATA_ID,
416                     selectionArgs);
417             if (rowCount > 0) {
418                 db.update(Metadata.TABLE, values, WHERE_METADATA_ID, selectionArgs);
419             } else {
420                 db.insert(Metadata.TABLE, null, values);
421                 rowCount = 1;
422             }
423         }
424         return rowCount;
425     }
426
427     private int matchUri(Uri uri) {
428         int match = sUriMatcher.match(uri);
429         if (match == UriMatcher.NO_MATCH) {
430             throw unknownUri(uri);
431         }
432         return match;
433     }
434
435     protected void notifyChanges(Uri uri) {
436         ContentResolver resolver = getContext().getContentResolver();
437         resolver.notifyChange(uri, null, false);
438     }
439
440     protected static IllegalArgumentException unknownUri(Uri uri) {
441         return new IllegalArgumentException("Unknown Uri format: " + uri);
442     }
443
444     protected static String nestSql(String base, String columnMatch, String nested) {
445         StringBuilder sql = new StringBuilder(base);
446         sql.append(WHERE);
447         sql.append(columnMatch);
448         sql.append(IN);
449         sql.append(NESTED_SELECT_START);
450         sql.append(nested);
451         sql.append(NESTED_SELECT_END);
452         return sql.toString();
453     }
454
455     protected static String addWhere(String base, String where) {
456         if (where == null || where.isEmpty()) {
457             return base;
458         }
459         return base + WHERE + where;
460     }
461
462     protected static int deleteCascade(SQLiteDatabase db, int match, String selection,
463             String[] selectionArgs, List<Uri> changeUris, Uri uri) {
464         switch (match) {
465             case MATCH_PHOTO:
466             case MATCH_PHOTO_ID: {
467                 String selectPhotoIdsSql = addWhere(SELECT_PHOTO_ID, selection);
468                 deleteCascadeMetadata(db, selectPhotoIdsSql, selectionArgs, changeUris);
469                 break;
470             }
471             case MATCH_ALBUM:
472             case MATCH_ALBUM_ID: {
473                 String selectAlbumIdSql = addWhere(SELECT_ALBUM_ID, selection);
474                 deleteCascadePhotos(db, selectAlbumIdSql, selectionArgs, changeUris);
475                 break;
476             }
477         }
478         String table = getTableFromMatch(match, uri);
479         changeUris.add(uri);
480         return db.delete(table, selection, selectionArgs);
481     }
482
483     protected static void execSql(SQLiteDatabase db, String sql, String[] args) {
484         if (args == null) {
485             db.execSQL(sql);
486         } else {
487             db.execSQL(sql, args);
488         }
489     }
490
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();
499         if (deleted > 0) {
500             changeUris.add(Photos.CONTENT_URI);
501         }
502     }
503
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();
510         if (deleted > 0) {
511             changeUris.add(Metadata.CONTENT_URI);
512         }
513     }
514 }