diff options
Diffstat (limited to 'src/com/android/gallery3d/ui/AlbumSlidingWindow.java')
-rw-r--r-- | src/com/android/gallery3d/ui/AlbumSlidingWindow.java | 365 |
1 files changed, 365 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..fec7d1e92 --- /dev/null +++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java @@ -0,0 +1,365 @@ +/* + * 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.app.AbstractGalleryActivity; +import com.android.gallery3d.app.AlbumDataLoader; +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.data.MediaObject; +import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback; +import com.android.gallery3d.data.Path; +import com.android.gallery3d.glrenderer.Texture; +import com.android.gallery3d.glrenderer.TiledTexture; +import com.android.gallery3d.util.Future; +import com.android.gallery3d.util.FutureListener; +import com.android.gallery3d.util.JobLimiter; + +public class AlbumSlidingWindow implements AlbumDataLoader.DataListener { + @SuppressWarnings("unused") + private static final String TAG = "AlbumSlidingWindow"; + + private static final int MSG_UPDATE_ENTRY = 0; + private static final int JOB_LIMIT = 2; + + public static interface Listener { + public void onSizeChanged(int size); + public void onContentChanged(); + } + + public static class AlbumEntry { + public MediaItem item; + public Path path; + public boolean isPanorama; + public int rotation; + public int mediaType; + public boolean isWaitDisplayed; + public TiledTexture bitmapTexture; + public Texture content; + private BitmapLoader contentLoader; + private PanoSupportListener mPanoSupportListener; + } + + private final AlbumDataLoader mSource; + private final AlbumEntry mData[]; + private final SynchronizedHandler mHandler; + private final JobLimiter mThreadPool; + private final TiledTexture.Uploader mTileUploader; + + private int mSize; + + private int mContentStart = 0; + private int mContentEnd = 0; + + private int mActiveStart = 0; + private int mActiveEnd = 0; + + private Listener mListener; + + private int mActiveRequestCount = 0; + private boolean mIsActive = false; + + private class PanoSupportListener implements PanoramaSupportCallback { + public final AlbumEntry mEntry; + public PanoSupportListener (AlbumEntry entry) { + mEntry = entry; + } + @Override + public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama, + boolean isPanorama360) { + if (mEntry != null) mEntry.isPanorama = isPanorama; + } + } + + public AlbumSlidingWindow(AbstractGalleryActivity activity, + AlbumDataLoader source, int cacheSize) { + source.setDataListener(this); + mSource = source; + mData = new AlbumEntry[cacheSize]; + mSize = source.size(); + + mHandler = new SynchronizedHandler(activity.getGLRoot()) { + @Override + public void handleMessage(Message message) { + Utils.assertTrue(message.what == MSG_UPDATE_ENTRY); + ((ThumbnailLoader) message.obj).updateEntry(); + } + }; + + mThreadPool = new JobLimiter(activity.getThreadPool(), JOB_LIMIT); + mTileUploader = new TiledTexture.Uploader(activity.getGLRoot()); + } + + public void setListener(Listener listener) { + mListener = listener; + } + + public AlbumEntry get(int slotIndex) { + if (!isActiveSlot(slotIndex)) { + Utils.fail("invalid slot: %s outsides (%s, %s)", + slotIndex, mActiveStart, mActiveEnd); + } + return mData[slotIndex % mData.length]; + } + + 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) { + if (!(start <= end && end - start <= mData.length && end <= mSize)) { + Utils.fail("%s, %s, %s, %s", start, end, mData.length, mSize); + } + AlbumEntry 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); + updateTextureUploadQueue(); + if (mIsActive) updateAllImageRequests(); + } + + private void uploadBgTextureInSlot(int index) { + if (index < mContentEnd && index >= mContentStart) { + AlbumEntry entry = mData[index % mData.length]; + if (entry.bitmapTexture != null) { + mTileUploader.addTexture(entry.bitmapTexture); + } + } + } + + private void updateTextureUploadQueue() { + if (!mIsActive) return; + mTileUploader.clear(); + + // add foreground textures + for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) { + AlbumEntry entry = mData[i % mData.length]; + if (entry.bitmapTexture != null) { + mTileUploader.addTexture(entry.bitmapTexture); + } + } + + // add background textures + int range = Math.max( + (mContentEnd - mActiveEnd), (mActiveStart - mContentStart)); + for (int i = 0; i < range; ++i) { + uploadBgTextureInSlot(mActiveEnd + i); + uploadBgTextureInSlot(mActiveStart - i - 1); + } + } + + // 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); + requestSlotImage(mActiveStart - 1 - i); + } + } + + // return whether the request is in progress or not + private boolean requestSlotImage(int slotIndex) { + if (slotIndex < mContentStart || slotIndex >= mContentEnd) return false; + AlbumEntry entry = mData[slotIndex % mData.length]; + if (entry.content != null || entry.item == null) return false; + + // Set up the panorama callback + entry.mPanoSupportListener = new PanoSupportListener(entry); + entry.item.getPanoramaSupport(entry.mPanoSupportListener); + + entry.contentLoader.startLoad(); + return entry.contentLoader.isRequestInProgress(); + } + + private void cancelNonactiveImages() { + int range = Math.max( + (mContentEnd - mActiveEnd), (mActiveStart - mContentStart)); + for (int i = 0 ;i < range; ++i) { + cancelSlotImage(mActiveEnd + i); + cancelSlotImage(mActiveStart - 1 - i); + } + } + + private void cancelSlotImage(int slotIndex) { + if (slotIndex < mContentStart || slotIndex >= mContentEnd) return; + AlbumEntry item = mData[slotIndex % mData.length]; + if (item.contentLoader != null) item.contentLoader.cancelLoad(); + } + + private void freeSlotContent(int slotIndex) { + AlbumEntry data[] = mData; + int index = slotIndex % data.length; + AlbumEntry entry = data[index]; + if (entry.contentLoader != null) entry.contentLoader.recycle(); + if (entry.bitmapTexture != null) entry.bitmapTexture.recycle(); + data[index] = null; + } + + private void prepareSlotContent(int slotIndex) { + AlbumEntry entry = new AlbumEntry(); + MediaItem item = mSource.get(slotIndex); // item could be null; + entry.item = item; + entry.mediaType = (item == null) + ? MediaItem.MEDIA_TYPE_UNKNOWN + : entry.item.getMediaType(); + entry.path = (item == null) ? null : item.getPath(); + entry.rotation = (item == null) ? 0 : item.getRotation(); + entry.contentLoader = new ThumbnailLoader(slotIndex, entry.item); + mData[slotIndex % mData.length] = entry; + } + + private void updateAllImageRequests() { + mActiveRequestCount = 0; + for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) { + if (requestSlotImage(i)) ++mActiveRequestCount; + } + if (mActiveRequestCount == 0) { + requestNonactiveImages(); + } else { + cancelNonactiveImages(); + } + } + + private class ThumbnailLoader extends BitmapLoader { + private final int mSlotIndex; + private final MediaItem mItem; + + public ThumbnailLoader(int slotIndex, MediaItem item) { + mSlotIndex = slotIndex; + mItem = item; + } + + @Override + protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) { + return mThreadPool.submit( + mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this); + } + + @Override + protected void onLoadComplete(Bitmap bitmap) { + mHandler.obtainMessage(MSG_UPDATE_ENTRY, this).sendToTarget(); + } + + public void updateEntry() { + Bitmap bitmap = getBitmap(); + if (bitmap == null) return; // error or recycled + AlbumEntry entry = mData[mSlotIndex % mData.length]; + entry.bitmapTexture = new TiledTexture(bitmap); + entry.content = entry.bitmapTexture; + + if (isActiveSlot(mSlotIndex)) { + mTileUploader.addTexture(entry.bitmapTexture); + --mActiveRequestCount; + if (mActiveRequestCount == 0) requestNonactiveImages(); + if (mListener != null) mListener.onContentChanged(); + } else { + mTileUploader.addTexture(entry.bitmapTexture); + } + } + } + + @Override + public void onSizeChanged(int size) { + if (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 (index >= mContentStart && index < mContentEnd && mIsActive) { + freeSlotContent(index); + prepareSlotContent(index); + updateAllImageRequests(); + if (mListener != null && isActiveSlot(index)) { + mListener.onContentChanged(); + } + } + } + + public void resume() { + mIsActive = true; + TiledTexture.prepareResources(); + for (int i = mContentStart, n = mContentEnd; i < n; ++i) { + prepareSlotContent(i); + } + updateAllImageRequests(); + } + + public void pause() { + mIsActive = false; + mTileUploader.clear(); + TiledTexture.freeResources(); + for (int i = mContentStart, n = mContentEnd; i < n; ++i) { + freeSlotContent(i); + } + } +} |