blob: 66f2874f7d4f816e8e4ed0c9c14fba34aa1e65c8 [file] [log] [blame]
Owen Linf9a0a432011-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 Lin2b3ee0e2012-03-14 17:27:24 +080019import android.graphics.Bitmap;
20import android.graphics.BitmapRegionDecoder;
21import android.os.Handler;
22import android.os.Message;
23
Owen Lin22493b22011-09-12 12:45:16 +080024import com.android.gallery3d.common.BitmapUtils;
Owen Linf9a0a432011-08-17 22:07:43 +080025import com.android.gallery3d.common.Utils;
Owen Linc2c0b012012-05-17 15:59:25 -070026import com.android.gallery3d.data.BitmapPool;
Owen Linf9a0a432011-08-17 22:07:43 +080027import com.android.gallery3d.data.ContentListener;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +080028import com.android.gallery3d.data.LocalMediaItem;
Owen Linf9a0a432011-08-17 22:07:43 +080029import com.android.gallery3d.data.MediaItem;
30import com.android.gallery3d.data.MediaObject;
31import com.android.gallery3d.data.MediaSet;
32import com.android.gallery3d.data.Path;
Chih-Chung Chang15b351a2012-03-15 16:38:45 +080033import com.android.gallery3d.ui.BitmapScreenNail;
Owen Linf9a0a432011-08-17 22:07:43 +080034import com.android.gallery3d.ui.PhotoView;
Chih-Chung Chang15b351a2012-03-15 16:38:45 +080035import com.android.gallery3d.ui.ScreenNail;
Owen Linf9a0a432011-08-17 22:07:43 +080036import com.android.gallery3d.ui.SynchronizedHandler;
37import com.android.gallery3d.ui.TileImageViewAdapter;
38import com.android.gallery3d.util.Future;
39import com.android.gallery3d.util.FutureListener;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +080040import com.android.gallery3d.util.MediaSetUtils;
Owen Linf9a0a432011-08-17 22:07:43 +080041import com.android.gallery3d.util.ThreadPool;
Owen Lin22493b22011-09-12 12:45:16 +080042import com.android.gallery3d.util.ThreadPool.Job;
43import com.android.gallery3d.util.ThreadPool.JobContext;
Owen Linf9a0a432011-08-17 22:07:43 +080044
Owen Linf9a0a432011-08-17 22:07:43 +080045import java.util.ArrayList;
46import java.util.Arrays;
47import java.util.HashMap;
48import java.util.HashSet;
49import java.util.concurrent.Callable;
50import java.util.concurrent.ExecutionException;
51import java.util.concurrent.FutureTask;
52
53public class PhotoDataAdapter implements PhotoPage.Model {
54 @SuppressWarnings("unused")
55 private static final String TAG = "PhotoDataAdapter";
56
57 private static final int MSG_LOAD_START = 1;
58 private static final int MSG_LOAD_FINISH = 2;
59 private static final int MSG_RUN_OBJECT = 3;
Chih-Chung Changb8be1e02012-04-17 20:35:14 +080060 private static final int MSG_UPDATE_IMAGE_REQUESTS = 4;
Owen Linf9a0a432011-08-17 22:07:43 +080061
62 private static final int MIN_LOAD_COUNT = 8;
63 private static final int DATA_CACHE_SIZE = 32;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +080064 private static final int SCREEN_NAIL_MAX = PhotoView.SCREEN_NAIL_MAX;
65 private static final int IMAGE_CACHE_SIZE = 2 * SCREEN_NAIL_MAX + 1;
Owen Linf9a0a432011-08-17 22:07:43 +080066
67 private static final int BIT_SCREEN_NAIL = 1;
68 private static final int BIT_FULL_IMAGE = 2;
69
Owen Linf9a0a432011-08-17 22:07:43 +080070 // sImageFetchSeq is the fetching sequence for images.
71 // We want to fetch the current screennail first (offset = 0), the next
72 // screennail (offset = +1), then the previous screennail (offset = -1) etc.
73 // After all the screennail are fetched, we fetch the full images (only some
74 // of them because of we don't want to use too much memory).
75 private static ImageFetch[] sImageFetchSeq;
76
77 private static class ImageFetch {
78 int indexOffset;
79 int imageBit;
80 public ImageFetch(int offset, int bit) {
81 indexOffset = offset;
82 imageBit = bit;
83 }
84 }
85
86 static {
87 int k = 0;
88 sImageFetchSeq = new ImageFetch[1 + (IMAGE_CACHE_SIZE - 1) * 2 + 3];
89 sImageFetchSeq[k++] = new ImageFetch(0, BIT_SCREEN_NAIL);
90
91 for (int i = 1; i < IMAGE_CACHE_SIZE; ++i) {
92 sImageFetchSeq[k++] = new ImageFetch(i, BIT_SCREEN_NAIL);
93 sImageFetchSeq[k++] = new ImageFetch(-i, BIT_SCREEN_NAIL);
94 }
95
96 sImageFetchSeq[k++] = new ImageFetch(0, BIT_FULL_IMAGE);
97 sImageFetchSeq[k++] = new ImageFetch(1, BIT_FULL_IMAGE);
98 sImageFetchSeq[k++] = new ImageFetch(-1, BIT_FULL_IMAGE);
99 }
100
101 private final TileImageViewAdapter mTileProvider = new TileImageViewAdapter();
102
103 // PhotoDataAdapter caches MediaItems (data) and ImageEntries (image).
104 //
105 // The MediaItems are stored in the mData array, which has DATA_CACHE_SIZE
106 // entries. The valid index range are [mContentStart, mContentEnd). We keep
107 // mContentEnd - mContentStart <= DATA_CACHE_SIZE, so we can use
108 // (i % DATA_CACHE_SIZE) as index to the array.
109 //
110 // The valid MediaItem window size (mContentEnd - mContentStart) may be
111 // smaller than DATA_CACHE_SIZE because we only update the window and reload
112 // the MediaItems when there are significant changes to the window position
113 // (>= MIN_LOAD_COUNT).
114 private final MediaItem mData[] = new MediaItem[DATA_CACHE_SIZE];
115 private int mContentStart = 0;
116 private int mContentEnd = 0;
117
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800118 // The ImageCache is a Path-to-ImageEntry map. It only holds the
119 // ImageEntries in the range of [mActiveStart, mActiveEnd). We also keep
120 // mActiveEnd - mActiveStart <= IMAGE_CACHE_SIZE. Besides, the
121 // [mActiveStart, mActiveEnd) range must be contained within
122 // the [mContentStart, mContentEnd) range.
123 private HashMap<Path, ImageEntry> mImageCache =
124 new HashMap<Path, ImageEntry>();
Owen Linf9a0a432011-08-17 22:07:43 +0800125 private int mActiveStart = 0;
126 private int mActiveEnd = 0;
127
128 // mCurrentIndex is the "center" image the user is viewing. The change of
129 // mCurrentIndex triggers the data loading and image loading.
130 private int mCurrentIndex;
131
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800132 // mChanges keeps the version number (of MediaItem) about the images. If any
133 // of the version number changes, we notify the view. This is used after a
134 // database reload or mCurrentIndex changes.
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800135 private final long mChanges[] = new long[IMAGE_CACHE_SIZE];
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800136 // mPaths keeps the corresponding Path (of MediaItem) for the images. This
137 // is used to determine the item movement.
138 private final Path mPaths[] = new Path[IMAGE_CACHE_SIZE];
Owen Linf9a0a432011-08-17 22:07:43 +0800139
140 private final Handler mMainHandler;
141 private final ThreadPool mThreadPool;
142
143 private final PhotoView mPhotoView;
144 private final MediaSet mSource;
145 private ReloadTask mReloadTask;
146
147 private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
148 private int mSize = 0;
149 private Path mItemPath;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800150 private int mCameraIndex;
Angus Kong43a80fd2012-05-17 12:47:26 -0700151 private boolean mIsPanorama;
Wu-cheng Lidbb6acc2012-08-19 17:04:02 +0800152 private boolean mIsStaticCamera;
Owen Linf9a0a432011-08-17 22:07:43 +0800153 private boolean mIsActive;
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800154 private boolean mNeedFullImage;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800155 private int mFocusHintDirection = FOCUS_HINT_NEXT;
156 private Path mFocusHintPath = null;
Owen Linf9a0a432011-08-17 22:07:43 +0800157
158 public interface DataListener extends LoadingListener {
159 public void onPhotoChanged(int index, Path item);
160 }
161
162 private DataListener mDataListener;
163
164 private final SourceListener mSourceListener = new SourceListener();
165
166 // The path of the current viewing item will be stored in mItemPath.
167 // If mItemPath is not null, mCurrentIndex is only a hint for where we
168 // can find the item. If mItemPath is null, then we use the mCurrentIndex to
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800169 // find the image being viewed. cameraIndex is the index of the camera
170 // preview. If cameraIndex < 0, there is no camera preview.
Owen Linb21b8e52012-08-24 12:25:57 +0800171 public PhotoDataAdapter(AbstractGalleryActivity activity, PhotoView view,
Angus Kong43a80fd2012-05-17 12:47:26 -0700172 MediaSet mediaSet, Path itemPath, int indexHint, int cameraIndex,
Wu-cheng Lidbb6acc2012-08-19 17:04:02 +0800173 boolean isPanorama, boolean isStaticCamera) {
Owen Linf9a0a432011-08-17 22:07:43 +0800174 mSource = Utils.checkNotNull(mediaSet);
175 mPhotoView = Utils.checkNotNull(view);
176 mItemPath = Utils.checkNotNull(itemPath);
177 mCurrentIndex = indexHint;
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800178 mCameraIndex = cameraIndex;
Angus Kong43a80fd2012-05-17 12:47:26 -0700179 mIsPanorama = isPanorama;
Wu-cheng Lidbb6acc2012-08-19 17:04:02 +0800180 mIsStaticCamera = isStaticCamera;
Owen Linf9a0a432011-08-17 22:07:43 +0800181 mThreadPool = activity.getThreadPool();
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800182 mNeedFullImage = true;
Owen Linf9a0a432011-08-17 22:07:43 +0800183
184 Arrays.fill(mChanges, MediaObject.INVALID_DATA_VERSION);
185
186 mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
187 @SuppressWarnings("unchecked")
188 @Override
189 public void handleMessage(Message message) {
190 switch (message.what) {
191 case MSG_RUN_OBJECT:
192 ((Runnable) message.obj).run();
193 return;
194 case MSG_LOAD_START: {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800195 if (mDataListener != null) {
196 mDataListener.onLoadingStarted();
197 }
Owen Linf9a0a432011-08-17 22:07:43 +0800198 return;
199 }
200 case MSG_LOAD_FINISH: {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800201 if (mDataListener != null) {
202 mDataListener.onLoadingFinished();
203 }
Owen Linf9a0a432011-08-17 22:07:43 +0800204 return;
205 }
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800206 case MSG_UPDATE_IMAGE_REQUESTS: {
207 updateImageRequests();
208 return;
209 }
Owen Linf9a0a432011-08-17 22:07:43 +0800210 default: throw new AssertionError();
211 }
212 }
213 };
214
215 updateSlidingWindow();
216 }
217
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800218 private MediaItem getItemInternal(int index) {
219 if (index < 0 || index >= mSize) return null;
Owen Linf9a0a432011-08-17 22:07:43 +0800220 if (index >= mContentStart && index < mContentEnd) {
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800221 return mData[index % DATA_CACHE_SIZE];
Owen Linf9a0a432011-08-17 22:07:43 +0800222 }
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800223 return null;
224 }
225
226 private long getVersion(int index) {
227 MediaItem item = getItemInternal(index);
228 if (item == null) return MediaObject.INVALID_DATA_VERSION;
229 return item.getDataVersion();
230 }
231
232 private Path getPath(int index) {
233 MediaItem item = getItemInternal(index);
234 if (item == null) return null;
235 return item.getPath();
Owen Linf9a0a432011-08-17 22:07:43 +0800236 }
237
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800238 private void fireDataChange() {
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800239 // First check if data actually changed.
240 boolean changed = false;
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800241 for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800242 long newVersion = getVersion(mCurrentIndex + i);
243 if (mChanges[i + SCREEN_NAIL_MAX] != newVersion) {
244 mChanges[i + SCREEN_NAIL_MAX] = newVersion;
245 changed = true;
246 }
Owen Linf9a0a432011-08-17 22:07:43 +0800247 }
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800248
249 if (!changed) return;
250
251 // Now calculate the fromIndex array. fromIndex represents the item
252 // movement. It records the index where the picture come from. The
253 // special value Integer.MAX_VALUE means it's a new picture.
254 final int N = IMAGE_CACHE_SIZE;
255 int fromIndex[] = new int[N];
256
257 // Remember the old path array.
258 Path oldPaths[] = new Path[N];
259 System.arraycopy(mPaths, 0, oldPaths, 0, N);
260
261 // Update the mPaths array.
262 for (int i = 0; i < N; ++i) {
263 mPaths[i] = getPath(mCurrentIndex + i - SCREEN_NAIL_MAX);
264 }
265
266 // Calculate the fromIndex array.
267 for (int i = 0; i < N; i++) {
268 Path p = mPaths[i];
269 if (p == null) {
270 fromIndex[i] = Integer.MAX_VALUE;
271 continue;
272 }
273
274 // Try to find the same path in the old array
275 int j;
276 for (j = 0; j < N; j++) {
277 if (oldPaths[j] == p) {
278 break;
279 }
280 }
281 fromIndex[i] = (j < N) ? j - SCREEN_NAIL_MAX : Integer.MAX_VALUE;
282 }
283
284 mPhotoView.notifyDataChange(fromIndex, -mCurrentIndex,
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800285 mSize - 1 - mCurrentIndex);
Owen Linf9a0a432011-08-17 22:07:43 +0800286 }
287
288 public void setDataListener(DataListener listener) {
289 mDataListener = listener;
290 }
291
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800292 private void updateScreenNail(Path path, Future<ScreenNail> future) {
293 ImageEntry entry = mImageCache.get(path);
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800294 ScreenNail screenNail = future.get();
295
Owen Line7327d02011-09-06 19:46:46 +0800296 if (entry == null || entry.screenNailTask != future) {
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800297 if (screenNail != null) screenNail.recycle();
Owen Linf9a0a432011-08-17 22:07:43 +0800298 return;
299 }
Owen Line7327d02011-09-06 19:46:46 +0800300
Owen Linf9a0a432011-08-17 22:07:43 +0800301 entry.screenNailTask = null;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800302
303 // Combine the ScreenNails if we already have a BitmapScreenNail
304 if (entry.screenNail instanceof BitmapScreenNail) {
305 BitmapScreenNail original = (BitmapScreenNail) entry.screenNail;
306 screenNail = original.combine(screenNail);
307 }
Owen Linf9a0a432011-08-17 22:07:43 +0800308
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800309 if (screenNail == null) {
Owen Linf9a0a432011-08-17 22:07:43 +0800310 entry.failToLoad = true;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800311 } else {
312 entry.failToLoad = false;
313 entry.screenNail = screenNail;
Yuli Huanga7b78e22012-02-07 15:51:24 +0800314 }
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800315
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800316 for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800317 if (path == getPath(mCurrentIndex + i)) {
Yuli Huanga7b78e22012-02-07 15:51:24 +0800318 if (i == 0) updateTileProvider(entry);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800319 mPhotoView.notifyImageChange(i);
320 break;
Owen Linf9a0a432011-08-17 22:07:43 +0800321 }
322 }
323 updateImageRequests();
324 }
325
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800326 private void updateFullImage(Path path, Future<BitmapRegionDecoder> future) {
327 ImageEntry entry = mImageCache.get(path);
Owen Line7327d02011-09-06 19:46:46 +0800328 if (entry == null || entry.fullImageTask != future) {
Owen Linf9a0a432011-08-17 22:07:43 +0800329 BitmapRegionDecoder fullImage = future.get();
330 if (fullImage != null) fullImage.recycle();
331 return;
332 }
Owen Line7327d02011-09-06 19:46:46 +0800333
Owen Linf9a0a432011-08-17 22:07:43 +0800334 entry.fullImageTask = null;
335 entry.fullImage = future.get();
336 if (entry.fullImage != null) {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800337 if (path == getPath(mCurrentIndex)) {
Owen Linf9a0a432011-08-17 22:07:43 +0800338 updateTileProvider(entry);
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800339 mPhotoView.notifyImageChange(0);
Owen Linf9a0a432011-08-17 22:07:43 +0800340 }
341 }
342 updateImageRequests();
343 }
344
Ahbong Chang78179792012-07-30 11:34:13 +0800345 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800346 public void resume() {
347 mIsActive = true;
348 mSource.addContentListener(mSourceListener);
349 updateImageCache();
350 updateImageRequests();
351
352 mReloadTask = new ReloadTask();
353 mReloadTask.start();
354
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800355 fireDataChange();
Owen Linf9a0a432011-08-17 22:07:43 +0800356 }
357
Ahbong Chang78179792012-07-30 11:34:13 +0800358 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800359 public void pause() {
360 mIsActive = false;
361
362 mReloadTask.terminate();
363 mReloadTask = null;
364
365 mSource.removeContentListener(mSourceListener);
366
367 for (ImageEntry entry : mImageCache.values()) {
368 if (entry.fullImageTask != null) entry.fullImageTask.cancel();
369 if (entry.screenNailTask != null) entry.screenNailTask.cancel();
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800370 if (entry.screenNail != null) entry.screenNail.recycle();
Owen Linf9a0a432011-08-17 22:07:43 +0800371 }
372 mImageCache.clear();
373 mTileProvider.clear();
374 }
375
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800376 private MediaItem getItem(int index) {
377 if (index < 0 || index >= mSize || !mIsActive) return null;
378 Utils.assertTrue(index >= mActiveStart && index < mActiveEnd);
379
380 if (index >= mContentStart && index < mContentEnd) {
381 return mData[index % DATA_CACHE_SIZE];
382 }
383 return null;
Owen Linf9a0a432011-08-17 22:07:43 +0800384 }
385
386 private void updateCurrentIndex(int index) {
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800387 if (mCurrentIndex == index) return;
Owen Linf9a0a432011-08-17 22:07:43 +0800388 mCurrentIndex = index;
389 updateSlidingWindow();
390
391 MediaItem item = mData[index % DATA_CACHE_SIZE];
392 mItemPath = item == null ? null : item.getPath();
393
394 updateImageCache();
395 updateImageRequests();
396 updateTileProvider();
Owen Linf9a0a432011-08-17 22:07:43 +0800397
398 if (mDataListener != null) {
399 mDataListener.onPhotoChanged(index, mItemPath);
400 }
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800401
402 fireDataChange();
Owen Linf9a0a432011-08-17 22:07:43 +0800403 }
404
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800405 @Override
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800406 public void moveTo(int index) {
407 updateCurrentIndex(index);
Chih-Chung Chang160e6d72012-04-25 11:50:08 +0800408 }
409
410 @Override
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800411 public ScreenNail getScreenNail(int offset) {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800412 int index = mCurrentIndex + offset;
413 if (index < 0 || index >= mSize || !mIsActive) return null;
414 Utils.assertTrue(index >= mActiveStart && index < mActiveEnd);
415
416 MediaItem item = getItem(index);
417 if (item == null) return null;
418
419 ImageEntry entry = mImageCache.get(item.getPath());
420 if (entry == null) return null;
421
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800422 // Create a default ScreenNail if the real one is not available yet,
423 // except for camera that a black screen is better than a gray tile.
424 if (entry.screenNail == null && !isCamera(offset)) {
Owen Lin49affdc2012-05-21 16:52:25 -0700425 entry.screenNail = newPlaceholderScreenNail(item);
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800426 if (offset == 0) updateTileProvider(entry);
427 }
428
429 return entry.screenNail;
Chih-Chung Changc3b2d472012-04-19 20:14:11 +0800430 }
431
432 @Override
433 public void getImageSize(int offset, PhotoView.Size size) {
434 MediaItem item = getItem(mCurrentIndex + offset);
435 if (item == null) {
436 size.width = 0;
437 size.height = 0;
438 } else {
439 size.width = item.getWidth();
440 size.height = item.getHeight();
441 }
442 }
443
444 @Override
445 public int getImageRotation(int offset) {
446 MediaItem item = getItem(mCurrentIndex + offset);
447 return (item == null) ? 0 : item.getFullImageRotation();
448 }
449
450 @Override
451 public void setNeedFullImage(boolean enabled) {
452 mNeedFullImage = enabled;
453 mMainHandler.sendEmptyMessage(MSG_UPDATE_IMAGE_REQUESTS);
454 }
455
Chih-Chung Changbd141b52012-04-26 10:10:49 +0800456 @Override
457 public boolean isCamera(int offset) {
458 return mCurrentIndex + offset == mCameraIndex;
459 }
460
Chih-Chung Changd9355112012-05-06 01:24:22 +0800461 @Override
Angus Kong43a80fd2012-05-17 12:47:26 -0700462 public boolean isPanorama(int offset) {
463 return isCamera(offset) && mIsPanorama;
464 }
465
466 @Override
Wu-cheng Lidbb6acc2012-08-19 17:04:02 +0800467 public boolean isStaticCamera(int offset) {
468 return isCamera(offset) && mIsStaticCamera;
469 }
470
471 @Override
Chih-Chung Changd9355112012-05-06 01:24:22 +0800472 public boolean isVideo(int offset) {
473 MediaItem item = getItem(mCurrentIndex + offset);
474 return (item == null)
475 ? false
476 : item.getMediaType() == MediaItem.MEDIA_TYPE_VIDEO;
477 }
478
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800479 @Override
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800480 public boolean isDeletable(int offset) {
481 MediaItem item = getItem(mCurrentIndex + offset);
482 return (item == null)
483 ? false
484 : (item.getSupportedOperations() & MediaItem.SUPPORT_DELETE) != 0;
485 }
486
487 @Override
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800488 public int getLoadingState(int offset) {
489 ImageEntry entry = mImageCache.get(getPath(mCurrentIndex + offset));
490 if (entry == null) return LOADING_INIT;
491 if (entry.failToLoad) return LOADING_FAIL;
492 if (entry.screenNail != null) return LOADING_COMPLETE;
493 return LOADING_INIT;
494 }
495
Ahbong Chang78179792012-07-30 11:34:13 +0800496 @Override
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800497 public ScreenNail getScreenNail() {
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800498 return getScreenNail(0);
Owen Linf9a0a432011-08-17 22:07:43 +0800499 }
500
Ahbong Chang78179792012-07-30 11:34:13 +0800501 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800502 public int getImageHeight() {
503 return mTileProvider.getImageHeight();
504 }
505
Ahbong Chang78179792012-07-30 11:34:13 +0800506 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800507 public int getImageWidth() {
508 return mTileProvider.getImageWidth();
509 }
510
Ahbong Chang78179792012-07-30 11:34:13 +0800511 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800512 public int getLevelCount() {
513 return mTileProvider.getLevelCount();
514 }
515
Ahbong Chang78179792012-07-30 11:34:13 +0800516 @Override
Chih-Chung Changcb4fb7c2012-03-21 16:19:21 +0800517 public Bitmap getTile(int level, int x, int y, int tileSize,
Owen Linc2c0b012012-05-17 15:59:25 -0700518 int borderSize, BitmapPool pool) {
519 return mTileProvider.getTile(level, x, y, tileSize, borderSize, pool);
Owen Linf9a0a432011-08-17 22:07:43 +0800520 }
521
Ahbong Chang78179792012-07-30 11:34:13 +0800522 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800523 public boolean isEmpty() {
524 return mSize == 0;
525 }
526
Ahbong Chang78179792012-07-30 11:34:13 +0800527 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800528 public int getCurrentIndex() {
529 return mCurrentIndex;
530 }
531
Ahbong Chang78179792012-07-30 11:34:13 +0800532 @Override
Owen Lin616a70f2012-05-07 16:35:53 +0800533 public MediaItem getMediaItem(int offset) {
534 int index = mCurrentIndex + offset;
535 if (index >= mContentStart && index < mContentEnd) {
536 return mData[index % DATA_CACHE_SIZE];
537 }
538 return null;
Owen Linf9a0a432011-08-17 22:07:43 +0800539 }
540
Ahbong Chang78179792012-07-30 11:34:13 +0800541 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800542 public void setCurrentPhoto(Path path, int indexHint) {
543 if (mItemPath == path) return;
544 mItemPath = path;
545 mCurrentIndex = indexHint;
546 updateSlidingWindow();
547 updateImageCache();
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800548 fireDataChange();
Owen Linf9a0a432011-08-17 22:07:43 +0800549
550 // We need to reload content if the path doesn't match.
Owen Lin616a70f2012-05-07 16:35:53 +0800551 MediaItem item = getMediaItem(0);
Owen Linf9a0a432011-08-17 22:07:43 +0800552 if (item != null && item.getPath() != path) {
553 if (mReloadTask != null) mReloadTask.notifyDirty();
554 }
555 }
556
Ahbong Chang78179792012-07-30 11:34:13 +0800557 @Override
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800558 public void setFocusHintDirection(int direction) {
559 mFocusHintDirection = direction;
560 }
561
Ahbong Chang78179792012-07-30 11:34:13 +0800562 @Override
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800563 public void setFocusHintPath(Path path) {
564 mFocusHintPath = path;
565 }
566
Owen Linf9a0a432011-08-17 22:07:43 +0800567 private void updateTileProvider() {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800568 ImageEntry entry = mImageCache.get(getPath(mCurrentIndex));
Owen Linf9a0a432011-08-17 22:07:43 +0800569 if (entry == null) { // in loading
570 mTileProvider.clear();
571 } else {
572 updateTileProvider(entry);
573 }
574 }
575
576 private void updateTileProvider(ImageEntry entry) {
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800577 ScreenNail screenNail = entry.screenNail;
Owen Linf9a0a432011-08-17 22:07:43 +0800578 BitmapRegionDecoder fullImage = entry.fullImage;
579 if (screenNail != null) {
580 if (fullImage != null) {
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800581 mTileProvider.setScreenNail(screenNail,
Owen Linf9a0a432011-08-17 22:07:43 +0800582 fullImage.getWidth(), fullImage.getHeight());
583 mTileProvider.setRegionDecoder(fullImage);
584 } else {
585 int width = screenNail.getWidth();
586 int height = screenNail.getHeight();
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800587 mTileProvider.setScreenNail(screenNail, width, height);
Owen Linf9a0a432011-08-17 22:07:43 +0800588 }
589 } else {
590 mTileProvider.clear();
Owen Linf9a0a432011-08-17 22:07:43 +0800591 }
592 }
593
594 private void updateSlidingWindow() {
595 // 1. Update the image window
596 int start = Utils.clamp(mCurrentIndex - IMAGE_CACHE_SIZE / 2,
597 0, Math.max(0, mSize - IMAGE_CACHE_SIZE));
598 int end = Math.min(mSize, start + IMAGE_CACHE_SIZE);
599
600 if (mActiveStart == start && mActiveEnd == end) return;
601
602 mActiveStart = start;
603 mActiveEnd = end;
604
605 // 2. Update the data window
606 start = Utils.clamp(mCurrentIndex - DATA_CACHE_SIZE / 2,
607 0, Math.max(0, mSize - DATA_CACHE_SIZE));
608 end = Math.min(mSize, start + DATA_CACHE_SIZE);
609 if (mContentStart > mActiveStart || mContentEnd < mActiveEnd
610 || Math.abs(start - mContentStart) > MIN_LOAD_COUNT) {
611 for (int i = mContentStart; i < mContentEnd; ++i) {
612 if (i < start || i >= end) {
613 mData[i % DATA_CACHE_SIZE] = null;
614 }
615 }
616 mContentStart = start;
617 mContentEnd = end;
618 if (mReloadTask != null) mReloadTask.notifyDirty();
619 }
620 }
621
622 private void updateImageRequests() {
623 if (!mIsActive) return;
624
625 int currentIndex = mCurrentIndex;
626 MediaItem item = mData[currentIndex % DATA_CACHE_SIZE];
627 if (item == null || item.getPath() != mItemPath) {
628 // current item mismatch - don't request image
629 return;
630 }
631
632 // 1. Find the most wanted request and start it (if not already started).
633 Future<?> task = null;
634 for (int i = 0; i < sImageFetchSeq.length; i++) {
635 int offset = sImageFetchSeq[i].indexOffset;
636 int bit = sImageFetchSeq[i].imageBit;
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800637 if (bit == BIT_FULL_IMAGE && !mNeedFullImage) continue;
Owen Linf9a0a432011-08-17 22:07:43 +0800638 task = startTaskIfNeeded(currentIndex + offset, bit);
639 if (task != null) break;
640 }
641
642 // 2. Cancel everything else.
643 for (ImageEntry entry : mImageCache.values()) {
644 if (entry.screenNailTask != null && entry.screenNailTask != task) {
645 entry.screenNailTask.cancel();
646 entry.screenNailTask = null;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800647 entry.requestedScreenNail = MediaObject.INVALID_DATA_VERSION;
Owen Linf9a0a432011-08-17 22:07:43 +0800648 }
649 if (entry.fullImageTask != null && entry.fullImageTask != task) {
650 entry.fullImageTask.cancel();
651 entry.fullImageTask = null;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800652 entry.requestedFullImage = MediaObject.INVALID_DATA_VERSION;
Owen Linf9a0a432011-08-17 22:07:43 +0800653 }
654 }
655 }
656
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800657 private class ScreenNailJob implements Job<ScreenNail> {
Owen Lin22493b22011-09-12 12:45:16 +0800658 private MediaItem mItem;
659
660 public ScreenNailJob(MediaItem item) {
661 mItem = item;
662 }
663
664 @Override
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800665 public ScreenNail run(JobContext jc) {
666 // We try to get a ScreenNail first, if it fails, we fallback to get
667 // a Bitmap and then wrap it in a BitmapScreenNail instead.
668 ScreenNail s = mItem.getScreenNail();
669 if (s != null) return s;
670
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800671 // If this is a temporary item, don't try to get its bitmap because
672 // it won't be available. We will get its bitmap after a data reload.
673 if (isTemporaryItem(mItem)) {
Owen Lin49affdc2012-05-21 16:52:25 -0700674 return newPlaceholderScreenNail(mItem);
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800675 }
676
Owen Lin22493b22011-09-12 12:45:16 +0800677 Bitmap bitmap = mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc);
678 if (jc.isCancelled()) return null;
679 if (bitmap != null) {
680 bitmap = BitmapUtils.rotateBitmap(bitmap,
681 mItem.getRotation() - mItem.getFullImageRotation(), true);
682 }
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800683 return bitmap == null ? null : new BitmapScreenNail(bitmap);
Owen Lin22493b22011-09-12 12:45:16 +0800684 }
685 }
686
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800687 private class FullImageJob implements Job<BitmapRegionDecoder> {
688 private MediaItem mItem;
689
690 public FullImageJob(MediaItem item) {
691 mItem = item;
692 }
693
694 @Override
695 public BitmapRegionDecoder run(JobContext jc) {
696 if (isTemporaryItem(mItem)) {
697 return null;
698 }
699 return mItem.requestLargeImage().run(jc);
700 }
701 }
702
703 // Returns true if we think this is a temporary item created by Camera. A
704 // temporary item is an image or a video whose data is still being
705 // processed, but an incomplete entry is created first in MediaProvider, so
706 // we can display them (in grey tile) even if they are not saved to disk
707 // yet. When the image or video data is actually saved, we will get
708 // notification from MediaProvider, reload data, and show the actual image
709 // or video data.
710 private boolean isTemporaryItem(MediaItem mediaItem) {
711 // Must have camera to create a temporary item.
712 if (mCameraIndex < 0) return false;
713 // Must be an item in camera roll.
714 if (!(mediaItem instanceof LocalMediaItem)) return false;
715 LocalMediaItem item = (LocalMediaItem) mediaItem;
716 if (item.getBucketId() != MediaSetUtils.CAMERA_BUCKET_ID) return false;
717 // Must have no size, but must have width and height information
718 if (item.getSize() != 0) return false;
719 if (item.getWidth() == 0) return false;
720 if (item.getHeight() == 0) return false;
721 // Must be created in the last 10 seconds.
722 if (item.getDateInMs() - System.currentTimeMillis() > 10000) return false;
723 return true;
724 }
725
726 // Create a default ScreenNail when a ScreenNail is needed, but we don't yet
727 // have one available (because the image data is still being saved, or the
728 // Bitmap is still being loaded.
Owen Lin49affdc2012-05-21 16:52:25 -0700729 private ScreenNail newPlaceholderScreenNail(MediaItem item) {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800730 int width = item.getWidth();
731 int height = item.getHeight();
732 return new BitmapScreenNail(width, height);
733 }
734
Owen Linf9a0a432011-08-17 22:07:43 +0800735 // Returns the task if we started the task or the task is already started.
736 private Future<?> startTaskIfNeeded(int index, int which) {
737 if (index < mActiveStart || index >= mActiveEnd) return null;
738
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800739 ImageEntry entry = mImageCache.get(getPath(index));
Owen Linf9a0a432011-08-17 22:07:43 +0800740 if (entry == null) return null;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800741 MediaItem item = mData[index % DATA_CACHE_SIZE];
742 Utils.assertTrue(item != null);
743 long version = item.getDataVersion();
Owen Linf9a0a432011-08-17 22:07:43 +0800744
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800745 if (which == BIT_SCREEN_NAIL && entry.screenNailTask != null
746 && entry.requestedScreenNail == version) {
Owen Linf9a0a432011-08-17 22:07:43 +0800747 return entry.screenNailTask;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800748 } else if (which == BIT_FULL_IMAGE && entry.fullImageTask != null
749 && entry.requestedFullImage == version) {
Owen Linf9a0a432011-08-17 22:07:43 +0800750 return entry.fullImageTask;
751 }
752
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800753 if (which == BIT_SCREEN_NAIL && entry.requestedScreenNail != version) {
754 entry.requestedScreenNail = version;
Owen Linf9a0a432011-08-17 22:07:43 +0800755 entry.screenNailTask = mThreadPool.submit(
Owen Lin22493b22011-09-12 12:45:16 +0800756 new ScreenNailJob(item),
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800757 new ScreenNailListener(item));
Owen Linf9a0a432011-08-17 22:07:43 +0800758 // request screen nail
759 return entry.screenNailTask;
760 }
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800761 if (which == BIT_FULL_IMAGE && entry.requestedFullImage != version
Owen Linf9a0a432011-08-17 22:07:43 +0800762 && (item.getSupportedOperations()
763 & MediaItem.SUPPORT_FULL_IMAGE) != 0) {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800764 entry.requestedFullImage = version;
Owen Linf9a0a432011-08-17 22:07:43 +0800765 entry.fullImageTask = mThreadPool.submit(
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800766 new FullImageJob(item),
767 new FullImageListener(item));
Owen Linf9a0a432011-08-17 22:07:43 +0800768 // request full image
769 return entry.fullImageTask;
770 }
771 return null;
772 }
773
774 private void updateImageCache() {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800775 HashSet<Path> toBeRemoved = new HashSet<Path>(mImageCache.keySet());
Owen Linf9a0a432011-08-17 22:07:43 +0800776 for (int i = mActiveStart; i < mActiveEnd; ++i) {
777 MediaItem item = mData[i % DATA_CACHE_SIZE];
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800778 if (item == null) continue;
779 Path path = item.getPath();
780 ImageEntry entry = mImageCache.get(path);
781 toBeRemoved.remove(path);
Owen Linf9a0a432011-08-17 22:07:43 +0800782 if (entry != null) {
783 if (Math.abs(i - mCurrentIndex) > 1) {
784 if (entry.fullImageTask != null) {
785 entry.fullImageTask.cancel();
786 entry.fullImageTask = null;
787 }
788 entry.fullImage = null;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800789 entry.requestedFullImage = MediaObject.INVALID_DATA_VERSION;
790 }
791 if (entry.requestedScreenNail != item.getDataVersion()) {
792 // This ScreenNail is outdated, we want to update it if it's
793 // still a placeholder.
794 if (entry.screenNail instanceof BitmapScreenNail) {
795 BitmapScreenNail s = (BitmapScreenNail) entry.screenNail;
796 s.updatePlaceholderSize(
797 item.getWidth(), item.getHeight());
798 }
Owen Linf9a0a432011-08-17 22:07:43 +0800799 }
800 } else {
801 entry = new ImageEntry();
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800802 mImageCache.put(path, entry);
Owen Linf9a0a432011-08-17 22:07:43 +0800803 }
804 }
805
806 // Clear the data and requests for ImageEntries outside the new window.
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800807 for (Path path : toBeRemoved) {
808 ImageEntry entry = mImageCache.remove(path);
Owen Linf9a0a432011-08-17 22:07:43 +0800809 if (entry.fullImageTask != null) entry.fullImageTask.cancel();
810 if (entry.screenNailTask != null) entry.screenNailTask.cancel();
Chih-Chung Changb8be1e02012-04-17 20:35:14 +0800811 if (entry.screenNail != null) entry.screenNail.recycle();
Owen Linf9a0a432011-08-17 22:07:43 +0800812 }
813 }
814
815 private class FullImageListener
816 implements Runnable, FutureListener<BitmapRegionDecoder> {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800817 private final Path mPath;
Owen Linf9a0a432011-08-17 22:07:43 +0800818 private Future<BitmapRegionDecoder> mFuture;
819
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800820 public FullImageListener(MediaItem item) {
821 mPath = item.getPath();
Owen Linf9a0a432011-08-17 22:07:43 +0800822 }
823
Owen Line7327d02011-09-06 19:46:46 +0800824 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800825 public void onFutureDone(Future<BitmapRegionDecoder> future) {
826 mFuture = future;
827 mMainHandler.sendMessage(
828 mMainHandler.obtainMessage(MSG_RUN_OBJECT, this));
829 }
830
Owen Line7327d02011-09-06 19:46:46 +0800831 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800832 public void run() {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800833 updateFullImage(mPath, mFuture);
Owen Linf9a0a432011-08-17 22:07:43 +0800834 }
835 }
836
837 private class ScreenNailListener
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800838 implements Runnable, FutureListener<ScreenNail> {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800839 private final Path mPath;
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800840 private Future<ScreenNail> mFuture;
Owen Linf9a0a432011-08-17 22:07:43 +0800841
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800842 public ScreenNailListener(MediaItem item) {
843 mPath = item.getPath();
Owen Linf9a0a432011-08-17 22:07:43 +0800844 }
845
Owen Line7327d02011-09-06 19:46:46 +0800846 @Override
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800847 public void onFutureDone(Future<ScreenNail> future) {
Owen Linf9a0a432011-08-17 22:07:43 +0800848 mFuture = future;
849 mMainHandler.sendMessage(
850 mMainHandler.obtainMessage(MSG_RUN_OBJECT, this));
851 }
852
Owen Line7327d02011-09-06 19:46:46 +0800853 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800854 public void run() {
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800855 updateScreenNail(mPath, mFuture);
Owen Linf9a0a432011-08-17 22:07:43 +0800856 }
857 }
858
859 private static class ImageEntry {
Owen Linf9a0a432011-08-17 22:07:43 +0800860 public BitmapRegionDecoder fullImage;
Chih-Chung Chang15b351a2012-03-15 16:38:45 +0800861 public ScreenNail screenNail;
862 public Future<ScreenNail> screenNailTask;
Owen Linf9a0a432011-08-17 22:07:43 +0800863 public Future<BitmapRegionDecoder> fullImageTask;
Chih-Chung Changf5ce6ae2012-05-11 17:55:02 +0800864 public long requestedScreenNail = MediaObject.INVALID_DATA_VERSION;
865 public long requestedFullImage = MediaObject.INVALID_DATA_VERSION;
Owen Linf9a0a432011-08-17 22:07:43 +0800866 public boolean failToLoad = false;
867 }
868
869 private class SourceListener implements ContentListener {
Ahbong Chang78179792012-07-30 11:34:13 +0800870 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800871 public void onContentDirty() {
872 if (mReloadTask != null) mReloadTask.notifyDirty();
873 }
874 }
875
876 private <T> T executeAndWait(Callable<T> callable) {
877 FutureTask<T> task = new FutureTask<T>(callable);
878 mMainHandler.sendMessage(
879 mMainHandler.obtainMessage(MSG_RUN_OBJECT, task));
880 try {
881 return task.get();
882 } catch (InterruptedException e) {
883 return null;
884 } catch (ExecutionException e) {
885 throw new RuntimeException(e);
886 }
887 }
888
889 private static class UpdateInfo {
890 public long version;
891 public boolean reloadContent;
892 public Path target;
893 public int indexHint;
894 public int contentStart;
895 public int contentEnd;
896
897 public int size;
898 public ArrayList<MediaItem> items;
899 }
900
901 private class GetUpdateInfo implements Callable<UpdateInfo> {
902
903 private boolean needContentReload() {
904 for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
905 if (mData[i % DATA_CACHE_SIZE] == null) return true;
906 }
907 MediaItem current = mData[mCurrentIndex % DATA_CACHE_SIZE];
908 return current == null || current.getPath() != mItemPath;
909 }
910
911 @Override
912 public UpdateInfo call() throws Exception {
Owen Lin113bfc72011-08-30 10:38:59 +0800913 // TODO: Try to load some data in first update
Owen Linf9a0a432011-08-17 22:07:43 +0800914 UpdateInfo info = new UpdateInfo();
915 info.version = mSourceVersion;
916 info.reloadContent = needContentReload();
917 info.target = mItemPath;
918 info.indexHint = mCurrentIndex;
919 info.contentStart = mContentStart;
920 info.contentEnd = mContentEnd;
921 info.size = mSize;
922 return info;
923 }
924 }
925
926 private class UpdateContent implements Callable<Void> {
927 UpdateInfo mUpdateInfo;
928
929 public UpdateContent(UpdateInfo updateInfo) {
930 mUpdateInfo = updateInfo;
931 }
932
933 @Override
934 public Void call() throws Exception {
935 UpdateInfo info = mUpdateInfo;
936 mSourceVersion = info.version;
937
938 if (info.size != mSize) {
939 mSize = info.size;
940 if (mContentEnd > mSize) mContentEnd = mSize;
941 if (mActiveEnd > mSize) mActiveEnd = mSize;
942 }
943
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800944 mCurrentIndex = info.indexHint;
Owen Linf9a0a432011-08-17 22:07:43 +0800945 updateSlidingWindow();
946
947 if (info.items != null) {
948 int start = Math.max(info.contentStart, mContentStart);
949 int end = Math.min(info.contentStart + info.items.size(), mContentEnd);
950 int dataIndex = start % DATA_CACHE_SIZE;
951 for (int i = start; i < end; ++i) {
952 mData[dataIndex] = info.items.get(i - info.contentStart);
953 if (++dataIndex == DATA_CACHE_SIZE) dataIndex = 0;
954 }
955 }
Chih-Chung Chang6b891c62012-06-07 20:09:13 +0800956
957 // update mItemPath
958 MediaItem current = mData[mCurrentIndex % DATA_CACHE_SIZE];
959 mItemPath = current == null ? null : current.getPath();
960
Owen Linf9a0a432011-08-17 22:07:43 +0800961 updateImageCache();
962 updateTileProvider();
963 updateImageRequests();
Bobby Georgescud6124852012-07-30 11:12:58 -0700964
965 if (mDataListener != null) {
966 mDataListener.onPhotoChanged(mCurrentIndex, mItemPath);
967 }
968
Chih-Chung Changb7ec5532012-04-03 12:21:16 +0800969 fireDataChange();
Owen Linf9a0a432011-08-17 22:07:43 +0800970 return null;
971 }
Owen Linf9a0a432011-08-17 22:07:43 +0800972 }
973
974 private class ReloadTask extends Thread {
975 private volatile boolean mActive = true;
976 private volatile boolean mDirty = true;
977
978 private boolean mIsLoading = false;
979
980 private void updateLoading(boolean loading) {
981 if (mIsLoading == loading) return;
982 mIsLoading = loading;
983 mMainHandler.sendEmptyMessage(loading ? MSG_LOAD_START : MSG_LOAD_FINISH);
984 }
985
986 @Override
987 public void run() {
988 while (mActive) {
989 synchronized (this) {
990 if (!mDirty && mActive) {
991 updateLoading(false);
992 Utils.waitWithoutInterrupt(this);
993 continue;
994 }
995 }
996 mDirty = false;
997 UpdateInfo info = executeAndWait(new GetUpdateInfo());
Owen Lin676d4762012-09-04 16:53:42 +0800998 updateLoading(true);
999 long version = mSource.reload();
1000 if (info.version != version) {
1001 info.reloadContent = true;
1002 info.size = mSource.getMediaItemCount();
Owen Linf9a0a432011-08-17 22:07:43 +08001003 }
Owen Lin676d4762012-09-04 16:53:42 +08001004 if (!info.reloadContent) continue;
1005 info.items = mSource.getMediaItem(
1006 info.contentStart, info.contentEnd);
1007
1008 int index = MediaSet.INDEX_NOT_FOUND;
1009
1010 // First try to focus on the given hint path if there is one.
1011 if (mFocusHintPath != null) {
1012 index = findIndexOfPathInCache(info, mFocusHintPath);
1013 mFocusHintPath = null;
1014 }
1015
1016 // Otherwise try to see if the currently focused item can be found.
1017 if (index == MediaSet.INDEX_NOT_FOUND) {
1018 MediaItem item = findCurrentMediaItem(info);
1019 if (item != null && item.getPath() == info.target) {
1020 index = info.indexHint;
1021 } else {
1022 index = findIndexOfTarget(info);
1023 }
1024 }
1025
1026 // The image has been deleted. Focus on the next image (keep
1027 // mCurrentIndex unchanged) or the previous image (decrease
1028 // mCurrentIndex by 1). In page mode we want to see the next
1029 // image, so we focus on the next one. In film mode we want the
1030 // later images to shift left to fill the empty space, so we
1031 // focus on the previous image (so it will not move). In any
1032 // case the index needs to be limited to [0, mSize).
1033 if (index == MediaSet.INDEX_NOT_FOUND) {
1034 index = info.indexHint;
1035 if (mFocusHintDirection == FOCUS_HINT_PREVIOUS
1036 && index > 0) {
1037 index--;
1038 }
1039 }
1040
1041 // Don't change index if mSize == 0
1042 if (mSize > 0) {
1043 if (index >= mSize) index = mSize - 1;
1044 }
1045
1046 info.indexHint = index;
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001047
Owen Linf9a0a432011-08-17 22:07:43 +08001048 executeAndWait(new UpdateContent(info));
1049 }
1050 }
1051
1052 public synchronized void notifyDirty() {
1053 mDirty = true;
1054 notifyAll();
1055 }
1056
1057 public synchronized void terminate() {
1058 mActive = false;
1059 notifyAll();
1060 }
1061
1062 private MediaItem findCurrentMediaItem(UpdateInfo info) {
1063 ArrayList<MediaItem> items = info.items;
1064 int index = info.indexHint - info.contentStart;
1065 return index < 0 || index >= items.size() ? null : items.get(index);
1066 }
1067
1068 private int findIndexOfTarget(UpdateInfo info) {
1069 if (info.target == null) return info.indexHint;
1070 ArrayList<MediaItem> items = info.items;
1071
1072 // First, try to find the item in the data just loaded
1073 if (items != null) {
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001074 int i = findIndexOfPathInCache(info, info.target);
1075 if (i != MediaSet.INDEX_NOT_FOUND) return i;
Owen Linf9a0a432011-08-17 22:07:43 +08001076 }
1077
1078 // Not found, find it in mSource.
1079 return mSource.getIndexOfItem(info.target, info.indexHint);
1080 }
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001081
1082 private int findIndexOfPathInCache(UpdateInfo info, Path path) {
1083 ArrayList<MediaItem> items = info.items;
1084 for (int i = 0, n = items.size(); i < n; ++i) {
Chih-Chung Chang0086dba2012-08-01 14:43:35 +08001085 MediaItem item = items.get(i);
1086 if (item != null && item.getPath() == path) {
Chih-Chung Chang6b891c62012-06-07 20:09:13 +08001087 return i + info.contentStart;
1088 }
1089 }
1090 return MediaSet.INDEX_NOT_FOUND;
1091 }
Owen Linf9a0a432011-08-17 22:07:43 +08001092 }
1093}