summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/app/AlbumSetDataLoader.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/app/AlbumSetDataLoader.java')
-rw-r--r--src/com/android/gallery3d/app/AlbumSetDataLoader.java393
1 files changed, 393 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/app/AlbumSetDataLoader.java b/src/com/android/gallery3d/app/AlbumSetDataLoader.java
new file mode 100644
index 000000000..cf380f812
--- /dev/null
+++ b/src/com/android/gallery3d/app/AlbumSetDataLoader.java
@@ -0,0 +1,393 @@
+/*
+ * 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.app;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.ui.SynchronizedHandler;
+
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+public class AlbumSetDataLoader {
+ @SuppressWarnings("unused")
+ private static final String TAG = "AlbumSetDataAdapter";
+
+ private static final int INDEX_NONE = -1;
+
+ private static final int MIN_LOAD_COUNT = 4;
+
+ private static final int MSG_LOAD_START = 1;
+ private static final int MSG_LOAD_FINISH = 2;
+ private static final int MSG_RUN_OBJECT = 3;
+
+ public static interface DataListener {
+ public void onContentChanged(int index);
+ public void onSizeChanged(int size);
+ }
+
+ private final MediaSet[] mData;
+ private final MediaItem[] mCoverItem;
+ private final int[] mTotalCount;
+ private final long[] mItemVersion;
+ private final long[] mSetVersion;
+
+ private int mActiveStart = 0;
+ private int mActiveEnd = 0;
+
+ private int mContentStart = 0;
+ private int mContentEnd = 0;
+
+ private final MediaSet mSource;
+ private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
+ private int mSize;
+
+ private DataListener mDataListener;
+ private LoadingListener mLoadingListener;
+ private ReloadTask mReloadTask;
+
+ private final Handler mMainHandler;
+
+ private final MySourceListener mSourceListener = new MySourceListener();
+
+ public AlbumSetDataLoader(AbstractGalleryActivity activity, MediaSet albumSet, int cacheSize) {
+ mSource = Utils.checkNotNull(albumSet);
+ mCoverItem = new MediaItem[cacheSize];
+ mData = new MediaSet[cacheSize];
+ mTotalCount = new int[cacheSize];
+ mItemVersion = new long[cacheSize];
+ mSetVersion = new long[cacheSize];
+ Arrays.fill(mItemVersion, MediaObject.INVALID_DATA_VERSION);
+ Arrays.fill(mSetVersion, MediaObject.INVALID_DATA_VERSION);
+
+ mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_RUN_OBJECT:
+ ((Runnable) message.obj).run();
+ return;
+ case MSG_LOAD_START:
+ if (mLoadingListener != null) mLoadingListener.onLoadingStarted();
+ return;
+ case MSG_LOAD_FINISH:
+ if (mLoadingListener != null) mLoadingListener.onLoadingFinished(false);
+ return;
+ }
+ }
+ };
+ }
+
+ public void pause() {
+ mReloadTask.terminate();
+ mReloadTask = null;
+ mSource.removeContentListener(mSourceListener);
+ }
+
+ public void resume() {
+ mSource.addContentListener(mSourceListener);
+ mReloadTask = new ReloadTask();
+ mReloadTask.start();
+ }
+
+ private void assertIsActive(int index) {
+ if (index < mActiveStart && index >= mActiveEnd) {
+ throw new IllegalArgumentException(String.format(
+ "%s not in (%s, %s)", index, mActiveStart, mActiveEnd));
+ }
+ }
+
+ public MediaSet getMediaSet(int index) {
+ assertIsActive(index);
+ return mData[index % mData.length];
+ }
+
+ public MediaItem getCoverItem(int index) {
+ assertIsActive(index);
+ return mCoverItem[index % mCoverItem.length];
+ }
+
+ public int getTotalCount(int index) {
+ assertIsActive(index);
+ return mTotalCount[index % mTotalCount.length];
+ }
+
+ public int getActiveStart() {
+ return mActiveStart;
+ }
+
+ public boolean isActive(int index) {
+ return index >= mActiveStart && index < mActiveEnd;
+ }
+
+ public int size() {
+ return mSize;
+ }
+
+ // Returns the index of the MediaSet with the given path or
+ // -1 if the path is not cached
+ public int findSet(Path id) {
+ int length = mData.length;
+ for (int i = mContentStart; i < mContentEnd; i++) {
+ MediaSet set = mData[i % length];
+ if (set != null && id == set.getPath()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void clearSlot(int slotIndex) {
+ mData[slotIndex] = null;
+ mCoverItem[slotIndex] = null;
+ mTotalCount[slotIndex] = 0;
+ mItemVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
+ mSetVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
+ }
+
+ private void setContentWindow(int contentStart, int contentEnd) {
+ if (contentStart == mContentStart && contentEnd == mContentEnd) return;
+ int length = mCoverItem.length;
+
+ int start = this.mContentStart;
+ int end = this.mContentEnd;
+
+ mContentStart = contentStart;
+ mContentEnd = contentEnd;
+
+ if (contentStart >= end || start >= contentEnd) {
+ for (int i = start, n = end; i < n; ++i) {
+ clearSlot(i % length);
+ }
+ } else {
+ for (int i = start; i < contentStart; ++i) {
+ clearSlot(i % length);
+ }
+ for (int i = contentEnd, n = end; i < n; ++i) {
+ clearSlot(i % length);
+ }
+ }
+ mReloadTask.notifyDirty();
+ }
+
+ public void setActiveWindow(int start, int end) {
+ if (start == mActiveStart && end == mActiveEnd) return;
+
+ Utils.assertTrue(start <= end
+ && end - start <= mCoverItem.length && end <= mSize);
+
+ mActiveStart = start;
+ mActiveEnd = end;
+
+ int length = mCoverItem.length;
+ // If no data is visible, keep the cache content
+ if (start == end) return;
+
+ int contentStart = Utils.clamp((start + end) / 2 - length / 2,
+ 0, Math.max(0, mSize - length));
+ int contentEnd = Math.min(contentStart + length, mSize);
+ if (mContentStart > start || mContentEnd < end
+ || Math.abs(contentStart - mContentStart) > MIN_LOAD_COUNT) {
+ setContentWindow(contentStart, contentEnd);
+ }
+ }
+
+ private class MySourceListener implements ContentListener {
+ @Override
+ public void onContentDirty() {
+ mReloadTask.notifyDirty();
+ }
+ }
+
+ public void setModelListener(DataListener listener) {
+ mDataListener = listener;
+ }
+
+ public void setLoadingListener(LoadingListener listener) {
+ mLoadingListener = listener;
+ }
+
+ private static class UpdateInfo {
+ public long version;
+ public int index;
+
+ public int size;
+ public MediaSet item;
+ public MediaItem cover;
+ public int totalCount;
+ }
+
+ private class GetUpdateInfo implements Callable<UpdateInfo> {
+
+ private final long mVersion;
+
+ public GetUpdateInfo(long version) {
+ mVersion = version;
+ }
+
+ private int getInvalidIndex(long version) {
+ long setVersion[] = mSetVersion;
+ int length = setVersion.length;
+ for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+ int index = i % length;
+ if (setVersion[i % length] != version) return i;
+ }
+ return INDEX_NONE;
+ }
+
+ @Override
+ public UpdateInfo call() throws Exception {
+ int index = getInvalidIndex(mVersion);
+ if (index == INDEX_NONE && mSourceVersion == mVersion) return null;
+ UpdateInfo info = new UpdateInfo();
+ info.version = mSourceVersion;
+ info.index = index;
+ info.size = mSize;
+ return info;
+ }
+ }
+
+ private class UpdateContent implements Callable<Void> {
+ private final UpdateInfo mUpdateInfo;
+
+ public UpdateContent(UpdateInfo info) {
+ mUpdateInfo = info;
+ }
+
+ @Override
+ public Void call() {
+ // Avoid notifying listeners of status change after pause
+ // Otherwise gallery will be in inconsistent state after resume.
+ if (mReloadTask == null) return null;
+ UpdateInfo info = mUpdateInfo;
+ mSourceVersion = info.version;
+ if (mSize != info.size) {
+ mSize = info.size;
+ if (mDataListener != null) mDataListener.onSizeChanged(mSize);
+ if (mContentEnd > mSize) mContentEnd = mSize;
+ if (mActiveEnd > mSize) mActiveEnd = mSize;
+ }
+ // Note: info.index could be INDEX_NONE, i.e., -1
+ if (info.index >= mContentStart && info.index < mContentEnd) {
+ int pos = info.index % mCoverItem.length;
+ mSetVersion[pos] = info.version;
+ long itemVersion = info.item.getDataVersion();
+ if (mItemVersion[pos] == itemVersion) return null;
+ mItemVersion[pos] = itemVersion;
+ mData[pos] = info.item;
+ mCoverItem[pos] = info.cover;
+ mTotalCount[pos] = info.totalCount;
+ if (mDataListener != null
+ && info.index >= mActiveStart && info.index < mActiveEnd) {
+ mDataListener.onContentChanged(info.index);
+ }
+ }
+ return null;
+ }
+ }
+
+ private <T> T executeAndWait(Callable<T> callable) {
+ FutureTask<T> task = new FutureTask<T>(callable);
+ mMainHandler.sendMessage(
+ mMainHandler.obtainMessage(MSG_RUN_OBJECT, task));
+ try {
+ return task.get();
+ } catch (InterruptedException e) {
+ return null;
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // TODO: load active range first
+ private class ReloadTask extends Thread {
+ private volatile boolean mActive = true;
+ private volatile boolean mDirty = true;
+ private volatile boolean mIsLoading = false;
+
+ private void updateLoading(boolean loading) {
+ if (mIsLoading == loading) return;
+ mIsLoading = loading;
+ mMainHandler.sendEmptyMessage(loading ? MSG_LOAD_START : MSG_LOAD_FINISH);
+ }
+
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+ boolean updateComplete = false;
+ while (mActive) {
+ synchronized (this) {
+ if (mActive && !mDirty && updateComplete) {
+ if (!mSource.isLoading()) updateLoading(false);
+ Utils.waitWithoutInterrupt(this);
+ continue;
+ }
+ }
+ mDirty = false;
+ updateLoading(true);
+
+ long version = mSource.reload();
+ UpdateInfo info = executeAndWait(new GetUpdateInfo(version));
+ updateComplete = info == null;
+ if (updateComplete) continue;
+ if (info.version != version) {
+ info.version = version;
+ info.size = mSource.getSubMediaSetCount();
+
+ // If the size becomes smaller after reload(), we may
+ // receive from GetUpdateInfo an index which is too
+ // big. Because the main thread is not aware of the size
+ // change until we call UpdateContent.
+ if (info.index >= info.size) {
+ info.index = INDEX_NONE;
+ }
+ }
+ if (info.index != INDEX_NONE) {
+ info.item = mSource.getSubMediaSet(info.index);
+ if (info.item == null) continue;
+ info.cover = info.item.getCoverMediaItem();
+ info.totalCount = info.item.getTotalMediaItemCount();
+ }
+ executeAndWait(new UpdateContent(info));
+ }
+ updateLoading(false);
+ }
+
+ public synchronized void notifyDirty() {
+ mDirty = true;
+ notifyAll();
+ }
+
+ public synchronized void terminate() {
+ mActive = false;
+ notifyAll();
+ }
+ }
+}
+
+