blob: eeab8a885376e4cf996911f32905568a16c3f7ab [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.gallery3d.data;
import android.content.Intent;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.support.v4.content.LocalBroadcastManager;
import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.MediaSet.ItemConsumer;
import com.android.gallery3d.data.MediaSource.PathId;
import com.android.gallery3d.picasasource.PicasaSource;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.WeakHashMap;
// DataManager manages all media sets and media items in the system.
//
// Each MediaSet and MediaItem has a unique 64 bits id. The most significant
// 32 bits represents its parent, and the least significant 32 bits represents
// the self id. For MediaSet the self id is is globally unique, but for
// MediaItem it's unique only relative to its parent.
//
// To make sure the id is the same when the MediaSet is re-created, a child key
// is provided to obtainSetId() to make sure the same self id will be used as
// when the parent and key are the same. A sequence of child keys is called a
// path. And it's used to identify a specific media set even if the process is
// killed and re-created, so child keys should be stable identifiers.
public class DataManager {
public static final int INCLUDE_IMAGE = 1;
public static final int INCLUDE_VIDEO = 2;
public static final int INCLUDE_ALL = INCLUDE_IMAGE | INCLUDE_VIDEO;
public static final int INCLUDE_LOCAL_ONLY = 4;
public static final int INCLUDE_LOCAL_IMAGE_ONLY =
INCLUDE_LOCAL_ONLY | INCLUDE_IMAGE;
public static final int INCLUDE_LOCAL_VIDEO_ONLY =
INCLUDE_LOCAL_ONLY | INCLUDE_VIDEO;
public static final int INCLUDE_LOCAL_ALL_ONLY =
INCLUDE_LOCAL_ONLY | INCLUDE_IMAGE | INCLUDE_VIDEO;
// Any one who would like to access data should require this lock
// to prevent concurrency issue.
public static final Object LOCK = new Object();
private static final String TAG = "DataManager";
// This is the path for the media set seen by the user at top level.
private static final String TOP_SET_PATH = ApiHelper.HAS_MTP
? "/combo/{/mtp,/local/all,/picasa/all}"
: "/combo/{/local/all,/picasa/all}";
private static final String TOP_IMAGE_SET_PATH = ApiHelper.HAS_MTP
? "/combo/{/mtp,/local/image,/picasa/image}"
: "/combo/{/local/image,/picasa/image}";
private static final String TOP_VIDEO_SET_PATH =
"/combo/{/local/video,/picasa/video}";
private static final String TOP_LOCAL_SET_PATH = "/local/all";
private static final String TOP_LOCAL_IMAGE_SET_PATH = "/local/image";
private static final String TOP_LOCAL_VIDEO_SET_PATH = "/local/video";
private static final String ACTION_DELETE_PICTURE =
"com.android.gallery3d.action.DELETE_PICTURE";
public static final Comparator<MediaItem> sDateTakenComparator =
new DateTakenComparator();
private static class DateTakenComparator implements Comparator<MediaItem> {
@Override
public int compare(MediaItem item1, MediaItem item2) {
return -Utils.compare(item1.getDateInMs(), item2.getDateInMs());
}
}
private final Handler mDefaultMainHandler;
private GalleryApp mApplication;
private int mActiveCount = 0;
private HashMap<Uri, NotifyBroker> mNotifierMap =
new HashMap<Uri, NotifyBroker>();
private HashMap<String, MediaSource> mSourceMap =
new LinkedHashMap<String, MediaSource>();
public DataManager(GalleryApp application) {
mApplication = application;
mDefaultMainHandler = new Handler(application.getMainLooper());
}
public synchronized void initializeSourceMap() {
if (!mSourceMap.isEmpty()) return;
// the order matters, the UriSource must come last
addSource(new LocalSource(mApplication));
addSource(new PicasaSource(mApplication));
if (ApiHelper.HAS_MTP) {
addSource(new MtpSource(mApplication));
}
addSource(new ComboSource(mApplication));
addSource(new ClusterSource(mApplication));
addSource(new FilterSource(mApplication));
addSource(new SecureSource(mApplication));
addSource(new UriSource(mApplication));
addSource(new SnailSource(mApplication));
if (mActiveCount > 0) {
for (MediaSource source : mSourceMap.values()) {
source.resume();
}
}
}
public String getTopSetPath(int typeBits) {
switch (typeBits) {
case INCLUDE_IMAGE: return TOP_IMAGE_SET_PATH;
case INCLUDE_VIDEO: return TOP_VIDEO_SET_PATH;
case INCLUDE_ALL: return TOP_SET_PATH;
case INCLUDE_LOCAL_IMAGE_ONLY: return TOP_LOCAL_IMAGE_SET_PATH;
case INCLUDE_LOCAL_VIDEO_ONLY: return TOP_LOCAL_VIDEO_SET_PATH;
case INCLUDE_LOCAL_ALL_ONLY: return TOP_LOCAL_SET_PATH;
default: throw new IllegalArgumentException();
}
}
// open for debug
void addSource(MediaSource source) {
mSourceMap.put(source.getPrefix(), source);
}
// A common usage of this method is:
// synchronized (DataManager.LOCK) {
// MediaObject object = peekMediaObject(path);
// if (object == null) {
// object = createMediaObject(...);
// }
// }
public MediaObject peekMediaObject(Path path) {
return path.getObject();
}
public MediaObject getMediaObject(Path path) {
synchronized (LOCK) {
MediaObject obj = path.getObject();
if (obj != null) return obj;
MediaSource source = mSourceMap.get(path.getPrefix());
if (source == null) {
Log.w(TAG, "cannot find media source for path: " + path);
return null;
}
try {
MediaObject object = source.createMediaObject(path);
if (object == null) {
Log.w(TAG, "cannot create media object: " + path);
}
return object;
} catch (Throwable t) {
Log.w(TAG, "exception in creating media object: " + path, t);
return null;
}
}
}
public MediaObject getMediaObject(String s) {
return getMediaObject(Path.fromString(s));
}
public MediaSet getMediaSet(Path path) {
return (MediaSet) getMediaObject(path);
}
public MediaSet getMediaSet(String s) {
return (MediaSet) getMediaObject(s);
}
public MediaSet[] getMediaSetsFromString(String segment) {
String[] seq = Path.splitSequence(segment);
int n = seq.length;
MediaSet[] sets = new MediaSet[n];
for (int i = 0; i < n; i++) {
sets[i] = getMediaSet(seq[i]);
}
return sets;
}
// Maps a list of Paths to MediaItems, and invoke consumer.consume()
// for each MediaItem (may not be in the same order as the input list).
// An index number is also passed to consumer.consume() to identify
// the original position in the input list of the corresponding Path (plus
// startIndex).
public void mapMediaItems(ArrayList<Path> list, ItemConsumer consumer,
int startIndex) {
HashMap<String, ArrayList<PathId>> map =
new HashMap<String, ArrayList<PathId>>();
// Group the path by the prefix.
int n = list.size();
for (int i = 0; i < n; i++) {
Path path = list.get(i);
String prefix = path.getPrefix();
ArrayList<PathId> group = map.get(prefix);
if (group == null) {
group = new ArrayList<PathId>();
map.put(prefix, group);
}
group.add(new PathId(path, i + startIndex));
}
// For each group, ask the corresponding media source to map it.
for (Entry<String, ArrayList<PathId>> entry : map.entrySet()) {
String prefix = entry.getKey();
MediaSource source = mSourceMap.get(prefix);
source.mapMediaItems(entry.getValue(), consumer);
}
}
// The following methods forward the request to the proper object.
public int getSupportedOperations(Path path) {
return getMediaObject(path).getSupportedOperations();
}
public void delete(Path path) {
getMediaObject(path).delete();
}
public void rotate(Path path, int degrees) {
getMediaObject(path).rotate(degrees);
}
public Uri getContentUri(Path path) {
return getMediaObject(path).getContentUri();
}
public int getMediaType(Path path) {
return getMediaObject(path).getMediaType();
}
public Path findPathByUri(Uri uri, String type) {
if (uri == null) return null;
for (MediaSource source : mSourceMap.values()) {
Path path = source.findPathByUri(uri, type);
if (path != null) return path;
}
return null;
}
public Path getDefaultSetOf(Path item) {
MediaSource source = mSourceMap.get(item.getPrefix());
return source == null ? null : source.getDefaultSetOf(item);
}
// Returns number of bytes used by cached pictures currently downloaded.
public long getTotalUsedCacheSize() {
long sum = 0;
for (MediaSource source : mSourceMap.values()) {
sum += source.getTotalUsedCacheSize();
}
return sum;
}
// Returns number of bytes used by cached pictures if all pending
// downloads and removals are completed.
public long getTotalTargetCacheSize() {
long sum = 0;
for (MediaSource source : mSourceMap.values()) {
sum += source.getTotalTargetCacheSize();
}
return sum;
}
public void registerChangeNotifier(Uri uri, ChangeNotifier notifier) {
NotifyBroker broker = null;
synchronized (mNotifierMap) {
broker = mNotifierMap.get(uri);
if (broker == null) {
broker = new NotifyBroker(mDefaultMainHandler);
mApplication.getContentResolver()
.registerContentObserver(uri, true, broker);
mNotifierMap.put(uri, broker);
}
}
broker.registerNotifier(notifier);
}
public void resume() {
if (++mActiveCount == 1) {
for (MediaSource source : mSourceMap.values()) {
source.resume();
}
}
}
public void pause() {
if (--mActiveCount == 0) {
for (MediaSource source : mSourceMap.values()) {
source.pause();
}
}
}
// Sends a local broadcast if a local image or video is deleted. This is
// used to update the thumbnail shown in the camera app.
public void broadcastLocalDeletion() {
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(
mApplication.getAndroidContext());
Intent intent = new Intent(ACTION_DELETE_PICTURE);
manager.sendBroadcast(intent);
}
private static class NotifyBroker extends ContentObserver {
private WeakHashMap<ChangeNotifier, Object> mNotifiers =
new WeakHashMap<ChangeNotifier, Object>();
public NotifyBroker(Handler handler) {
super(handler);
}
public synchronized void registerNotifier(ChangeNotifier notifier) {
mNotifiers.put(notifier, null);
}
@Override
public synchronized void onChange(boolean selfChange) {
for(ChangeNotifier notifier : mNotifiers.keySet()) {
notifier.onChange(selfChange);
}
}
}
}