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