blob: 1d642f9318a72ee09459baca3b45ac895f110e7f [file] [log] [blame]
Owen Lina2fba682011-08-17 22:07:43 +08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package com.android.gallery3d.app;
18
Ray Chen73e791c2011-10-04 15:19:44 +080019import android.os.Handler;
20import android.os.Message;
Owen Line80b4762012-03-13 14:17:01 +080021import android.os.SystemClock;
Ray Chen73e791c2011-10-04 15:19:44 +080022
Owen Lina2fba682011-08-17 22:07:43 +080023import com.android.gallery3d.common.Utils;
24import com.android.gallery3d.data.ContentListener;
25import com.android.gallery3d.data.DataManager;
26import com.android.gallery3d.data.MediaItem;
27import com.android.gallery3d.data.MediaObject;
28import com.android.gallery3d.data.MediaSet;
29import com.android.gallery3d.ui.AlbumSetView;
30import com.android.gallery3d.ui.SynchronizedHandler;
31
Owen Lina2fba682011-08-17 22:07:43 +080032import java.util.Arrays;
33import java.util.concurrent.Callable;
34import java.util.concurrent.ExecutionException;
35import java.util.concurrent.FutureTask;
36
37public class AlbumSetDataAdapter implements AlbumSetView.Model {
38 @SuppressWarnings("unused")
39 private static final String TAG = "AlbumSetDataAdapter";
40
41 private static final int INDEX_NONE = -1;
42
43 private static final int MIN_LOAD_COUNT = 4;
Chih-Chung Chang07069de2011-09-14 20:50:28 +080044 private static final int MAX_COVER_COUNT = 1;
Owen Lina2fba682011-08-17 22:07:43 +080045
46 private static final int MSG_LOAD_START = 1;
47 private static final int MSG_LOAD_FINISH = 2;
48 private static final int MSG_RUN_OBJECT = 3;
49
50 private static final MediaItem[] EMPTY_MEDIA_ITEMS = new MediaItem[0];
51
52 private final MediaSet[] mData;
53 private final MediaItem[][] mCoverData;
54 private final long[] mItemVersion;
55 private final long[] mSetVersion;
56
57 private int mActiveStart = 0;
58 private int mActiveEnd = 0;
59
60 private int mContentStart = 0;
61 private int mContentEnd = 0;
62
63 private final MediaSet mSource;
64 private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
65 private int mSize;
66
67 private AlbumSetView.ModelListener mModelListener;
68 private LoadingListener mLoadingListener;
69 private ReloadTask mReloadTask;
70
71 private final Handler mMainHandler;
72
Ray Chen73e791c2011-10-04 15:19:44 +080073 private final MySourceListener mSourceListener = new MySourceListener();
Owen Lina2fba682011-08-17 22:07:43 +080074
75 public AlbumSetDataAdapter(GalleryActivity activity, MediaSet albumSet, int cacheSize) {
76 mSource = Utils.checkNotNull(albumSet);
77 mCoverData = new MediaItem[cacheSize][];
78 mData = new MediaSet[cacheSize];
79 mItemVersion = new long[cacheSize];
80 mSetVersion = new long[cacheSize];
81 Arrays.fill(mItemVersion, MediaObject.INVALID_DATA_VERSION);
82 Arrays.fill(mSetVersion, MediaObject.INVALID_DATA_VERSION);
83
84 mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
85 @Override
86 public void handleMessage(Message message) {
87 switch (message.what) {
88 case MSG_RUN_OBJECT:
89 ((Runnable) message.obj).run();
90 return;
91 case MSG_LOAD_START:
92 if (mLoadingListener != null) mLoadingListener.onLoadingStarted();
93 return;
94 case MSG_LOAD_FINISH:
95 if (mLoadingListener != null) mLoadingListener.onLoadingFinished();
96 return;
97 }
98 }
99 };
100 }
101
102 public void pause() {
103 mReloadTask.terminate();
104 mReloadTask = null;
105 mSource.removeContentListener(mSourceListener);
106 }
107
108 public void resume() {
109 mSource.addContentListener(mSourceListener);
110 mReloadTask = new ReloadTask();
111 mReloadTask.start();
112 }
113
114 public MediaSet getMediaSet(int index) {
115 if (index < mActiveStart && index >= mActiveEnd) {
116 throw new IllegalArgumentException(String.format(
117 "%s not in (%s, %s)", index, mActiveStart, mActiveEnd));
118 }
119 return mData[index % mData.length];
120 }
121
122 public MediaItem[] getCoverItems(int index) {
123 if (index < mActiveStart && index >= mActiveEnd) {
124 throw new IllegalArgumentException(String.format(
125 "%s not in (%s, %s)", index, mActiveStart, mActiveEnd));
126 }
127 MediaItem[] result = mCoverData[index % mCoverData.length];
128
129 // If the result is not ready yet, return an empty array
130 return result == null ? EMPTY_MEDIA_ITEMS : result;
131 }
132
133 public int getActiveStart() {
134 return mActiveStart;
135 }
136
Owen Lina2fba682011-08-17 22:07:43 +0800137 public boolean isActive(int index) {
138 return index >= mActiveStart && index < mActiveEnd;
139 }
140
141 public int size() {
142 return mSize;
143 }
144
145 private void clearSlot(int slotIndex) {
146 mData[slotIndex] = null;
147 mCoverData[slotIndex] = null;
148 mItemVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
149 mSetVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
150 }
151
152 private void setContentWindow(int contentStart, int contentEnd) {
153 if (contentStart == mContentStart && contentEnd == mContentEnd) return;
154 MediaItem[][] data = mCoverData;
155 int length = data.length;
156
157 int start = this.mContentStart;
158 int end = this.mContentEnd;
159
160 mContentStart = contentStart;
161 mContentEnd = contentEnd;
162
163 if (contentStart >= end || start >= contentEnd) {
164 for (int i = start, n = end; i < n; ++i) {
165 clearSlot(i % length);
166 }
167 } else {
168 for (int i = start; i < contentStart; ++i) {
169 clearSlot(i % length);
170 }
171 for (int i = contentEnd, n = end; i < n; ++i) {
172 clearSlot(i % length);
173 }
174 }
175 mReloadTask.notifyDirty();
176 }
177
178 public void setActiveWindow(int start, int end) {
179 if (start == mActiveStart && end == mActiveEnd) return;
180
181 Utils.assertTrue(start <= end
182 && end - start <= mCoverData.length && end <= mSize);
183
184 mActiveStart = start;
185 mActiveEnd = end;
186
187 int length = mCoverData.length;
188 // If no data is visible, keep the cache content
189 if (start == end) return;
190
191 int contentStart = Utils.clamp((start + end) / 2 - length / 2,
192 0, Math.max(0, mSize - length));
193 int contentEnd = Math.min(contentStart + length, mSize);
194 if (mContentStart > start || mContentEnd < end
195 || Math.abs(contentStart - mContentStart) > MIN_LOAD_COUNT) {
196 setContentWindow(contentStart, contentEnd);
197 }
198 }
199
200 private class MySourceListener implements ContentListener {
201 public void onContentDirty() {
202 mReloadTask.notifyDirty();
203 }
204 }
205
206 public void setModelListener(AlbumSetView.ModelListener listener) {
207 mModelListener = listener;
208 }
209
210 public void setLoadingListener(LoadingListener listener) {
211 mLoadingListener = listener;
212 }
213
Owen Lina2fba682011-08-17 22:07:43 +0800214 private static class UpdateInfo {
215 public long version;
216 public int index;
217
218 public int size;
219 public MediaSet item;
220 public MediaItem covers[];
221 }
222
223 private class GetUpdateInfo implements Callable<UpdateInfo> {
224
225 private final long mVersion;
226
227 public GetUpdateInfo(long version) {
228 mVersion = version;
229 }
230
231 private int getInvalidIndex(long version) {
232 long setVersion[] = mSetVersion;
233 int length = setVersion.length;
234 for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
235 int index = i % length;
236 if (setVersion[i % length] != version) return i;
237 }
238 return INDEX_NONE;
239 }
240
241 @Override
242 public UpdateInfo call() throws Exception {
243 int index = getInvalidIndex(mVersion);
Ray Chen73e791c2011-10-04 15:19:44 +0800244 if (index == INDEX_NONE && mSourceVersion == mVersion) return null;
Owen Lina2fba682011-08-17 22:07:43 +0800245 UpdateInfo info = new UpdateInfo();
246 info.version = mSourceVersion;
247 info.index = index;
248 info.size = mSize;
249 return info;
250 }
251 }
252
253 private class UpdateContent implements Callable<Void> {
Ray Chen73e791c2011-10-04 15:19:44 +0800254 private final UpdateInfo mUpdateInfo;
Owen Lina2fba682011-08-17 22:07:43 +0800255
256 public UpdateContent(UpdateInfo info) {
257 mUpdateInfo = info;
258 }
259
260 public Void call() {
Ray Chen73e791c2011-10-04 15:19:44 +0800261 // Avoid notifying listeners of status change after pause
262 // Otherwise gallery will be in inconsistent state after resume.
263 if (mReloadTask == null) return null;
Owen Lina2fba682011-08-17 22:07:43 +0800264 UpdateInfo info = mUpdateInfo;
265 mSourceVersion = info.version;
266 if (mSize != info.size) {
267 mSize = info.size;
268 if (mModelListener != null) mModelListener.onSizeChanged(mSize);
269 if (mContentEnd > mSize) mContentEnd = mSize;
270 if (mActiveEnd > mSize) mActiveEnd = mSize;
271 }
272 // Note: info.index could be INDEX_NONE, i.e., -1
273 if (info.index >= mContentStart && info.index < mContentEnd) {
274 int pos = info.index % mCoverData.length;
275 mSetVersion[pos] = info.version;
276 long itemVersion = info.item.getDataVersion();
277 if (mItemVersion[pos] == itemVersion) return null;
278 mItemVersion[pos] = itemVersion;
279 mData[pos] = info.item;
280 mCoverData[pos] = info.covers;
281 if (mModelListener != null
282 && info.index >= mActiveStart && info.index < mActiveEnd) {
283 mModelListener.onWindowContentChanged(info.index);
284 }
285 }
286 return null;
287 }
288 }
289
290 private <T> T executeAndWait(Callable<T> callable) {
291 FutureTask<T> task = new FutureTask<T>(callable);
292 mMainHandler.sendMessage(
293 mMainHandler.obtainMessage(MSG_RUN_OBJECT, task));
294 try {
295 return task.get();
296 } catch (InterruptedException e) {
297 return null;
298 } catch (ExecutionException e) {
299 throw new RuntimeException(e);
300 }
301 }
302
303 // TODO: load active range first
304 private class ReloadTask extends Thread {
305 private volatile boolean mActive = true;
306 private volatile boolean mDirty = true;
307 private volatile boolean mIsLoading = false;
308
309 private void updateLoading(boolean loading) {
310 if (mIsLoading == loading) return;
311 mIsLoading = loading;
312 mMainHandler.sendEmptyMessage(loading ? MSG_LOAD_START : MSG_LOAD_FINISH);
313 }
314
315 @Override
316 public void run() {
317 boolean updateComplete = false;
318 while (mActive) {
319 synchronized (this) {
320 if (mActive && !mDirty && updateComplete) {
321 updateLoading(false);
322 Utils.waitWithoutInterrupt(this);
323 continue;
324 }
325 }
326 mDirty = false;
327 updateLoading(true);
328
329 long version;
330 synchronized (DataManager.LOCK) {
Owen Line80b4762012-03-13 14:17:01 +0800331 long start = SystemClock.uptimeMillis();
Owen Lina2fba682011-08-17 22:07:43 +0800332 version = mSource.reload();
Owen Line80b4762012-03-13 14:17:01 +0800333 long duration = SystemClock.uptimeMillis() - start;
334 if (duration > 20) {
335 Log.v("DebugLoadingTime", "finish reload - " + duration);
336 }
Owen Lina2fba682011-08-17 22:07:43 +0800337 }
338 UpdateInfo info = executeAndWait(new GetUpdateInfo(version));
339 updateComplete = info == null;
340 if (updateComplete) continue;
341
342 synchronized (DataManager.LOCK) {
343 if (info.version != version) {
344 info.version = version;
345 info.size = mSource.getSubMediaSetCount();
Chih-Chung Chang47d6ad02011-09-14 18:00:42 +0800346
347 // If the size becomes smaller after reload(), we may
348 // receive from GetUpdateInfo an index which is too
349 // big. Because the main thread is not aware of the size
350 // change until we call UpdateContent.
351 if (info.index >= info.size) {
352 info.index = INDEX_NONE;
353 }
Owen Lina2fba682011-08-17 22:07:43 +0800354 }
355 if (info.index != INDEX_NONE) {
356 info.item = mSource.getSubMediaSet(info.index);
357 if (info.item == null) continue;
Owen Lin735d0102011-10-27 21:24:21 +0800358 MediaItem cover = info.item.getCoverMediaItem();
359 info.covers = cover == null ? new MediaItem[0] : new MediaItem[] {cover};
Owen Lina2fba682011-08-17 22:07:43 +0800360 }
361 }
362 executeAndWait(new UpdateContent(info));
363 }
364 updateLoading(false);
365 }
366
367 public synchronized void notifyDirty() {
368 mDirty = true;
369 notifyAll();
370 }
371
372 public synchronized void terminate() {
373 mActive = false;
374 notifyAll();
375 }
376 }
377}
378
379