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 */ 16package com.android.dreams.phototable; 17 18import android.content.Context; 19import android.content.SharedPreferences; 20import android.database.Cursor; 21import android.net.Uri; 22import android.provider.MediaStore; 23 24import java.io.FileInputStream; 25import java.io.InputStream; 26import java.util.Collection; 27import java.util.HashMap; 28import java.util.LinkedList; 29import java.util.Set; 30 31/** 32 * Loads images from the local store. 33 */ 34public 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 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 51 private Set<String> getFoundAlbums() { 52 if (mFoundAlbumIds == null) { 53 findAlbums(); 54 } 55 return mFoundAlbumIds; 56 } 57 58 @Override 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 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 121 public static String constructId(boolean internal, String bucketId) { 122 return TAG + ":" + bucketId + (internal ? ":i" : ""); 123 } 124 125 @Override 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 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 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 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 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 238 private boolean isInternalId(String id) { 239 return id.endsWith("i"); 240 } 241 242 @Override 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