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