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