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