blob: d37d4e81248b59d03814d7083173123b9f7a57ec [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.TimeLineDataLoader;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.Path;
import com.android.gallery3d.data.TimeLineTitleMediaItem;
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 TimeLineSlidingWindow implements TimeLineDataLoader.DataListener {
@SuppressWarnings("unused")
private static final String TAG = "TimeLineSlidingWindow";
private static final int MSG_UPDATE_ENTRY = 0;
private static final int JOB_LIMIT = 2;
public interface Listener {
void onSizeChanged(int[] size);
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 final TimeLineDataLoader mSource;
private final AlbumEntry mData[];
private final SynchronizedHandler mHandler;
private final JobLimiter mThreadPool;
private final TiledTexture.Uploader mTileUploader;
private int mSize;
private int mSlotWidth;
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 final TimeLineTitleMaker mTitleMaker;
public TimeLineSlidingWindow(AbstractGalleryActivity activity, TimeLineDataLoader source,
int cacheSize, TimeLineSlotRenderer.LabelSpec labelSpec, TimeLineSlotView slotView) {
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());
mTitleMaker = new TimeLineTitleMaker(activity.getAndroidContext(), labelSpec, slotView);
}
public void setListener(Listener listener) {
mListener = listener;
}
public AlbumEntry get(int slotIndex) {
if (!isActiveSlot(slotIndex)) {
return null;
}
return mData[slotIndex % mData.length];
}
private 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)) {
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 == null) return;
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 == null) {
continue;
}
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 == null || entry.item == null) return false;
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 == null) return;
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 != null) {
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 updateAllTimelineTitleContent(boolean needRequest) {
for (int slotIndex = mContentStart, n = mContentEnd; slotIndex < n; ++slotIndex) {
AlbumEntry entry = mData[slotIndex % mData.length];
if (entry == null) {
continue;
}
if (entry.mediaType == MediaItem.MEDIA_TYPE_TIMELINE_TITLE) {
freeSlotContent(slotIndex);
prepareSlotContent(slotIndex);
if (needRequest) {
requestSlotImage(slotIndex);
}
}
}
}
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) {
if (mItem.getMediaType() != MediaObject.MEDIA_TYPE_TIMELINE_TITLE) {
return mThreadPool.submit(
mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
} else if( mItem.getMediaType() == MediaObject.MEDIA_TYPE_TIMELINE_TITLE ){
return mThreadPool.submit(
((TimeLineTitleMediaItem) mItem).requestTitle(
MediaItem.TYPE_MICROTHUMBNAIL, mTitleMaker), this);
}
return null;
}
@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];
if (entry == null) return;
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 = mSource.size();
if (mSize != size) {
mSize = size;
if (mListener != null) mListener.onSizeChanged(mSource.getSubMediaSetCount());
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();
updateTextureUploadQueue();
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);
}
}
public void onSlotSizeChanged(int width, int height) {
if (mSlotWidth == width) {
updateAllTimelineTitleContent(true);
return;
}
mSlotWidth = width;
if (!mIsActive) return;
updateAllTimelineTitleContent(false);
updateAllImageRequests();
updateTextureUploadQueue();
}
}