2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.gallery3d.data;
19 import android.content.Intent;
20 import android.database.ContentObserver;
21 import android.net.Uri;
22 import android.os.Handler;
23 import android.support.v4.content.LocalBroadcastManager;
25 import com.android.gallery3d.app.GalleryApp;
26 import com.android.gallery3d.common.ApiHelper;
27 import com.android.gallery3d.common.Utils;
28 import com.android.gallery3d.data.MediaSet.ItemConsumer;
29 import com.android.gallery3d.data.MediaSource.PathId;
30 import com.android.gallery3d.picasasource.PicasaSource;
32 import java.util.ArrayList;
33 import java.util.Comparator;
34 import java.util.HashMap;
35 import java.util.LinkedHashMap;
36 import java.util.Map.Entry;
37 import java.util.WeakHashMap;
39 // DataManager manages all media sets and media items in the system.
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.
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.
52 public class DataManager {
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;
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();
68 private static final String TAG = "DataManager";
70 // This is the path for the media set seen by the user at top level.
71 private static final String TOP_SET_PATH =
72 "/combo/{/mtp,/local/all,/picasa/all}";
73 private static final String TOP_IMAGE_SET_PATH =
74 "/combo/{/mtp,/local/image,/picasa/image}";
75 private static final String TOP_VIDEO_SET_PATH =
76 "/combo/{/local/video,/picasa/video}";
77 private static final String TOP_LOCAL_SET_PATH =
79 private static final String TOP_LOCAL_IMAGE_SET_PATH =
81 private static final String TOP_LOCAL_VIDEO_SET_PATH =
84 private static final String ACTION_DELETE_PICTURE =
85 "com.android.gallery3d.action.DELETE_PICTURE";
87 public static final Comparator<MediaItem> sDateTakenComparator =
88 new DateTakenComparator();
90 private static class DateTakenComparator implements Comparator<MediaItem> {
91 public int compare(MediaItem item1, MediaItem item2) {
92 return -Utils.compare(item1.getDateInMs(), item2.getDateInMs());
96 private final Handler mDefaultMainHandler;
98 private GalleryApp mApplication;
99 private int mActiveCount = 0;
101 private HashMap<Uri, NotifyBroker> mNotifierMap =
102 new HashMap<Uri, NotifyBroker>();
105 private HashMap<String, MediaSource> mSourceMap =
106 new LinkedHashMap<String, MediaSource>();
108 public DataManager(GalleryApp application) {
109 mApplication = application;
110 mDefaultMainHandler = new Handler(application.getMainLooper());
113 public synchronized void initializeSourceMap() {
114 if (!mSourceMap.isEmpty()) return;
116 // the order matters, the UriSource must come last
117 addSource(new LocalSource(mApplication));
118 addSource(new PicasaSource(mApplication));
119 if (ApiHelper.HAS_MTP) {
120 addSource(new MtpSource(mApplication));
122 addSource(new ComboSource(mApplication));
123 addSource(new ClusterSource(mApplication));
124 addSource(new FilterSource(mApplication));
125 addSource(new UriSource(mApplication));
126 addSource(new SnailSource(mApplication));
128 if (mActiveCount > 0) {
129 for (MediaSource source : mSourceMap.values()) {
135 public String getTopSetPath(int typeBits) {
138 case INCLUDE_IMAGE: return TOP_IMAGE_SET_PATH;
139 case INCLUDE_VIDEO: return TOP_VIDEO_SET_PATH;
140 case INCLUDE_ALL: return TOP_SET_PATH;
141 case INCLUDE_LOCAL_IMAGE_ONLY: return TOP_LOCAL_IMAGE_SET_PATH;
142 case INCLUDE_LOCAL_VIDEO_ONLY: return TOP_LOCAL_VIDEO_SET_PATH;
143 case INCLUDE_LOCAL_ALL_ONLY: return TOP_LOCAL_SET_PATH;
144 default: throw new IllegalArgumentException();
149 void addSource(MediaSource source) {
150 mSourceMap.put(source.getPrefix(), source);
153 public MediaObject peekMediaObject(Path path) {
154 return path.getObject();
157 public MediaObject getMediaObject(Path path) {
158 MediaObject obj = path.getObject();
159 if (obj != null) return obj;
161 MediaSource source = mSourceMap.get(path.getPrefix());
162 if (source == null) {
163 Log.w(TAG, "cannot find media source for path: " + path);
168 MediaObject object = source.createMediaObject(path);
169 if (object == null) {
170 Log.w(TAG, "cannot create media object: " + path);
173 } catch (Throwable t) {
174 Log.w(TAG, "exception in creating media object: " + path, t);
179 public MediaObject getMediaObject(String s) {
180 return getMediaObject(Path.fromString(s));
183 public MediaSet getMediaSet(Path path) {
184 return (MediaSet) getMediaObject(path);
187 public MediaSet getMediaSet(String s) {
188 return (MediaSet) getMediaObject(s);
191 public MediaSet[] getMediaSetsFromString(String segment) {
192 String[] seq = Path.splitSequence(segment);
194 MediaSet[] sets = new MediaSet[n];
195 for (int i = 0; i < n; i++) {
196 sets[i] = getMediaSet(seq[i]);
201 // Maps a list of Paths to MediaItems, and invoke consumer.consume()
202 // for each MediaItem (may not be in the same order as the input list).
203 // An index number is also passed to consumer.consume() to identify
204 // the original position in the input list of the corresponding Path (plus
206 public void mapMediaItems(ArrayList<Path> list, ItemConsumer consumer,
208 HashMap<String, ArrayList<PathId>> map =
209 new HashMap<String, ArrayList<PathId>>();
211 // Group the path by the prefix.
213 for (int i = 0; i < n; i++) {
214 Path path = list.get(i);
215 String prefix = path.getPrefix();
216 ArrayList<PathId> group = map.get(prefix);
218 group = new ArrayList<PathId>();
219 map.put(prefix, group);
221 group.add(new PathId(path, i + startIndex));
224 // For each group, ask the corresponding media source to map it.
225 for (Entry<String, ArrayList<PathId>> entry : map.entrySet()) {
226 String prefix = entry.getKey();
227 MediaSource source = mSourceMap.get(prefix);
228 source.mapMediaItems(entry.getValue(), consumer);
232 // The following methods forward the request to the proper object.
233 public int getSupportedOperations(Path path) {
234 return getMediaObject(path).getSupportedOperations();
237 public void delete(Path path) {
238 getMediaObject(path).delete();
241 public void rotate(Path path, int degrees) {
242 getMediaObject(path).rotate(degrees);
245 public Uri getContentUri(Path path) {
246 return getMediaObject(path).getContentUri();
249 public int getMediaType(Path path) {
250 return getMediaObject(path).getMediaType();
253 public Path findPathByUri(Uri uri, String type) {
254 if (uri == null) return null;
255 for (MediaSource source : mSourceMap.values()) {
256 Path path = source.findPathByUri(uri, type);
257 if (path != null) return path;
262 public Path getDefaultSetOf(Path item) {
263 MediaSource source = mSourceMap.get(item.getPrefix());
264 return source == null ? null : source.getDefaultSetOf(item);
267 // Returns number of bytes used by cached pictures currently downloaded.
268 public long getTotalUsedCacheSize() {
270 for (MediaSource source : mSourceMap.values()) {
271 sum += source.getTotalUsedCacheSize();
276 // Returns number of bytes used by cached pictures if all pending
277 // downloads and removals are completed.
278 public long getTotalTargetCacheSize() {
280 for (MediaSource source : mSourceMap.values()) {
281 sum += source.getTotalTargetCacheSize();
286 public void registerChangeNotifier(Uri uri, ChangeNotifier notifier) {
287 NotifyBroker broker = null;
288 synchronized (mNotifierMap) {
289 broker = mNotifierMap.get(uri);
290 if (broker == null) {
291 broker = new NotifyBroker(mDefaultMainHandler);
292 mApplication.getContentResolver()
293 .registerContentObserver(uri, true, broker);
294 mNotifierMap.put(uri, broker);
297 broker.registerNotifier(notifier);
300 public void resume() {
301 if (++mActiveCount == 1) {
302 for (MediaSource source : mSourceMap.values()) {
308 public void pause() {
309 if (--mActiveCount == 0) {
310 for (MediaSource source : mSourceMap.values()) {
316 // Sends a local broadcast if a local image or video is deleted. This is
317 // used to update the thumbnail shown in the camera app.
318 public void broadcastLocalDeletion() {
319 LocalBroadcastManager manager = LocalBroadcastManager.getInstance(
320 mApplication.getAndroidContext());
321 Intent intent = new Intent(ACTION_DELETE_PICTURE);
322 manager.sendBroadcast(intent);
325 private static class NotifyBroker extends ContentObserver {
326 private WeakHashMap<ChangeNotifier, Object> mNotifiers =
327 new WeakHashMap<ChangeNotifier, Object>();
329 public NotifyBroker(Handler handler) {
333 public synchronized void registerNotifier(ChangeNotifier notifier) {
334 mNotifiers.put(notifier, null);
338 public synchronized void onChange(boolean selfChange) {
339 for(ChangeNotifier notifier : mNotifiers.keySet()) {
340 notifier.onChange(selfChange);