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.java543
1 files changed, 543 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..92d8b4156
--- /dev/null
+++ b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
@@ -0,0 +1,543 @@
+/*
+ * 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 com.android.gallery3d.R;
+import com.android.gallery3d.app.GalleryActivity;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.ui.AlbumSetView.AlbumSetItem;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.MediaSetUtils;
+import com.android.gallery3d.util.ThreadPool;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.Message;
+
+public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
+ private static final String TAG = "GallerySlidingWindow";
+ private static final int MSG_LOAD_BITMAP_DONE = 0;
+
+ public static interface Listener {
+ public void onSizeChanged(int size);
+ public void onContentInvalidated();
+ public void onWindowContentChanged(
+ int slot, AlbumSetItem old, AlbumSetItem update);
+ }
+
+ private final AlbumSetView.Model mSource;
+ private int mSize;
+ private int mLabelWidth;
+ private int mDisplayItemSize;
+ private int mLabelFontSize;
+
+ private int mContentStart = 0;
+ private int mContentEnd = 0;
+
+ private int mActiveStart = 0;
+ private int mActiveEnd = 0;
+
+ private Listener mListener;
+
+ private final MyAlbumSetItem mData[];
+ private SelectionDrawer mSelectionDrawer;
+ private final ColorTexture mWaitLoadingTexture;
+
+ private SynchronizedHandler mHandler;
+ private ThreadPool mThreadPool;
+
+ private int mActiveRequestCount = 0;
+ private String mLoadingLabel;
+ private boolean mIsActive = false;
+
+ private static class MyAlbumSetItem extends AlbumSetItem {
+ public Path setPath;
+ public int sourceType;
+ public int cacheFlag;
+ public int cacheStatus;
+ }
+
+ public AlbumSetSlidingWindow(GalleryActivity activity, int labelWidth,
+ int displayItemSize, int labelFontSize, SelectionDrawer drawer,
+ AlbumSetView.Model source, int cacheSize) {
+ source.setModelListener(this);
+ mLabelWidth = labelWidth;
+ mDisplayItemSize = displayItemSize;
+ mLabelFontSize = labelFontSize;
+ mLoadingLabel = activity.getAndroidContext().getString(R.string.loading);
+ mSource = source;
+ mSelectionDrawer = drawer;
+ mData = new MyAlbumSetItem[cacheSize];
+ mSize = source.size();
+
+ mWaitLoadingTexture = new ColorTexture(Color.TRANSPARENT);
+ mWaitLoadingTexture.setSize(1, 1);
+
+ mHandler = new SynchronizedHandler(activity.getGLRoot()) {
+ @Override
+ public void handleMessage(Message message) {
+ Utils.assertTrue(message.what == MSG_LOAD_BITMAP_DONE);
+ ((GalleryDisplayItem) message.obj).onLoadBitmapDone();
+ }
+ };
+
+ mThreadPool = activity.getThreadPool();
+ }
+
+ public void setSelectionDrawer(SelectionDrawer drawer) {
+ mSelectionDrawer = drawer;
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ public AlbumSetItem get(int slotIndex) {
+ Utils.assertTrue(isActiveSlot(slotIndex),
+ "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) {
+ Utils.assertTrue(
+ start <= end && end - start <= mData.length && end <= mSize,
+ "start = %s, end = %s, length = %s, size = %s",
+ start, end, mData.length, mSize);
+
+ AlbumSetItem data[] = mData;
+
+ mActiveStart = start;
+ mActiveEnd = end;
+
+ // If no data is visible, keep the cache content
+ if (start == end) return;
+
+ 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) 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;
+ AlbumSetItem items = mData[slotIndex % mData.length];
+ for (DisplayItem item : items.covers) {
+ ((GalleryDisplayItem) item).requestImage();
+ }
+ }
+
+ private void cancelImagesInSlot(int slotIndex) {
+ if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
+ AlbumSetItem items = mData[slotIndex % mData.length];
+ for (DisplayItem item : items.covers) {
+ ((GalleryDisplayItem) item).cancelImageRequest();
+ }
+ }
+
+ private void freeSlotContent(int slotIndex) {
+ AlbumSetItem data[] = mData;
+ int index = slotIndex % data.length;
+ AlbumSetItem original = data[index];
+ if (original != null) {
+ data[index] = null;
+ for (DisplayItem item : original.covers) {
+ ((GalleryDisplayItem) item).recycle();
+ }
+ }
+ }
+
+ private long getMediaSetDataVersion(MediaSet set) {
+ return set == null
+ ? MediaSet.INVALID_DATA_VERSION
+ : set.getDataVersion();
+ }
+
+ private void prepareSlotContent(int slotIndex) {
+ MediaSet set = mSource.getMediaSet(slotIndex);
+
+ MyAlbumSetItem item = new MyAlbumSetItem();
+ MediaItem[] coverItems = mSource.getCoverItems(slotIndex);
+ item.covers = new GalleryDisplayItem[coverItems.length];
+ item.sourceType = identifySourceType(set);
+ item.cacheFlag = identifyCacheFlag(set);
+ item.cacheStatus = identifyCacheStatus(set);
+ item.setPath = set == null ? null : set.getPath();
+
+ for (int i = 0; i < coverItems.length; ++i) {
+ item.covers[i] = new GalleryDisplayItem(slotIndex, i, coverItems[i]);
+ }
+ item.labelItem = new LabelDisplayItem(slotIndex);
+ item.setDataVersion = getMediaSetDataVersion(set);
+ mData[slotIndex % mData.length] = item;
+ }
+
+ private boolean isCoverItemsChanged(int slotIndex) {
+ AlbumSetItem original = mData[slotIndex % mData.length];
+ if (original == null) return true;
+ MediaItem[] coverItems = mSource.getCoverItems(slotIndex);
+
+ if (original.covers.length != coverItems.length) return true;
+ for (int i = 0, n = coverItems.length; i < n; ++i) {
+ GalleryDisplayItem g = (GalleryDisplayItem) original.covers[i];
+ if (g.mDataVersion != coverItems[i].getDataVersion()) return true;
+ }
+ return false;
+ }
+
+ private void updateSlotContent(final int slotIndex) {
+
+ MyAlbumSetItem data[] = mData;
+ int pos = slotIndex % data.length;
+ MyAlbumSetItem original = data[pos];
+
+ if (!isCoverItemsChanged(slotIndex)) {
+ MediaSet set = mSource.getMediaSet(slotIndex);
+ original.sourceType = identifySourceType(set);
+ original.cacheFlag = identifyCacheFlag(set);
+ original.cacheStatus = identifyCacheStatus(set);
+ original.setPath = set == null ? null : set.getPath();
+ ((LabelDisplayItem) original.labelItem).updateContent();
+ if (mListener != null) mListener.onContentInvalidated();
+ return;
+ }
+
+ prepareSlotContent(slotIndex);
+ AlbumSetItem update = data[pos];
+
+ if (mListener != null && isActiveSlot(slotIndex)) {
+ mListener.onWindowContentChanged(slotIndex, original, update);
+ }
+ if (original != null) {
+ for (DisplayItem item : original.covers) {
+ ((GalleryDisplayItem) item).recycle();
+ }
+ }
+ }
+
+ private void notifySlotChanged(int slotIndex) {
+ // If the updated content is not cached, ignore it
+ if (slotIndex < mContentStart || slotIndex >= mContentEnd) {
+ Log.w(TAG, String.format(
+ "invalid update: %s is outside (%s, %s)",
+ slotIndex, mContentStart, mContentEnd) );
+ return;
+ }
+ updateSlotContent(slotIndex);
+ boolean isActiveSlot = isActiveSlot(slotIndex);
+ if (mActiveRequestCount == 0 || isActiveSlot) {
+ for (DisplayItem item : mData[slotIndex % mData.length].covers) {
+ GalleryDisplayItem galleryItem = (GalleryDisplayItem) item;
+ galleryItem.requestImage();
+ if (isActiveSlot && galleryItem.isRequestInProgress()) {
+ ++mActiveRequestCount;
+ }
+ }
+ }
+ }
+
+ private void updateAllImageRequests() {
+ mActiveRequestCount = 0;
+ for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
+ for (DisplayItem item : mData[i % mData.length].covers) {
+ GalleryDisplayItem coverItem = (GalleryDisplayItem) item;
+ coverItem.requestImage();
+ if (coverItem.isRequestInProgress()) ++mActiveRequestCount;
+ }
+ }
+ if (mActiveRequestCount == 0) {
+ requestNonactiveImages();
+ } else {
+ cancelNonactiveImages();
+ }
+ }
+
+ private class GalleryDisplayItem extends AbstractDisplayItem
+ implements FutureListener<Bitmap> {
+ private Future<Bitmap> mFuture;
+ private final int mSlotIndex;
+ private final int mCoverIndex;
+ private final int mMediaType;
+ private Texture mContent;
+ private final long mDataVersion;
+
+ public GalleryDisplayItem(int slotIndex, int coverIndex, MediaItem item) {
+ super(item);
+ mSlotIndex = slotIndex;
+ mCoverIndex = coverIndex;
+ mMediaType = item.getMediaType();
+ mDataVersion = item.getDataVersion();
+ updateContent(mWaitLoadingTexture);
+ }
+
+ @Override
+ protected void onBitmapAvailable(Bitmap bitmap) {
+ if (isActiveSlot(mSlotIndex)) {
+ --mActiveRequestCount;
+ if (mActiveRequestCount == 0) requestNonactiveImages();
+ }
+ if (bitmap != null) {
+ BitmapTexture texture = new BitmapTexture(bitmap);
+ texture.setThrottled(true);
+ updateContent(texture);
+ if (mListener != null) mListener.onContentInvalidated();
+ }
+ }
+
+ private void updateContent(Texture content) {
+ mContent = content;
+
+ int width = content.getWidth();
+ int height = content.getHeight();
+
+ float scale = (float) mDisplayItemSize / Math.max(width, height);
+
+ width = (int) Math.floor(width * scale);
+ height = (int) Math.floor(height * scale);
+
+ setSize(width, height);
+ }
+
+ @Override
+ public boolean render(GLCanvas canvas, int pass) {
+ int sourceType = SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED;
+ int cacheFlag = MediaSet.CACHE_FLAG_NO;
+ int cacheStatus = MediaSet.CACHE_STATUS_NOT_CACHED;
+ MyAlbumSetItem set = mData[mSlotIndex % mData.length];
+ Path path = set.setPath;
+ if (mCoverIndex == 0) {
+ sourceType = set.sourceType;
+ cacheFlag = set.cacheFlag;
+ cacheStatus = set.cacheStatus;
+ }
+
+ mSelectionDrawer.draw(canvas, mContent, mWidth, mHeight,
+ getRotation(), path, mCoverIndex, sourceType, mMediaType,
+ cacheFlag == MediaSet.CACHE_FLAG_FULL,
+ (cacheFlag == MediaSet.CACHE_FLAG_FULL)
+ && (cacheStatus != MediaSet.CACHE_STATUS_CACHED_FULL));
+ return false;
+ }
+
+ @Override
+ public void startLoadBitmap() {
+ mFuture = mThreadPool.submit(mMediaItem.requestImage(
+ MediaItem.TYPE_MICROTHUMBNAIL), this);
+ }
+
+ @Override
+ public void cancelLoadBitmap() {
+ mFuture.cancel();
+ }
+
+ @Override
+ public void onFutureDone(Future<Bitmap> future) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_LOAD_BITMAP_DONE, this));
+ }
+
+ private void onLoadBitmapDone() {
+ Future<Bitmap> future = mFuture;
+ mFuture = null;
+ updateImage(future.get(), future.isCancelled());
+ }
+
+ @Override
+ public String toString() {
+ return String.format("GalleryDisplayItem(%s, %s)", mSlotIndex, mCoverIndex);
+ }
+ }
+
+ private static int identifySourceType(MediaSet set) {
+ if (set == null) {
+ return SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED;
+ }
+
+ Path path = set.getPath();
+ if (MediaSetUtils.isCameraSource(path)) {
+ return SelectionDrawer.DATASOURCE_TYPE_CAMERA;
+ }
+
+ int type = SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED;
+ String prefix = path.getPrefix();
+
+ if (prefix.equals("picasa")) {
+ type = SelectionDrawer.DATASOURCE_TYPE_PICASA;
+ } else if (prefix.equals("local") || prefix.equals("merge")) {
+ type = SelectionDrawer.DATASOURCE_TYPE_LOCAL;
+ } else if (prefix.equals("mtp")) {
+ type = SelectionDrawer.DATASOURCE_TYPE_MTP;
+ }
+
+ return type;
+ }
+
+ 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 LabelDisplayItem extends DisplayItem {
+ private static final int FONT_COLOR = Color.WHITE;
+
+ private StringTexture mTexture;
+ private String mLabel;
+ private String mPostfix;
+ private final int mSlotIndex;
+
+ public LabelDisplayItem(int slotIndex) {
+ mSlotIndex = slotIndex;
+ updateContent();
+ }
+
+ public boolean updateContent() {
+ String label = mLoadingLabel;
+ String postfix = null;
+ MediaSet set = mSource.getMediaSet(mSlotIndex);
+ if (set != null) {
+ label = Utils.ensureNotNull(set.getName());
+ postfix = " (" + set.getTotalMediaItemCount() + ")";
+ }
+ if (Utils.equals(label, mLabel)
+ && Utils.equals(postfix, mPostfix)) return false;
+ mTexture = StringTexture.newInstance(
+ label, postfix, mLabelFontSize, FONT_COLOR, mLabelWidth, true);
+ setSize(mTexture.getWidth(), mTexture.getHeight());
+ return true;
+ }
+
+ @Override
+ public boolean render(GLCanvas canvas, int pass) {
+ mTexture.draw(canvas, -mWidth / 2, -mHeight / 2);
+ return false;
+ }
+
+ @Override
+ public long getIdentity() {
+ return System.identityHashCode(this);
+ }
+ }
+
+ public void onSizeChanged(int size) {
+ if (mSize != size) {
+ mSize = size;
+ if (mListener != null && mIsActive) mListener.onSizeChanged(mSize);
+ }
+ }
+
+ public void onWindowContentChanged(int index) {
+ if (!mIsActive) {
+ // paused, ignore slot changed event
+ return;
+ }
+ notifySlotChanged(index);
+ }
+
+ public void pause() {
+ mIsActive = false;
+ for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+ freeSlotContent(i);
+ }
+ }
+
+ public void resume() {
+ mIsActive = true;
+ for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+ prepareSlotContent(i);
+ }
+ updateAllImageRequests();
+ }
+}