/*
* 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.app;
import android.graphics.Bitmap;
import com.android.gallery3d.app.SlideshowPage.Slide;
import com.android.gallery3d.data.ContentListener;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
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 java.util.LinkedList;
import java.util.concurrent.atomic.AtomicBoolean;
public class SlideshowDataAdapter implements SlideshowPage.Model {
@SuppressWarnings("unused")
private static final String TAG = "SlideshowDataAdapter";
private static final int IMAGE_QUEUE_CAPACITY = 3;
public interface SlideshowSource {
public void addContentListener(ContentListener listener);
public void removeContentListener(ContentListener listener);
public long reload();
public MediaItem getMediaItem(int index);
public int findItemIndex(Path path, int hint);
}
private final SlideshowSource mSource;
private int mLoadIndex = 0;
private int mNextOutput = 0;
private boolean mIsActive = false;
private boolean mNeedReset;
private boolean mDataReady;
private Path mInitialPath;
private final LinkedList<Slide> mImageQueue = new LinkedList<Slide>();
private Future<Void> mReloadTask;
private final ThreadPool mThreadPool;
private long mDataVersion = MediaObject.INVALID_DATA_VERSION;
private final AtomicBoolean mNeedReload = new AtomicBoolean(false);
private final SourceListener mSourceListener = new SourceListener();
// The index is just a hint if initialPath is set
public SlideshowDataAdapter(GalleryContext context, SlideshowSource source, int index,
Path initialPath) {
mSource = source;
mInitialPath = initialPath;
mLoadIndex = index;
mNextOutput = index;
mThreadPool = context.getThreadPool();
}
private MediaItem loadItem() {
if (mNeedReload.compareAndSet(true, false)) {
long v = mSource.reload();
if (v != mDataVersion) {
mDataVersion = v;
mNeedReset = true;
return null;
}
}
int index = mLoadIndex;
if (mInitialPath != null) {
index = mSource.findItemIndex(mInitialPath, index);
mInitialPath = null;
}
return mSource.getMediaItem(index);
}
private class ReloadTask implements Job<Void> {
@Override
public Void run(JobContext jc) {
while (true) {
synchronized (SlideshowDataAdapter.this) {
while (mIsActive && (!mDataReady
|| mImageQueue.size() >= IMAGE_QUEUE_CAPACITY)) {
try {
SlideshowDataAdapter.this.wait();
} catch (InterruptedException ex) {
// ignored.
}
continue;
}
}
if (!mIsActive) return null;
mNeedReset = false;
MediaItem item = loadItem();
if (mNeedReset) {
synchronized (SlideshowDataAdapter.this) {
mImageQueue.clear();
mLoadIndex = mNextOutput;
}
continue;
}
if (item == null) {
synchronized (SlideshowDataAdapter.this) {
if (!mNeedReload.get()) mDataReady = false;
SlideshowDataAdapter.this.notifyAll();
}
continue;
}
Bitmap bitmap = item
.requestImage(MediaItem.TYPE_THUMBNAIL)
.run(jc);
if (bitmap != null) {
synchronized (SlideshowDataAdapter.this) {
mImageQueue.addLast(
new Slide(item, mLoadIndex, bitmap));
if (mImageQueue.size() == 1) {
SlideshowDataAdapter.this.notifyAll();
}
}
}
++mLoadIndex;
}
}
}
private class SourceListener implements ContentListener {
@Override
public void onContentDirty() {
synchronized (SlideshowDataAdapter.this) {
mNeedReload.set(true);
mDataReady = true;
SlideshowDataAdapter.this.notifyAll();
}
}
}
private synchronized Slide innerNextBitmap() {
while (mIsActive && mDataReady && mImageQueue.isEmpty()) {
try {
wait();
} catch (InterruptedException t) {
throw new AssertionError();
}
}
if (mImageQueue.isEmpty()) return null;
mNextOutput++;
this.notifyAll();
return mImageQueue.removeFirst();
}
@Override
public Future<Slide> nextSlide(FutureListener<Slide> listener) {
return mThreadPool.submit(new Job<Slide>() {
@Override
public Slide run(JobContext jc) {
jc.setMode(ThreadPool.MODE_NONE);
return innerNextBitmap();
}
}, listener);
}
@Override
public void pause() {
synchronized (this) {
mIsActive = false;
notifyAll();
}
mSource.removeContentListener(mSourceListener);
mReloadTask.cancel();
mReloadTask.waitDone();
mReloadTask = null;
}
@Override
public synchronized void resume() {
mIsActive = true;
mSource.addContentListener(mSourceListener);
mNeedReload.set(true);
mDataReady = true;
mReloadTask = mThreadPool.submit(new ReloadTask());
}
}