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