diff options
Diffstat (limited to 'src/com/android/gallery3d/ui/AlbumSlidingWindow.java')
-rw-r--r-- | src/com/android/gallery3d/ui/AlbumSlidingWindow.java | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java new file mode 100644 index 000000000..9e44bd1d2 --- /dev/null +++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java @@ -0,0 +1,433 @@ +/* + * 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.app.GalleryActivity; +import com.android.gallery3d.common.BitmapUtils; +import com.android.gallery3d.common.LruCache; +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.data.Path; +import com.android.gallery3d.util.Future; +import com.android.gallery3d.util.FutureListener; +import com.android.gallery3d.util.ThreadPool; +import com.android.gallery3d.util.ThreadPool.Job; +import com.android.gallery3d.util.ThreadPool.JobContext; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.Message; + +public class AlbumSlidingWindow implements AlbumView.ModelListener { + @SuppressWarnings("unused") + private static final String TAG = "AlbumSlidingWindow"; + + private static final int MSG_LOAD_BITMAP_DONE = 0; + private static final int MSG_UPDATE_SLOT = 1; + private static final int MIN_THUMB_SIZE = 100; + + public static interface Listener { + public void onSizeChanged(int size); + public void onContentInvalidated(); + public void onWindowContentChanged( + int slot, DisplayItem old, DisplayItem update); + } + + private final AlbumView.Model mSource; + private int mSize; + + private int mContentStart = 0; + private int mContentEnd = 0; + + private int mActiveStart = 0; + private int mActiveEnd = 0; + + private Listener mListener; + private int mFocusIndex = -1; + + private final AlbumDisplayItem mData[]; + private final ColorTexture mWaitLoadingTexture; + private SelectionDrawer mSelectionDrawer; + + private SynchronizedHandler mHandler; + private ThreadPool mThreadPool; + private int mSlotWidth, mSlotHeight; + + private int mActiveRequestCount = 0; + private boolean mIsActive = false; + + private int mDisplayItemSize; // 0: disabled + private LruCache<Path, Bitmap> mImageCache = new LruCache<Path, Bitmap>(1000); + + public AlbumSlidingWindow(GalleryActivity activity, + AlbumView.Model source, int cacheSize, + int slotWidth, int slotHeight, int displayItemSize) { + source.setModelListener(this); + mSource = source; + mData = new AlbumDisplayItem[cacheSize]; + mSize = source.size(); + mSlotWidth = slotWidth; + mSlotHeight = slotHeight; + mDisplayItemSize = displayItemSize; + + mWaitLoadingTexture = new ColorTexture(Color.TRANSPARENT); + mWaitLoadingTexture.setSize(1, 1); + + mHandler = new SynchronizedHandler(activity.getGLRoot()) { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MSG_LOAD_BITMAP_DONE: { + ((AlbumDisplayItem) message.obj).onLoadBitmapDone(); + break; + } + case MSG_UPDATE_SLOT: { + updateSlotContent(message.arg1); + break; + } + } + } + }; + + mThreadPool = activity.getThreadPool(); + } + + public void setSelectionDrawer(SelectionDrawer drawer) { + mSelectionDrawer = drawer; + } + + public void setListener(Listener listener) { + mListener = listener; + } + + public void setFocusIndex(int slotIndex) { + mFocusIndex = slotIndex; + } + + public DisplayItem 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 (!mIsActive) { + mContentStart = contentStart; + mContentEnd = contentEnd; + mSource.setActiveWindow(contentStart, contentEnd); + 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, + "%s, %s, %s, %s", start, end, mData.length, mSize); + DisplayItem 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) { + requestSlotImage(mActiveEnd + i, false); + requestSlotImage(mActiveStart - 1 - i, false); + } + } + + private void requestSlotImage(int slotIndex, boolean isActive) { + if (slotIndex < mContentStart || slotIndex >= mContentEnd) return; + AlbumDisplayItem item = mData[slotIndex % mData.length]; + item.requestImage(); + } + + private void cancelNonactiveImages() { + int range = Math.max( + (mContentEnd - mActiveEnd), (mActiveStart - mContentStart)); + for (int i = 0 ;i < range; ++i) { + cancelSlotImage(mActiveEnd + i, false); + cancelSlotImage(mActiveStart - 1 - i, false); + } + } + + private void cancelSlotImage(int slotIndex, boolean isActive) { + if (slotIndex < mContentStart || slotIndex >= mContentEnd) return; + AlbumDisplayItem item = mData[slotIndex % mData.length]; + item.cancelImageRequest(); + } + + private void freeSlotContent(int slotIndex) { + AlbumDisplayItem data[] = mData; + int index = slotIndex % data.length; + AlbumDisplayItem original = data[index]; + if (original != null) { + original.recycle(); + data[index] = null; + } + } + + private void prepareSlotContent(final int slotIndex) { + mData[slotIndex % mData.length] = new AlbumDisplayItem( + slotIndex, mSource.get(slotIndex)); + } + + private void updateSlotContent(final int slotIndex) { + MediaItem item = mSource.get(slotIndex); + AlbumDisplayItem data[] = mData; + int index = slotIndex % data.length; + AlbumDisplayItem original = data[index]; + AlbumDisplayItem update = new AlbumDisplayItem(slotIndex, item); + data[index] = update; + boolean isActive = isActiveSlot(slotIndex); + if (mListener != null && isActive) { + mListener.onWindowContentChanged(slotIndex, original, update); + } + if (original != null) { + if (isActive && original.isRequestInProgress()) { + --mActiveRequestCount; + } + original.recycle(); + } + if (isActive) { + if (mActiveRequestCount == 0) cancelNonactiveImages(); + ++mActiveRequestCount; + update.requestImage(); + } else { + if (mActiveRequestCount == 0) update.requestImage(); + } + } + + private void updateAllImageRequests() { + mActiveRequestCount = 0; + AlbumDisplayItem data[] = mData; + for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) { + AlbumDisplayItem item = data[i % data.length]; + item.requestImage(); + if (item.isRequestInProgress()) ++mActiveRequestCount; + } + if (mActiveRequestCount == 0) { + requestNonactiveImages(); + } else { + cancelNonactiveImages(); + } + } + + private class AlbumDisplayItem extends AbstractDisplayItem + implements FutureListener<Bitmap>, Job<Bitmap> { + private Future<Bitmap> mFuture; + private final int mSlotIndex; + private final int mMediaType; + private Texture mContent; + + public AlbumDisplayItem(int slotIndex, MediaItem item) { + super(item); + mMediaType = (item == null) + ? MediaItem.MEDIA_TYPE_UNKNOWN + : item.getMediaType(); + mSlotIndex = slotIndex; + updateContent(mWaitLoadingTexture); + } + + @Override + protected void onBitmapAvailable(Bitmap bitmap) { + boolean isActiveSlot = isActiveSlot(mSlotIndex); + if (isActiveSlot) { + --mActiveRequestCount; + if (mActiveRequestCount == 0) requestNonactiveImages(); + } + if (bitmap != null) { + BitmapTexture texture = new BitmapTexture(bitmap); + texture.setThrottled(true); + updateContent(texture); + if (mListener != null && isActiveSlot) { + mListener.onContentInvalidated(); + } + } + } + + private void updateContent(Texture content) { + mContent = content; + + int width = mContent.getWidth(); + int height = mContent.getHeight(); + + float scalex = mDisplayItemSize / (float) width; + float scaley = mDisplayItemSize / (float) height; + float scale = Math.min(scalex, scaley); + + width = (int) Math.floor(width * scale); + height = (int) Math.floor(height * scale); + + setSize(width, height); + } + + @Override + public boolean render(GLCanvas canvas, int pass) { + if (pass == 0) { + Path path = null; + if (mMediaItem != null) path = mMediaItem.getPath(); + mSelectionDrawer.draw(canvas, mContent, mWidth, mHeight, + getRotation(), path, mMediaType); + return (mFocusIndex == mSlotIndex); + } else if (pass == 1) { + mSelectionDrawer.drawFocus(canvas, mWidth, mHeight); + } + return false; + } + + @Override + public void startLoadBitmap() { + if (mDisplayItemSize < MIN_THUMB_SIZE) { + Path path = mMediaItem.getPath(); + if (mImageCache.containsKey(path)) { + Bitmap bitmap = mImageCache.get(path); + updateImage(bitmap, false); + return; + } + mFuture = mThreadPool.submit(this, this); + } else { + mFuture = mThreadPool.submit(mMediaItem.requestImage( + MediaItem.TYPE_MICROTHUMBNAIL), this); + } + } + + // This gets the bitmap and scale it down. + public Bitmap run(JobContext jc) { + Job<Bitmap> job = mMediaItem.requestImage( + MediaItem.TYPE_MICROTHUMBNAIL); + Bitmap bitmap = job.run(jc); + if (bitmap != null) { + bitmap = BitmapUtils.resizeDownBySideLength( + bitmap, mDisplayItemSize, true); + } + return bitmap; + } + + @Override + public void cancelLoadBitmap() { + if (mFuture != null) { + mFuture.cancel(); + } + } + + @Override + public void onFutureDone(Future<Bitmap> bitmap) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_LOAD_BITMAP_DONE, this)); + } + + private void onLoadBitmapDone() { + Future<Bitmap> future = mFuture; + mFuture = null; + Bitmap bitmap = future.get(); + boolean isCancelled = future.isCancelled(); + if (mDisplayItemSize < MIN_THUMB_SIZE && (bitmap != null || !isCancelled)) { + Path path = mMediaItem.getPath(); + mImageCache.put(path, bitmap); + } + updateImage(bitmap, isCancelled); + } + + @Override + public String toString() { + return String.format("AlbumDisplayItem[%s]", mSlotIndex); + } + } + + public void onSizeChanged(int size) { + if (mSize != size) { + mSize = size; + if (mListener != null) mListener.onSizeChanged(mSize); + } + } + + public void onWindowContentChanged(int index) { + if (index >= mContentStart && index < mContentEnd && mIsActive) { + updateSlotContent(index); + } + } + + public void resume() { + mIsActive = true; + for (int i = mContentStart, n = mContentEnd; i < n; ++i) { + prepareSlotContent(i); + } + updateAllImageRequests(); + } + + public void pause() { + mIsActive = false; + for (int i = mContentStart, n = mContentEnd; i < n; ++i) { + freeSlotContent(i); + } + mImageCache.clear(); + } +} |