summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/ui/AlbumSlidingWindow.java')
-rw-r--r--src/com/android/gallery3d/ui/AlbumSlidingWindow.java365
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);
+ }
+ }
+}