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.provider.MediaStore; 24 import android.provider.MediaStore.Images; 25 import android.provider.MediaStore.Images.ImageColumns; 26 import android.provider.MediaStore.Video; 27 import android.provider.MediaStore.Video.VideoColumns; 28 29 import com.android.gallery3d.R; 30 import com.android.gallery3d.app.GalleryApp; 31 import com.android.gallery3d.common.Utils; 32 import com.android.gallery3d.util.GalleryUtils; 33 import com.android.gallery3d.util.MediaSetUtils; 34 35 import 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. 39 public 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 LocalAlbumSet.getBucketName(application.getContentResolver(), 90 bucketId)); 91 } 92 93 @Override 94 public Uri getContentUri() { 95 if (mIsImage) { 96 return MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon() 97 .appendQueryParameter(LocalSource.KEY_BUCKET_ID, 98 String.valueOf(mBucketId)).build(); 99 } else { 100 return MediaStore.Video.Media.EXTERNAL_CONTENT_URI.buildUpon() 101 .appendQueryParameter(LocalSource.KEY_BUCKET_ID, 102 String.valueOf(mBucketId)).build(); 103 } 104 } 105 106 @Override 107 public ArrayList<MediaItem> getMediaItem(int start, int count) { 108 DataManager dataManager = mApplication.getDataManager(); 109 Uri uri = mBaseUri.buildUpon() 110 .appendQueryParameter("limit", start + "," + count).build(); 111 ArrayList<MediaItem> list = new ArrayList<MediaItem>(); 112 GalleryUtils.assertNotInRenderThread(); 113 Cursor cursor = mResolver.query( 114 uri, mProjection, mWhereClause, 115 new String[]{String.valueOf(mBucketId)}, 116 mOrderClause); 117 if (cursor == null) { 118 Log.w(TAG, "query fail: " + uri); 119 return list; 120 } 121 122 try { 123 while (cursor.moveToNext()) { 124 int id = cursor.getInt(0); // _id must be in the first column 125 Path childPath = mItemPath.getChild(id); 126 MediaItem item = loadOrUpdateItem(childPath, cursor, 127 dataManager, mApplication, mIsImage); 128 list.add(item); 129 } 130 } finally { 131 cursor.close(); 132 } 133 return list; 134 } 135 136 private static MediaItem loadOrUpdateItem(Path path, Cursor cursor, 137 DataManager dataManager, GalleryApp app, boolean isImage) { 138 LocalMediaItem item = (LocalMediaItem) dataManager.peekMediaObject(path); 139 if (item == null) { 140 if (isImage) { 141 item = new LocalImage(path, app, cursor); 142 } else { 143 item = new LocalVideo(path, app, cursor); 144 } 145 } else { 146 item.updateContent(cursor); 147 } 148 return item; 149 } 150 151 // The pids array are sorted by the (path) id. 152 public static MediaItem[] getMediaItemById( 153 GalleryApp application, boolean isImage, ArrayList<Integer> ids) { 154 // get the lower and upper bound of (path) id 155 MediaItem[] result = new MediaItem[ids.size()]; 156 if (ids.isEmpty()) return result; 157 int idLow = ids.get(0); 158 int idHigh = ids.get(ids.size() - 1); 159 160 // prepare the query parameters 161 Uri baseUri; 162 String[] projection; 163 Path itemPath; 164 if (isImage) { 165 baseUri = Images.Media.EXTERNAL_CONTENT_URI; 166 projection = LocalImage.PROJECTION; 167 itemPath = LocalImage.ITEM_PATH; 168 } else { 169 baseUri = Video.Media.EXTERNAL_CONTENT_URI; 170 projection = LocalVideo.PROJECTION; 171 itemPath = LocalVideo.ITEM_PATH; 172 } 173 174 ContentResolver resolver = application.getContentResolver(); 175 DataManager dataManager = application.getDataManager(); 176 Cursor cursor = resolver.query(baseUri, projection, "_id BETWEEN ? AND ?", 177 new String[]{String.valueOf(idLow), String.valueOf(idHigh)}, 178 "_id"); 179 if (cursor == null) { 180 Log.w(TAG, "query fail" + baseUri); 181 return result; 182 } 183 try { 184 int n = ids.size(); 185 int i = 0; 186 187 while (i < n && cursor.moveToNext()) { 188 int id = cursor.getInt(0); // _id must be in the first column 189 190 // Match id with the one on the ids list. 191 if (ids.get(i) > id) { 192 continue; 193 } 194 195 while (ids.get(i) < id) { 196 if (++i >= n) { 197 return result; 198 } 199 } 200 201 Path childPath = itemPath.getChild(id); 202 MediaItem item = loadOrUpdateItem(childPath, cursor, dataManager, 203 application, isImage); 204 result[i] = item; 205 ++i; 206 } 207 return result; 208 } finally { 209 cursor.close(); 210 } 211 } 212 213 public static Cursor getItemCursor(ContentResolver resolver, Uri uri, 214 String[] projection, int id) { 215 return resolver.query(uri, projection, "_id=?", 216 new String[]{String.valueOf(id)}, null); 217 } 218 219 @Override 220 public int getMediaItemCount() { 221 if (mCachedCount == INVALID_COUNT) { 222 Cursor cursor = mResolver.query( 223 mBaseUri, COUNT_PROJECTION, mWhereClause, 224 new String[]{String.valueOf(mBucketId)}, null); 225 if (cursor == null) { 226 Log.w(TAG, "query fail"); 227 return 0; 228 } 229 try { 230 Utils.assertTrue(cursor.moveToNext()); 231 mCachedCount = cursor.getInt(0); 232 } finally { 233 cursor.close(); 234 } 235 } 236 return mCachedCount; 237 } 238 239 @Override 240 public String getName() { 241 return mName; 242 } 243 244 @Override 245 public long reload() { 246 if (mNotifier.isDirty()) { 247 mDataVersion = nextVersionNumber(); 248 mCachedCount = INVALID_COUNT; 249 } 250 return mDataVersion; 251 } 252 253 @Override 254 public int getSupportedOperations() { 255 return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_INFO; 256 } 257 258 @Override 259 public void delete() { 260 GalleryUtils.assertNotInRenderThread(); 261 mResolver.delete(mBaseUri, mWhereClause, 262 new String[]{String.valueOf(mBucketId)}); 263 mApplication.getDataManager().broadcastLocalDeletion(); 264 } 265 266 @Override 267 public boolean isLeafAlbum() { 268 return true; 269 } 270 271 private static String getLocalizedName(Resources res, int bucketId, 272 String name) { 273 if (bucketId == MediaSetUtils.CAMERA_BUCKET_ID) { 274 return res.getString(R.string.folder_camera); 275 } else if (bucketId == MediaSetUtils.DOWNLOAD_BUCKET_ID) { 276 return res.getString(R.string.folder_download); 277 } else if (bucketId == MediaSetUtils.IMPORTED_BUCKET_ID) { 278 return res.getString(R.string.folder_imported); 279 } else if (bucketId == MediaSetUtils.SNAPSHOT_BUCKET_ID) { 280 return res.getString(R.string.folder_screenshot); 281 } else { 282 return name; 283 } 284 } 285 } 286