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.ContentProviderClient;
20import android.content.ContentUris;
21import android.content.UriMatcher;
22import android.net.Uri;
23import android.provider.MediaStore;
24
25import com.android.gallery3d.app.GalleryActivity;
26import com.android.gallery3d.app.GalleryApp;
27import com.android.gallery3d.data.MediaSet.ItemConsumer;
28
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.Comparator;
32
33class 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
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
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
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
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
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
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
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
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
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
265    public void resume() {
266        mClient = mApplication.getContentResolver()
267                .acquireContentProviderClient(MediaStore.AUTHORITY);
268    }
269
270    @Override
271    public void pause() {
272        mClient.release();
273        mClient = null;
274    }
275}
276