LocalPhotoSource.java revision f9a0a4306d589b4a4e20554fed512a603426bfa1
1f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin/* 2f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Copyright (C) 2011 The Android Open Source Project 3f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 4f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Licensed under the Apache License, Version 2.0 (the "License"); 5f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * you may not use this file except in compliance with the License. 6f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * You may obtain a copy of the License at 7f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 8f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * http://www.apache.org/licenses/LICENSE-2.0 9f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 10f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Unless required by applicable law or agreed to in writing, software 11f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * distributed under the License is distributed on an "AS IS" BASIS, 12f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * See the License for the specific language governing permissions and 14f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * limitations under the License. 15f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin */ 16f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 17f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpackage com.android.gallery3d.widget; 18f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 19f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.app.GalleryApp; 20f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.common.Utils; 21f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.data.ContentListener; 22f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.data.DataManager; 23f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.data.MediaItem; 24f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.data.Path; 25f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.util.GalleryUtils; 26f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 27f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.content.ContentResolver; 28f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.content.Context; 29f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.database.ContentObserver; 30f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.database.Cursor; 31f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.graphics.Bitmap; 32f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.net.Uri; 33f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.os.Environment; 34f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.os.Handler; 35f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.provider.MediaStore.Images.Media; 36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 37f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.ArrayList; 38f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.Arrays; 39f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.HashSet; 40f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.Random; 41f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 42f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpublic class LocalPhotoSource implements WidgetSource { 43f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 44f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String TAG = "LocalPhotoSource"; 45f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 46f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int MAX_PHOTO_COUNT = 128; 47f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 48f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin /* Static fields used to query for the correct set of images */ 49f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final Uri CONTENT_URI = Media.EXTERNAL_CONTENT_URI; 50f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String DATE_TAKEN = Media.DATE_TAKEN; 51f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String[] PROJECTION = {Media._ID}; 52f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String[] COUNT_PROJECTION = {"count(*)"}; 53f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin /* We don't want to include the download directory */ 54f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String SELECTION = 55f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String.format("%s != %s", Media.BUCKET_ID, getDownloadBucketId()); 56f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String ORDER = String.format("%s DESC", DATE_TAKEN); 57f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 58f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private Context mContext; 59f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private ArrayList<Long> mPhotos = new ArrayList<Long>(); 60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private ContentListener mContentListener; 61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private ContentObserver mContentObserver; 62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private boolean mContentDirty = true; 63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private DataManager mDataManager; 64f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final Path LOCAL_IMAGE_ROOT = Path.fromString("/local/image/item"); 65f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 66f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public LocalPhotoSource(Context context) { 67f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContext = context; 68f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mDataManager = ((GalleryApp) context.getApplicationContext()).getDataManager(); 69f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContentObserver = new ContentObserver(new Handler()) { 70f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 71f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void onChange(boolean selfChange) { 72f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContentDirty = true; 73f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mContentListener != null) mContentListener.onContentDirty(); 74f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 75f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin }; 76f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContext.getContentResolver() 77f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .registerContentObserver(CONTENT_URI, true, mContentObserver); 78f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 79f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 80f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void close() { 81f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContext.getContentResolver().unregisterContentObserver(mContentObserver); 82f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 83f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 84f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 85f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public Uri getContentUri(int index) { 86f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (index < mPhotos.size()) { 87f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return CONTENT_URI.buildUpon() 88f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .appendPath(String.valueOf(mPhotos.get(index))) 89f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .build(); 90f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 91f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return null; 92f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 93f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 94f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 95f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public Bitmap getImage(int index) { 96f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (index >= mPhotos.size()) return null; 97f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long id = mPhotos.get(index); 98f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin MediaItem image = (MediaItem) 99f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mDataManager.getMediaObject(LOCAL_IMAGE_ROOT.getChild(id)); 100f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (image == null) return null; 101f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 102f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return WidgetUtils.createWidgetBitmap(image); 103f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 104f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 105f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private int[] getExponentialIndice(int total, int count) { 106f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Random random = new Random(); 107f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (count > total) count = total; 108f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin HashSet<Integer> selected = new HashSet<Integer>(count); 109f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin while (selected.size() < count) { 110f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int row = (int)(-Math.log(random.nextDouble()) * total / 2); 111f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (row < total) selected.add(row); 112f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 113f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int values[] = new int[count]; 114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int index = 0; 115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int value : selected) { 116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin values[index++] = value; 117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return values; 119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 120f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private int getPhotoCount(ContentResolver resolver) { 122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Cursor cursor = resolver.query( 123f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin CONTENT_URI, COUNT_PROJECTION, SELECTION, null, null); 124f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (cursor == null) return 0; 125f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin try { 126f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Utils.assertTrue(cursor.moveToNext()); 127f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return cursor.getInt(0); 128f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } finally { 129f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin cursor.close(); 130f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 131f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 132f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 133f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private boolean isContentSound(int totalCount) { 134f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mPhotos.size() < Math.min(totalCount, MAX_PHOTO_COUNT)) return false; 135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mPhotos.size() == 0) return true; // totalCount is also 0 136f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 137f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin StringBuilder builder = new StringBuilder(); 138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (Long imageId : mPhotos) { 139f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (builder.length() > 0) builder.append(","); 140f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin builder.append(imageId); 141f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 142f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Cursor cursor = mContext.getContentResolver().query( 143f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin CONTENT_URI, COUNT_PROJECTION, 144f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String.format("%s in (%s)", Media._ID, builder.toString()), 145f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin null, null); 146f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (cursor == null) return false; 147f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin try { 148f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Utils.assertTrue(cursor.moveToNext()); 149f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return cursor.getInt(0) == mPhotos.size(); 150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } finally { 151f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin cursor.close(); 152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 153f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 155f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void reload() { 156f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (!mContentDirty) return; 157f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContentDirty = false; 158f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 159f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ContentResolver resolver = mContext.getContentResolver(); 160f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int photoCount = getPhotoCount(resolver); 161f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (isContentSound(photoCount)) return; 162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int choosedIds[] = getExponentialIndice(photoCount, MAX_PHOTO_COUNT); 164f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Arrays.sort(choosedIds); 165f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 166f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mPhotos.clear(); 167f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Cursor cursor = mContext.getContentResolver().query( 168f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin CONTENT_URI, PROJECTION, SELECTION, null, ORDER); 169f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (cursor == null) return; 170f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin try { 171f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int index : choosedIds) { 172f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (cursor.moveToPosition(index)) { 173f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mPhotos.add(cursor.getLong(0)); 174f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 175f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 176f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } finally { 177f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin cursor.close(); 178f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 179f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 180f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public int size() { 183f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin reload(); 184f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mPhotos.size(); 185f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 186f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 187f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin /** 188f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Builds the bucket ID for the public external storage Downloads directory 189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * @return the bucket ID 190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin */ 191f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static int getDownloadBucketId() { 192f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String downloadsPath = Environment 193f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) 194f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .getAbsolutePath(); 195f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return GalleryUtils.getBucketId(downloadsPath); 196f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 197f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 199f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void setContentListener(ContentListener listener) { 200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContentListener = listener; 201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 202f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin} 203