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 17d6db8eab6df048b9065834113a6d46a885af01d3Owen Linpackage com.android.gallery3d.gadget; 18f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 19f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.content.ContentResolver; 20f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.content.Context; 21f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.database.ContentObserver; 22f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.database.Cursor; 23f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.graphics.Bitmap; 24f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.net.Uri; 25f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.os.Environment; 26f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.os.Handler; 27f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.provider.MediaStore.Images.Media; 28f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 292b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.app.GalleryApp; 302b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.common.Utils; 312b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.data.ContentListener; 322b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.data.DataManager; 332b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.data.MediaItem; 342b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.data.Path; 352b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.util.GalleryUtils; 362b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen 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 447817979db0c52ffeacb951625b1e821eba303285Ahbong Chang @SuppressWarnings("unused") 45f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String TAG = "LocalPhotoSource"; 46f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 47f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int MAX_PHOTO_COUNT = 128; 48f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 49f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin /* Static fields used to query for the correct set of images */ 50f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final Uri CONTENT_URI = Media.EXTERNAL_CONTENT_URI; 51f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String DATE_TAKEN = Media.DATE_TAKEN; 52f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String[] PROJECTION = {Media._ID}; 53f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String[] COUNT_PROJECTION = {"count(*)"}; 54f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin /* We don't want to include the download directory */ 55f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String SELECTION = 56f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String.format("%s != %s", Media.BUCKET_ID, getDownloadBucketId()); 57f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String ORDER = String.format("%s DESC", DATE_TAKEN); 58f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 59f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private Context mContext; 60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private ArrayList<Long> mPhotos = new ArrayList<Long>(); 61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private ContentListener mContentListener; 62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private ContentObserver mContentObserver; 63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private boolean mContentDirty = true; 64f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private DataManager mDataManager; 65f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final Path LOCAL_IMAGE_ROOT = Path.fromString("/local/image/item"); 66f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 67f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public LocalPhotoSource(Context context) { 68f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContext = context; 69f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mDataManager = ((GalleryApp) context.getApplicationContext()).getDataManager(); 70f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContentObserver = new ContentObserver(new Handler()) { 71f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 72f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void onChange(boolean selfChange) { 73f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContentDirty = true; 74f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mContentListener != null) mContentListener.onContentDirty(); 75f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 76f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin }; 77f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContext.getContentResolver() 78f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .registerContentObserver(CONTENT_URI, true, mContentObserver); 79f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 80f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 817817979db0c52ffeacb951625b1e821eba303285Ahbong Chang @Override 82f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void close() { 83f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContext.getContentResolver().unregisterContentObserver(mContentObserver); 84f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 85f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 86f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 87f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public Uri getContentUri(int index) { 88f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (index < mPhotos.size()) { 89f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return CONTENT_URI.buildUpon() 90f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .appendPath(String.valueOf(mPhotos.get(index))) 91f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .build(); 92f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 93f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return null; 94f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 95f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 96f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 97f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public Bitmap getImage(int index) { 98f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (index >= mPhotos.size()) return null; 99f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long id = mPhotos.get(index); 100f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin MediaItem image = (MediaItem) 101f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mDataManager.getMediaObject(LOCAL_IMAGE_ROOT.getChild(id)); 102f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (image == null) return null; 103f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 104f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return WidgetUtils.createWidgetBitmap(image); 105f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 106f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 107f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private int[] getExponentialIndice(int total, int count) { 108f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Random random = new Random(); 109f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (count > total) count = total; 110f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin HashSet<Integer> selected = new HashSet<Integer>(count); 111f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin while (selected.size() < count) { 112f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int row = (int)(-Math.log(random.nextDouble()) * total / 2); 113f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (row < total) selected.add(row); 114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int values[] = new int[count]; 116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int index = 0; 117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int value : selected) { 118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin values[index++] = value; 119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 120f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return values; 121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 123f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private int getPhotoCount(ContentResolver resolver) { 124f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Cursor cursor = resolver.query( 125f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin CONTENT_URI, COUNT_PROJECTION, SELECTION, null, null); 126f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (cursor == null) return 0; 127f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin try { 128f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Utils.assertTrue(cursor.moveToNext()); 129f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return cursor.getInt(0); 130f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } finally { 131f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin cursor.close(); 132f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 133f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 134f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private boolean isContentSound(int totalCount) { 136f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mPhotos.size() < Math.min(totalCount, MAX_PHOTO_COUNT)) return false; 137f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mPhotos.size() == 0) return true; // totalCount is also 0 138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 139f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin StringBuilder builder = new StringBuilder(); 140f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (Long imageId : mPhotos) { 141f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (builder.length() > 0) builder.append(","); 142f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin builder.append(imageId); 143f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 144f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Cursor cursor = mContext.getContentResolver().query( 145f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin CONTENT_URI, COUNT_PROJECTION, 146f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String.format("%s in (%s)", Media._ID, builder.toString()), 147f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin null, null); 148f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (cursor == null) return false; 149f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin try { 150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Utils.assertTrue(cursor.moveToNext()); 151f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return cursor.getInt(0) == mPhotos.size(); 152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } finally { 153f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin cursor.close(); 154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 155f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 156f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 1577817979db0c52ffeacb951625b1e821eba303285Ahbong Chang @Override 158f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void reload() { 159f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (!mContentDirty) return; 160f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContentDirty = false; 161f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ContentResolver resolver = mContext.getContentResolver(); 163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int photoCount = getPhotoCount(resolver); 164f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (isContentSound(photoCount)) return; 165f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 166f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int choosedIds[] = getExponentialIndice(photoCount, MAX_PHOTO_COUNT); 167f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Arrays.sort(choosedIds); 168f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 169f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mPhotos.clear(); 170f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Cursor cursor = mContext.getContentResolver().query( 171f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin CONTENT_URI, PROJECTION, SELECTION, null, ORDER); 172f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (cursor == null) return; 173f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin try { 174f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int index : choosedIds) { 175f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (cursor.moveToPosition(index)) { 176f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mPhotos.add(cursor.getLong(0)); 177f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 178f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 179f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } finally { 180f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin cursor.close(); 181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 183f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 184f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 185f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public int size() { 186f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin reload(); 187f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mPhotos.size(); 188f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin /** 191f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Builds the bucket ID for the public external storage Downloads directory 192f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * @return the bucket ID 193f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin */ 194f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static int getDownloadBucketId() { 195f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String downloadsPath = Environment 196f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) 197f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .getAbsolutePath(); 198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return GalleryUtils.getBucketId(downloadsPath); 199f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 202f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void setContentListener(ContentListener listener) { 203f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContentListener = listener; 204f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 205f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin} 206