1/* 2 * Copyright (C) 2010 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 17package com.android.gallery3d.data; 18 19import android.content.ContentResolver; 20import android.content.res.Resources; 21import android.database.Cursor; 22import android.net.Uri; 23import android.provider.MediaStore; 24import android.provider.MediaStore.Images; 25import android.provider.MediaStore.Images.ImageColumns; 26import android.provider.MediaStore.Video; 27import android.provider.MediaStore.Video.VideoColumns; 28 29import com.android.gallery3d.R; 30import com.android.gallery3d.app.GalleryApp; 31import com.android.gallery3d.common.Utils; 32import com.android.gallery3d.util.GalleryUtils; 33import com.android.gallery3d.util.MediaSetUtils; 34 35import java.util.ArrayList; 36 37// LocalAlbumSet lists all media items in one bucket on local storage. 38// The media items need to be all images or all videos, but not both. 39public class LocalAlbum extends MediaSet { 40 private static final String TAG = "LocalAlbum"; 41 private static final String[] COUNT_PROJECTION = { "count(*)" }; 42 43 private static final int INVALID_COUNT = -1; 44 private final String mWhereClause; 45 private final String mOrderClause; 46 private final Uri mBaseUri; 47 private final String[] mProjection; 48 49 private final GalleryApp mApplication; 50 private final ContentResolver mResolver; 51 private final int mBucketId; 52 private final String mName; 53 private final boolean mIsImage; 54 private final ChangeNotifier mNotifier; 55 private final Path mItemPath; 56 private int mCachedCount = INVALID_COUNT; 57 58 public LocalAlbum(Path path, GalleryApp application, int bucketId, 59 boolean isImage, String name) { 60 super(path, nextVersionNumber()); 61 mApplication = application; 62 mResolver = application.getContentResolver(); 63 mBucketId = bucketId; 64 mName = getLocalizedName(application.getResources(), bucketId, name); 65 mIsImage = isImage; 66 67 if (isImage) { 68 mWhereClause = ImageColumns.BUCKET_ID + " = ?"; 69 mOrderClause = ImageColumns.DATE_TAKEN + " DESC, " 70 + ImageColumns._ID + " DESC"; 71 mBaseUri = Images.Media.EXTERNAL_CONTENT_URI; 72 mProjection = LocalImage.PROJECTION; 73 mItemPath = LocalImage.ITEM_PATH; 74 } else { 75 mWhereClause = VideoColumns.BUCKET_ID + " = ?"; 76 mOrderClause = VideoColumns.DATE_TAKEN + " DESC, " 77 + VideoColumns._ID + " DESC"; 78 mBaseUri = Video.Media.EXTERNAL_CONTENT_URI; 79 mProjection = LocalVideo.PROJECTION; 80 mItemPath = LocalVideo.ITEM_PATH; 81 } 82 83 mNotifier = new ChangeNotifier(this, mBaseUri, application); 84 } 85 86 public LocalAlbum(Path path, GalleryApp application, int bucketId, 87 boolean isImage) { 88 this(path, application, bucketId, isImage, 89 BucketHelper.getBucketName( 90 application.getContentResolver(), bucketId)); 91 } 92 93 @Override 94 public boolean isCameraRoll() { 95 return mBucketId == MediaSetUtils.CAMERA_BUCKET_ID; 96 } 97 98 @Override 99 public Uri getContentUri() { 100 if (mIsImage) { 101 return MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon() 102 .appendQueryParameter(LocalSource.KEY_BUCKET_ID, 103 String.valueOf(mBucketId)).build(); 104 } else { 105 return MediaStore.Video.Media.EXTERNAL_CONTENT_URI.buildUpon() 106 .appendQueryParameter(LocalSource.KEY_BUCKET_ID, 107 String.valueOf(mBucketId)).build(); 108 } 109 } 110 111 @Override 112 public ArrayList<MediaItem> getMediaItem(int start, int count) { 113 DataManager dataManager = mApplication.getDataManager(); 114 Uri uri = mBaseUri.buildUpon() 115 .appendQueryParameter("limit", start + "," + count).build(); 116 ArrayList<MediaItem> list = new ArrayList<MediaItem>(); 117 GalleryUtils.assertNotInRenderThread(); 118 Cursor cursor = mResolver.query( 119 uri, mProjection, mWhereClause, 120 new String[]{String.valueOf(mBucketId)}, 121 mOrderClause); 122 if (cursor == null) { 123 Log.w(TAG, "query fail: " + uri); 124 return list; 125 } 126 127 try { 128 while (cursor.moveToNext()) { 129 int id = cursor.getInt(0); // _id must be in the first column 130 Path childPath = mItemPath.getChild(id); 131 MediaItem item = loadOrUpdateItem(childPath, cursor, 132 dataManager, mApplication, mIsImage); 133 list.add(item); 134 } 135 } finally { 136 cursor.close(); 137 } 138 return list; 139 } 140 141 private static MediaItem loadOrUpdateItem(Path path, Cursor cursor, 142 DataManager dataManager, GalleryApp app, boolean isImage) { 143 synchronized (DataManager.LOCK) { 144 LocalMediaItem item = (LocalMediaItem) dataManager.peekMediaObject(path); 145 if (item == null) { 146 if (isImage) { 147 item = new LocalImage(path, app, cursor); 148 } else { 149 item = new LocalVideo(path, app, cursor); 150 } 151 } else { 152 item.updateContent(cursor); 153 } 154 return item; 155 } 156 } 157 158 // The pids array are sorted by the (path) id. 159 public static MediaItem[] getMediaItemById( 160 GalleryApp application, boolean isImage, ArrayList<Integer> ids) { 161 // get the lower and upper bound of (path) id 162 MediaItem[] result = new MediaItem[ids.size()]; 163 if (ids.isEmpty()) return result; 164 int idLow = ids.get(0); 165 int idHigh = ids.get(ids.size() - 1); 166 167 // prepare the query parameters 168 Uri baseUri; 169 String[] projection; 170 Path itemPath; 171 if (isImage) { 172 baseUri = Images.Media.EXTERNAL_CONTENT_URI; 173 projection = LocalImage.PROJECTION; 174 itemPath = LocalImage.ITEM_PATH; 175 } else { 176 baseUri = Video.Media.EXTERNAL_CONTENT_URI; 177 projection = LocalVideo.PROJECTION; 178 itemPath = LocalVideo.ITEM_PATH; 179 } 180 181 ContentResolver resolver = application.getContentResolver(); 182 DataManager dataManager = application.getDataManager(); 183 Cursor cursor = resolver.query(baseUri, projection, "_id BETWEEN ? AND ?", 184 new String[]{String.valueOf(idLow), String.valueOf(idHigh)}, 185 "_id"); 186 if (cursor == null) { 187 Log.w(TAG, "query fail" + baseUri); 188 return result; 189 } 190 try { 191 int n = ids.size(); 192 int i = 0; 193 194 while (i < n && cursor.moveToNext()) { 195 int id = cursor.getInt(0); // _id must be in the first column 196 197 // Match id with the one on the ids list. 198 if (ids.get(i) > id) { 199 continue; 200 } 201 202 while (ids.get(i) < id) { 203 if (++i >= n) { 204 return result; 205 } 206 } 207 208 Path childPath = itemPath.getChild(id); 209 MediaItem item = loadOrUpdateItem(childPath, cursor, dataManager, 210 application, isImage); 211 result[i] = item; 212 ++i; 213 } 214 return result; 215 } finally { 216 cursor.close(); 217 } 218 } 219 220 public static Cursor getItemCursor(ContentResolver resolver, Uri uri, 221 String[] projection, int id) { 222 return resolver.query(uri, projection, "_id=?", 223 new String[]{String.valueOf(id)}, null); 224 } 225 226 @Override 227 public int getMediaItemCount() { 228 if (mCachedCount == INVALID_COUNT) { 229 Cursor cursor = mResolver.query( 230 mBaseUri, COUNT_PROJECTION, mWhereClause, 231 new String[]{String.valueOf(mBucketId)}, null); 232 if (cursor == null) { 233 Log.w(TAG, "query fail"); 234 return 0; 235 } 236 try { 237 Utils.assertTrue(cursor.moveToNext()); 238 mCachedCount = cursor.getInt(0); 239 } finally { 240 cursor.close(); 241 } 242 } 243 return mCachedCount; 244 } 245 246 @Override 247 public String getName() { 248 return mName; 249 } 250 251 @Override 252 public long reload() { 253 if (mNotifier.isDirty()) { 254 mDataVersion = nextVersionNumber(); 255 mCachedCount = INVALID_COUNT; 256 } 257 return mDataVersion; 258 } 259 260 @Override 261 public int getSupportedOperations() { 262 return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_INFO; 263 } 264 265 @Override 266 public void delete() { 267 GalleryUtils.assertNotInRenderThread(); 268 mResolver.delete(mBaseUri, mWhereClause, 269 new String[]{String.valueOf(mBucketId)}); 270 } 271 272 @Override 273 public boolean isLeafAlbum() { 274 return true; 275 } 276 277 public static String getLocalizedName(Resources res, int bucketId, 278 String name) { 279 if (bucketId == MediaSetUtils.CAMERA_BUCKET_ID) { 280 return res.getString(R.string.folder_camera); 281 } else if (bucketId == MediaSetUtils.DOWNLOAD_BUCKET_ID) { 282 return res.getString(R.string.folder_download); 283 } else if (bucketId == MediaSetUtils.IMPORTED_BUCKET_ID) { 284 return res.getString(R.string.folder_imported); 285 } else if (bucketId == MediaSetUtils.SNAPSHOT_BUCKET_ID) { 286 return res.getString(R.string.folder_screenshot); 287 } else if (bucketId == MediaSetUtils.EDITED_ONLINE_PHOTOS_BUCKET_ID) { 288 return res.getString(R.string.folder_edited_online_photos); 289 } else { 290 return name; 291 } 292 } 293} 294