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