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 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; 24 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 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 83 private static final String ACTION_DELETE_PICTURE = 84 "com.android.gallery3d.action.DELETE_PICTURE"; 85 86 public static final Comparator<MediaItem> sDateTakenComparator = 87 new DateTakenComparator(); 88 89 private static class DateTakenComparator implements Comparator<MediaItem> { compare(MediaItem item1, MediaItem item2)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 DataManager(GalleryApp application)107 public DataManager(GalleryApp application) { 108 mApplication = application; 109 mDefaultMainHandler = new Handler(application.getMainLooper()); 110 } 111 initializeSourceMap()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)); 123 addSource(new SnailSource(mApplication)); 124 125 if (mActiveCount > 0) { 126 for (MediaSource source : mSourceMap.values()) { 127 source.resume(); 128 } 129 } 130 } 131 getTopSetPath(int typeBits)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 addSource(MediaSource source)146 void addSource(MediaSource source) { 147 mSourceMap.put(source.getPrefix(), source); 148 } 149 peekMediaObject(Path path)150 public MediaObject peekMediaObject(Path path) { 151 return path.getObject(); 152 } 153 getMediaObject(Path path)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 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; 173 } 174 } 175 getMediaObject(String s)176 public MediaObject getMediaObject(String s) { 177 return getMediaObject(Path.fromString(s)); 178 } 179 getMediaSet(Path path)180 public MediaSet getMediaSet(Path path) { 181 return (MediaSet) getMediaObject(path); 182 } 183 getMediaSet(String s)184 public MediaSet getMediaSet(String s) { 185 return (MediaSet) getMediaObject(s); 186 } 187 getMediaSetsFromString(String segment)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). mapMediaItems(ArrayList<Path> list, ItemConsumer consumer, int 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. getSupportedOperations(Path path)230 public int getSupportedOperations(Path path) { 231 return getMediaObject(path).getSupportedOperations(); 232 } 233 delete(Path path)234 public void delete(Path path) { 235 getMediaObject(path).delete(); 236 } 237 rotate(Path path, int degrees)238 public void rotate(Path path, int degrees) { 239 getMediaObject(path).rotate(degrees); 240 } 241 getContentUri(Path path)242 public Uri getContentUri(Path path) { 243 return getMediaObject(path).getContentUri(); 244 } 245 getMediaType(Path path)246 public int getMediaType(Path path) { 247 return getMediaObject(path).getMediaType(); 248 } 249 findPathByUri(Uri uri, String type)250 public Path findPathByUri(Uri uri, String type) { 251 if (uri == null) return null; 252 for (MediaSource source : mSourceMap.values()) { 253 Path path = source.findPathByUri(uri, type); 254 if (path != null) return path; 255 } 256 return null; 257 } 258 getDefaultSetOf(Path item)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. getTotalUsedCacheSize()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. getTotalTargetCacheSize()275 public long getTotalTargetCacheSize() { 276 long sum = 0; 277 for (MediaSource source : mSourceMap.values()) { 278 sum += source.getTotalTargetCacheSize(); 279 } 280 return sum; 281 } 282 registerChangeNotifier(Uri uri, ChangeNotifier notifier)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 resume()297 public void resume() { 298 if (++mActiveCount == 1) { 299 for (MediaSource source : mSourceMap.values()) { 300 source.resume(); 301 } 302 } 303 } 304 pause()305 public void pause() { 306 if (--mActiveCount == 0) { 307 for (MediaSource source : mSourceMap.values()) { 308 source.pause(); 309 } 310 } 311 } 312 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. broadcastLocalDeletion()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 322 private static class NotifyBroker extends ContentObserver { 323 private WeakHashMap<ChangeNotifier, Object> mNotifiers = 324 new WeakHashMap<ChangeNotifier, Object>(); 325 NotifyBroker(Handler handler)326 public NotifyBroker(Handler handler) { 327 super(handler); 328 } 329 registerNotifier(ChangeNotifier notifier)330 public synchronized void registerNotifier(ChangeNotifier notifier) { 331 mNotifiers.put(notifier, null); 332 } 333 334 @Override onChange(boolean selfChange)335 public synchronized void onChange(boolean selfChange) { 336 for(ChangeNotifier notifier : mNotifiers.keySet()) { 337 notifier.onChange(selfChange); 338 } 339 } 340 } 341 } 342