summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/data/MediaSet.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/data/MediaSet.java')
-rw-r--r--src/com/android/gallery3d/data/MediaSet.java348
1 files changed, 348 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/data/MediaSet.java b/src/com/android/gallery3d/data/MediaSet.java
new file mode 100644
index 000000000..683aa6b32
--- /dev/null
+++ b/src/com/android/gallery3d/data/MediaSet.java
@@ -0,0 +1,348 @@
+/*
+ * 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 com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.Future;
+
+import java.util.ArrayList;
+import java.util.WeakHashMap;
+
+// MediaSet is a directory-like data structure.
+// It contains MediaItems and sub-MediaSets.
+//
+// The primary interface are:
+// getMediaItemCount(), getMediaItem() and
+// getSubMediaSetCount(), getSubMediaSet().
+//
+// getTotalMediaItemCount() returns the number of all MediaItems, including
+// those in sub-MediaSets.
+public abstract class MediaSet extends MediaObject {
+ @SuppressWarnings("unused")
+ private static final String TAG = "MediaSet";
+
+ public static final int MEDIAITEM_BATCH_FETCH_COUNT = 500;
+ public static final int INDEX_NOT_FOUND = -1;
+
+ public static final int SYNC_RESULT_SUCCESS = 0;
+ public static final int SYNC_RESULT_CANCELLED = 1;
+ public static final int SYNC_RESULT_ERROR = 2;
+
+ /** Listener to be used with requestSync(SyncListener). */
+ public static interface SyncListener {
+ /**
+ * Called when the sync task completed. Completion may be due to normal termination,
+ * an exception, or cancellation.
+ *
+ * @param mediaSet the MediaSet that's done with sync
+ * @param resultCode one of the SYNC_RESULT_* constants
+ */
+ void onSyncDone(MediaSet mediaSet, int resultCode);
+ }
+
+ public MediaSet(Path path, long version) {
+ super(path, version);
+ }
+
+ public int getMediaItemCount() {
+ return 0;
+ }
+
+ // Returns the media items in the range [start, start + count).
+ //
+ // The number of media items returned may be less than the specified count
+ // if there are not enough media items available. The number of
+ // media items available may not be consistent with the return value of
+ // getMediaItemCount() because the contents of database may have already
+ // changed.
+ public ArrayList<MediaItem> getMediaItem(int start, int count) {
+ return new ArrayList<MediaItem>();
+ }
+
+ public MediaItem getCoverMediaItem() {
+ ArrayList<MediaItem> items = getMediaItem(0, 1);
+ if (items.size() > 0) return items.get(0);
+ for (int i = 0, n = getSubMediaSetCount(); i < n; i++) {
+ MediaItem cover = getSubMediaSet(i).getCoverMediaItem();
+ if (cover != null) return cover;
+ }
+ return null;
+ }
+
+ public int getSubMediaSetCount() {
+ return 0;
+ }
+
+ public MediaSet getSubMediaSet(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ public boolean isLeafAlbum() {
+ return false;
+ }
+
+ public boolean isCameraRoll() {
+ return false;
+ }
+
+ /**
+ * Method {@link #reload()} may process the loading task in background, this method tells
+ * its client whether the loading is still in process or not.
+ */
+ public boolean isLoading() {
+ return false;
+ }
+
+ public int getTotalMediaItemCount() {
+ int total = getMediaItemCount();
+ for (int i = 0, n = getSubMediaSetCount(); i < n; i++) {
+ total += getSubMediaSet(i).getTotalMediaItemCount();
+ }
+ return total;
+ }
+
+ // TODO: we should have better implementation of sub classes
+ public int getIndexOfItem(Path path, int hint) {
+ // hint < 0 is handled below
+ // first, try to find it around the hint
+ int start = Math.max(0,
+ hint - MEDIAITEM_BATCH_FETCH_COUNT / 2);
+ ArrayList<MediaItem> list = getMediaItem(
+ start, MEDIAITEM_BATCH_FETCH_COUNT);
+ int index = getIndexOf(path, list);
+ if (index != INDEX_NOT_FOUND) return start + index;
+
+ // try to find it globally
+ start = start == 0 ? MEDIAITEM_BATCH_FETCH_COUNT : 0;
+ list = getMediaItem(start, MEDIAITEM_BATCH_FETCH_COUNT);
+ while (true) {
+ index = getIndexOf(path, list);
+ if (index != INDEX_NOT_FOUND) return start + index;
+ if (list.size() < MEDIAITEM_BATCH_FETCH_COUNT) return INDEX_NOT_FOUND;
+ start += MEDIAITEM_BATCH_FETCH_COUNT;
+ list = getMediaItem(start, MEDIAITEM_BATCH_FETCH_COUNT);
+ }
+ }
+
+ protected int getIndexOf(Path path, ArrayList<MediaItem> list) {
+ for (int i = 0, n = list.size(); i < n; ++i) {
+ // item could be null only in ClusterAlbum
+ MediaObject item = list.get(i);
+ if (item != null && item.mPath == path) return i;
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ public abstract String getName();
+
+ private WeakHashMap<ContentListener, Object> mListeners =
+ new WeakHashMap<ContentListener, Object>();
+
+ // NOTE: The MediaSet only keeps a weak reference to the listener. The
+ // listener is automatically removed when there is no other reference to
+ // the listener.
+ public void addContentListener(ContentListener listener) {
+ mListeners.put(listener, null);
+ }
+
+ public void removeContentListener(ContentListener listener) {
+ mListeners.remove(listener);
+ }
+
+ // This should be called by subclasses when the content is changed.
+ public void notifyContentChanged() {
+ for (ContentListener listener : mListeners.keySet()) {
+ listener.onContentDirty();
+ }
+ }
+
+ // Reload the content. Return the current data version. reload() should be called
+ // in the same thread as getMediaItem(int, int) and getSubMediaSet(int).
+ public abstract long reload();
+
+ @Override
+ public MediaDetails getDetails() {
+ MediaDetails details = super.getDetails();
+ details.addDetail(MediaDetails.INDEX_TITLE, getName());
+ return details;
+ }
+
+ // Enumerate all media items in this media set (including the ones in sub
+ // media sets), in an efficient order. ItemConsumer.consumer() will be
+ // called for each media item with its index.
+ public void enumerateMediaItems(ItemConsumer consumer) {
+ enumerateMediaItems(consumer, 0);
+ }
+
+ public void enumerateTotalMediaItems(ItemConsumer consumer) {
+ enumerateTotalMediaItems(consumer, 0);
+ }
+
+ public static interface ItemConsumer {
+ void consume(int index, MediaItem item);
+ }
+
+ // The default implementation uses getMediaItem() for enumerateMediaItems().
+ // Subclasses may override this and use more efficient implementations.
+ // Returns the number of items enumerated.
+ protected int enumerateMediaItems(ItemConsumer consumer, int startIndex) {
+ int total = getMediaItemCount();
+ int start = 0;
+ while (start < total) {
+ int count = Math.min(MEDIAITEM_BATCH_FETCH_COUNT, total - start);
+ ArrayList<MediaItem> items = getMediaItem(start, count);
+ for (int i = 0, n = items.size(); i < n; i++) {
+ MediaItem item = items.get(i);
+ consumer.consume(startIndex + start + i, item);
+ }
+ start += count;
+ }
+ return total;
+ }
+
+ // Recursively enumerate all media items under this set.
+ // Returns the number of items enumerated.
+ protected int enumerateTotalMediaItems(
+ ItemConsumer consumer, int startIndex) {
+ int start = 0;
+ start += enumerateMediaItems(consumer, startIndex);
+ int m = getSubMediaSetCount();
+ for (int i = 0; i < m; i++) {
+ start += getSubMediaSet(i).enumerateTotalMediaItems(
+ consumer, startIndex + start);
+ }
+ return start;
+ }
+
+ /**
+ * Requests sync on this MediaSet. It returns a Future object that can be used by the caller
+ * to query the status of the sync. The sync result code is one of the SYNC_RESULT_* constants
+ * defined in this class and can be obtained by Future.get().
+ *
+ * Subclasses should perform sync on a different thread.
+ *
+ * The default implementation here returns a Future stub that does nothing and returns
+ * SYNC_RESULT_SUCCESS by get().
+ */
+ public Future<Integer> requestSync(SyncListener listener) {
+ listener.onSyncDone(this, SYNC_RESULT_SUCCESS);
+ return FUTURE_STUB;
+ }
+
+ private static final Future<Integer> FUTURE_STUB = new Future<Integer>() {
+ @Override
+ public void cancel() {}
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ @Override
+ public Integer get() {
+ return SYNC_RESULT_SUCCESS;
+ }
+
+ @Override
+ public void waitDone() {}
+ };
+
+ protected Future<Integer> requestSyncOnMultipleSets(MediaSet[] sets, SyncListener listener) {
+ return new MultiSetSyncFuture(sets, listener);
+ }
+
+ private class MultiSetSyncFuture implements Future<Integer>, SyncListener {
+ @SuppressWarnings("hiding")
+ private static final String TAG = "Gallery.MultiSetSync";
+
+ private final SyncListener mListener;
+ private final Future<Integer> mFutures[];
+
+ private boolean mIsCancelled = false;
+ private int mResult = -1;
+ private int mPendingCount;
+
+ @SuppressWarnings("unchecked")
+ MultiSetSyncFuture(MediaSet[] sets, SyncListener listener) {
+ mListener = listener;
+ mPendingCount = sets.length;
+ mFutures = new Future[sets.length];
+
+ synchronized (this) {
+ for (int i = 0, n = sets.length; i < n; ++i) {
+ mFutures[i] = sets[i].requestSync(this);
+ Log.d(TAG, " request sync: " + Utils.maskDebugInfo(sets[i].getName()));
+ }
+ }
+ }
+
+ @Override
+ public synchronized void cancel() {
+ if (mIsCancelled) return;
+ mIsCancelled = true;
+ for (Future<Integer> future : mFutures) future.cancel();
+ if (mResult < 0) mResult = SYNC_RESULT_CANCELLED;
+ }
+
+ @Override
+ public synchronized boolean isCancelled() {
+ return mIsCancelled;
+ }
+
+ @Override
+ public synchronized boolean isDone() {
+ return mPendingCount == 0;
+ }
+
+ @Override
+ public synchronized Integer get() {
+ waitDone();
+ return mResult;
+ }
+
+ @Override
+ public synchronized void waitDone() {
+ try {
+ while (!isDone()) wait();
+ } catch (InterruptedException e) {
+ Log.d(TAG, "waitDone() interrupted");
+ }
+ }
+
+ // SyncListener callback
+ @Override
+ public void onSyncDone(MediaSet mediaSet, int resultCode) {
+ SyncListener listener = null;
+ synchronized (this) {
+ if (resultCode == SYNC_RESULT_ERROR) mResult = SYNC_RESULT_ERROR;
+ --mPendingCount;
+ if (mPendingCount == 0) {
+ listener = mListener;
+ notifyAll();
+ }
+ Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName())
+ + " #pending=" + mPendingCount);
+ }
+ if (listener != null) listener.onSyncDone(MediaSet.this, mResult);
+ }
+ }
+}