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(_id)" };
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 
LocalAlbum(Path path, GalleryApp application, int bucketId, boolean isImage, String name)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 
LocalAlbum(Path path, GalleryApp application, int bucketId, boolean isImage)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
isCameraRoll()97     public boolean isCameraRoll() {
98         return mBucketId == MediaSetUtils.CAMERA_BUCKET_ID;
99     }
100 
101     @Override
getContentUri()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
getMediaItem(int start, int count)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 
loadOrUpdateItem(Path path, Cursor cursor, DataManager dataManager, GalleryApp app, boolean isImage)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.
getMediaItemById( GalleryApp application, boolean isImage, ArrayList<Integer> ids)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 
getItemCursor(ContentResolver resolver, Uri uri, String[] projection, int id)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
getMediaItemCount()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
getName()250     public String getName() {
251         return getLocalizedName(mApplication.getResources(), mBucketId, mName);
252     }
253 
254     @Override
reload()255     public long reload() {
256         if (mNotifier.isDirty()) {
257             mDataVersion = nextVersionNumber();
258             mCachedCount = INVALID_COUNT;
259         }
260         return mDataVersion;
261     }
262 
263     @Override
getSupportedOperations()264     public int getSupportedOperations() {
265         return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_INFO;
266     }
267 
268     @Override
delete()269     public void delete() {
270         GalleryUtils.assertNotInRenderThread();
271         mResolver.delete(mBaseUri, mWhereClause,
272                 new String[]{String.valueOf(mBucketId)});
273     }
274 
275     @Override
isLeafAlbum()276     public boolean isLeafAlbum() {
277         return true;
278     }
279 
getLocalizedName(Resources res, int bucketId, String name)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
getRelativePath(int bucketId)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