1 /*
2  * Copyright (C) 2012 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 package com.android.dreams.phototable;
17 
18 import android.content.Context;
19 import android.content.SharedPreferences;
20 import android.database.Cursor;
21 import android.net.Uri;
22 import android.provider.MediaStore;
23 
24 import java.io.FileInputStream;
25 import java.io.InputStream;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.LinkedList;
29 import java.util.Set;
30 
31 /**
32  * Loads images from the local store.
33  */
34 public class LocalSource extends CursorPhotoSource {
35     private static final String TAG = "PhotoTable.LocalSource";
36 
37     private final String mUnknownAlbumName;
38     private final String mLocalSourceName;
39     private Set<String> mFoundAlbumIds;
40     private int mLastPosition;
41 
LocalSource(Context context, SharedPreferences settings)42     public LocalSource(Context context, SharedPreferences settings) {
43         super(context, settings);
44         mLocalSourceName = mResources.getString(R.string.local_source_name, "Photos on Device");
45         mUnknownAlbumName = mResources.getString(R.string.unknown_album_name, "Unknown");
46         mSourceName = TAG;
47         mLastPosition = INVALID;
48         fillQueue();
49     }
50 
getFoundAlbums()51     private Set<String> getFoundAlbums() {
52         if (mFoundAlbumIds == null) {
53             findAlbums();
54         }
55         return mFoundAlbumIds;
56     }
57 
58     @Override
findAlbums()59     public Collection<AlbumData> findAlbums() {
60         log(TAG, "finding albums");
61         HashMap<String, AlbumData> foundAlbums = new HashMap<String, AlbumData>();
62         findAlbums(false, foundAlbums);
63         findAlbums(true, foundAlbums);
64 
65         log(TAG, "found " + foundAlbums.size() + " items.");
66         mFoundAlbumIds = foundAlbums.keySet();
67         return foundAlbums.values();
68     }
69 
findAlbums(boolean internal, HashMap<String, AlbumData> foundAlbums)70     public void findAlbums(boolean internal, HashMap<String, AlbumData> foundAlbums) {
71         Uri uri = internal ? MediaStore.Images.Media.INTERNAL_CONTENT_URI
72             : MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
73         String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.BUCKET_ID,
74                 MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media.DATE_TAKEN};
75         // This is a horrible hack that closes the where clause and injects a grouping clause.
76         Cursor cursor = mResolver.query(uri, projection, null, null, null);
77         if (cursor != null) {
78             cursor.moveToPosition(-1);
79 
80             int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
81             int bucketIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID);
82             int nameIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);
83             int updatedIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN);
84 
85             if (bucketIndex < 0) {
86                 log(TAG, "can't find the ID column!");
87             } else {
88                 while (cursor.moveToNext()) {
89                     String id = constructId(internal, cursor.getString(bucketIndex));
90                     AlbumData data = foundAlbums.get(id);
91                     if (foundAlbums.get(id) == null) {
92                         data = new AlbumData();
93                         data.id = id;
94                         data.account = mLocalSourceName;
95 
96                         if (dataIndex >= 0) {
97                             data.thumbnailUrl = cursor.getString(dataIndex);
98                         }
99 
100                         if (nameIndex >= 0) {
101                             data.title = cursor.getString(nameIndex);
102                         } else {
103                             data.title = mUnknownAlbumName;
104                         }
105 
106                         log(TAG, data.title + " found");
107                         foundAlbums.put(id, data);
108                     }
109                     if (updatedIndex >= 0) {
110                         long updated = cursor.getLong(updatedIndex);
111                         data.updated = (data.updated == 0 ?
112                                         updated :
113                                         Math.min(data.updated, updated));
114                     }
115                 }
116             }
117             cursor.close();
118         }
119     }
120 
constructId(boolean internal, String bucketId)121     public static String constructId(boolean internal, String bucketId) {
122         return TAG + ":" + bucketId + (internal ? ":i" : "");
123     }
124 
125     @Override
openCursor(ImageData data)126     protected void openCursor(ImageData data) {
127         log(TAG, "opening single album");
128 
129         String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.ORIENTATION,
130                 MediaStore.Images.Media.BUCKET_ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME};
131         String selection = MediaStore.Images.Media.BUCKET_ID + " = '" + data.albumId + "'";
132 
133         data.cursor = mResolver.query(data.uri, projection, selection, null, null);
134     }
135 
136     @Override
findPosition(ImageData data)137     protected void findPosition(ImageData data) {
138         if (data.position == -1) {
139             if (data.cursor == null) {
140                 openCursor(data);
141             }
142             if (data.cursor != null) {
143                 int dataIndex = data.cursor.getColumnIndex(MediaStore.Images.Media.DATA);
144                 data.cursor.moveToPosition(-1);
145                 while (data.position == -1 && data.cursor.moveToNext()) {
146                     String url = data.cursor.getString(dataIndex);
147                     if (url != null && url.equals(data.url)) {
148                         data.position = data.cursor.getPosition();
149                     }
150                 }
151                 if (data.position == -1) {
152                     // oops!  The image isn't in this album. How did we get here?
153                     data.position = INVALID;
154                 }
155             }
156         }
157     }
158 
159     @Override
unpackImageData(Cursor cursor, ImageData data)160     protected ImageData unpackImageData(Cursor cursor, ImageData data) {
161         if (data == null) {
162             data = new ImageData();
163         }
164         int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
165         int orientationIndex = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION);
166         int bucketIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID);
167 
168         data.url = cursor.getString(dataIndex);
169         data.albumId = cursor.getString(bucketIndex);
170         data.position = UNINITIALIZED;
171         data.cursor = null;
172         data.orientation = cursor.getInt(orientationIndex);
173 
174         return data;
175     }
176 
177     @Override
findImages(int howMany)178     protected Collection<ImageData> findImages(int howMany) {
179         log(TAG, "finding images");
180         LinkedList<ImageData> foundImages = new LinkedList<ImageData>();
181         boolean internalFirst = mRNG.nextInt(2) == 0;  // filp a coin to be fair
182         findImages(internalFirst, howMany, foundImages);
183         findImages(!internalFirst, howMany - foundImages.size(), foundImages);
184         log(TAG, "found " + foundImages.size() + " items.");
185         return foundImages;
186     }
187 
findImages(boolean internal, int howMany, LinkedList<ImageData> foundImages )188     protected void findImages(boolean internal, int howMany, LinkedList<ImageData> foundImages ) {
189         Uri uri = internal ? MediaStore.Images.Media.INTERNAL_CONTENT_URI
190             : MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
191         String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.ORIENTATION,
192                 MediaStore.Images.Media.BUCKET_ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME};
193         String selection = "";
194         for (String id : getFoundAlbums()) {
195             if (isInternalId(id) == internal && mSettings.isAlbumEnabled(id)) {
196                 String[] parts = id.split(":");
197                 if (parts.length > 1) {
198                     if (selection.length() > 0) {
199                         selection += " OR ";
200                     }
201                     selection += MediaStore.Images.Media.BUCKET_ID + " = '" + parts[1] + "'";
202                 }
203             }
204         }
205         if (selection.isEmpty()) {
206             return;
207         }
208         Cursor cursor = mResolver.query(uri, projection, selection, null, null);
209         if (cursor != null) {
210             int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
211 
212             if (cursor.getCount() > howMany && mLastPosition == INVALID) {
213                 mLastPosition = pickRandomStart(cursor.getCount(), howMany);
214             }
215             cursor.moveToPosition(mLastPosition);
216 
217             if (dataIndex < 0) {
218                 log(TAG, "can't find the DATA column!");
219             } else {
220                 while (foundImages.size() < howMany && cursor.moveToNext()) {
221                     ImageData data = unpackImageData(cursor, null);
222                     data.uri = uri;
223                     foundImages.offer(data);
224                     mLastPosition = cursor.getPosition();
225                 }
226                 if (cursor.isAfterLast()) {
227                     mLastPosition = -1;
228                 }
229                 if (cursor.isBeforeFirst()) {
230                     mLastPosition = INVALID;
231                 }
232             }
233 
234             cursor.close();
235         }
236     }
237 
isInternalId(String id)238     private boolean isInternalId(String id) {
239         return id.endsWith("i");
240     }
241 
242     @Override
getStream(ImageData data, int longSide)243     protected InputStream getStream(ImageData data, int longSide) {
244         FileInputStream fis = null;
245         try {
246             log(TAG, "opening:" + data.url);
247             fis = new FileInputStream(data.url);
248         } catch (Exception ex) {
249             log(TAG, ex.toString());
250             fis = null;
251         }
252 
253         return (InputStream) fis;
254     }
255 }
256 
257