blob: a4fd411afe810cbc4cd1a2a359061877b883edc8 [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
Owen Lin73a04ff2012-03-14 17:27:24 +080019import android.graphics.Bitmap;
20import android.graphics.BitmapRegionDecoder;
21import android.os.Handler;
22import android.os.Message;
23
Owen Linb251df22011-09-12 12:45:16 +080024import com.android.gallery3d.common.BitmapUtils;
Owen Lina2fba682011-08-17 22:07:43 +080025import com.android.gallery3d.common.Utils;
26import com.android.gallery3d.data.ContentListener;
27import com.android.gallery3d.data.DataManager;
28import com.android.gallery3d.data.MediaItem;
29import com.android.gallery3d.data.MediaObject;
30import com.android.gallery3d.data.MediaSet;
31import com.android.gallery3d.data.Path;
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +080032import com.android.gallery3d.ui.BitmapScreenNail;
Owen Lina2fba682011-08-17 22:07:43 +080033import com.android.gallery3d.ui.PhotoView;
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +080034import com.android.gallery3d.ui.ScreenNail;
Owen Lina2fba682011-08-17 22:07:43 +080035import com.android.gallery3d.ui.SynchronizedHandler;
36import com.android.gallery3d.ui.TileImageViewAdapter;
37import com.android.gallery3d.util.Future;
38import com.android.gallery3d.util.FutureListener;
39import com.android.gallery3d.util.ThreadPool;
Owen Linb251df22011-09-12 12:45:16 +080040import com.android.gallery3d.util.ThreadPool.Job;
41import com.android.gallery3d.util.ThreadPool.JobContext;
Owen Lina2fba682011-08-17 22:07:43 +080042
Owen Lina2fba682011-08-17 22:07:43 +080043import java.util.ArrayList;
44import java.util.Arrays;
45import java.util.HashMap;
46import java.util.HashSet;
47import java.util.concurrent.Callable;
48import java.util.concurrent.ExecutionException;
49import java.util.concurrent.FutureTask;
50
51public class PhotoDataAdapter implements PhotoPage.Model {
52 @SuppressWarnings("unused")
53 private static final String TAG = "PhotoDataAdapter";
54
55 private static final int MSG_LOAD_START = 1;
56 private static final int MSG_LOAD_FINISH = 2;
57 private static final int MSG_RUN_OBJECT = 3;
58
59 private static final int MIN_LOAD_COUNT = 8;
60 private static final int DATA_CACHE_SIZE = 32;
61 private static final int IMAGE_CACHE_SIZE = 5;
62
63 private static final int BIT_SCREEN_NAIL = 1;
64 private static final int BIT_FULL_IMAGE = 2;
65
66 private static final long VERSION_OUT_OF_RANGE = MediaObject.nextVersionNumber();
67
68 // sImageFetchSeq is the fetching sequence for images.
69 // We want to fetch the current screennail first (offset = 0), the next
70 // screennail (offset = +1), then the previous screennail (offset = -1) etc.
71 // After all the screennail are fetched, we fetch the full images (only some
72 // of them because of we don't want to use too much memory).
73 private static ImageFetch[] sImageFetchSeq;
74
75 private static class ImageFetch {
76 int indexOffset;
77 int imageBit;
78 public ImageFetch(int offset, int bit) {
79 indexOffset = offset;
80 imageBit = bit;
81 }
82 }
83
84 static {
85 int k = 0;
86 sImageFetchSeq = new ImageFetch[1 + (IMAGE_CACHE_SIZE - 1) * 2 + 3];
87 sImageFetchSeq[k++] = new ImageFetch(0, BIT_SCREEN_NAIL);
88
89 for (int i = 1; i < IMAGE_CACHE_SIZE; ++i) {
90 sImageFetchSeq[k++] = new ImageFetch(i, BIT_SCREEN_NAIL);
91 sImageFetchSeq[k++] = new ImageFetch(-i, BIT_SCREEN_NAIL);
92 }
93
94 sImageFetchSeq[k++] = new ImageFetch(0, BIT_FULL_IMAGE);
95 sImageFetchSeq[k++] = new ImageFetch(1, BIT_FULL_IMAGE);
96 sImageFetchSeq[k++] = new ImageFetch(-1, BIT_FULL_IMAGE);
97 }
98
99 private final TileImageViewAdapter mTileProvider = new TileImageViewAdapter();
100
101 // PhotoDataAdapter caches MediaItems (data) and ImageEntries (image).
102 //
103 // The MediaItems are stored in the mData array, which has DATA_CACHE_SIZE
104 // entries. The valid index range are [mContentStart, mContentEnd). We keep
105 // mContentEnd - mContentStart <= DATA_CACHE_SIZE, so we can use
106 // (i % DATA_CACHE_SIZE) as index to the array.
107 //
108 // The valid MediaItem window size (mContentEnd - mContentStart) may be
109 // smaller than DATA_CACHE_SIZE because we only update the window and reload
110 // the MediaItems when there are significant changes to the window position
111 // (>= MIN_LOAD_COUNT).
112 private final MediaItem mData[] = new MediaItem[DATA_CACHE_SIZE];
113 private int mContentStart = 0;
114 private int mContentEnd = 0;
115
116 /*
117 * The ImageCache is a version-to-ImageEntry map. It only holds
118 * the ImageEntries in the range of [mActiveStart, mActiveEnd).
119 * We also keep mActiveEnd - mActiveStart <= IMAGE_CACHE_SIZE.
120 * Besides, the [mActiveStart, mActiveEnd) range must be contained
121 * within the[mContentStart, mContentEnd) range.
122 */
123 private HashMap<Long, ImageEntry> mImageCache = new HashMap<Long, ImageEntry>();
124 private int mActiveStart = 0;
125 private int mActiveEnd = 0;
126
127 // mCurrentIndex is the "center" image the user is viewing. The change of
128 // mCurrentIndex triggers the data loading and image loading.
129 private int mCurrentIndex;
130
131 // mChanges keeps the version number (of MediaItem) about the previous,
132 // current, and next image. If the version number changes, we invalidate
133 // the model. This is used after a database reload or mCurrentIndex changes.
134 private final long mChanges[] = new long[3];
135
136 private final Handler mMainHandler;
137 private final ThreadPool mThreadPool;
138
139 private final PhotoView mPhotoView;
140 private final MediaSet mSource;
141 private ReloadTask mReloadTask;
142
143 private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
144 private int mSize = 0;
145 private Path mItemPath;
146 private boolean mIsActive;
147
148 public interface DataListener extends LoadingListener {
Owen Linace280a2011-08-30 10:38:59 +0800149 public void onPhotoAvailable(long version, boolean fullImage);
Owen Lina2fba682011-08-17 22:07:43 +0800150 public void onPhotoChanged(int index, Path item);
151 }
152
153 private DataListener mDataListener;
154
155 private final SourceListener mSourceListener = new SourceListener();
156
157 // The path of the current viewing item will be stored in mItemPath.
158 // If mItemPath is not null, mCurrentIndex is only a hint for where we
159 // can find the item. If mItemPath is null, then we use the mCurrentIndex to
160 // find the image being viewed.
161 public PhotoDataAdapter(GalleryActivity activity,
162 PhotoView view, MediaSet mediaSet, Path itemPath, int indexHint) {
163 mSource = Utils.checkNotNull(mediaSet);
164 mPhotoView = Utils.checkNotNull(view);
165 mItemPath = Utils.checkNotNull(itemPath);
166 mCurrentIndex = indexHint;
167 mThreadPool = activity.getThreadPool();
168
169 Arrays.fill(mChanges, MediaObject.INVALID_DATA_VERSION);
170
171 mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
172 @SuppressWarnings("unchecked")
173 @Override
174 public void handleMessage(Message message) {
175 switch (message.what) {
176 case MSG_RUN_OBJECT:
177 ((Runnable) message.obj).run();
178 return;
179 case MSG_LOAD_START: {
180 if (mDataListener != null) mDataListener.onLoadingStarted();
181 return;
182 }
183 case MSG_LOAD_FINISH: {
184 if (mDataListener != null) mDataListener.onLoadingFinished();
185 return;
186 }
187 default: throw new AssertionError();
188 }
189 }
190 };
191
192 updateSlidingWindow();
193 }
194
195 private long getVersion(int index) {
196 if (index < 0 || index >= mSize) return VERSION_OUT_OF_RANGE;
197 if (index >= mContentStart && index < mContentEnd) {
198 MediaItem item = mData[index % DATA_CACHE_SIZE];
199 if (item != null) return item.getDataVersion();
200 }
201 return MediaObject.INVALID_DATA_VERSION;
202 }
203
204 private void fireModelInvalidated() {
205 for (int i = -1; i <= 1; ++i) {
206 long current = getVersion(mCurrentIndex + i);
207 long change = mChanges[i + 1];
208 if (current != change) {
209 mPhotoView.notifyImageInvalidated(i);
210 mChanges[i + 1] = current;
211 }
212 }
213 }
214
215 public void setDataListener(DataListener listener) {
216 mDataListener = listener;
217 }
218
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800219 private void updateScreenNail(long version, Future<ScreenNail> future) {
Owen Lina2fba682011-08-17 22:07:43 +0800220 ImageEntry entry = mImageCache.get(version);
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800221 ScreenNail screenNail = future.get();
222
Owen Linae550552011-09-06 19:46:46 +0800223 if (entry == null || entry.screenNailTask != future) {
Owen Lina2fba682011-08-17 22:07:43 +0800224 if (screenNail != null) screenNail.recycle();
225 return;
226 }
Owen Linae550552011-09-06 19:46:46 +0800227
Owen Lina2fba682011-08-17 22:07:43 +0800228 entry.screenNailTask = null;
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800229 entry.screenNail = screenNail;
Owen Lina2fba682011-08-17 22:07:43 +0800230
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800231 if (screenNail == null) {
Owen Lina2fba682011-08-17 22:07:43 +0800232 entry.failToLoad = true;
Yuli Huanga565ca22012-02-07 15:51:24 +0800233 }
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800234
Yuli Huanga565ca22012-02-07 15:51:24 +0800235 if (mDataListener != null) {
236 mDataListener.onPhotoAvailable(version, false);
237 }
238 for (int i = -1; i <= 1; ++i) {
239 if (version == getVersion(mCurrentIndex + i)) {
240 if (i == 0) updateTileProvider(entry);
241 mPhotoView.notifyImageInvalidated(i);
Owen Lina2fba682011-08-17 22:07:43 +0800242 }
243 }
244 updateImageRequests();
245 }
246
247 private void updateFullImage(long version, Future<BitmapRegionDecoder> future) {
248 ImageEntry entry = mImageCache.get(version);
Owen Linae550552011-09-06 19:46:46 +0800249 if (entry == null || entry.fullImageTask != future) {
Owen Lina2fba682011-08-17 22:07:43 +0800250 BitmapRegionDecoder fullImage = future.get();
251 if (fullImage != null) fullImage.recycle();
252 return;
253 }
Owen Linae550552011-09-06 19:46:46 +0800254
Owen Lina2fba682011-08-17 22:07:43 +0800255 entry.fullImageTask = null;
256 entry.fullImage = future.get();
257 if (entry.fullImage != null) {
Owen Linace280a2011-08-30 10:38:59 +0800258 if (mDataListener != null) {
259 mDataListener.onPhotoAvailable(version, true);
260 }
Owen Lina2fba682011-08-17 22:07:43 +0800261 if (version == getVersion(mCurrentIndex)) {
262 updateTileProvider(entry);
263 mPhotoView.notifyImageInvalidated(0);
264 }
265 }
266 updateImageRequests();
267 }
268
269 public void resume() {
270 mIsActive = true;
271 mSource.addContentListener(mSourceListener);
272 updateImageCache();
273 updateImageRequests();
274
275 mReloadTask = new ReloadTask();
276 mReloadTask.start();
277
278 mPhotoView.notifyModelInvalidated();
279 }
280
281 public void pause() {
282 mIsActive = false;
283
284 mReloadTask.terminate();
285 mReloadTask = null;
286
287 mSource.removeContentListener(mSourceListener);
288
289 for (ImageEntry entry : mImageCache.values()) {
290 if (entry.fullImageTask != null) entry.fullImageTask.cancel();
291 if (entry.screenNailTask != null) entry.screenNailTask.cancel();
292 }
293 mImageCache.clear();
294 mTileProvider.clear();
295 }
296
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800297 private ScreenNail getImage(int index) {
Owen Lina2fba682011-08-17 22:07:43 +0800298 if (index < 0 || index >= mSize || !mIsActive) return null;
299 Utils.assertTrue(index >= mActiveStart && index < mActiveEnd);
300
301 ImageEntry entry = mImageCache.get(getVersion(index));
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800302 return entry == null ? null : entry.screenNail;
Owen Lina2fba682011-08-17 22:07:43 +0800303 }
304
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800305 public ScreenNail getPrevScreenNail() {
Owen Lina2fba682011-08-17 22:07:43 +0800306 return getImage(mCurrentIndex - 1);
307 }
308
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800309 public ScreenNail getNextScreenNail() {
Owen Lina2fba682011-08-17 22:07:43 +0800310 return getImage(mCurrentIndex + 1);
311 }
312
313 private void updateCurrentIndex(int index) {
314 mCurrentIndex = index;
315 updateSlidingWindow();
316
317 MediaItem item = mData[index % DATA_CACHE_SIZE];
318 mItemPath = item == null ? null : item.getPath();
319
320 updateImageCache();
321 updateImageRequests();
322 updateTileProvider();
323 mPhotoView.notifyOnNewImage();
324
325 if (mDataListener != null) {
326 mDataListener.onPhotoChanged(index, mItemPath);
327 }
328 fireModelInvalidated();
329 }
330
331 public void next() {
332 updateCurrentIndex(mCurrentIndex + 1);
333 }
334
335 public void previous() {
336 updateCurrentIndex(mCurrentIndex - 1);
337 }
338
339 public void jumpTo(int index) {
340 if (mCurrentIndex == index) return;
341 updateCurrentIndex(index);
342 }
343
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800344 public ScreenNail getScreenNail() {
345 return mTileProvider.getScreenNail();
Owen Lina2fba682011-08-17 22:07:43 +0800346 }
347
348 public int getImageHeight() {
349 return mTileProvider.getImageHeight();
350 }
351
352 public int getImageWidth() {
353 return mTileProvider.getImageWidth();
354 }
355
356 public int getImageRotation() {
357 ImageEntry entry = mImageCache.get(getVersion(mCurrentIndex));
358 return entry == null ? 0 : entry.rotation;
359 }
360
361 public int getLevelCount() {
362 return mTileProvider.getLevelCount();
363 }
364
Chih-Chung Chang4a01c6e2012-03-21 16:19:21 +0800365 public Bitmap getTile(int level, int x, int y, int tileSize,
366 int borderSize) {
367 return mTileProvider.getTile(level, x, y, tileSize, borderSize);
Owen Lina2fba682011-08-17 22:07:43 +0800368 }
369
370 public boolean isFailedToLoad() {
371 return mTileProvider.isFailedToLoad();
372 }
373
374 public boolean isEmpty() {
375 return mSize == 0;
376 }
377
378 public int getCurrentIndex() {
379 return mCurrentIndex;
380 }
381
382 public MediaItem getCurrentMediaItem() {
383 return mData[mCurrentIndex % DATA_CACHE_SIZE];
384 }
385
386 public void setCurrentPhoto(Path path, int indexHint) {
387 if (mItemPath == path) return;
388 mItemPath = path;
389 mCurrentIndex = indexHint;
390 updateSlidingWindow();
391 updateImageCache();
392 fireModelInvalidated();
393
394 // We need to reload content if the path doesn't match.
395 MediaItem item = getCurrentMediaItem();
396 if (item != null && item.getPath() != path) {
397 if (mReloadTask != null) mReloadTask.notifyDirty();
398 }
399 }
400
401 private void updateTileProvider() {
402 ImageEntry entry = mImageCache.get(getVersion(mCurrentIndex));
403 if (entry == null) { // in loading
404 mTileProvider.clear();
405 } else {
406 updateTileProvider(entry);
407 }
408 }
409
410 private void updateTileProvider(ImageEntry entry) {
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800411 ScreenNail screenNail = entry.screenNail;
Owen Lina2fba682011-08-17 22:07:43 +0800412 BitmapRegionDecoder fullImage = entry.fullImage;
413 if (screenNail != null) {
414 if (fullImage != null) {
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800415 mTileProvider.setScreenNail(screenNail,
Owen Lina2fba682011-08-17 22:07:43 +0800416 fullImage.getWidth(), fullImage.getHeight());
417 mTileProvider.setRegionDecoder(fullImage);
418 } else {
419 int width = screenNail.getWidth();
420 int height = screenNail.getHeight();
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800421 mTileProvider.setScreenNail(screenNail, width, height);
Owen Lina2fba682011-08-17 22:07:43 +0800422 }
423 } else {
424 mTileProvider.clear();
425 if (entry.failToLoad) mTileProvider.setFailedToLoad();
426 }
427 }
428
429 private void updateSlidingWindow() {
430 // 1. Update the image window
431 int start = Utils.clamp(mCurrentIndex - IMAGE_CACHE_SIZE / 2,
432 0, Math.max(0, mSize - IMAGE_CACHE_SIZE));
433 int end = Math.min(mSize, start + IMAGE_CACHE_SIZE);
434
435 if (mActiveStart == start && mActiveEnd == end) return;
436
437 mActiveStart = start;
438 mActiveEnd = end;
439
440 // 2. Update the data window
441 start = Utils.clamp(mCurrentIndex - DATA_CACHE_SIZE / 2,
442 0, Math.max(0, mSize - DATA_CACHE_SIZE));
443 end = Math.min(mSize, start + DATA_CACHE_SIZE);
444 if (mContentStart > mActiveStart || mContentEnd < mActiveEnd
445 || Math.abs(start - mContentStart) > MIN_LOAD_COUNT) {
446 for (int i = mContentStart; i < mContentEnd; ++i) {
447 if (i < start || i >= end) {
448 mData[i % DATA_CACHE_SIZE] = null;
449 }
450 }
451 mContentStart = start;
452 mContentEnd = end;
453 if (mReloadTask != null) mReloadTask.notifyDirty();
454 }
455 }
456
457 private void updateImageRequests() {
458 if (!mIsActive) return;
459
460 int currentIndex = mCurrentIndex;
461 MediaItem item = mData[currentIndex % DATA_CACHE_SIZE];
462 if (item == null || item.getPath() != mItemPath) {
463 // current item mismatch - don't request image
464 return;
465 }
466
467 // 1. Find the most wanted request and start it (if not already started).
468 Future<?> task = null;
469 for (int i = 0; i < sImageFetchSeq.length; i++) {
470 int offset = sImageFetchSeq[i].indexOffset;
471 int bit = sImageFetchSeq[i].imageBit;
472 task = startTaskIfNeeded(currentIndex + offset, bit);
473 if (task != null) break;
474 }
475
476 // 2. Cancel everything else.
477 for (ImageEntry entry : mImageCache.values()) {
478 if (entry.screenNailTask != null && entry.screenNailTask != task) {
479 entry.screenNailTask.cancel();
480 entry.screenNailTask = null;
481 entry.requestedBits &= ~BIT_SCREEN_NAIL;
482 }
483 if (entry.fullImageTask != null && entry.fullImageTask != task) {
484 entry.fullImageTask.cancel();
485 entry.fullImageTask = null;
486 entry.requestedBits &= ~BIT_FULL_IMAGE;
487 }
488 }
489 }
490
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800491 private static class ScreenNailJob implements Job<ScreenNail> {
Owen Linb251df22011-09-12 12:45:16 +0800492 private MediaItem mItem;
493
494 public ScreenNailJob(MediaItem item) {
495 mItem = item;
496 }
497
498 @Override
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800499 public ScreenNail run(JobContext jc) {
500 // We try to get a ScreenNail first, if it fails, we fallback to get
501 // a Bitmap and then wrap it in a BitmapScreenNail instead.
502 ScreenNail s = mItem.getScreenNail();
503 if (s != null) return s;
504
Owen Linb251df22011-09-12 12:45:16 +0800505 Bitmap bitmap = mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc);
506 if (jc.isCancelled()) return null;
507 if (bitmap != null) {
508 bitmap = BitmapUtils.rotateBitmap(bitmap,
509 mItem.getRotation() - mItem.getFullImageRotation(), true);
510 }
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800511 return new BitmapScreenNail(bitmap, mItem.getFullImageRotation());
Owen Linb251df22011-09-12 12:45:16 +0800512 }
513 }
514
Owen Lina2fba682011-08-17 22:07:43 +0800515 // Returns the task if we started the task or the task is already started.
516 private Future<?> startTaskIfNeeded(int index, int which) {
517 if (index < mActiveStart || index >= mActiveEnd) return null;
518
519 ImageEntry entry = mImageCache.get(getVersion(index));
520 if (entry == null) return null;
521
522 if (which == BIT_SCREEN_NAIL && entry.screenNailTask != null) {
523 return entry.screenNailTask;
524 } else if (which == BIT_FULL_IMAGE && entry.fullImageTask != null) {
525 return entry.fullImageTask;
526 }
527
528 MediaItem item = mData[index % DATA_CACHE_SIZE];
529 Utils.assertTrue(item != null);
530
531 if (which == BIT_SCREEN_NAIL
532 && (entry.requestedBits & BIT_SCREEN_NAIL) == 0) {
533 entry.requestedBits |= BIT_SCREEN_NAIL;
534 entry.screenNailTask = mThreadPool.submit(
Owen Linb251df22011-09-12 12:45:16 +0800535 new ScreenNailJob(item),
Owen Lina2fba682011-08-17 22:07:43 +0800536 new ScreenNailListener(item.getDataVersion()));
537 // request screen nail
538 return entry.screenNailTask;
539 }
540 if (which == BIT_FULL_IMAGE
541 && (entry.requestedBits & BIT_FULL_IMAGE) == 0
542 && (item.getSupportedOperations()
543 & MediaItem.SUPPORT_FULL_IMAGE) != 0) {
544 entry.requestedBits |= BIT_FULL_IMAGE;
545 entry.fullImageTask = mThreadPool.submit(
546 item.requestLargeImage(),
547 new FullImageListener(item.getDataVersion()));
548 // request full image
549 return entry.fullImageTask;
550 }
551 return null;
552 }
553
554 private void updateImageCache() {
555 HashSet<Long> toBeRemoved = new HashSet<Long>(mImageCache.keySet());
556 for (int i = mActiveStart; i < mActiveEnd; ++i) {
557 MediaItem item = mData[i % DATA_CACHE_SIZE];
558 long version = item == null
559 ? MediaObject.INVALID_DATA_VERSION
560 : item.getDataVersion();
561 if (version == MediaObject.INVALID_DATA_VERSION) continue;
562 ImageEntry entry = mImageCache.get(version);
563 toBeRemoved.remove(version);
564 if (entry != null) {
565 if (Math.abs(i - mCurrentIndex) > 1) {
566 if (entry.fullImageTask != null) {
567 entry.fullImageTask.cancel();
568 entry.fullImageTask = null;
569 }
570 entry.fullImage = null;
571 entry.requestedBits &= ~BIT_FULL_IMAGE;
572 }
573 } else {
574 entry = new ImageEntry();
Owen Linb251df22011-09-12 12:45:16 +0800575 entry.rotation = item.getFullImageRotation();
Owen Lina2fba682011-08-17 22:07:43 +0800576 mImageCache.put(version, entry);
577 }
578 }
579
580 // Clear the data and requests for ImageEntries outside the new window.
581 for (Long version : toBeRemoved) {
582 ImageEntry entry = mImageCache.remove(version);
583 if (entry.fullImageTask != null) entry.fullImageTask.cancel();
584 if (entry.screenNailTask != null) entry.screenNailTask.cancel();
585 }
586 }
587
588 private class FullImageListener
589 implements Runnable, FutureListener<BitmapRegionDecoder> {
590 private final long mVersion;
591 private Future<BitmapRegionDecoder> mFuture;
592
593 public FullImageListener(long version) {
594 mVersion = version;
595 }
596
Owen Linae550552011-09-06 19:46:46 +0800597 @Override
Owen Lina2fba682011-08-17 22:07:43 +0800598 public void onFutureDone(Future<BitmapRegionDecoder> future) {
599 mFuture = future;
600 mMainHandler.sendMessage(
601 mMainHandler.obtainMessage(MSG_RUN_OBJECT, this));
602 }
603
Owen Linae550552011-09-06 19:46:46 +0800604 @Override
Owen Lina2fba682011-08-17 22:07:43 +0800605 public void run() {
606 updateFullImage(mVersion, mFuture);
607 }
608 }
609
610 private class ScreenNailListener
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800611 implements Runnable, FutureListener<ScreenNail> {
Owen Lina2fba682011-08-17 22:07:43 +0800612 private final long mVersion;
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800613 private Future<ScreenNail> mFuture;
Owen Lina2fba682011-08-17 22:07:43 +0800614
615 public ScreenNailListener(long version) {
616 mVersion = version;
617 }
618
Owen Linae550552011-09-06 19:46:46 +0800619 @Override
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800620 public void onFutureDone(Future<ScreenNail> future) {
Owen Lina2fba682011-08-17 22:07:43 +0800621 mFuture = future;
622 mMainHandler.sendMessage(
623 mMainHandler.obtainMessage(MSG_RUN_OBJECT, this));
624 }
625
Owen Linae550552011-09-06 19:46:46 +0800626 @Override
Owen Lina2fba682011-08-17 22:07:43 +0800627 public void run() {
628 updateScreenNail(mVersion, mFuture);
629 }
630 }
631
632 private static class ImageEntry {
633 public int requestedBits = 0;
634 public int rotation;
635 public BitmapRegionDecoder fullImage;
Chih-Chung Chang706a7cf2012-03-15 16:38:45 +0800636 public ScreenNail screenNail;
637 public Future<ScreenNail> screenNailTask;
Owen Lina2fba682011-08-17 22:07:43 +0800638 public Future<BitmapRegionDecoder> fullImageTask;
639 public boolean failToLoad = false;
640 }
641
642 private class SourceListener implements ContentListener {
643 public void onContentDirty() {
644 if (mReloadTask != null) mReloadTask.notifyDirty();
645 }
646 }
647
648 private <T> T executeAndWait(Callable<T> callable) {
649 FutureTask<T> task = new FutureTask<T>(callable);
650 mMainHandler.sendMessage(
651 mMainHandler.obtainMessage(MSG_RUN_OBJECT, task));
652 try {
653 return task.get();
654 } catch (InterruptedException e) {
655 return null;
656 } catch (ExecutionException e) {
657 throw new RuntimeException(e);
658 }
659 }
660
661 private static class UpdateInfo {
662 public long version;
663 public boolean reloadContent;
664 public Path target;
665 public int indexHint;
666 public int contentStart;
667 public int contentEnd;
668
669 public int size;
670 public ArrayList<MediaItem> items;
671 }
672
673 private class GetUpdateInfo implements Callable<UpdateInfo> {
674
675 private boolean needContentReload() {
676 for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
677 if (mData[i % DATA_CACHE_SIZE] == null) return true;
678 }
679 MediaItem current = mData[mCurrentIndex % DATA_CACHE_SIZE];
680 return current == null || current.getPath() != mItemPath;
681 }
682
683 @Override
684 public UpdateInfo call() throws Exception {
Owen Linace280a2011-08-30 10:38:59 +0800685 // TODO: Try to load some data in first update
Owen Lina2fba682011-08-17 22:07:43 +0800686 UpdateInfo info = new UpdateInfo();
687 info.version = mSourceVersion;
688 info.reloadContent = needContentReload();
689 info.target = mItemPath;
690 info.indexHint = mCurrentIndex;
691 info.contentStart = mContentStart;
692 info.contentEnd = mContentEnd;
693 info.size = mSize;
694 return info;
695 }
696 }
697
698 private class UpdateContent implements Callable<Void> {
699 UpdateInfo mUpdateInfo;
700
701 public UpdateContent(UpdateInfo updateInfo) {
702 mUpdateInfo = updateInfo;
703 }
704
705 @Override
706 public Void call() throws Exception {
707 UpdateInfo info = mUpdateInfo;
708 mSourceVersion = info.version;
709
710 if (info.size != mSize) {
711 mSize = info.size;
712 if (mContentEnd > mSize) mContentEnd = mSize;
713 if (mActiveEnd > mSize) mActiveEnd = mSize;
714 }
715
716 if (info.indexHint == MediaSet.INDEX_NOT_FOUND) {
717 // The image has been deleted, clear mItemPath, the
718 // mCurrentIndex will be updated in the updateCurrentItem().
719 mItemPath = null;
720 updateCurrentItem();
721 } else {
722 mCurrentIndex = info.indexHint;
723 }
724
725 updateSlidingWindow();
726
727 if (info.items != null) {
728 int start = Math.max(info.contentStart, mContentStart);
729 int end = Math.min(info.contentStart + info.items.size(), mContentEnd);
730 int dataIndex = start % DATA_CACHE_SIZE;
731 for (int i = start; i < end; ++i) {
732 mData[dataIndex] = info.items.get(i - info.contentStart);
733 if (++dataIndex == DATA_CACHE_SIZE) dataIndex = 0;
734 }
735 }
736 if (mItemPath == null) {
737 MediaItem current = mData[mCurrentIndex % DATA_CACHE_SIZE];
738 mItemPath = current == null ? null : current.getPath();
739 }
740 updateImageCache();
741 updateTileProvider();
742 updateImageRequests();
743 fireModelInvalidated();
744 return null;
745 }
746
747 private void updateCurrentItem() {
748 if (mSize == 0) return;
749 if (mCurrentIndex >= mSize) {
750 mCurrentIndex = mSize - 1;
751 mPhotoView.notifyOnNewImage();
752 mPhotoView.startSlideInAnimation(PhotoView.TRANS_SLIDE_IN_LEFT);
753 } else {
754 mPhotoView.notifyOnNewImage();
755 mPhotoView.startSlideInAnimation(PhotoView.TRANS_SLIDE_IN_RIGHT);
756 }
757 }
758 }
759
760 private class ReloadTask extends Thread {
761 private volatile boolean mActive = true;
762 private volatile boolean mDirty = true;
763
764 private boolean mIsLoading = false;
765
766 private void updateLoading(boolean loading) {
767 if (mIsLoading == loading) return;
768 mIsLoading = loading;
769 mMainHandler.sendEmptyMessage(loading ? MSG_LOAD_START : MSG_LOAD_FINISH);
770 }
771
772 @Override
773 public void run() {
774 while (mActive) {
775 synchronized (this) {
776 if (!mDirty && mActive) {
777 updateLoading(false);
778 Utils.waitWithoutInterrupt(this);
779 continue;
780 }
781 }
782 mDirty = false;
783 UpdateInfo info = executeAndWait(new GetUpdateInfo());
784 synchronized (DataManager.LOCK) {
785 updateLoading(true);
786 long version = mSource.reload();
787 if (info.version != version) {
788 info.reloadContent = true;
789 info.size = mSource.getMediaItemCount();
790 }
791 if (!info.reloadContent) continue;
792 info.items = mSource.getMediaItem(info.contentStart, info.contentEnd);
793 MediaItem item = findCurrentMediaItem(info);
794 if (item == null || item.getPath() != info.target) {
795 info.indexHint = findIndexOfTarget(info);
796 }
797 }
798 executeAndWait(new UpdateContent(info));
799 }
800 }
801
802 public synchronized void notifyDirty() {
803 mDirty = true;
804 notifyAll();
805 }
806
807 public synchronized void terminate() {
808 mActive = false;
809 notifyAll();
810 }
811
812 private MediaItem findCurrentMediaItem(UpdateInfo info) {
813 ArrayList<MediaItem> items = info.items;
814 int index = info.indexHint - info.contentStart;
815 return index < 0 || index >= items.size() ? null : items.get(index);
816 }
817
818 private int findIndexOfTarget(UpdateInfo info) {
819 if (info.target == null) return info.indexHint;
820 ArrayList<MediaItem> items = info.items;
821
822 // First, try to find the item in the data just loaded
823 if (items != null) {
824 for (int i = 0, n = items.size(); i < n; ++i) {
825 if (items.get(i).getPath() == info.target) return i + info.contentStart;
826 }
827 }
828
829 // Not found, find it in mSource.
830 return mSource.getIndexOfItem(info.target, info.indexHint);
831 }
832 }
833}