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