Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 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 | |
| 17 | package com.android.gallery3d.data; |
| 18 | |
Chih-Chung Chang | c7e89da | 2012-06-26 19:12:09 +0800 | [diff] [blame^] | 19 | import android.content.Intent; |
Owen Lin | 2b3ee0e | 2012-03-14 17:27:24 +0800 | [diff] [blame] | 20 | import android.database.ContentObserver; |
| 21 | import android.net.Uri; |
| 22 | import android.os.Handler; |
Chih-Chung Chang | c7e89da | 2012-06-26 19:12:09 +0800 | [diff] [blame^] | 23 | import android.support.v4.content.LocalBroadcastManager; |
Owen Lin | 2b3ee0e | 2012-03-14 17:27:24 +0800 | [diff] [blame] | 24 | |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 25 | import com.android.gallery3d.app.GalleryApp; |
| 26 | import com.android.gallery3d.common.Utils; |
| 27 | import com.android.gallery3d.data.MediaSet.ItemConsumer; |
| 28 | import com.android.gallery3d.data.MediaSource.PathId; |
| 29 | import com.android.gallery3d.picasasource.PicasaSource; |
| 30 | |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 31 | import java.util.ArrayList; |
| 32 | import java.util.Comparator; |
| 33 | import java.util.HashMap; |
| 34 | import java.util.LinkedHashMap; |
| 35 | import java.util.Map.Entry; |
| 36 | import 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 | |
| 51 | public 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 Chang | c7e89da | 2012-06-26 19:12:09 +0800 | [diff] [blame^] | 83 | private static final String ACTION_DELETE_PICTURE = |
| 84 | "com.android.gallery3d.action.DELETE_PICTURE"; |
| 85 | |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 86 | 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 Chang | 15b351a | 2012-03-15 16:38:45 +0800 | [diff] [blame] | 123 | addSource(new SnailSource(mApplication)); |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 124 | |
| 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 Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 154 | 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 Ting | f31954e | 2012-01-13 17:48:10 +0800 | [diff] [blame] | 164 | 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 Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 173 | } |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 174 | } |
| 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 Lin | 21a412c | 2012-04-20 18:42:22 +0800 | [diff] [blame] | 250 | public Path findPathByUri(Uri uri, String type) { |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 251 | if (uri == null) return null; |
| 252 | for (MediaSource source : mSourceMap.values()) { |
Owen Lin | 21a412c | 2012-04-20 18:42:22 +0800 | [diff] [blame] | 253 | Path path = source.findPathByUri(uri, type); |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 254 | 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 Chang | c7e89da | 2012-06-26 19:12:09 +0800 | [diff] [blame^] | 313 | // 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 Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 322 | 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 | } |