DataManager.java revision 4b4dbd225685502f4249c2bf25bf74f7ce526645
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.database.ContentObserver;
20import android.net.Uri;
21import android.os.Handler;
22
23import com.android.gallery3d.app.GalleryApp;
24import com.android.gallery3d.app.StitchingChangeListener;
25import com.android.gallery3d.common.ApiHelper;
26import com.android.gallery3d.common.Utils;
27import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
28import com.android.gallery3d.data.MediaSet.ItemConsumer;
29import com.android.gallery3d.data.MediaSource.PathId;
30import com.android.gallery3d.picasasource.PicasaSource;
31
32import java.util.ArrayList;
33import java.util.Comparator;
34import java.util.HashMap;
35import java.util.LinkedHashMap;
36import java.util.Map.Entry;
37import java.util.WeakHashMap;
38
39// DataManager manages all media sets and media items in the system.
40//
41// Each MediaSet and MediaItem has a unique 64 bits id. The most significant
42// 32 bits represents its parent, and the least significant 32 bits represents
43// the self id. For MediaSet the self id is is globally unique, but for
44// MediaItem it's unique only relative to its parent.
45//
46// To make sure the id is the same when the MediaSet is re-created, a child key
47// is provided to obtainSetId() to make sure the same self id will be used as
48// when the parent and key are the same. A sequence of child keys is called a
49// path. And it's used to identify a specific media set even if the process is
50// killed and re-created, so child keys should be stable identifiers.
51
52public class DataManager implements StitchingChangeListener {
53    public static final int INCLUDE_IMAGE = 1;
54    public static final int INCLUDE_VIDEO = 2;
55    public static final int INCLUDE_ALL = INCLUDE_IMAGE | INCLUDE_VIDEO;
56    public static final int INCLUDE_LOCAL_ONLY = 4;
57    public static final int INCLUDE_LOCAL_IMAGE_ONLY =
58            INCLUDE_LOCAL_ONLY | INCLUDE_IMAGE;
59    public static final int INCLUDE_LOCAL_VIDEO_ONLY =
60            INCLUDE_LOCAL_ONLY | INCLUDE_VIDEO;
61    public static final int INCLUDE_LOCAL_ALL_ONLY =
62            INCLUDE_LOCAL_ONLY | INCLUDE_IMAGE | INCLUDE_VIDEO;
63
64    // Any one who would like to access data should require this lock
65    // to prevent concurrency issue.
66    public static final Object LOCK = new Object();
67
68    private static final String TAG = "DataManager";
69
70    // This is the path for the media set seen by the user at top level.
71    private static final String TOP_SET_PATH = ApiHelper.HAS_MTP
72            ? "/combo/{/mtp,/local/all,/picasa/all}"
73            : "/combo/{/local/all,/picasa/all}";
74
75    private static final String TOP_IMAGE_SET_PATH = ApiHelper.HAS_MTP
76            ? "/combo/{/mtp,/local/image,/picasa/image}"
77            : "/combo/{/local/image,/picasa/image}";
78
79    private static final String TOP_VIDEO_SET_PATH =
80            "/combo/{/local/video,/picasa/video}";
81
82    private static final String TOP_LOCAL_SET_PATH = "/local/all";
83
84    private static final String TOP_LOCAL_IMAGE_SET_PATH = "/local/image";
85
86    private static final String TOP_LOCAL_VIDEO_SET_PATH = "/local/video";
87
88    public static final Comparator<MediaItem> sDateTakenComparator =
89            new DateTakenComparator();
90
91    private static class DateTakenComparator implements Comparator<MediaItem> {
92        @Override
93        public int compare(MediaItem item1, MediaItem item2) {
94            return -Utils.compare(item1.getDateInMs(), item2.getDateInMs());
95        }
96    }
97
98    private final Handler mDefaultMainHandler;
99
100    private GalleryApp mApplication;
101    private int mActiveCount = 0;
102
103    private HashMap<Uri, NotifyBroker> mNotifierMap =
104            new HashMap<Uri, NotifyBroker>();
105
106
107    private HashMap<String, MediaSource> mSourceMap =
108            new LinkedHashMap<String, MediaSource>();
109
110    public DataManager(GalleryApp application) {
111        mApplication = application;
112        mDefaultMainHandler = new Handler(application.getMainLooper());
113    }
114
115    public synchronized void initializeSourceMap() {
116        if (!mSourceMap.isEmpty()) return;
117
118        // the order matters, the UriSource must come last
119        addSource(new LocalSource(mApplication));
120        addSource(new PicasaSource(mApplication));
121        if (ApiHelper.HAS_MTP) {
122            addSource(new MtpSource(mApplication));
123        }
124        addSource(new ComboSource(mApplication));
125        addSource(new ClusterSource(mApplication));
126        addSource(new FilterSource(mApplication));
127        addSource(new SecureSource(mApplication));
128        addSource(new UriSource(mApplication));
129        addSource(new SnailSource(mApplication));
130
131        if (mActiveCount > 0) {
132            for (MediaSource source : mSourceMap.values()) {
133                source.resume();
134            }
135        }
136    }
137
138    public String getTopSetPath(int typeBits) {
139
140        switch (typeBits) {
141            case INCLUDE_IMAGE: return TOP_IMAGE_SET_PATH;
142            case INCLUDE_VIDEO: return TOP_VIDEO_SET_PATH;
143            case INCLUDE_ALL: return TOP_SET_PATH;
144            case INCLUDE_LOCAL_IMAGE_ONLY: return TOP_LOCAL_IMAGE_SET_PATH;
145            case INCLUDE_LOCAL_VIDEO_ONLY: return TOP_LOCAL_VIDEO_SET_PATH;
146            case INCLUDE_LOCAL_ALL_ONLY: return TOP_LOCAL_SET_PATH;
147            default: throw new IllegalArgumentException();
148        }
149    }
150
151    // open for debug
152    void addSource(MediaSource source) {
153        if (source == null) return;
154        mSourceMap.put(source.getPrefix(), source);
155    }
156
157    // A common usage of this method is:
158    // synchronized (DataManager.LOCK) {
159    //     MediaObject object = peekMediaObject(path);
160    //     if (object == null) {
161    //         object = createMediaObject(...);
162    //     }
163    // }
164    public MediaObject peekMediaObject(Path path) {
165        return path.getObject();
166    }
167
168    public MediaObject getMediaObject(Path path) {
169        synchronized (LOCK) {
170            MediaObject obj = path.getObject();
171            if (obj != null) return obj;
172
173            MediaSource source = mSourceMap.get(path.getPrefix());
174            if (source == null) {
175                Log.w(TAG, "cannot find media source for path: " + path);
176                return null;
177            }
178
179            try {
180                MediaObject object = source.createMediaObject(path);
181                if (object == null) {
182                    Log.w(TAG, "cannot create media object: " + path);
183                }
184                return object;
185            } catch (Throwable t) {
186                Log.w(TAG, "exception in creating media object: " + path, t);
187                return null;
188            }
189        }
190    }
191
192    public MediaObject getMediaObject(String s) {
193        return getMediaObject(Path.fromString(s));
194    }
195
196    public MediaSet getMediaSet(Path path) {
197        return (MediaSet) getMediaObject(path);
198    }
199
200    public MediaSet getMediaSet(String s) {
201        return (MediaSet) getMediaObject(s);
202    }
203
204    public MediaSet[] getMediaSetsFromString(String segment) {
205        String[] seq = Path.splitSequence(segment);
206        int n = seq.length;
207        MediaSet[] sets = new MediaSet[n];
208        for (int i = 0; i < n; i++) {
209            sets[i] = getMediaSet(seq[i]);
210        }
211        return sets;
212    }
213
214    // Maps a list of Paths to MediaItems, and invoke consumer.consume()
215    // for each MediaItem (may not be in the same order as the input list).
216    // An index number is also passed to consumer.consume() to identify
217    // the original position in the input list of the corresponding Path (plus
218    // startIndex).
219    public void mapMediaItems(ArrayList<Path> list, ItemConsumer consumer,
220            int startIndex) {
221        HashMap<String, ArrayList<PathId>> map =
222                new HashMap<String, ArrayList<PathId>>();
223
224        // Group the path by the prefix.
225        int n = list.size();
226        for (int i = 0; i < n; i++) {
227            Path path = list.get(i);
228            String prefix = path.getPrefix();
229            ArrayList<PathId> group = map.get(prefix);
230            if (group == null) {
231                group = new ArrayList<PathId>();
232                map.put(prefix, group);
233            }
234            group.add(new PathId(path, i + startIndex));
235        }
236
237        // For each group, ask the corresponding media source to map it.
238        for (Entry<String, ArrayList<PathId>> entry : map.entrySet()) {
239            String prefix = entry.getKey();
240            MediaSource source = mSourceMap.get(prefix);
241            source.mapMediaItems(entry.getValue(), consumer);
242        }
243    }
244
245    // The following methods forward the request to the proper object.
246    public int getSupportedOperations(Path path) {
247        return getMediaObject(path).getSupportedOperations();
248    }
249
250    public void getPanoramaSupport(Path path, PanoramaSupportCallback callback) {
251        getMediaObject(path).getPanoramaSupport(callback);
252    }
253
254    public void delete(Path path) {
255        getMediaObject(path).delete();
256    }
257
258    public void rotate(Path path, int degrees) {
259        getMediaObject(path).rotate(degrees);
260    }
261
262    public Uri getContentUri(Path path) {
263        return getMediaObject(path).getContentUri();
264    }
265
266    public int getMediaType(Path path) {
267        return getMediaObject(path).getMediaType();
268    }
269
270    public Path findPathByUri(Uri uri, String type) {
271        if (uri == null) return null;
272        for (MediaSource source : mSourceMap.values()) {
273            Path path = source.findPathByUri(uri, type);
274            if (path != null) return path;
275        }
276        return null;
277    }
278
279    public Path getDefaultSetOf(Path item) {
280        MediaSource source = mSourceMap.get(item.getPrefix());
281        return source == null ? null : source.getDefaultSetOf(item);
282    }
283
284    // Returns number of bytes used by cached pictures currently downloaded.
285    public long getTotalUsedCacheSize() {
286        long sum = 0;
287        for (MediaSource source : mSourceMap.values()) {
288            sum += source.getTotalUsedCacheSize();
289        }
290        return sum;
291    }
292
293    // Returns number of bytes used by cached pictures if all pending
294    // downloads and removals are completed.
295    public long getTotalTargetCacheSize() {
296        long sum = 0;
297        for (MediaSource source : mSourceMap.values()) {
298            sum += source.getTotalTargetCacheSize();
299        }
300        return sum;
301    }
302
303    public void registerChangeNotifier(Uri uri, ChangeNotifier notifier) {
304        NotifyBroker broker = null;
305        synchronized (mNotifierMap) {
306            broker = mNotifierMap.get(uri);
307            if (broker == null) {
308                broker = new NotifyBroker(mDefaultMainHandler);
309                mApplication.getContentResolver()
310                        .registerContentObserver(uri, true, broker);
311                mNotifierMap.put(uri, broker);
312            }
313        }
314        broker.registerNotifier(notifier);
315    }
316
317    public void resume() {
318        if (++mActiveCount == 1) {
319            for (MediaSource source : mSourceMap.values()) {
320                source.resume();
321            }
322        }
323    }
324
325    public void pause() {
326        if (--mActiveCount == 0) {
327            for (MediaSource source : mSourceMap.values()) {
328                source.pause();
329            }
330        }
331    }
332
333    private static class NotifyBroker extends ContentObserver {
334        private WeakHashMap<ChangeNotifier, Object> mNotifiers =
335                new WeakHashMap<ChangeNotifier, Object>();
336
337        public NotifyBroker(Handler handler) {
338            super(handler);
339        }
340
341        public synchronized void registerNotifier(ChangeNotifier notifier) {
342            mNotifiers.put(notifier, null);
343        }
344
345        @Override
346        public synchronized void onChange(boolean selfChange) {
347            for(ChangeNotifier notifier : mNotifiers.keySet()) {
348                notifier.onChange(selfChange);
349            }
350        }
351    }
352
353    @Override
354    public void onStitchingQueued(Uri uri) {
355        // Do nothing.
356    }
357
358    @Override
359    public void onStitchingResult(Uri uri) {
360        Path path = findPathByUri(uri, null);
361        if (path != null) {
362            MediaObject mediaObject = getMediaObject(path);
363            if (mediaObject != null) {
364                mediaObject.clearCachedPanoramaSupport();
365            }
366        }
367    }
368
369    @Override
370    public void onStitchingProgress(Uri uri, int progress) {
371        // Do nothing.
372    }
373}
374