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.ContentProviderClient;
20 import android.content.ContentUris;
21 import android.content.UriMatcher;
22 import android.net.Uri;
23 import android.provider.MediaStore;
24 
25 import com.android.gallery3d.app.GalleryActivity;
26 import com.android.gallery3d.app.GalleryApp;
27 import com.android.gallery3d.data.MediaSet.ItemConsumer;
28 
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Comparator;
32 
33 class LocalSource extends MediaSource {
34 
35     public static final String KEY_BUCKET_ID = "bucketId";
36 
37     private GalleryApp mApplication;
38     private PathMatcher mMatcher;
39     private static final int NO_MATCH = -1;
40     private final UriMatcher mUriMatcher = new UriMatcher(NO_MATCH);
41     public static final Comparator<PathId> sIdComparator = new IdComparator();
42 
43     private static final int LOCAL_IMAGE_ALBUMSET = 0;
44     private static final int LOCAL_VIDEO_ALBUMSET = 1;
45     private static final int LOCAL_IMAGE_ALBUM = 2;
46     private static final int LOCAL_VIDEO_ALBUM = 3;
47     private static final int LOCAL_IMAGE_ITEM = 4;
48     private static final int LOCAL_VIDEO_ITEM = 5;
49     private static final int LOCAL_ALL_ALBUMSET = 6;
50     private static final int LOCAL_ALL_ALBUM = 7;
51 
52     private static final String TAG = "LocalSource";
53 
54     private ContentProviderClient mClient;
55 
LocalSource(GalleryApp context)56     public LocalSource(GalleryApp context) {
57         super("local");
58         mApplication = context;
59         mMatcher = new PathMatcher();
60         mMatcher.add("/local/image", LOCAL_IMAGE_ALBUMSET);
61         mMatcher.add("/local/video", LOCAL_VIDEO_ALBUMSET);
62         mMatcher.add("/local/all", LOCAL_ALL_ALBUMSET);
63 
64         mMatcher.add("/local/image/*", LOCAL_IMAGE_ALBUM);
65         mMatcher.add("/local/video/*", LOCAL_VIDEO_ALBUM);
66         mMatcher.add("/local/all/*", LOCAL_ALL_ALBUM);
67         mMatcher.add("/local/image/item/*", LOCAL_IMAGE_ITEM);
68         mMatcher.add("/local/video/item/*", LOCAL_VIDEO_ITEM);
69 
70         mUriMatcher.addURI(MediaStore.AUTHORITY,
71                 "external/images/media/#", LOCAL_IMAGE_ITEM);
72         mUriMatcher.addURI(MediaStore.AUTHORITY,
73                 "external/video/media/#", LOCAL_VIDEO_ITEM);
74         mUriMatcher.addURI(MediaStore.AUTHORITY,
75                 "external/images/media", LOCAL_IMAGE_ALBUM);
76         mUriMatcher.addURI(MediaStore.AUTHORITY,
77                 "external/video/media", LOCAL_VIDEO_ALBUM);
78         mUriMatcher.addURI(MediaStore.AUTHORITY,
79                 "external/file", LOCAL_ALL_ALBUM);
80     }
81 
82     @Override
createMediaObject(Path path)83     public MediaObject createMediaObject(Path path) {
84         GalleryApp app = mApplication;
85         switch (mMatcher.match(path)) {
86             case LOCAL_ALL_ALBUMSET:
87             case LOCAL_IMAGE_ALBUMSET:
88             case LOCAL_VIDEO_ALBUMSET:
89                 return new LocalAlbumSet(path, mApplication);
90             case LOCAL_IMAGE_ALBUM:
91                 return new LocalAlbum(path, app, mMatcher.getIntVar(0), true);
92             case LOCAL_VIDEO_ALBUM:
93                 return new LocalAlbum(path, app, mMatcher.getIntVar(0), false);
94             case LOCAL_ALL_ALBUM: {
95                 int bucketId = mMatcher.getIntVar(0);
96                 DataManager dataManager = app.getDataManager();
97                 MediaSet imageSet = (MediaSet) dataManager.getMediaObject(
98                         LocalAlbumSet.PATH_IMAGE.getChild(bucketId));
99                 MediaSet videoSet = (MediaSet) dataManager.getMediaObject(
100                         LocalAlbumSet.PATH_VIDEO.getChild(bucketId));
101                 Comparator<MediaItem> comp = DataManager.sDateTakenComparator;
102                 return new LocalMergeAlbum(
103                         path, comp, new MediaSet[] {imageSet, videoSet}, bucketId);
104             }
105             case LOCAL_IMAGE_ITEM:
106                 return new LocalImage(path, mApplication, mMatcher.getIntVar(0));
107             case LOCAL_VIDEO_ITEM:
108                 return new LocalVideo(path, mApplication, mMatcher.getIntVar(0));
109             default:
110                 throw new RuntimeException("bad path: " + path);
111         }
112     }
113 
getMediaType(String type, int defaultType)114     private static int getMediaType(String type, int defaultType) {
115         if (type == null) return defaultType;
116         try {
117             int value = Integer.parseInt(type);
118             if ((value & (MEDIA_TYPE_IMAGE
119                     | MEDIA_TYPE_VIDEO)) != 0) return value;
120         } catch (NumberFormatException e) {
121             Log.w(TAG, "invalid type: " + type, e);
122         }
123         return defaultType;
124     }
125 
126     // The media type bit passed by the intent
127     private static final int MEDIA_TYPE_ALL = 0;
128     private static final int MEDIA_TYPE_IMAGE = 1;
129     private static final int MEDIA_TYPE_VIDEO = 4;
130 
getAlbumPath(Uri uri, int defaultType)131     private Path getAlbumPath(Uri uri, int defaultType) {
132         int mediaType = getMediaType(
133                 uri.getQueryParameter(GalleryActivity.KEY_MEDIA_TYPES),
134                 defaultType);
135         String bucketId = uri.getQueryParameter(KEY_BUCKET_ID);
136         int id = 0;
137         try {
138             id = Integer.parseInt(bucketId);
139         } catch (NumberFormatException e) {
140             Log.w(TAG, "invalid bucket id: " + bucketId, e);
141             return null;
142         }
143         switch (mediaType) {
144             case MEDIA_TYPE_IMAGE:
145                 return Path.fromString("/local/image").getChild(id);
146             case MEDIA_TYPE_VIDEO:
147                 return Path.fromString("/local/video").getChild(id);
148             default:
149                 return Path.fromString("/local/all").getChild(id);
150         }
151     }
152 
153     @Override
findPathByUri(Uri uri, String type)154     public Path findPathByUri(Uri uri, String type) {
155         try {
156             switch (mUriMatcher.match(uri)) {
157                 case LOCAL_IMAGE_ITEM: {
158                     long id = ContentUris.parseId(uri);
159                     return id >= 0 ? LocalImage.ITEM_PATH.getChild(id) : null;
160                 }
161                 case LOCAL_VIDEO_ITEM: {
162                     long id = ContentUris.parseId(uri);
163                     return id >= 0 ? LocalVideo.ITEM_PATH.getChild(id) : null;
164                 }
165                 case LOCAL_IMAGE_ALBUM: {
166                     return getAlbumPath(uri, MEDIA_TYPE_IMAGE);
167                 }
168                 case LOCAL_VIDEO_ALBUM: {
169                     return getAlbumPath(uri, MEDIA_TYPE_VIDEO);
170                 }
171                 case LOCAL_ALL_ALBUM: {
172                     return getAlbumPath(uri, MEDIA_TYPE_ALL);
173                 }
174             }
175         } catch (NumberFormatException e) {
176             Log.w(TAG, "uri: " + uri.toString(), e);
177         }
178         return null;
179     }
180 
181     @Override
getDefaultSetOf(Path item)182     public Path getDefaultSetOf(Path item) {
183         MediaObject object = mApplication.getDataManager().getMediaObject(item);
184         if (object instanceof LocalMediaItem) {
185             return Path.fromString("/local/all").getChild(
186                     String.valueOf(((LocalMediaItem) object).getBucketId()));
187         }
188         return null;
189     }
190 
191     @Override
mapMediaItems(ArrayList<PathId> list, ItemConsumer consumer)192     public void mapMediaItems(ArrayList<PathId> list, ItemConsumer consumer) {
193         ArrayList<PathId> imageList = new ArrayList<PathId>();
194         ArrayList<PathId> videoList = new ArrayList<PathId>();
195         int n = list.size();
196         for (int i = 0; i < n; i++) {
197             PathId pid = list.get(i);
198             // We assume the form is: "/local/{image,video}/item/#"
199             // We don't use mMatcher for efficiency's reason.
200             Path parent = pid.path.getParent();
201             if (parent == LocalImage.ITEM_PATH) {
202                 imageList.add(pid);
203             } else if (parent == LocalVideo.ITEM_PATH) {
204                 videoList.add(pid);
205             }
206         }
207         // TODO: use "files" table so we can merge the two cases.
208         processMapMediaItems(imageList, consumer, true);
209         processMapMediaItems(videoList, consumer, false);
210     }
211 
processMapMediaItems(ArrayList<PathId> list, ItemConsumer consumer, boolean isImage)212     private void processMapMediaItems(ArrayList<PathId> list,
213             ItemConsumer consumer, boolean isImage) {
214         // Sort path by path id
215         Collections.sort(list, sIdComparator);
216         int n = list.size();
217         for (int i = 0; i < n; ) {
218             PathId pid = list.get(i);
219 
220             // Find a range of items.
221             ArrayList<Integer> ids = new ArrayList<Integer>();
222             int startId = Integer.parseInt(pid.path.getSuffix());
223             ids.add(startId);
224 
225             int j;
226             for (j = i + 1; j < n; j++) {
227                 PathId pid2 = list.get(j);
228                 int curId = Integer.parseInt(pid2.path.getSuffix());
229                 if (curId - startId >= MediaSet.MEDIAITEM_BATCH_FETCH_COUNT) {
230                     break;
231                 }
232                 ids.add(curId);
233             }
234 
235             MediaItem[] items = LocalAlbum.getMediaItemById(
236                     mApplication, isImage, ids);
237             for(int k = i ; k < j; k++) {
238                 PathId pid2 = list.get(k);
239                 consumer.consume(pid2.id, items[k - i]);
240             }
241 
242             i = j;
243         }
244     }
245 
246     // This is a comparator which compares the suffix number in two Paths.
247     private static class IdComparator implements Comparator<PathId> {
248         @Override
compare(PathId p1, PathId p2)249         public int compare(PathId p1, PathId p2) {
250             String s1 = p1.path.getSuffix();
251             String s2 = p2.path.getSuffix();
252             int len1 = s1.length();
253             int len2 = s2.length();
254             if (len1 < len2) {
255                 return -1;
256             } else if (len1 > len2) {
257                 return 1;
258             } else {
259                 return s1.compareTo(s2);
260             }
261         }
262     }
263 
264     @Override
resume()265     public void resume() {
266         mClient = mApplication.getContentResolver()
267                 .acquireContentProviderClient(MediaStore.AUTHORITY);
268     }
269 
270     @Override
pause()271     public void pause() {
272         mClient.release();
273         mClient = null;
274     }
275 }
276