blob: a99cf93fd975cbd91e76871a00273943e700b439 [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.app;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.ContentListener;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.ui.AlbumSlotRenderer;
import com.android.gallery3d.ui.SynchronizedHandler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class AlbumDataLoader {
@SuppressWarnings("unused")
private static final String TAG = "AlbumDataAdapter";
private static final int DATA_CACHE_SIZE = 1000;
private static final int MSG_LOAD_START = 1;
private static final int MSG_LOAD_FINISH = 2;
private static final int MSG_RUN_OBJECT = 3;
private static final int MIN_LOAD_COUNT = 32;
private static final int MAX_LOAD_COUNT = 64;
private final MediaItem[] mData;
private final long[] mItemVersion;
private final long[] mSetVersion;
public static interface DataListener {
public void onContentChanged(int index);
public void onSizeChanged(int size);
}
private int mActiveStart = 0;
private int mActiveEnd = 0;
private int mContentStart = 0;
private int mContentEnd = 0;
private final MediaSet mSource;
private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
private final Handler mMainHandler;
private int mSize = 0;
private DataListener mDataListener;
private MySourceListener mSourceListener = new MySourceListener();
private LoadingListener mLoadingListener;
private ReloadTask mReloadTask;
public AlbumDataLoader(GalleryActivity context, MediaSet mediaSet) {
mSource = mediaSet;
mData = new MediaItem[DATA_CACHE_SIZE];
mItemVersion = new long[DATA_CACHE_SIZE];
mSetVersion = new long[DATA_CACHE_SIZE];
Arrays.fill(mItemVersion, MediaObject.INVALID_DATA_VERSION);
Arrays.fill(mSetVersion, MediaObject.INVALID_DATA_VERSION);
mMainHandler = new SynchronizedHandler(context.getGLRoot()) {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_RUN_OBJECT:
((Runnable) message.obj).run();
return;
case MSG_LOAD_START:
if (mLoadingListener != null) mLoadingListener.onLoadingStarted();
return;
case MSG_LOAD_FINISH:
if (mLoadingListener != null) mLoadingListener.onLoadingFinished();
return;
}
}
};
}
public void resume() {
mSource.addContentListener(mSourceListener);
mReloadTask = new ReloadTask();
mReloadTask.start();
}
public void pause() {
mReloadTask.terminate();
mReloadTask = null;
mSource.removeContentListener(mSourceListener);
}
public MediaItem get(int index) {
if (!isActive(index)) {
throw new IllegalArgumentException(String.format(
"%s not in (%s, %s)", index, mActiveStart, mActiveEnd));
}
return mData[index % mData.length];
}
public int getActiveStart() {
return mActiveStart;
}
public boolean isActive(int index) {
return index >= mActiveStart && index < mActiveEnd;
}
public int size() {
return mSize;
}
private void clearSlot(int slotIndex) {
mData[slotIndex] = null;
mItemVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
mSetVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
}
private void setContentWindow(int contentStart, int contentEnd) {
if (contentStart == mContentStart && contentEnd == mContentEnd) return;
int end = mContentEnd;
int start = mContentStart;
// We need change the content window before calling reloadData(...)
synchronized (this) {
mContentStart = contentStart;
mContentEnd = contentEnd;
}
long[] itemVersion = mItemVersion;
long[] setVersion = mSetVersion;
if (contentStart >= end || start >= contentEnd) {
for (int i = start, n = end; i < n; ++i) {
clearSlot(i % DATA_CACHE_SIZE);
}
} else {
for (int i = start; i < contentStart; ++i) {
clearSlot(i % DATA_CACHE_SIZE);
}
for (int i = contentEnd, n = end; i < n; ++i) {
clearSlot(i % DATA_CACHE_SIZE);
}
}
if (mReloadTask != null) mReloadTask.notifyDirty();
}
public void setActiveWindow(int start, int end) {
if (start == mActiveStart && end == mActiveEnd) return;
Utils.assertTrue(start <= end
&& end - start <= mData.length && end <= mSize);
int length = mData.length;
mActiveStart = start;
mActiveEnd = end;
// If no data is visible, keep the cache content
if (start == end) return;
int contentStart = Utils.clamp((start + end) / 2 - length / 2,
0, Math.max(0, mSize - length));
int contentEnd = Math.min(contentStart + length, mSize);
if (mContentStart > start || mContentEnd < end
|| Math.abs(contentStart - mContentStart) > MIN_LOAD_COUNT) {
setContentWindow(contentStart, contentEnd);
}
}
private class MySourceListener implements ContentListener {
public void onContentDirty() {
if (mReloadTask != null) mReloadTask.notifyDirty();
}
}
public void setDataListener(DataListener listener) {
mDataListener = listener;
}
public void setLoadingListener(LoadingListener listener) {
mLoadingListener = listener;
}
private <T> T executeAndWait(Callable<T> callable) {
FutureTask<T> task = new FutureTask<T>(callable);
mMainHandler.sendMessage(
mMainHandler.obtainMessage(MSG_RUN_OBJECT, task));
try {
return task.get();
} catch (InterruptedException e) {
return null;
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
private static class UpdateInfo {
public long version;
public int reloadStart;
public int reloadCount;
public int size;
public ArrayList<MediaItem> items;
}
private class GetUpdateInfo implements Callable<UpdateInfo> {
private final long mVersion;
public GetUpdateInfo(long version) {
mVersion = version;
}
public UpdateInfo call() throws Exception {
UpdateInfo info = new UpdateInfo();
long version = mVersion;
info.version = mSourceVersion;
info.size = mSize;
long setVersion[] = mSetVersion;
for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
int index = i % DATA_CACHE_SIZE;
if (setVersion[index] != version) {
info.reloadStart = i;
info.reloadCount = Math.min(MAX_LOAD_COUNT, n - i);
return info;
}
}
return mSourceVersion == mVersion ? null : info;
}
}
private class UpdateContent implements Callable<Void> {
private UpdateInfo mUpdateInfo;
public UpdateContent(UpdateInfo info) {
mUpdateInfo = info;
}
@Override
public Void call() throws Exception {
UpdateInfo info = mUpdateInfo;
mSourceVersion = info.version;
if (mSize != info.size) {
mSize = info.size;
if (mDataListener != null) mDataListener.onSizeChanged(mSize);
if (mContentEnd > mSize) mContentEnd = mSize;
if (mActiveEnd > mSize) mActiveEnd = mSize;
}
ArrayList<MediaItem> items = info.items;
if (items == null) return null;
int start = Math.max(info.reloadStart, mContentStart);
int end = Math.min(info.reloadStart + items.size(), mContentEnd);
for (int i = start; i < end; ++i) {
int index = i % DATA_CACHE_SIZE;
mSetVersion[index] = info.version;
MediaItem updateItem = items.get(i - info.reloadStart);
long itemVersion = updateItem.getDataVersion();
if (mItemVersion[index] != itemVersion) {
mItemVersion[index] = itemVersion;
mData[index] = updateItem;
if (mDataListener != null && i >= mActiveStart && i < mActiveEnd) {
mDataListener.onContentChanged(i);
}
}
}
return null;
}
}
/*
* The thread model of ReloadTask
* *
* [Reload Task] [Main Thread]
* | |
* getUpdateInfo() --> | (synchronous call)
* (wait) <---- getUpdateInfo()
* | |
* Load Data |
* | |
* updateContent() --> | (synchronous call)
* (wait) updateContent()
* | |
* | |
*/
private class ReloadTask extends Thread {
private volatile boolean mActive = true;
private volatile boolean mDirty = true;
private boolean mIsLoading = false;
private void updateLoading(boolean loading) {
if (mIsLoading == loading) return;
mIsLoading = loading;
mMainHandler.sendEmptyMessage(loading ? MSG_LOAD_START : MSG_LOAD_FINISH);
}
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
boolean updateComplete = false;
while (mActive) {
synchronized (this) {
if (mActive && !mDirty && updateComplete) {
updateLoading(false);
Utils.waitWithoutInterrupt(this);
continue;
}
}
mDirty = false;
updateLoading(true);
long version;
synchronized (DataManager.LOCK) {
version = mSource.reload();
}
UpdateInfo info = executeAndWait(new GetUpdateInfo(version));
updateComplete = info == null;
if (updateComplete) continue;
synchronized (DataManager.LOCK) {
if (info.version != version) {
info.size = mSource.getMediaItemCount();
info.version = version;
}
if (info.reloadCount > 0) {
info.items = mSource.getMediaItem(info.reloadStart, info.reloadCount);
}
}
executeAndWait(new UpdateContent(info));
}
updateLoading(false);
}
public synchronized void notifyDirty() {
mDirty = true;
notifyAll();
}
public synchronized void terminate() {
mActive = false;
notifyAll();
}
}
}