diff options
author | Owen Lin <owenlin@google.com> | 2012-07-31 14:22:02 +0800 |
---|---|---|
committer | Owen Lin <owenlin@google.com> | 2012-08-01 16:57:47 +0800 |
commit | 29446731c8cfa6e0456fe8b39fc3ce21c69207a6 (patch) | |
tree | 0056200a4b8a0aabe6d9001f233ba6c7347abab5 | |
parent | e7e234f2d41dbf2d3a3d1bd7669d848f2c8ac66d (diff) | |
download | android_packages_apps_Snap-29446731c8cfa6e0456fe8b39fc3ce21c69207a6.tar.gz android_packages_apps_Snap-29446731c8cfa6e0456fe8b39fc3ce21c69207a6.tar.bz2 android_packages_apps_Snap-29446731c8cfa6e0456fe8b39fc3ce21c69207a6.zip |
Don't use Files.getContentUri in previous API levels.
Change-Id: I4d096734d22c93b83b2bf8082450ad0d73ade837
-rw-r--r-- | gallerycommon/src/com/android/gallery3d/common/ApiHelper.java | 3 | ||||
-rw-r--r-- | src/com/android/gallery3d/data/BucketHelper.java | 241 | ||||
-rw-r--r-- | src/com/android/gallery3d/data/LocalAlbum.java | 4 | ||||
-rw-r--r-- | src/com/android/gallery3d/data/LocalAlbumSet.java | 134 | ||||
-rw-r--r-- | src/com/android/gallery3d/data/LocalMergeAlbum.java | 16 |
5 files changed, 263 insertions, 135 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java index ac14d9bbd..54b0587e8 100644 --- a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java +++ b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java @@ -146,6 +146,9 @@ public class ApiHelper { public static final boolean HAS_ACTION_BAR_SET_HOME_BUTTON_ENABLED = Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH; + public static final boolean HAS_MEDIA_PROVIDER_FILES_TABLE = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + private static boolean hasField(Class<?> klass, String fieldName) { try { klass.getDeclaredField(fieldName); diff --git a/src/com/android/gallery3d/data/BucketHelper.java b/src/com/android/gallery3d/data/BucketHelper.java new file mode 100644 index 000000000..71c0ad4ed --- /dev/null +++ b/src/com/android/gallery3d/data/BucketHelper.java @@ -0,0 +1,241 @@ +package com.android.gallery3d.data; + +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore.Files; +import android.provider.MediaStore.Files.FileColumns; +import android.provider.MediaStore.Images; +import android.provider.MediaStore.Images.ImageColumns; +import android.provider.MediaStore.Video; +import android.util.Log; + +import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.util.ThreadPool.JobContext; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; + +class BucketHelper { + + private static final String TAG = "BucketHelper"; + private static final String EXTERNAL_MEDIA = "external"; + + // BUCKET_DISPLAY_NAME is a string like "Camera" which is the directory + // name of where an image or video is in. BUCKET_ID is a hash of the path + // name of that directory (see computeBucketValues() in MediaProvider for + // details). MEDIA_TYPE is video, image, audio, etc. + // + // The "albums" are not explicitly recorded in the database, but each image + // or video has the two columns (BUCKET_ID, MEDIA_TYPE). We define an + // "album" to be the collection of images/videos which have the same value + // for the two columns. + // + // The goal of the query (used in loadSubMediaSetsFromFilesTable()) is to + // find all albums, that is, all unique values for (BUCKET_ID, MEDIA_TYPE). + // In the meantime sort them by the timestamp of the latest image/video in + // each of the album. + // + // The order of columns below is important: it must match to the index in + // MediaStore. + private static final String[] PROJECTION_BUCKET = { + ImageColumns.BUCKET_ID, + FileColumns.MEDIA_TYPE, + ImageColumns.BUCKET_DISPLAY_NAME}; + + // The indices should match the above projections. + private static final int INDEX_BUCKET_ID = 0; + private static final int INDEX_MEDIA_TYPE = 1; + private static final int INDEX_BUCKET_NAME = 2; + + // We want to order the albums by reverse chronological order. We abuse the + // "WHERE" parameter to insert a "GROUP BY" clause into the SQL statement. + // The template for "WHERE" parameter is like: + // SELECT ... FROM ... WHERE (%s) + // and we make it look like: + // SELECT ... FROM ... WHERE (1) GROUP BY 1,(2) + // The "(1)" means true. The "1,(2)" means the first two columns specified + // after SELECT. Note that because there is a ")" in the template, we use + // "(2" to match it. + private static final String BUCKET_GROUP_BY = "1) GROUP BY 1,(2"; + + private static final String BUCKET_ORDER_BY = "MAX(datetaken) DESC"; + + // Before HoneyComb there is no Files table. Thus, we need to query the + // bucket info from the Images and Video tables and then merge them + // together. + // + // A bucket can exist in both tables. In this case, we need to find the + // latest timestamp from the two tables and sort ourselves. So we add the + // MAX(date_taken) to the projection and remove the media_type since we + // already know the media type from the table we query from. + private static final String[] PROJECTION_BUCKET_IN_ONE_TABLE = { + ImageColumns.BUCKET_ID, + "MAX(date_taken)", + ImageColumns.BUCKET_DISPLAY_NAME}; + + // We keep the INDEX_BUCKET_ID and INDEX_BUCKET_NAME the same as + // PROJECTION_BUCKET so we can reuse the values defined before. + private static final int INDEX_DATE_TAKEN = 1; + + // When query from the Images or Video tables, we only need to group by BUCKET_ID. + private static final String BUCKET_GROUP_BY_IN_ONE_TABLE = "1) GROUP BY (1"; + + public static BucketEntry[] loadBucketEntries( + JobContext jc, ContentResolver resolver, int type) { + if (ApiHelper.HAS_MEDIA_PROVIDER_FILES_TABLE) { + return loadBucketEntriesFromFilesTable(jc, resolver, type); + } else { + return loadBucketEntriesFromImagesAndVideoTable(jc, resolver, type); + } + } + + private static void updateBucketEntriesFromTable(JobContext jc, + ContentResolver resolver, Uri tableUri, HashMap<Integer, BucketEntry> buckets) { + Cursor cursor = resolver.query(tableUri, PROJECTION_BUCKET_IN_ONE_TABLE, + BUCKET_GROUP_BY_IN_ONE_TABLE, null, null); + if (cursor == null) { + Log.w(TAG, "cannot open media database: " + tableUri); + return; + } + try { + while (cursor.moveToNext()) { + int bucketId = cursor.getInt(INDEX_BUCKET_ID); + int dateTaken = cursor.getInt(INDEX_DATE_TAKEN); + BucketEntry entry = buckets.get(bucketId); + if (entry == null) { + entry = new BucketEntry(bucketId, cursor.getString(INDEX_BUCKET_NAME)); + buckets.put(bucketId, entry); + entry.dateTaken = dateTaken; + } else { + entry.dateTaken = Math.max(entry.dateTaken, dateTaken); + } + } + } finally { + Utils.closeSilently(cursor); + } + } + + private static BucketEntry[] loadBucketEntriesFromImagesAndVideoTable( + JobContext jc, ContentResolver resolver, int type) { + HashMap<Integer, BucketEntry> buckets = new HashMap<Integer, BucketEntry>(64); + if ((type & MediaObject.MEDIA_TYPE_IMAGE) != 0) { + updateBucketEntriesFromTable( + jc, resolver, Images.Media.EXTERNAL_CONTENT_URI, buckets); + } + if ((type & MediaObject.MEDIA_TYPE_VIDEO) != 0) { + updateBucketEntriesFromTable( + jc, resolver, Video.Media.EXTERNAL_CONTENT_URI, buckets); + } + BucketEntry[] entries = buckets.values().toArray(new BucketEntry[buckets.size()]); + Arrays.sort(entries, new Comparator<BucketEntry>() { + @Override + public int compare(BucketEntry a, BucketEntry b) { + // sorted by dateTaken in descending order + return b.dateTaken - a.dateTaken; + } + }); + return entries; + } + + private static BucketEntry[] loadBucketEntriesFromFilesTable( + JobContext jc, ContentResolver resolver, int type) { + Uri uri = getFilesContentUri(); + + Cursor cursor = resolver.query(uri, + PROJECTION_BUCKET, BUCKET_GROUP_BY, + null, BUCKET_ORDER_BY); + if (cursor == null) { + Log.w(TAG, "cannot open local database: " + uri); + return new BucketEntry[0]; + } + ArrayList<BucketEntry> buffer = new ArrayList<BucketEntry>(); + int typeBits = 0; + if ((type & MediaObject.MEDIA_TYPE_IMAGE) != 0) { + typeBits |= (1 << FileColumns.MEDIA_TYPE_IMAGE); + } + if ((type & MediaObject.MEDIA_TYPE_VIDEO) != 0) { + typeBits |= (1 << FileColumns.MEDIA_TYPE_VIDEO); + } + try { + while (cursor.moveToNext()) { + if ((typeBits & (1 << cursor.getInt(INDEX_MEDIA_TYPE))) != 0) { + BucketEntry entry = new BucketEntry( + cursor.getInt(INDEX_BUCKET_ID), + cursor.getString(INDEX_BUCKET_NAME)); + if (!buffer.contains(entry)) { + buffer.add(entry); + } + } + if (jc.isCancelled()) return null; + } + } finally { + Utils.closeSilently(cursor); + } + return buffer.toArray(new BucketEntry[buffer.size()]); + } + + private static String getBucketNameInTable( + ContentResolver resolver, Uri tableUri, int bucketId) { + String selectionArgs[] = new String[] {String.valueOf(bucketId)}; + Uri uri = tableUri.buildUpon() + .appendQueryParameter("limit", "1") + .build(); + Cursor cursor = resolver.query(uri, PROJECTION_BUCKET_IN_ONE_TABLE, + "bucket_id = ?", selectionArgs, null); + try { + if (cursor != null && cursor.moveToNext()) { + return cursor.getString(INDEX_BUCKET_NAME); + } + } finally { + Utils.closeSilently(cursor); + } + return null; + } + + @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) + private static Uri getFilesContentUri() { + return Files.getContentUri(EXTERNAL_MEDIA); + } + + public static String getBucketName(ContentResolver resolver, int bucketId) { + if (ApiHelper.HAS_MEDIA_PROVIDER_FILES_TABLE) { + String result = getBucketNameInTable(resolver, getFilesContentUri(), bucketId); + return result == null ? "" : result; + } else { + String result = getBucketNameInTable( + resolver, Images.Media.EXTERNAL_CONTENT_URI, bucketId); + if (result != null) return result; + result = getBucketNameInTable( + resolver, Video.Media.EXTERNAL_CONTENT_URI, bucketId); + return result == null ? "" : result; + } + } + + public static class BucketEntry { + public String bucketName; + public int bucketId; + public int dateTaken; + + public BucketEntry(int id, String name) { + bucketId = id; + bucketName = Utils.ensureNotNull(name); + } + + @Override + public int hashCode() { + return bucketId; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof BucketEntry)) return false; + BucketEntry entry = (BucketEntry) object; + return bucketId == entry.bucketId; + } + } +} diff --git a/src/com/android/gallery3d/data/LocalAlbum.java b/src/com/android/gallery3d/data/LocalAlbum.java index f45eebfd8..fed704078 100644 --- a/src/com/android/gallery3d/data/LocalAlbum.java +++ b/src/com/android/gallery3d/data/LocalAlbum.java @@ -86,8 +86,8 @@ public class LocalAlbum extends MediaSet { public LocalAlbum(Path path, GalleryApp application, int bucketId, boolean isImage) { this(path, application, bucketId, isImage, - LocalAlbumSet.getBucketName(application.getContentResolver(), - bucketId)); + BucketHelper.getBucketName( + application.getContentResolver(), bucketId)); } @Override diff --git a/src/com/android/gallery3d/data/LocalAlbumSet.java b/src/com/android/gallery3d/data/LocalAlbumSet.java index 07741efcb..579a71e9b 100644 --- a/src/com/android/gallery3d/data/LocalAlbumSet.java +++ b/src/com/android/gallery3d/data/LocalAlbumSet.java @@ -16,20 +16,14 @@ package com.android.gallery3d.data; -import android.content.ContentResolver; -import android.database.Cursor; import android.net.Uri; import android.os.Handler; -import android.provider.MediaStore.Files; -import android.provider.MediaStore.Files.FileColumns; import android.provider.MediaStore.Images; -import android.provider.MediaStore.Images.ImageColumns; import android.provider.MediaStore.Video; -import android.util.Log; import com.android.gallery3d.R; import com.android.gallery3d.app.GalleryApp; -import com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.BucketHelper.BucketEntry; import com.android.gallery3d.util.Future; import com.android.gallery3d.util.FutureListener; import com.android.gallery3d.util.MediaSetUtils; @@ -48,51 +42,10 @@ public class LocalAlbumSet extends MediaSet public static final Path PATH_VIDEO = Path.fromString("/local/video"); private static final String TAG = "LocalAlbumSet"; - private static final String EXTERNAL_MEDIA = "external"; - // The indices should match the following projections. - private static final int INDEX_BUCKET_ID = 0; - private static final int INDEX_MEDIA_TYPE = 1; - private static final int INDEX_BUCKET_NAME = 2; - - private static final Uri mBaseUri = Files.getContentUri(EXTERNAL_MEDIA); private static final Uri mWatchUriImage = Images.Media.EXTERNAL_CONTENT_URI; private static final Uri mWatchUriVideo = Video.Media.EXTERNAL_CONTENT_URI; - // BUCKET_DISPLAY_NAME is a string like "Camera" which is the directory - // name of where an image or video is in. BUCKET_ID is a hash of the path - // name of that directory (see computeBucketValues() in MediaProvider for - // details). MEDIA_TYPE is video, image, audio, etc. - // - // The "albums" are not explicitly recorded in the database, but each image - // or video has the two columns (BUCKET_ID, MEDIA_TYPE). We define an - // "album" to be the collection of images/videos which have the same value - // for the two columns. - // - // The goal of the query (used in loadSubMediaSets()) is to find all albums, - // that is, all unique values for (BUCKET_ID, MEDIA_TYPE). In the meantime - // sort them by the timestamp of the latest image/video in each of the album. - // - // The order of columns below is important: it must match to the index in - // MediaStore. - private static final String[] PROJECTION_BUCKET = { - ImageColumns.BUCKET_ID, - FileColumns.MEDIA_TYPE, - ImageColumns.BUCKET_DISPLAY_NAME }; - - // We want to order the albums by reverse chronological order. We abuse the - // "WHERE" parameter to insert a "GROUP BY" clause into the SQL statement. - // The template for "WHERE" parameter is like: - // SELECT ... FROM ... WHERE (%s) - // and we make it look like: - // SELECT ... FROM ... WHERE (1) GROUP BY 1,(2) - // The "(1)" means true. The "1,(2)" means the first two columns specified - // after SELECT. Note that because there is a ")" in the template, we use - // "(2" to match it. - private static final String BUCKET_GROUP_BY = - "1) GROUP BY 1,(2"; - private static final String BUCKET_ORDER_BY = "MAX(datetaken) DESC"; - private final GalleryApp mApplication; private final int mType; private ArrayList<MediaSet> mAlbums = new ArrayList<MediaSet>(); @@ -139,44 +92,6 @@ public class LocalAlbumSet extends MediaSet return mName; } - private BucketEntry[] loadBucketEntries(JobContext jc) { - Uri uri = mBaseUri; - - Log.v("DebugLoadingTime", "start quering media provider"); - Cursor cursor = mApplication.getContentResolver().query( - uri, PROJECTION_BUCKET, BUCKET_GROUP_BY, null, BUCKET_ORDER_BY); - if (cursor == null) { - Log.w(TAG, "cannot open local database: " + uri); - return new BucketEntry[0]; - } - ArrayList<BucketEntry> buffer = new ArrayList<BucketEntry>(); - int typeBits = 0; - if ((mType & MEDIA_TYPE_IMAGE) != 0) { - typeBits |= (1 << FileColumns.MEDIA_TYPE_IMAGE); - } - if ((mType & MEDIA_TYPE_VIDEO) != 0) { - typeBits |= (1 << FileColumns.MEDIA_TYPE_VIDEO); - } - try { - while (cursor.moveToNext()) { - if ((typeBits & (1 << cursor.getInt(INDEX_MEDIA_TYPE))) != 0) { - BucketEntry entry = new BucketEntry( - cursor.getInt(INDEX_BUCKET_ID), - cursor.getString(INDEX_BUCKET_NAME)); - if (!buffer.contains(entry)) { - buffer.add(entry); - } - } - if (jc.isCancelled()) return null; - } - Log.v("DebugLoadingTime", "got " + buffer.size() + " buckets"); - } finally { - cursor.close(); - } - return buffer.toArray(new BucketEntry[buffer.size()]); - } - - private static int findBucket(BucketEntry entries[], int bucketId) { for (int i = 0, n = entries.length; i < n ; ++i) { if (entries[i].bucketId == bucketId) return i; @@ -191,7 +106,8 @@ public class LocalAlbumSet extends MediaSet public ArrayList<MediaSet> run(JobContext jc) { // Note: it will be faster if we only select media_type and bucket_id. // need to test the performance if that is worth - BucketEntry[] entries = loadBucketEntries(jc); + BucketEntry[] entries = BucketHelper.loadBucketEntries( + jc, mApplication.getContentResolver(), mType); if (jc.isCancelled()) return null; @@ -239,28 +155,6 @@ public class LocalAlbumSet extends MediaSet } } - public static String getBucketName(ContentResolver resolver, int bucketId) { - Uri uri = mBaseUri.buildUpon() - .appendQueryParameter("limit", "1") - .build(); - - Cursor cursor = resolver.query( - uri, PROJECTION_BUCKET, "bucket_id = ?", - new String[]{String.valueOf(bucketId)}, null); - - if (cursor == null) { - Log.w(TAG, "query fail: " + uri); - return ""; - } - try { - return cursor.moveToNext() - ? cursor.getString(INDEX_BUCKET_NAME) - : ""; - } finally { - cursor.close(); - } - } - @Override public synchronized boolean isLoading() { return mIsLoading; @@ -308,28 +202,6 @@ public class LocalAlbumSet extends MediaSet mNotifierVideo.fakeChange(); } - private static class BucketEntry { - public String bucketName; - public int bucketId; - - public BucketEntry(int id, String name) { - bucketId = id; - bucketName = Utils.ensureNotNull(name); - } - - @Override - public int hashCode() { - return bucketId; - } - - @Override - public boolean equals(Object object) { - if (!(object instanceof BucketEntry)) return false; - BucketEntry entry = (BucketEntry) object; - return bucketId == entry.bucketId; - } - } - // Circular shift the array range from a[i] to a[j] (inclusive). That is, // a[i] -> a[i+1] -> a[i+2] -> ... -> a[j], and a[j] -> a[i] private static <T> void circularShiftRight(T[] array, int i, int j) { diff --git a/src/com/android/gallery3d/data/LocalMergeAlbum.java b/src/com/android/gallery3d/data/LocalMergeAlbum.java index 1e34e7817..da3d5113c 100644 --- a/src/com/android/gallery3d/data/LocalMergeAlbum.java +++ b/src/com/android/gallery3d/data/LocalMergeAlbum.java @@ -19,6 +19,8 @@ package com.android.gallery3d.data; import android.net.Uri; import android.provider.MediaStore; +import com.android.gallery3d.common.ApiHelper; + import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Comparator; @@ -82,8 +84,18 @@ public class LocalMergeAlbum extends MediaSet implements ContentListener { @Override public Uri getContentUri() { - return MediaStore.Files.getContentUri("external").buildUpon().appendQueryParameter( - LocalSource.KEY_BUCKET_ID, String.valueOf(mBucketId)).build(); + String bucketId = String.valueOf(mBucketId); + if (ApiHelper.HAS_MEDIA_PROVIDER_FILES_TABLE) { + return MediaStore.Files.getContentUri("external").buildUpon() + .appendQueryParameter(LocalSource.KEY_BUCKET_ID, bucketId) + .build(); + } else { + // We don't have a single URL for a merged image before ICS + // So we used the image's URL as a substitute. + return MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon() + .appendQueryParameter(LocalSource.KEY_BUCKET_ID, bucketId) + .build(); + } } @Override |