summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/app/PhotoDataAdapter.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/app/PhotoDataAdapter.java')
-rw-r--r--src/com/android/gallery3d/app/PhotoDataAdapter.java1133
1 files changed, 0 insertions, 1133 deletions
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java
deleted file mode 100644
index fd3a7cf73..000000000
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ /dev/null
@@ -1,1133 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.app;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapRegionDecoder;
-import android.os.Handler;
-import android.os.Message;
-
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.ContentListener;
-import com.android.gallery3d.data.LocalMediaItem;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.data.MediaObject;
-import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.data.Path;
-import com.android.gallery3d.glrenderer.TiledTexture;
-import com.android.gallery3d.ui.PhotoView;
-import com.android.gallery3d.ui.ScreenNail;
-import com.android.gallery3d.ui.SynchronizedHandler;
-import com.android.gallery3d.ui.TileImageViewAdapter;
-import com.android.gallery3d.ui.TiledScreenNail;
-import com.android.gallery3d.util.Future;
-import com.android.gallery3d.util.FutureListener;
-import com.android.gallery3d.util.MediaSetUtils;
-import com.android.gallery3d.util.ThreadPool;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-
-public class PhotoDataAdapter implements PhotoPage.Model {
- @SuppressWarnings("unused")
- private static final String TAG = "PhotoDataAdapter";
-
- private static final int MSG_LOAD_START = 1;
- private static final int MSG_LOAD_FINISH = 2;
- private static final int MSG_RUN_OBJECT = 3;
- private static final int MSG_UPDATE_IMAGE_REQUESTS = 4;
-
- private static final int MIN_LOAD_COUNT = 16;
- private static final int DATA_CACHE_SIZE = 256;
- private static final int SCREEN_NAIL_MAX = PhotoView.SCREEN_NAIL_MAX;
- private static final int IMAGE_CACHE_SIZE = 2 * SCREEN_NAIL_MAX + 1;
-
- private static final int BIT_SCREEN_NAIL = 1;
- private static final int BIT_FULL_IMAGE = 2;
-
- // sImageFetchSeq is the fetching sequence for images.
- // We want to fetch the current screennail first (offset = 0), the next
- // screennail (offset = +1), then the previous screennail (offset = -1) etc.
- // After all the screennail are fetched, we fetch the full images (only some
- // of them because of we don't want to use too much memory).
- private static ImageFetch[] sImageFetchSeq;
-
- private static class ImageFetch {
- int indexOffset;
- int imageBit;
- public ImageFetch(int offset, int bit) {
- indexOffset = offset;
- imageBit = bit;
- }
- }
-
- static {
- int k = 0;
- sImageFetchSeq = new ImageFetch[1 + (IMAGE_CACHE_SIZE - 1) * 2 + 3];
- sImageFetchSeq[k++] = new ImageFetch(0, BIT_SCREEN_NAIL);
-
- for (int i = 1; i < IMAGE_CACHE_SIZE; ++i) {
- sImageFetchSeq[k++] = new ImageFetch(i, BIT_SCREEN_NAIL);
- sImageFetchSeq[k++] = new ImageFetch(-i, BIT_SCREEN_NAIL);
- }
-
- sImageFetchSeq[k++] = new ImageFetch(0, BIT_FULL_IMAGE);
- sImageFetchSeq[k++] = new ImageFetch(1, BIT_FULL_IMAGE);
- sImageFetchSeq[k++] = new ImageFetch(-1, BIT_FULL_IMAGE);
- }
-
- private final TileImageViewAdapter mTileProvider = new TileImageViewAdapter();
-
- // PhotoDataAdapter caches MediaItems (data) and ImageEntries (image).
- //
- // The MediaItems are stored in the mData array, which has DATA_CACHE_SIZE
- // entries. The valid index range are [mContentStart, mContentEnd). We keep
- // mContentEnd - mContentStart <= DATA_CACHE_SIZE, so we can use
- // (i % DATA_CACHE_SIZE) as index to the array.
- //
- // The valid MediaItem window size (mContentEnd - mContentStart) may be
- // smaller than DATA_CACHE_SIZE because we only update the window and reload
- // the MediaItems when there are significant changes to the window position
- // (>= MIN_LOAD_COUNT).
- private final MediaItem mData[] = new MediaItem[DATA_CACHE_SIZE];
- private int mContentStart = 0;
- private int mContentEnd = 0;
-
- // The ImageCache is a Path-to-ImageEntry map. It only holds the
- // ImageEntries in the range of [mActiveStart, mActiveEnd). We also keep
- // mActiveEnd - mActiveStart <= IMAGE_CACHE_SIZE. Besides, the
- // [mActiveStart, mActiveEnd) range must be contained within
- // the [mContentStart, mContentEnd) range.
- private HashMap<Path, ImageEntry> mImageCache =
- new HashMap<Path, ImageEntry>();
- private int mActiveStart = 0;
- private int mActiveEnd = 0;
-
- // mCurrentIndex is the "center" image the user is viewing. The change of
- // mCurrentIndex triggers the data loading and image loading.
- private int mCurrentIndex;
-
- // mChanges keeps the version number (of MediaItem) about the images. If any
- // of the version number changes, we notify the view. This is used after a
- // database reload or mCurrentIndex changes.
- private final long mChanges[] = new long[IMAGE_CACHE_SIZE];
- // mPaths keeps the corresponding Path (of MediaItem) for the images. This
- // is used to determine the item movement.
- private final Path mPaths[] = new Path[IMAGE_CACHE_SIZE];
-
- private final Handler mMainHandler;
- private final ThreadPool mThreadPool;
-
- private final PhotoView mPhotoView;
- private final MediaSet mSource;
- private ReloadTask mReloadTask;
-
- private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
- private int mSize = 0;
- private Path mItemPath;
- private int mCameraIndex;
- private boolean mIsPanorama;
- private boolean mIsStaticCamera;
- private boolean mIsActive;
- private boolean mNeedFullImage;
- private int mFocusHintDirection = FOCUS_HINT_NEXT;
- private Path mFocusHintPath = null;
-
- public interface DataListener extends LoadingListener {
- public void onPhotoChanged(int index, Path item);
- }
-
- private DataListener mDataListener;
-
- private final SourceListener mSourceListener = new SourceListener();
- private final TiledTexture.Uploader mUploader;
-
- // The path of the current viewing item will be stored in mItemPath.
- // If mItemPath is not null, mCurrentIndex is only a hint for where we
- // can find the item. If mItemPath is null, then we use the mCurrentIndex to
- // find the image being viewed. cameraIndex is the index of the camera
- // preview. If cameraIndex < 0, there is no camera preview.
- public PhotoDataAdapter(AbstractGalleryActivity activity, PhotoView view,
- MediaSet mediaSet, Path itemPath, int indexHint, int cameraIndex,
- boolean isPanorama, boolean isStaticCamera) {
- mSource = Utils.checkNotNull(mediaSet);
- mPhotoView = Utils.checkNotNull(view);
- mItemPath = Utils.checkNotNull(itemPath);
- mCurrentIndex = indexHint;
- mCameraIndex = cameraIndex;
- mIsPanorama = isPanorama;
- mIsStaticCamera = isStaticCamera;
- mThreadPool = activity.getThreadPool();
- mNeedFullImage = true;
-
- Arrays.fill(mChanges, MediaObject.INVALID_DATA_VERSION);
-
- mUploader = new TiledTexture.Uploader(activity.getGLRoot());
-
- mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
- @SuppressWarnings("unchecked")
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_RUN_OBJECT:
- ((Runnable) message.obj).run();
- return;
- case MSG_LOAD_START: {
- if (mDataListener != null) {
- mDataListener.onLoadingStarted();
- }
- return;
- }
- case MSG_LOAD_FINISH: {
- if (mDataListener != null) {
- mDataListener.onLoadingFinished(false);
- }
- return;
- }
- case MSG_UPDATE_IMAGE_REQUESTS: {
- updateImageRequests();
- return;
- }
- default: throw new AssertionError();
- }
- }
- };
-
- updateSlidingWindow();
- }
-
- private MediaItem getItemInternal(int index) {
- if (index < 0 || index >= mSize) return null;
- if (index >= mContentStart && index < mContentEnd) {
- return mData[index % DATA_CACHE_SIZE];
- }
- return null;
- }
-
- private long getVersion(int index) {
- MediaItem item = getItemInternal(index);
- if (item == null) return MediaObject.INVALID_DATA_VERSION;
- return item.getDataVersion();
- }
-
- private Path getPath(int index) {
- MediaItem item = getItemInternal(index);
- if (item == null) return null;
- return item.getPath();
- }
-
- private void fireDataChange() {
- // First check if data actually changed.
- boolean changed = false;
- for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
- long newVersion = getVersion(mCurrentIndex + i);
- if (mChanges[i + SCREEN_NAIL_MAX] != newVersion) {
- mChanges[i + SCREEN_NAIL_MAX] = newVersion;
- changed = true;
- }
- }
-
- if (!changed) return;
-
- // Now calculate the fromIndex array. fromIndex represents the item
- // movement. It records the index where the picture come from. The
- // special value Integer.MAX_VALUE means it's a new picture.
- final int N = IMAGE_CACHE_SIZE;
- int fromIndex[] = new int[N];
-
- // Remember the old path array.
- Path oldPaths[] = new Path[N];
- System.arraycopy(mPaths, 0, oldPaths, 0, N);
-
- // Update the mPaths array.
- for (int i = 0; i < N; ++i) {
- mPaths[i] = getPath(mCurrentIndex + i - SCREEN_NAIL_MAX);
- }
-
- // Calculate the fromIndex array.
- for (int i = 0; i < N; i++) {
- Path p = mPaths[i];
- if (p == null) {
- fromIndex[i] = Integer.MAX_VALUE;
- continue;
- }
-
- // Try to find the same path in the old array
- int j;
- for (j = 0; j < N; j++) {
- if (oldPaths[j] == p) {
- break;
- }
- }
- fromIndex[i] = (j < N) ? j - SCREEN_NAIL_MAX : Integer.MAX_VALUE;
- }
-
- mPhotoView.notifyDataChange(fromIndex, -mCurrentIndex,
- mSize - 1 - mCurrentIndex);
- }
-
- public void setDataListener(DataListener listener) {
- mDataListener = listener;
- }
-
- private void updateScreenNail(Path path, Future<ScreenNail> future) {
- ImageEntry entry = mImageCache.get(path);
- ScreenNail screenNail = future.get();
-
- if (entry == null || entry.screenNailTask != future) {
- if (screenNail != null) screenNail.recycle();
- return;
- }
-
- entry.screenNailTask = null;
-
- // Combine the ScreenNails if we already have a BitmapScreenNail
- if (entry.screenNail instanceof TiledScreenNail) {
- TiledScreenNail original = (TiledScreenNail) entry.screenNail;
- screenNail = original.combine(screenNail);
- }
-
- if (screenNail == null) {
- entry.failToLoad = true;
- } else {
- entry.failToLoad = false;
- entry.screenNail = screenNail;
- }
-
- for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
- if (path == getPath(mCurrentIndex + i)) {
- if (i == 0) updateTileProvider(entry);
- mPhotoView.notifyImageChange(i);
- break;
- }
- }
- updateImageRequests();
- updateScreenNailUploadQueue();
- }
-
- private void updateFullImage(Path path, Future<BitmapRegionDecoder> future) {
- ImageEntry entry = mImageCache.get(path);
- if (entry == null || entry.fullImageTask != future) {
- BitmapRegionDecoder fullImage = future.get();
- if (fullImage != null) fullImage.recycle();
- return;
- }
-
- entry.fullImageTask = null;
- entry.fullImage = future.get();
- if (entry.fullImage != null) {
- if (path == getPath(mCurrentIndex)) {
- updateTileProvider(entry);
- mPhotoView.notifyImageChange(0);
- }
- }
- updateImageRequests();
- }
-
- @Override
- public void resume() {
- mIsActive = true;
- TiledTexture.prepareResources();
-
- mSource.addContentListener(mSourceListener);
- updateImageCache();
- updateImageRequests();
-
- mReloadTask = new ReloadTask();
- mReloadTask.start();
-
- fireDataChange();
- }
-
- @Override
- public void pause() {
- mIsActive = false;
-
- mReloadTask.terminate();
- mReloadTask = null;
-
- mSource.removeContentListener(mSourceListener);
-
- for (ImageEntry entry : mImageCache.values()) {
- if (entry.fullImageTask != null) entry.fullImageTask.cancel();
- if (entry.screenNailTask != null) entry.screenNailTask.cancel();
- if (entry.screenNail != null) entry.screenNail.recycle();
- }
- mImageCache.clear();
- mTileProvider.clear();
-
- mUploader.clear();
- TiledTexture.freeResources();
- }
-
- private MediaItem getItem(int index) {
- if (index < 0 || index >= mSize || !mIsActive) return null;
- Utils.assertTrue(index >= mActiveStart && index < mActiveEnd);
-
- if (index >= mContentStart && index < mContentEnd) {
- return mData[index % DATA_CACHE_SIZE];
- }
- return null;
- }
-
- private void updateCurrentIndex(int index) {
- if (mCurrentIndex == index) return;
- mCurrentIndex = index;
- updateSlidingWindow();
-
- MediaItem item = mData[index % DATA_CACHE_SIZE];
- mItemPath = item == null ? null : item.getPath();
-
- updateImageCache();
- updateImageRequests();
- updateTileProvider();
-
- if (mDataListener != null) {
- mDataListener.onPhotoChanged(index, mItemPath);
- }
-
- fireDataChange();
- }
-
- private void uploadScreenNail(int offset) {
- int index = mCurrentIndex + offset;
- if (index < mActiveStart || index >= mActiveEnd) return;
-
- MediaItem item = getItem(index);
- if (item == null) return;
-
- ImageEntry e = mImageCache.get(item.getPath());
- if (e == null) return;
-
- ScreenNail s = e.screenNail;
- if (s instanceof TiledScreenNail) {
- TiledTexture t = ((TiledScreenNail) s).getTexture();
- if (t != null && !t.isReady()) mUploader.addTexture(t);
- }
- }
-
- private void updateScreenNailUploadQueue() {
- mUploader.clear();
- uploadScreenNail(0);
- for (int i = 1; i < IMAGE_CACHE_SIZE; ++i) {
- uploadScreenNail(i);
- uploadScreenNail(-i);
- }
- }
-
- @Override
- public void moveTo(int index) {
- updateCurrentIndex(index);
- }
-
- @Override
- public ScreenNail getScreenNail(int offset) {
- int index = mCurrentIndex + offset;
- if (index < 0 || index >= mSize || !mIsActive) return null;
- Utils.assertTrue(index >= mActiveStart && index < mActiveEnd);
-
- MediaItem item = getItem(index);
- if (item == null) return null;
-
- ImageEntry entry = mImageCache.get(item.getPath());
- if (entry == null) return null;
-
- // Create a default ScreenNail if the real one is not available yet,
- // except for camera that a black screen is better than a gray tile.
- if (entry.screenNail == null && !isCamera(offset)) {
- entry.screenNail = newPlaceholderScreenNail(item);
- if (offset == 0) updateTileProvider(entry);
- }
-
- return entry.screenNail;
- }
-
- @Override
- public void getImageSize(int offset, PhotoView.Size size) {
- MediaItem item = getItem(mCurrentIndex + offset);
- if (item == null) {
- size.width = 0;
- size.height = 0;
- } else {
- size.width = item.getWidth();
- size.height = item.getHeight();
- }
- }
-
- @Override
- public int getImageRotation(int offset) {
- MediaItem item = getItem(mCurrentIndex + offset);
- return (item == null) ? 0 : item.getFullImageRotation();
- }
-
- @Override
- public void setNeedFullImage(boolean enabled) {
- mNeedFullImage = enabled;
- mMainHandler.sendEmptyMessage(MSG_UPDATE_IMAGE_REQUESTS);
- }
-
- @Override
- public boolean isCamera(int offset) {
- return mCurrentIndex + offset == mCameraIndex;
- }
-
- @Override
- public boolean isPanorama(int offset) {
- return isCamera(offset) && mIsPanorama;
- }
-
- @Override
- public boolean isStaticCamera(int offset) {
- return isCamera(offset) && mIsStaticCamera;
- }
-
- @Override
- public boolean isVideo(int offset) {
- MediaItem item = getItem(mCurrentIndex + offset);
- return (item == null)
- ? false
- : item.getMediaType() == MediaItem.MEDIA_TYPE_VIDEO;
- }
-
- @Override
- public boolean isDeletable(int offset) {
- MediaItem item = getItem(mCurrentIndex + offset);
- return (item == null)
- ? false
- : (item.getSupportedOperations() & MediaItem.SUPPORT_DELETE) != 0;
- }
-
- @Override
- public int getLoadingState(int offset) {
- ImageEntry entry = mImageCache.get(getPath(mCurrentIndex + offset));
- if (entry == null) return LOADING_INIT;
- if (entry.failToLoad) return LOADING_FAIL;
- if (entry.screenNail != null) return LOADING_COMPLETE;
- return LOADING_INIT;
- }
-
- @Override
- public ScreenNail getScreenNail() {
- return getScreenNail(0);
- }
-
- @Override
- public int getImageHeight() {
- return mTileProvider.getImageHeight();
- }
-
- @Override
- public int getImageWidth() {
- return mTileProvider.getImageWidth();
- }
-
- @Override
- public int getLevelCount() {
- return mTileProvider.getLevelCount();
- }
-
- @Override
- public Bitmap getTile(int level, int x, int y, int tileSize) {
- return mTileProvider.getTile(level, x, y, tileSize);
- }
-
- @Override
- public boolean isEmpty() {
- return mSize == 0;
- }
-
- @Override
- public int getCurrentIndex() {
- return mCurrentIndex;
- }
-
- @Override
- public MediaItem getMediaItem(int offset) {
- int index = mCurrentIndex + offset;
- if (index >= mContentStart && index < mContentEnd) {
- return mData[index % DATA_CACHE_SIZE];
- }
- return null;
- }
-
- @Override
- public void setCurrentPhoto(Path path, int indexHint) {
- if (mItemPath == path) return;
- mItemPath = path;
- mCurrentIndex = indexHint;
- updateSlidingWindow();
- updateImageCache();
- fireDataChange();
-
- // We need to reload content if the path doesn't match.
- MediaItem item = getMediaItem(0);
- if (item != null && item.getPath() != path) {
- if (mReloadTask != null) mReloadTask.notifyDirty();
- }
- }
-
- @Override
- public void setFocusHintDirection(int direction) {
- mFocusHintDirection = direction;
- }
-
- @Override
- public void setFocusHintPath(Path path) {
- mFocusHintPath = path;
- }
-
- private void updateTileProvider() {
- ImageEntry entry = mImageCache.get(getPath(mCurrentIndex));
- if (entry == null) { // in loading
- mTileProvider.clear();
- } else {
- updateTileProvider(entry);
- }
- }
-
- private void updateTileProvider(ImageEntry entry) {
- ScreenNail screenNail = entry.screenNail;
- BitmapRegionDecoder fullImage = entry.fullImage;
- if (screenNail != null) {
- if (fullImage != null) {
- mTileProvider.setScreenNail(screenNail,
- fullImage.getWidth(), fullImage.getHeight());
- mTileProvider.setRegionDecoder(fullImage);
- } else {
- int width = screenNail.getWidth();
- int height = screenNail.getHeight();
- mTileProvider.setScreenNail(screenNail, width, height);
- }
- } else {
- mTileProvider.clear();
- }
- }
-
- private void updateSlidingWindow() {
- // 1. Update the image window
- int start = Utils.clamp(mCurrentIndex - IMAGE_CACHE_SIZE / 2,
- 0, Math.max(0, mSize - IMAGE_CACHE_SIZE));
- int end = Math.min(mSize, start + IMAGE_CACHE_SIZE);
-
- if (mActiveStart == start && mActiveEnd == end) return;
-
- mActiveStart = start;
- mActiveEnd = end;
-
- // 2. Update the data window
- start = Utils.clamp(mCurrentIndex - DATA_CACHE_SIZE / 2,
- 0, Math.max(0, mSize - DATA_CACHE_SIZE));
- end = Math.min(mSize, start + DATA_CACHE_SIZE);
- if (mContentStart > mActiveStart || mContentEnd < mActiveEnd
- || Math.abs(start - mContentStart) > MIN_LOAD_COUNT) {
- for (int i = mContentStart; i < mContentEnd; ++i) {
- if (i < start || i >= end) {
- mData[i % DATA_CACHE_SIZE] = null;
- }
- }
- mContentStart = start;
- mContentEnd = end;
- if (mReloadTask != null) mReloadTask.notifyDirty();
- }
- }
-
- private void updateImageRequests() {
- if (!mIsActive) return;
-
- int currentIndex = mCurrentIndex;
- MediaItem item = mData[currentIndex % DATA_CACHE_SIZE];
- if (item == null || item.getPath() != mItemPath) {
- // current item mismatch - don't request image
- return;
- }
-
- // 1. Find the most wanted request and start it (if not already started).
- Future<?> task = null;
- for (int i = 0; i < sImageFetchSeq.length; i++) {
- int offset = sImageFetchSeq[i].indexOffset;
- int bit = sImageFetchSeq[i].imageBit;
- if (bit == BIT_FULL_IMAGE && !mNeedFullImage) continue;
- task = startTaskIfNeeded(currentIndex + offset, bit);
- if (task != null) break;
- }
-
- // 2. Cancel everything else.
- for (ImageEntry entry : mImageCache.values()) {
- if (entry.screenNailTask != null && entry.screenNailTask != task) {
- entry.screenNailTask.cancel();
- entry.screenNailTask = null;
- entry.requestedScreenNail = MediaObject.INVALID_DATA_VERSION;
- }
- if (entry.fullImageTask != null && entry.fullImageTask != task) {
- entry.fullImageTask.cancel();
- entry.fullImageTask = null;
- entry.requestedFullImage = MediaObject.INVALID_DATA_VERSION;
- }
- }
- }
-
- private class ScreenNailJob implements Job<ScreenNail> {
- private MediaItem mItem;
-
- public ScreenNailJob(MediaItem item) {
- mItem = item;
- }
-
- @Override
- public ScreenNail run(JobContext jc) {
- // We try to get a ScreenNail first, if it fails, we fallback to get
- // a Bitmap and then wrap it in a BitmapScreenNail instead.
- ScreenNail s = mItem.getScreenNail();
- if (s != null) return s;
-
- // If this is a temporary item, don't try to get its bitmap because
- // it won't be available. We will get its bitmap after a data reload.
- if (isTemporaryItem(mItem)) {
- return newPlaceholderScreenNail(mItem);
- }
-
- Bitmap bitmap = mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc);
- if (jc.isCancelled()) return null;
- if (bitmap != null) {
- bitmap = BitmapUtils.rotateBitmap(bitmap,
- mItem.getRotation() - mItem.getFullImageRotation(), true);
- }
- return bitmap == null ? null : new TiledScreenNail(bitmap);
- }
- }
-
- private class FullImageJob implements Job<BitmapRegionDecoder> {
- private MediaItem mItem;
-
- public FullImageJob(MediaItem item) {
- mItem = item;
- }
-
- @Override
- public BitmapRegionDecoder run(JobContext jc) {
- if (isTemporaryItem(mItem)) {
- return null;
- }
- return mItem.requestLargeImage().run(jc);
- }
- }
-
- // Returns true if we think this is a temporary item created by Camera. A
- // temporary item is an image or a video whose data is still being
- // processed, but an incomplete entry is created first in MediaProvider, so
- // we can display them (in grey tile) even if they are not saved to disk
- // yet. When the image or video data is actually saved, we will get
- // notification from MediaProvider, reload data, and show the actual image
- // or video data.
- private boolean isTemporaryItem(MediaItem mediaItem) {
- // Must have camera to create a temporary item.
- if (mCameraIndex < 0) return false;
- // Must be an item in camera roll.
- if (!(mediaItem instanceof LocalMediaItem)) return false;
- LocalMediaItem item = (LocalMediaItem) mediaItem;
- if (item.getBucketId() != MediaSetUtils.CAMERA_BUCKET_ID) return false;
- // Must have no size, but must have width and height information
- if (item.getSize() != 0) return false;
- if (item.getWidth() == 0) return false;
- if (item.getHeight() == 0) return false;
- // Must be created in the last 10 seconds.
- if (item.getDateInMs() - System.currentTimeMillis() > 10000) return false;
- return true;
- }
-
- // Create a default ScreenNail when a ScreenNail is needed, but we don't yet
- // have one available (because the image data is still being saved, or the
- // Bitmap is still being loaded.
- private ScreenNail newPlaceholderScreenNail(MediaItem item) {
- int width = item.getWidth();
- int height = item.getHeight();
- return new TiledScreenNail(width, height);
- }
-
- // Returns the task if we started the task or the task is already started.
- private Future<?> startTaskIfNeeded(int index, int which) {
- if (index < mActiveStart || index >= mActiveEnd) return null;
-
- ImageEntry entry = mImageCache.get(getPath(index));
- if (entry == null) return null;
- MediaItem item = mData[index % DATA_CACHE_SIZE];
- Utils.assertTrue(item != null);
- long version = item.getDataVersion();
-
- if (which == BIT_SCREEN_NAIL && entry.screenNailTask != null
- && entry.requestedScreenNail == version) {
- return entry.screenNailTask;
- } else if (which == BIT_FULL_IMAGE && entry.fullImageTask != null
- && entry.requestedFullImage == version) {
- return entry.fullImageTask;
- }
-
- if (which == BIT_SCREEN_NAIL && entry.requestedScreenNail != version) {
- entry.requestedScreenNail = version;
- entry.screenNailTask = mThreadPool.submit(
- new ScreenNailJob(item),
- new ScreenNailListener(item));
- // request screen nail
- return entry.screenNailTask;
- }
- if (which == BIT_FULL_IMAGE && entry.requestedFullImage != version
- && (item.getSupportedOperations()
- & MediaItem.SUPPORT_FULL_IMAGE) != 0) {
- entry.requestedFullImage = version;
- entry.fullImageTask = mThreadPool.submit(
- new FullImageJob(item),
- new FullImageListener(item));
- // request full image
- return entry.fullImageTask;
- }
- return null;
- }
-
- private void updateImageCache() {
- HashSet<Path> toBeRemoved = new HashSet<Path>(mImageCache.keySet());
- for (int i = mActiveStart; i < mActiveEnd; ++i) {
- MediaItem item = mData[i % DATA_CACHE_SIZE];
- if (item == null) continue;
- Path path = item.getPath();
- ImageEntry entry = mImageCache.get(path);
- toBeRemoved.remove(path);
- if (entry != null) {
- if (Math.abs(i - mCurrentIndex) > 1) {
- if (entry.fullImageTask != null) {
- entry.fullImageTask.cancel();
- entry.fullImageTask = null;
- }
- entry.fullImage = null;
- entry.requestedFullImage = MediaObject.INVALID_DATA_VERSION;
- }
- if (entry.requestedScreenNail != item.getDataVersion()) {
- // This ScreenNail is outdated, we want to update it if it's
- // still a placeholder.
- if (entry.screenNail instanceof TiledScreenNail) {
- TiledScreenNail s = (TiledScreenNail) entry.screenNail;
- s.updatePlaceholderSize(
- item.getWidth(), item.getHeight());
- }
- }
- } else {
- entry = new ImageEntry();
- mImageCache.put(path, entry);
- }
- }
-
- // Clear the data and requests for ImageEntries outside the new window.
- for (Path path : toBeRemoved) {
- ImageEntry entry = mImageCache.remove(path);
- if (entry.fullImageTask != null) entry.fullImageTask.cancel();
- if (entry.screenNailTask != null) entry.screenNailTask.cancel();
- if (entry.screenNail != null) entry.screenNail.recycle();
- }
-
- updateScreenNailUploadQueue();
- }
-
- private class FullImageListener
- implements Runnable, FutureListener<BitmapRegionDecoder> {
- private final Path mPath;
- private Future<BitmapRegionDecoder> mFuture;
-
- public FullImageListener(MediaItem item) {
- mPath = item.getPath();
- }
-
- @Override
- public void onFutureDone(Future<BitmapRegionDecoder> future) {
- mFuture = future;
- mMainHandler.sendMessage(
- mMainHandler.obtainMessage(MSG_RUN_OBJECT, this));
- }
-
- @Override
- public void run() {
- updateFullImage(mPath, mFuture);
- }
- }
-
- private class ScreenNailListener
- implements Runnable, FutureListener<ScreenNail> {
- private final Path mPath;
- private Future<ScreenNail> mFuture;
-
- public ScreenNailListener(MediaItem item) {
- mPath = item.getPath();
- }
-
- @Override
- public void onFutureDone(Future<ScreenNail> future) {
- mFuture = future;
- mMainHandler.sendMessage(
- mMainHandler.obtainMessage(MSG_RUN_OBJECT, this));
- }
-
- @Override
- public void run() {
- updateScreenNail(mPath, mFuture);
- }
- }
-
- private static class ImageEntry {
- public BitmapRegionDecoder fullImage;
- public ScreenNail screenNail;
- public Future<ScreenNail> screenNailTask;
- public Future<BitmapRegionDecoder> fullImageTask;
- public long requestedScreenNail = MediaObject.INVALID_DATA_VERSION;
- public long requestedFullImage = MediaObject.INVALID_DATA_VERSION;
- public boolean failToLoad = false;
- }
-
- private class SourceListener implements ContentListener {
- @Override
- public void onContentDirty() {
- if (mReloadTask != null) mReloadTask.notifyDirty();
- }
- }
-
- private <T> T executeAndWait(Callable<T> callable) {
- FutureTask<T> task = new FutureTask<T>(callable);
- mMainHandler.sendMessage(
- mMainHandler.obtainMessage(MSG_RUN_OBJECT, task));
- try {
- return task.get();
- } catch (InterruptedException e) {
- return null;
- } catch (ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static class UpdateInfo {
- public long version;
- public boolean reloadContent;
- public Path target;
- public int indexHint;
- public int contentStart;
- public int contentEnd;
-
- public int size;
- public ArrayList<MediaItem> items;
- }
-
- private class GetUpdateInfo implements Callable<UpdateInfo> {
-
- private boolean needContentReload() {
- for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
- if (mData[i % DATA_CACHE_SIZE] == null) return true;
- }
- MediaItem current = mData[mCurrentIndex % DATA_CACHE_SIZE];
- return current == null || current.getPath() != mItemPath;
- }
-
- @Override
- public UpdateInfo call() throws Exception {
- // TODO: Try to load some data in first update
- UpdateInfo info = new UpdateInfo();
- info.version = mSourceVersion;
- info.reloadContent = needContentReload();
- info.target = mItemPath;
- info.indexHint = mCurrentIndex;
- info.contentStart = mContentStart;
- info.contentEnd = mContentEnd;
- info.size = mSize;
- return info;
- }
- }
-
- private class UpdateContent implements Callable<Void> {
- UpdateInfo mUpdateInfo;
-
- public UpdateContent(UpdateInfo updateInfo) {
- mUpdateInfo = updateInfo;
- }
-
- @Override
- public Void call() throws Exception {
- UpdateInfo info = mUpdateInfo;
- mSourceVersion = info.version;
-
- if (info.size != mSize) {
- mSize = info.size;
- if (mContentEnd > mSize) mContentEnd = mSize;
- if (mActiveEnd > mSize) mActiveEnd = mSize;
- }
-
- mCurrentIndex = info.indexHint;
- updateSlidingWindow();
-
- if (info.items != null) {
- int start = Math.max(info.contentStart, mContentStart);
- int end = Math.min(info.contentStart + info.items.size(), mContentEnd);
- int dataIndex = start % DATA_CACHE_SIZE;
- for (int i = start; i < end; ++i) {
- mData[dataIndex] = info.items.get(i - info.contentStart);
- if (++dataIndex == DATA_CACHE_SIZE) dataIndex = 0;
- }
- }
-
- // update mItemPath
- MediaItem current = mData[mCurrentIndex % DATA_CACHE_SIZE];
- mItemPath = current == null ? null : current.getPath();
-
- updateImageCache();
- updateTileProvider();
- updateImageRequests();
-
- if (mDataListener != null) {
- mDataListener.onPhotoChanged(mCurrentIndex, mItemPath);
- }
-
- fireDataChange();
- return null;
- }
- }
-
- private class ReloadTask extends Thread {
- private volatile boolean mActive = true;
- private volatile boolean mDirty = true;
-
- private boolean mIsLoading = false;
-
- private void updateLoading(boolean loading) {
- if (mIsLoading == loading) return;
- mIsLoading = loading;
- mMainHandler.sendEmptyMessage(loading ? MSG_LOAD_START : MSG_LOAD_FINISH);
- }
-
- @Override
- public void run() {
- while (mActive) {
- synchronized (this) {
- if (!mDirty && mActive) {
- updateLoading(false);
- Utils.waitWithoutInterrupt(this);
- continue;
- }
- }
- mDirty = false;
- UpdateInfo info = executeAndWait(new GetUpdateInfo());
- updateLoading(true);
- long version = mSource.reload();
- if (info.version != version) {
- info.reloadContent = true;
- info.size = mSource.getMediaItemCount();
- }
- if (!info.reloadContent) continue;
- info.items = mSource.getMediaItem(
- info.contentStart, info.contentEnd);
-
- int index = MediaSet.INDEX_NOT_FOUND;
-
- // First try to focus on the given hint path if there is one.
- if (mFocusHintPath != null) {
- index = findIndexOfPathInCache(info, mFocusHintPath);
- mFocusHintPath = null;
- }
-
- // Otherwise try to see if the currently focused item can be found.
- if (index == MediaSet.INDEX_NOT_FOUND) {
- MediaItem item = findCurrentMediaItem(info);
- if (item != null && item.getPath() == info.target) {
- index = info.indexHint;
- } else {
- index = findIndexOfTarget(info);
- }
- }
-
- // The image has been deleted. Focus on the next image (keep
- // mCurrentIndex unchanged) or the previous image (decrease
- // mCurrentIndex by 1). In page mode we want to see the next
- // image, so we focus on the next one. In film mode we want the
- // later images to shift left to fill the empty space, so we
- // focus on the previous image (so it will not move). In any
- // case the index needs to be limited to [0, mSize).
- if (index == MediaSet.INDEX_NOT_FOUND) {
- index = info.indexHint;
- int focusHintDirection = mFocusHintDirection;
- if (index == (mCameraIndex + 1)) {
- focusHintDirection = FOCUS_HINT_NEXT;
- }
- if (focusHintDirection == FOCUS_HINT_PREVIOUS
- && index > 0) {
- index--;
- }
- }
-
- // Don't change index if mSize == 0
- if (mSize > 0) {
- if (index >= mSize) index = mSize - 1;
- }
-
- info.indexHint = index;
-
- executeAndWait(new UpdateContent(info));
- }
- }
-
- public synchronized void notifyDirty() {
- mDirty = true;
- notifyAll();
- }
-
- public synchronized void terminate() {
- mActive = false;
- notifyAll();
- }
-
- private MediaItem findCurrentMediaItem(UpdateInfo info) {
- ArrayList<MediaItem> items = info.items;
- int index = info.indexHint - info.contentStart;
- return index < 0 || index >= items.size() ? null : items.get(index);
- }
-
- private int findIndexOfTarget(UpdateInfo info) {
- if (info.target == null) return info.indexHint;
- ArrayList<MediaItem> items = info.items;
-
- // First, try to find the item in the data just loaded
- if (items != null) {
- int i = findIndexOfPathInCache(info, info.target);
- if (i != MediaSet.INDEX_NOT_FOUND) return i;
- }
-
- // Not found, find it in mSource.
- return mSource.getIndexOfItem(info.target, info.indexHint);
- }
-
- private int findIndexOfPathInCache(UpdateInfo info, Path path) {
- ArrayList<MediaItem> items = info.items;
- for (int i = 0, n = items.size(); i < n; ++i) {
- MediaItem item = items.get(i);
- if (item != null && item.getPath() == path) {
- return i + info.contentStart;
- }
- }
- return MediaSet.INDEX_NOT_FOUND;
- }
- }
-}