blob: 8cd2cf500d71455dbea1ecd8ee19707909eb8a60 [file] [log] [blame]
/*
* 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.BitmapPool;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.Path;
import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
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 void recycleBitmap(Bitmap bitmap) {
BitmapPool pool = MediaItem.getMicroThumbPool();
if (pool != null) pool.recycle(bitmap);
}
@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);
}
}
}