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