2 * Copyright (C) 2010 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.gallery3d.app;
19 import com.android.gallery3d.common.Utils;
20 import com.android.gallery3d.data.ContentListener;
21 import com.android.gallery3d.data.DataManager;
22 import com.android.gallery3d.data.MediaItem;
23 import com.android.gallery3d.data.MediaObject;
24 import com.android.gallery3d.data.MediaSet;
25 import com.android.gallery3d.ui.AlbumView;
26 import com.android.gallery3d.ui.SynchronizedHandler;
28 import android.os.Handler;
29 import android.os.Message;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.concurrent.Callable;
34 import java.util.concurrent.ExecutionException;
35 import java.util.concurrent.FutureTask;
37 public class AlbumDataAdapter implements AlbumView.Model {
38 @SuppressWarnings("unused")
39 private static final String TAG = "AlbumDataAdapter";
40 private static final int DATA_CACHE_SIZE = 1000;
42 private static final int MSG_LOAD_START = 1;
43 private static final int MSG_LOAD_FINISH = 2;
44 private static final int MSG_RUN_OBJECT = 3;
46 private static final int MIN_LOAD_COUNT = 32;
47 private static final int MAX_LOAD_COUNT = 64;
49 private final MediaItem[] mData;
50 private final long[] mItemVersion;
51 private final long[] mSetVersion;
53 private int mActiveStart = 0;
54 private int mActiveEnd = 0;
56 private int mContentStart = 0;
57 private int mContentEnd = 0;
59 private final MediaSet mSource;
60 private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
62 private final Handler mMainHandler;
63 private int mSize = 0;
65 private AlbumView.ModelListener mModelListener;
66 private MySourceListener mSourceListener = new MySourceListener();
67 private LoadingListener mLoadingListener;
69 private ReloadTask mReloadTask;
71 public AlbumDataAdapter(GalleryActivity context, MediaSet mediaSet) {
74 mData = new MediaItem[DATA_CACHE_SIZE];
75 mItemVersion = new long[DATA_CACHE_SIZE];
76 mSetVersion = new long[DATA_CACHE_SIZE];
77 Arrays.fill(mItemVersion, MediaObject.INVALID_DATA_VERSION);
78 Arrays.fill(mSetVersion, MediaObject.INVALID_DATA_VERSION);
80 mMainHandler = new SynchronizedHandler(context.getGLRoot()) {
82 public void handleMessage(Message message) {
83 switch (message.what) {
85 ((Runnable) message.obj).run();
88 if (mLoadingListener != null) mLoadingListener.onLoadingStarted();
91 if (mLoadingListener != null) mLoadingListener.onLoadingFinished();
98 public void resume() {
99 mSource.addContentListener(mSourceListener);
100 mReloadTask = new ReloadTask();
104 public void pause() {
105 mReloadTask.terminate();
107 mSource.removeContentListener(mSourceListener);
110 public MediaItem get(int index) {
111 if (!isActive(index)) {
112 throw new IllegalArgumentException(String.format(
113 "%s not in (%s, %s)", index, mActiveStart, mActiveEnd));
115 return mData[index % mData.length];
118 public int getActiveStart() {
122 public int getActiveEnd() {
126 public boolean isActive(int index) {
127 return index >= mActiveStart && index < mActiveEnd;
134 private void clearSlot(int slotIndex) {
135 mData[slotIndex] = null;
136 mItemVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
137 mSetVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
140 private void setContentWindow(int contentStart, int contentEnd) {
141 if (contentStart == mContentStart && contentEnd == mContentEnd) return;
142 int end = mContentEnd;
143 int start = mContentStart;
145 // We need change the content window before calling reloadData(...)
146 synchronized (this) {
147 mContentStart = contentStart;
148 mContentEnd = contentEnd;
150 long[] itemVersion = mItemVersion;
151 long[] setVersion = mSetVersion;
152 if (contentStart >= end || start >= contentEnd) {
153 for (int i = start, n = end; i < n; ++i) {
154 clearSlot(i % DATA_CACHE_SIZE);
157 for (int i = start; i < contentStart; ++i) {
158 clearSlot(i % DATA_CACHE_SIZE);
160 for (int i = contentEnd, n = end; i < n; ++i) {
161 clearSlot(i % DATA_CACHE_SIZE);
164 if (mReloadTask != null) mReloadTask.notifyDirty();
167 public void setActiveWindow(int start, int end) {
168 if (start == mActiveStart && end == mActiveEnd) return;
170 Utils.assertTrue(start <= end
171 && end - start <= mData.length && end <= mSize);
173 int length = mData.length;
174 mActiveStart = start;
177 // If no data is visible, keep the cache content
178 if (start == end) return;
180 int contentStart = Utils.clamp((start + end) / 2 - length / 2,
181 0, Math.max(0, mSize - length));
182 int contentEnd = Math.min(contentStart + length, mSize);
183 if (mContentStart > start || mContentEnd < end
184 || Math.abs(contentStart - mContentStart) > MIN_LOAD_COUNT) {
185 setContentWindow(contentStart, contentEnd);
189 private class MySourceListener implements ContentListener {
190 public void onContentDirty() {
191 if (mReloadTask != null) mReloadTask.notifyDirty();
195 public void setModelListener(AlbumView.ModelListener listener) {
196 mModelListener = listener;
199 public void setLoadingListener(LoadingListener listener) {
200 mLoadingListener = listener;
203 private <T> T executeAndWait(Callable<T> callable) {
204 FutureTask<T> task = new FutureTask<T>(callable);
205 mMainHandler.sendMessage(
206 mMainHandler.obtainMessage(MSG_RUN_OBJECT, task));
209 } catch (InterruptedException e) {
211 } catch (ExecutionException e) {
212 throw new RuntimeException(e);
216 private static class UpdateInfo {
218 public int reloadStart;
219 public int reloadCount;
222 public ArrayList<MediaItem> items;
225 private class GetUpdateInfo implements Callable<UpdateInfo> {
226 private final long mVersion;
228 public GetUpdateInfo(long version) {
232 public UpdateInfo call() throws Exception {
233 UpdateInfo info = new UpdateInfo();
234 long version = mVersion;
235 info.version = mSourceVersion;
237 long setVersion[] = mSetVersion;
238 for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
239 int index = i % DATA_CACHE_SIZE;
240 if (setVersion[index] != version) {
241 info.reloadStart = i;
242 info.reloadCount = Math.min(MAX_LOAD_COUNT, n - i);
246 return mSourceVersion == mVersion ? null : info;
250 private class UpdateContent implements Callable<Void> {
252 private UpdateInfo mUpdateInfo;
254 public UpdateContent(UpdateInfo info) {
259 public Void call() throws Exception {
260 UpdateInfo info = mUpdateInfo;
261 mSourceVersion = info.version;
262 if (mSize != info.size) {
264 if (mModelListener != null) mModelListener.onSizeChanged(mSize);
265 if (mContentEnd > mSize) mContentEnd = mSize;
266 if (mActiveEnd > mSize) mActiveEnd = mSize;
269 ArrayList<MediaItem> items = info.items;
271 if (items == null) return null;
272 int start = Math.max(info.reloadStart, mContentStart);
273 int end = Math.min(info.reloadStart + items.size(), mContentEnd);
275 for (int i = start; i < end; ++i) {
276 int index = i % DATA_CACHE_SIZE;
277 mSetVersion[index] = info.version;
278 MediaItem updateItem = items.get(i - info.reloadStart);
279 long itemVersion = updateItem.getDataVersion();
280 if (mItemVersion[index] != itemVersion) {
281 mItemVersion[index] = itemVersion;
282 mData[index] = updateItem;
283 if (mModelListener != null && i >= mActiveStart && i < mActiveEnd) {
284 mModelListener.onWindowContentChanged(i);
293 * The thread model of ReloadTask
295 * [Reload Task] [Main Thread]
297 * getUpdateInfo() --> | (synchronous call)
298 * (wait) <---- getUpdateInfo()
302 * updateContent() --> | (synchronous call)
303 * (wait) updateContent()
307 private class ReloadTask extends Thread {
309 private volatile boolean mActive = true;
310 private volatile boolean mDirty = true;
311 private boolean mIsLoading = false;
313 private void updateLoading(boolean loading) {
314 if (mIsLoading == loading) return;
315 mIsLoading = loading;
316 mMainHandler.sendEmptyMessage(loading ? MSG_LOAD_START : MSG_LOAD_FINISH);
321 boolean updateComplete = false;
323 synchronized (this) {
324 if (mActive && !mDirty && updateComplete) {
325 updateLoading(false);
326 Utils.waitWithoutInterrupt(this);
333 synchronized (DataManager.LOCK) {
334 version = mSource.reload();
336 UpdateInfo info = executeAndWait(new GetUpdateInfo(version));
337 updateComplete = info == null;
338 if (updateComplete) continue;
339 synchronized (DataManager.LOCK) {
340 if (info.version != version) {
341 info.size = mSource.getMediaItemCount();
342 info.version = version;
344 if (info.reloadCount > 0) {
345 info.items = mSource.getMediaItem(info.reloadStart, info.reloadCount);
348 executeAndWait(new UpdateContent(info));
350 updateLoading(false);
353 public synchronized void notifyDirty() {
358 public synchronized void terminate() {