summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java')
-rw-r--r--src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java549
1 files changed, 549 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
new file mode 100644
index 000000000..8149df4b3
--- /dev/null
+++ b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
@@ -0,0 +1,549 @@
+/*T
+ * 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.ui;
+
+import android.graphics.Bitmap;
+import android.os.Message;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.AbstractGalleryActivity;
+import com.android.gallery3d.app.AlbumSetDataLoader;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.DataSourceType;
+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.glrenderer.BitmapTexture;
+import com.android.gallery3d.glrenderer.Texture;
+import com.android.gallery3d.glrenderer.TextureUploader;
+import com.android.gallery3d.glrenderer.TiledTexture;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.ThreadPool;
+
+public class AlbumSetSlidingWindow implements AlbumSetDataLoader.DataListener {
+ private static final String TAG = "AlbumSetSlidingWindow";
+ private static final int MSG_UPDATE_ALBUM_ENTRY = 1;
+
+ public static interface Listener {
+ public void onSizeChanged(int size);
+ public void onContentChanged();
+ }
+
+ private final AlbumSetDataLoader mSource;
+ private int mSize;
+
+ private int mContentStart = 0;
+ private int mContentEnd = 0;
+
+ private int mActiveStart = 0;
+ private int mActiveEnd = 0;
+
+ private Listener mListener;
+
+ private final AlbumSetEntry mData[];
+ private final SynchronizedHandler mHandler;
+ private final ThreadPool mThreadPool;
+ private final AlbumLabelMaker mLabelMaker;
+ private final String mLoadingText;
+
+ private final TiledTexture.Uploader mContentUploader;
+ private final TextureUploader mLabelUploader;
+
+ private int mActiveRequestCount = 0;
+ private boolean mIsActive = false;
+ private BitmapTexture mLoadingLabel;
+
+ private int mSlotWidth;
+
+ public static class AlbumSetEntry {
+ public MediaSet album;
+ public MediaItem coverItem;
+ public Texture content;
+ public BitmapTexture labelTexture;
+ public TiledTexture bitmapTexture;
+ public Path setPath;
+ public String title;
+ public int totalCount;
+ public int sourceType;
+ public int cacheFlag;
+ public int cacheStatus;
+ public int rotation;
+ public boolean isWaitLoadingDisplayed;
+ public long setDataVersion;
+ public long coverDataVersion;
+ private BitmapLoader labelLoader;
+ private BitmapLoader coverLoader;
+ }
+
+ public AlbumSetSlidingWindow(AbstractGalleryActivity activity,
+ AlbumSetDataLoader source, AlbumSetSlotRenderer.LabelSpec labelSpec, int cacheSize) {
+ source.setModelListener(this);
+ mSource = source;
+ mData = new AlbumSetEntry[cacheSize];
+ mSize = source.size();
+ mThreadPool = activity.getThreadPool();
+
+ mLabelMaker = new AlbumLabelMaker(activity.getAndroidContext(), labelSpec);
+ mLoadingText = activity.getAndroidContext().getString(R.string.loading);
+ mContentUploader = new TiledTexture.Uploader(activity.getGLRoot());
+ mLabelUploader = new TextureUploader(activity.getGLRoot());
+
+ mHandler = new SynchronizedHandler(activity.getGLRoot()) {
+ @Override
+ public void handleMessage(Message message) {
+ Utils.assertTrue(message.what == MSG_UPDATE_ALBUM_ENTRY);
+ ((EntryUpdater) message.obj).updateEntry();
+ }
+ };
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ public AlbumSetEntry get(int slotIndex) {
+ if (!isActiveSlot(slotIndex)) {
+ Utils.fail("invalid slot: %s outsides (%s, %s)",
+ slotIndex, mActiveStart, mActiveEnd);
+ }
+ return mData[slotIndex % mData.length];
+ }
+
+ public int size() {
+ return mSize;
+ }
+
+ public boolean isActiveSlot(int slotIndex) {
+ return slotIndex >= mActiveStart && slotIndex < mActiveEnd;
+ }
+
+ private void setContentWindow(int contentStart, int contentEnd) {
+ if (contentStart == mContentStart && contentEnd == mContentEnd) return;
+
+ if (contentStart >= mContentEnd || mContentStart >= contentEnd) {
+ for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+ freeSlotContent(i);
+ }
+ mSource.setActiveWindow(contentStart, contentEnd);
+ for (int i = contentStart; i < contentEnd; ++i) {
+ prepareSlotContent(i);
+ }
+ } else {
+ for (int i = mContentStart; i < contentStart; ++i) {
+ freeSlotContent(i);
+ }
+ for (int i = contentEnd, n = mContentEnd; i < n; ++i) {
+ freeSlotContent(i);
+ }
+ mSource.setActiveWindow(contentStart, contentEnd);
+ for (int i = contentStart, n = mContentStart; i < n; ++i) {
+ prepareSlotContent(i);
+ }
+ for (int i = mContentEnd; i < contentEnd; ++i) {
+ prepareSlotContent(i);
+ }
+ }
+
+ mContentStart = contentStart;
+ mContentEnd = contentEnd;
+ }
+
+ public void setActiveWindow(int start, int end) {
+ if (!(start <= end && end - start <= mData.length && end <= mSize)) {
+ Utils.fail("start = %s, end = %s, length = %s, size = %s",
+ start, end, mData.length, mSize);
+ }
+
+ AlbumSetEntry data[] = mData;
+ mActiveStart = start;
+ mActiveEnd = end;
+ int contentStart = Utils.clamp((start + end) / 2 - data.length / 2,
+ 0, Math.max(0, mSize - data.length));
+ int contentEnd = Math.min(contentStart + data.length, mSize);
+ setContentWindow(contentStart, contentEnd);
+
+ if (mIsActive) {
+ updateTextureUploadQueue();
+ updateAllImageRequests();
+ }
+ }
+
+ // We would like to request non active slots in the following order:
+ // Order: 8 6 4 2 1 3 5 7
+ // |---------|---------------|---------|
+ // |<- active ->|
+ // |<-------- cached range ----------->|
+ private void requestNonactiveImages() {
+ int range = Math.max(
+ mContentEnd - mActiveEnd, mActiveStart - mContentStart);
+ for (int i = 0 ;i < range; ++i) {
+ requestImagesInSlot(mActiveEnd + i);
+ requestImagesInSlot(mActiveStart - 1 - i);
+ }
+ }
+
+ private void cancelNonactiveImages() {
+ int range = Math.max(
+ mContentEnd - mActiveEnd, mActiveStart - mContentStart);
+ for (int i = 0 ;i < range; ++i) {
+ cancelImagesInSlot(mActiveEnd + i);
+ cancelImagesInSlot(mActiveStart - 1 - i);
+ }
+ }
+
+ private void requestImagesInSlot(int slotIndex) {
+ if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
+ AlbumSetEntry entry = mData[slotIndex % mData.length];
+ if (entry.coverLoader != null) entry.coverLoader.startLoad();
+ if (entry.labelLoader != null) entry.labelLoader.startLoad();
+ }
+
+ private void cancelImagesInSlot(int slotIndex) {
+ if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
+ AlbumSetEntry entry = mData[slotIndex % mData.length];
+ if (entry.coverLoader != null) entry.coverLoader.cancelLoad();
+ if (entry.labelLoader != null) entry.labelLoader.cancelLoad();
+ }
+
+ private static long getDataVersion(MediaObject object) {
+ return object == null
+ ? MediaSet.INVALID_DATA_VERSION
+ : object.getDataVersion();
+ }
+
+ private void freeSlotContent(int slotIndex) {
+ AlbumSetEntry entry = mData[slotIndex % mData.length];
+ if (entry.coverLoader != null) entry.coverLoader.recycle();
+ if (entry.labelLoader != null) entry.labelLoader.recycle();
+ if (entry.labelTexture != null) entry.labelTexture.recycle();
+ if (entry.bitmapTexture != null) entry.bitmapTexture.recycle();
+ mData[slotIndex % mData.length] = null;
+ }
+
+ private boolean isLabelChanged(
+ AlbumSetEntry entry, String title, int totalCount, int sourceType) {
+ return !Utils.equals(entry.title, title)
+ || entry.totalCount != totalCount
+ || entry.sourceType != sourceType;
+ }
+
+ private void updateAlbumSetEntry(AlbumSetEntry entry, int slotIndex) {
+ MediaSet album = mSource.getMediaSet(slotIndex);
+ MediaItem cover = mSource.getCoverItem(slotIndex);
+ int totalCount = mSource.getTotalCount(slotIndex);
+
+ entry.album = album;
+ entry.setDataVersion = getDataVersion(album);
+ entry.cacheFlag = identifyCacheFlag(album);
+ entry.cacheStatus = identifyCacheStatus(album);
+ entry.setPath = (album == null) ? null : album.getPath();
+
+ String title = (album == null) ? "" : Utils.ensureNotNull(album.getName());
+ int sourceType = DataSourceType.identifySourceType(album);
+ if (isLabelChanged(entry, title, totalCount, sourceType)) {
+ entry.title = title;
+ entry.totalCount = totalCount;
+ entry.sourceType = sourceType;
+ if (entry.labelLoader != null) {
+ entry.labelLoader.recycle();
+ entry.labelLoader = null;
+ entry.labelTexture = null;
+ }
+ if (album != null) {
+ entry.labelLoader = new AlbumLabelLoader(
+ slotIndex, title, totalCount, sourceType);
+ }
+ }
+
+ entry.coverItem = cover;
+ if (getDataVersion(cover) != entry.coverDataVersion) {
+ entry.coverDataVersion = getDataVersion(cover);
+ entry.rotation = (cover == null) ? 0 : cover.getRotation();
+ if (entry.coverLoader != null) {
+ entry.coverLoader.recycle();
+ entry.coverLoader = null;
+ entry.bitmapTexture = null;
+ entry.content = null;
+ }
+ if (cover != null) {
+ entry.coverLoader = new AlbumCoverLoader(slotIndex, cover);
+ }
+ }
+ }
+
+ private void prepareSlotContent(int slotIndex) {
+ AlbumSetEntry entry = new AlbumSetEntry();
+ updateAlbumSetEntry(entry, slotIndex);
+ mData[slotIndex % mData.length] = entry;
+ }
+
+ private static boolean startLoadBitmap(BitmapLoader loader) {
+ if (loader == null) return false;
+ loader.startLoad();
+ return loader.isRequestInProgress();
+ }
+
+ private void uploadBackgroundTextureInSlot(int index) {
+ if (index < mContentStart || index >= mContentEnd) return;
+ AlbumSetEntry entry = mData[index % mData.length];
+ if (entry.bitmapTexture != null) {
+ mContentUploader.addTexture(entry.bitmapTexture);
+ }
+ if (entry.labelTexture != null) {
+ mLabelUploader.addBgTexture(entry.labelTexture);
+ }
+ }
+
+ private void updateTextureUploadQueue() {
+ if (!mIsActive) return;
+ mContentUploader.clear();
+ mLabelUploader.clear();
+
+ // Upload foreground texture
+ for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
+ AlbumSetEntry entry = mData[i % mData.length];
+ if (entry.bitmapTexture != null) {
+ mContentUploader.addTexture(entry.bitmapTexture);
+ }
+ if (entry.labelTexture != null) {
+ mLabelUploader.addFgTexture(entry.labelTexture);
+ }
+ }
+
+ // add background textures
+ int range = Math.max(
+ (mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
+ for (int i = 0; i < range; ++i) {
+ uploadBackgroundTextureInSlot(mActiveEnd + i);
+ uploadBackgroundTextureInSlot(mActiveStart - i - 1);
+ }
+ }
+
+ private void updateAllImageRequests() {
+ mActiveRequestCount = 0;
+ for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
+ AlbumSetEntry entry = mData[i % mData.length];
+ if (startLoadBitmap(entry.coverLoader)) ++mActiveRequestCount;
+ if (startLoadBitmap(entry.labelLoader)) ++mActiveRequestCount;
+ }
+ if (mActiveRequestCount == 0) {
+ requestNonactiveImages();
+ } else {
+ cancelNonactiveImages();
+ }
+ }
+
+ @Override
+ public void onSizeChanged(int size) {
+ if (mIsActive && mSize != size) {
+ mSize = size;
+ if (mListener != null) mListener.onSizeChanged(mSize);
+ if (mContentEnd > mSize) mContentEnd = mSize;
+ if (mActiveEnd > mSize) mActiveEnd = mSize;
+ }
+ }
+
+ @Override
+ public void onContentChanged(int index) {
+ if (!mIsActive) {
+ // paused, ignore slot changed event
+ return;
+ }
+
+ // If the updated content is not cached, ignore it
+ if (index < mContentStart || index >= mContentEnd) {
+ Log.w(TAG, String.format(
+ "invalid update: %s is outside (%s, %s)",
+ index, mContentStart, mContentEnd) );
+ return;
+ }
+
+ AlbumSetEntry entry = mData[index % mData.length];
+ updateAlbumSetEntry(entry, index);
+ updateAllImageRequests();
+ updateTextureUploadQueue();
+ if (mListener != null && isActiveSlot(index)) {
+ mListener.onContentChanged();
+ }
+ }
+
+ public BitmapTexture getLoadingTexture() {
+ if (mLoadingLabel == null) {
+ Bitmap bitmap = mLabelMaker.requestLabel(
+ mLoadingText, "", DataSourceType.TYPE_NOT_CATEGORIZED)
+ .run(ThreadPool.JOB_CONTEXT_STUB);
+ mLoadingLabel = new BitmapTexture(bitmap);
+ mLoadingLabel.setOpaque(false);
+ }
+ return mLoadingLabel;
+ }
+
+ public void pause() {
+ mIsActive = false;
+ mLabelUploader.clear();
+ mContentUploader.clear();
+ TiledTexture.freeResources();
+ for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+ freeSlotContent(i);
+ }
+ }
+
+ public void resume() {
+ mIsActive = true;
+ TiledTexture.prepareResources();
+ for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+ prepareSlotContent(i);
+ }
+ updateAllImageRequests();
+ }
+
+ private static interface EntryUpdater {
+ public void updateEntry();
+ }
+
+ private class AlbumCoverLoader extends BitmapLoader implements EntryUpdater {
+ private MediaItem mMediaItem;
+ private final int mSlotIndex;
+
+ public AlbumCoverLoader(int slotIndex, MediaItem item) {
+ mSlotIndex = slotIndex;
+ mMediaItem = item;
+ }
+
+ @Override
+ protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
+ return mThreadPool.submit(mMediaItem.requestImage(
+ MediaItem.TYPE_MICROTHUMBNAIL), l);
+ }
+
+ @Override
+ protected void onLoadComplete(Bitmap bitmap) {
+ mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget();
+ }
+
+ @Override
+ public void updateEntry() {
+ Bitmap bitmap = getBitmap();
+ if (bitmap == null) return; // error or recycled
+
+ AlbumSetEntry entry = mData[mSlotIndex % mData.length];
+ TiledTexture texture = new TiledTexture(bitmap);
+ entry.bitmapTexture = texture;
+ entry.content = texture;
+
+ if (isActiveSlot(mSlotIndex)) {
+ mContentUploader.addTexture(texture);
+ --mActiveRequestCount;
+ if (mActiveRequestCount == 0) requestNonactiveImages();
+ if (mListener != null) mListener.onContentChanged();
+ } else {
+ mContentUploader.addTexture(texture);
+ }
+ }
+ }
+
+ private static int identifyCacheFlag(MediaSet set) {
+ if (set == null || (set.getSupportedOperations()
+ & MediaSet.SUPPORT_CACHE) == 0) {
+ return MediaSet.CACHE_FLAG_NO;
+ }
+
+ return set.getCacheFlag();
+ }
+
+ private static int identifyCacheStatus(MediaSet set) {
+ if (set == null || (set.getSupportedOperations()
+ & MediaSet.SUPPORT_CACHE) == 0) {
+ return MediaSet.CACHE_STATUS_NOT_CACHED;
+ }
+
+ return set.getCacheStatus();
+ }
+
+ private class AlbumLabelLoader extends BitmapLoader implements EntryUpdater {
+ private final int mSlotIndex;
+ private final String mTitle;
+ private final int mTotalCount;
+ private final int mSourceType;
+
+ public AlbumLabelLoader(
+ int slotIndex, String title, int totalCount, int sourceType) {
+ mSlotIndex = slotIndex;
+ mTitle = title;
+ mTotalCount = totalCount;
+ mSourceType = sourceType;
+ }
+
+ @Override
+ protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
+ return mThreadPool.submit(mLabelMaker.requestLabel(
+ mTitle, String.valueOf(mTotalCount), mSourceType), l);
+ }
+
+ @Override
+ protected void onLoadComplete(Bitmap bitmap) {
+ mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget();
+ }
+
+ @Override
+ public void updateEntry() {
+ Bitmap bitmap = getBitmap();
+ if (bitmap == null) return; // Error or recycled
+
+ AlbumSetEntry entry = mData[mSlotIndex % mData.length];
+ BitmapTexture texture = new BitmapTexture(bitmap);
+ texture.setOpaque(false);
+ entry.labelTexture = texture;
+
+ if (isActiveSlot(mSlotIndex)) {
+ mLabelUploader.addFgTexture(texture);
+ --mActiveRequestCount;
+ if (mActiveRequestCount == 0) requestNonactiveImages();
+ if (mListener != null) mListener.onContentChanged();
+ } else {
+ mLabelUploader.addBgTexture(texture);
+ }
+ }
+ }
+
+ public void onSlotSizeChanged(int width, int height) {
+ if (mSlotWidth == width) return;
+
+ mSlotWidth = width;
+ mLoadingLabel = null;
+ mLabelMaker.setLabelWidth(mSlotWidth);
+
+ if (!mIsActive) return;
+
+ for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+ AlbumSetEntry entry = mData[i % mData.length];
+ if (entry.labelLoader != null) {
+ entry.labelLoader.recycle();
+ entry.labelLoader = null;
+ entry.labelTexture = null;
+ }
+ if (entry.album != null) {
+ entry.labelLoader = new AlbumLabelLoader(i,
+ entry.title, entry.totalCount, entry.sourceType);
+ }
+ }
+ updateAllImageRequests();
+ updateTextureUploadQueue();
+ }
+}