summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/android/gallery3d/app/PhotoDataAdapter.java228
-rw-r--r--src/com/android/gallery3d/app/SinglePhotoDataAdapter.java16
-rw-r--r--src/com/android/gallery3d/data/LocalImage.java2
-rw-r--r--src/com/android/gallery3d/data/LocalMediaItem.java2
-rw-r--r--src/com/android/gallery3d/data/LocalVideo.java30
-rw-r--r--src/com/android/gallery3d/ui/BitmapScreenNail.java120
-rw-r--r--src/com/android/gallery3d/ui/BitmapTileProvider.java4
-rw-r--r--src/com/android/gallery3d/ui/PhotoView.java141
-rw-r--r--src/com/android/gallery3d/ui/PositionController.java4
-rw-r--r--src/com/android/gallery3d/ui/TileImageView.java11
-rw-r--r--src/com/android/gallery3d/ui/TileImageViewAdapter.java14
11 files changed, 373 insertions, 199 deletions
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java
index 66b423f69..54c7115c3 100644
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java
@@ -25,6 +25,7 @@ import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.ContentListener;
import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.LocalMediaItem;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
@@ -36,6 +37,7 @@ import com.android.gallery3d.ui.SynchronizedHandler;
import com.android.gallery3d.ui.TileImageViewAdapter;
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;
@@ -113,14 +115,13 @@ public class PhotoDataAdapter implements PhotoPage.Model {
private int mContentStart = 0;
private int mContentEnd = 0;
- /*
- * The ImageCache is a version-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<Long, ImageEntry> mImageCache = new HashMap<Long, ImageEntry>();
+ // 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;
@@ -187,11 +188,15 @@ public class PhotoDataAdapter implements PhotoPage.Model {
((Runnable) message.obj).run();
return;
case MSG_LOAD_START: {
- if (mDataListener != null) mDataListener.onLoadingStarted();
+ if (mDataListener != null) {
+ mDataListener.onLoadingStarted();
+ }
return;
}
case MSG_LOAD_FINISH: {
- if (mDataListener != null) mDataListener.onLoadingFinished();
+ if (mDataListener != null) {
+ mDataListener.onLoadingFinished();
+ }
return;
}
case MSG_UPDATE_IMAGE_REQUESTS: {
@@ -280,8 +285,8 @@ public class PhotoDataAdapter implements PhotoPage.Model {
mDataListener = listener;
}
- private void updateScreenNail(long version, Future<ScreenNail> future) {
- ImageEntry entry = mImageCache.get(version);
+ private void updateScreenNail(Path path, Future<ScreenNail> future) {
+ ImageEntry entry = mImageCache.get(path);
ScreenNail screenNail = future.get();
if (entry == null || entry.screenNailTask != future) {
@@ -290,15 +295,22 @@ public class PhotoDataAdapter implements PhotoPage.Model {
}
entry.screenNailTask = null;
- Utils.assertTrue(entry.screenNail == null);
- entry.screenNail = screenNail;
+
+ // Combine the ScreenNails if we already have a BitmapScreenNail
+ if (entry.screenNail instanceof BitmapScreenNail) {
+ BitmapScreenNail original = (BitmapScreenNail) 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 (version == getVersion(mCurrentIndex + i)) {
+ if (path == getPath(mCurrentIndex + i)) {
if (i == 0) updateTileProvider(entry);
mPhotoView.notifyImageChange(i);
break;
@@ -307,8 +319,8 @@ public class PhotoDataAdapter implements PhotoPage.Model {
updateImageRequests();
}
- private void updateFullImage(long version, Future<BitmapRegionDecoder> future) {
- ImageEntry entry = mImageCache.get(version);
+ 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();
@@ -318,7 +330,7 @@ public class PhotoDataAdapter implements PhotoPage.Model {
entry.fullImageTask = null;
entry.fullImage = future.get();
if (entry.fullImage != null) {
- if (version == getVersion(mCurrentIndex)) {
+ if (path == getPath(mCurrentIndex)) {
updateTileProvider(entry);
mPhotoView.notifyImageChange(0);
}
@@ -355,14 +367,6 @@ public class PhotoDataAdapter implements PhotoPage.Model {
mTileProvider.clear();
}
- private ScreenNail getImage(int index) {
- if (index < 0 || index >= mSize || !mIsActive) return null;
- Utils.assertTrue(index >= mActiveStart && index < mActiveEnd);
-
- ImageEntry entry = mImageCache.get(getVersion(index));
- return entry == null ? null : entry.screenNail;
- }
-
private MediaItem getItem(int index) {
if (index < 0 || index >= mSize || !mIsActive) return null;
Utils.assertTrue(index >= mActiveStart && index < mActiveEnd);
@@ -399,7 +403,23 @@ public class PhotoDataAdapter implements PhotoPage.Model {
@Override
public ScreenNail getScreenNail(int offset) {
- return getImage(mCurrentIndex + 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.
+ if (entry.screenNail == null) {
+ entry.screenNail = newDefaultScreenNail(item);
+ if (offset == 0) updateTileProvider(entry);
+ }
+
+ return entry.screenNail;
}
@Override
@@ -444,6 +464,15 @@ public class PhotoDataAdapter implements PhotoPage.Model {
: item.getMediaType() == MediaItem.MEDIA_TYPE_VIDEO;
}
+ @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;
+ }
+
public ScreenNail getScreenNail() {
return mTileProvider.getScreenNail();
}
@@ -465,10 +494,6 @@ public class PhotoDataAdapter implements PhotoPage.Model {
return mTileProvider.getTile(level, x, y, tileSize, borderSize);
}
- public boolean isFailedToLoad() {
- return mTileProvider.isFailedToLoad();
- }
-
public boolean isEmpty() {
return mSize == 0;
}
@@ -501,7 +526,7 @@ public class PhotoDataAdapter implements PhotoPage.Model {
}
private void updateTileProvider() {
- ImageEntry entry = mImageCache.get(getVersion(mCurrentIndex));
+ ImageEntry entry = mImageCache.get(getPath(mCurrentIndex));
if (entry == null) { // in loading
mTileProvider.clear();
} else {
@@ -524,7 +549,6 @@ public class PhotoDataAdapter implements PhotoPage.Model {
}
} else {
mTileProvider.clear();
- if (entry.failToLoad) mTileProvider.setFailedToLoad();
}
}
@@ -581,17 +605,17 @@ public class PhotoDataAdapter implements PhotoPage.Model {
if (entry.screenNailTask != null && entry.screenNailTask != task) {
entry.screenNailTask.cancel();
entry.screenNailTask = null;
- entry.requestedBits &= ~BIT_SCREEN_NAIL;
+ entry.requestedScreenNail = MediaObject.INVALID_DATA_VERSION;
}
if (entry.fullImageTask != null && entry.fullImageTask != task) {
entry.fullImageTask.cancel();
entry.fullImageTask = null;
- entry.requestedBits &= ~BIT_FULL_IMAGE;
+ entry.requestedFullImage = MediaObject.INVALID_DATA_VERSION;
}
}
}
- private static class ScreenNailJob implements Job<ScreenNail> {
+ private class ScreenNailJob implements Job<ScreenNail> {
private MediaItem mItem;
public ScreenNailJob(MediaItem item) {
@@ -605,6 +629,12 @@ public class PhotoDataAdapter implements PhotoPage.Model {
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 newDefaultScreenNail(mItem);
+ }
+
Bitmap bitmap = mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc);
if (jc.isCancelled()) return null;
if (bitmap != null) {
@@ -615,39 +645,87 @@ public class PhotoDataAdapter implements PhotoPage.Model {
}
}
+ 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 newDefaultScreenNail(MediaItem item) {
+ int width = item.getWidth();
+ int height = item.getHeight();
+ return new BitmapScreenNail(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(getVersion(index));
+ 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) {
+ if (which == BIT_SCREEN_NAIL && entry.screenNailTask != null
+ && entry.requestedScreenNail == version) {
return entry.screenNailTask;
- } else if (which == BIT_FULL_IMAGE && entry.fullImageTask != null) {
+ } else if (which == BIT_FULL_IMAGE && entry.fullImageTask != null
+ && entry.requestedFullImage == version) {
return entry.fullImageTask;
}
- MediaItem item = mData[index % DATA_CACHE_SIZE];
- Utils.assertTrue(item != null);
-
- if (which == BIT_SCREEN_NAIL
- && (entry.requestedBits & BIT_SCREEN_NAIL) == 0) {
- entry.requestedBits |= BIT_SCREEN_NAIL;
+ if (which == BIT_SCREEN_NAIL && entry.requestedScreenNail != version) {
+ entry.requestedScreenNail = version;
entry.screenNailTask = mThreadPool.submit(
new ScreenNailJob(item),
- new ScreenNailListener(item.getDataVersion()));
+ new ScreenNailListener(item));
// request screen nail
return entry.screenNailTask;
}
- if (which == BIT_FULL_IMAGE
- && (entry.requestedBits & BIT_FULL_IMAGE) == 0
+ if (which == BIT_FULL_IMAGE && entry.requestedFullImage != version
&& (item.getSupportedOperations()
& MediaItem.SUPPORT_FULL_IMAGE) != 0) {
- entry.requestedBits |= BIT_FULL_IMAGE;
+ entry.requestedFullImage = version;
entry.fullImageTask = mThreadPool.submit(
- item.requestLargeImage(),
- new FullImageListener(item.getDataVersion()));
+ new FullImageJob(item),
+ new FullImageListener(item));
// request full image
return entry.fullImageTask;
}
@@ -655,15 +733,13 @@ public class PhotoDataAdapter implements PhotoPage.Model {
}
private void updateImageCache() {
- HashSet<Long> toBeRemoved = new HashSet<Long>(mImageCache.keySet());
+ HashSet<Path> toBeRemoved = new HashSet<Path>(mImageCache.keySet());
for (int i = mActiveStart; i < mActiveEnd; ++i) {
MediaItem item = mData[i % DATA_CACHE_SIZE];
- long version = item == null
- ? MediaObject.INVALID_DATA_VERSION
- : item.getDataVersion();
- if (version == MediaObject.INVALID_DATA_VERSION) continue;
- ImageEntry entry = mImageCache.get(version);
- toBeRemoved.remove(version);
+ 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) {
@@ -671,17 +747,26 @@ public class PhotoDataAdapter implements PhotoPage.Model {
entry.fullImageTask = null;
}
entry.fullImage = null;
- entry.requestedBits &= ~BIT_FULL_IMAGE;
+ 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 BitmapScreenNail) {
+ BitmapScreenNail s = (BitmapScreenNail) entry.screenNail;
+ s.updatePlaceholderSize(
+ item.getWidth(), item.getHeight());
+ }
}
} else {
entry = new ImageEntry();
- mImageCache.put(version, entry);
+ mImageCache.put(path, entry);
}
}
// Clear the data and requests for ImageEntries outside the new window.
- for (Long version : toBeRemoved) {
- ImageEntry entry = mImageCache.remove(version);
+ 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();
@@ -690,11 +775,11 @@ public class PhotoDataAdapter implements PhotoPage.Model {
private class FullImageListener
implements Runnable, FutureListener<BitmapRegionDecoder> {
- private final long mVersion;
+ private final Path mPath;
private Future<BitmapRegionDecoder> mFuture;
- public FullImageListener(long version) {
- mVersion = version;
+ public FullImageListener(MediaItem item) {
+ mPath = item.getPath();
}
@Override
@@ -706,17 +791,17 @@ public class PhotoDataAdapter implements PhotoPage.Model {
@Override
public void run() {
- updateFullImage(mVersion, mFuture);
+ updateFullImage(mPath, mFuture);
}
}
private class ScreenNailListener
implements Runnable, FutureListener<ScreenNail> {
- private final long mVersion;
+ private final Path mPath;
private Future<ScreenNail> mFuture;
- public ScreenNailListener(long version) {
- mVersion = version;
+ public ScreenNailListener(MediaItem item) {
+ mPath = item.getPath();
}
@Override
@@ -728,16 +813,17 @@ public class PhotoDataAdapter implements PhotoPage.Model {
@Override
public void run() {
- updateScreenNail(mVersion, mFuture);
+ updateScreenNail(mPath, mFuture);
}
}
private static class ImageEntry {
- public int requestedBits = 0;
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;
}
diff --git a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
index f26f405d4..111333e3b 100644
--- a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
@@ -49,6 +49,7 @@ public class SinglePhotoDataAdapter extends TileImageViewAdapter
private PhotoView mPhotoView;
private ThreadPool mThreadPool;
+ private int mLoadingState = LOADING_INIT;
public SinglePhotoDataAdapter(
GalleryActivity activity, PhotoView view, MediaItem item) {
@@ -123,7 +124,12 @@ public class SinglePhotoDataAdapter extends TileImageViewAdapter
private void onDecodeThumbComplete(Future<Bitmap> future) {
try {
Bitmap backup = future.get();
- if (backup == null) return;
+ if (backup == null) {
+ mLoadingState = LOADING_FAIL;
+ return;
+ } else {
+ mLoadingState = LOADING_COMPLETE;
+ }
setScreenNail(backup, backup.getWidth(), backup.getHeight());
mPhotoView.notifyImageChange(0);
} catch (Throwable t) {
@@ -199,15 +205,23 @@ public class SinglePhotoDataAdapter extends TileImageViewAdapter
return mItem.getMediaType() == MediaItem.MEDIA_TYPE_VIDEO;
}
+ @Override
public MediaItem getMediaItem(int offset) {
return offset == 0 ? mItem : null;
}
+ @Override
public int getCurrentIndex() {
return 0;
}
+ @Override
public void setCurrentPhoto(Path path, int indexHint) {
// ignore
}
+
+ @Override
+ public int getLoadingState(int offset) {
+ return mLoadingState;
+ }
}
diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java
index f96aca3e3..aa27c6f42 100644
--- a/src/com/android/gallery3d/data/LocalImage.java
+++ b/src/com/android/gallery3d/data/LocalImage.java
@@ -81,8 +81,6 @@ public class LocalImage extends LocalMediaItem {
private final GalleryApp mApplication;
public int rotation;
- public int width;
- public int height;
public LocalImage(Path path, GalleryApp application, Cursor cursor) {
super(path, nextVersionNumber());
diff --git a/src/com/android/gallery3d/data/LocalMediaItem.java b/src/com/android/gallery3d/data/LocalMediaItem.java
index 2749ebe55..7a54e8062 100644
--- a/src/com/android/gallery3d/data/LocalMediaItem.java
+++ b/src/com/android/gallery3d/data/LocalMediaItem.java
@@ -44,6 +44,8 @@ public abstract class LocalMediaItem extends MediaItem {
public long dateModifiedInSec;
public String filePath;
public int bucketId;
+ public int width;
+ public int height;
public LocalMediaItem(Path path, long version) {
super(path, version);
diff --git a/src/com/android/gallery3d/data/LocalVideo.java b/src/com/android/gallery3d/data/LocalVideo.java
index 0ba59f581..8eb6f914a 100644
--- a/src/com/android/gallery3d/data/LocalVideo.java
+++ b/src/com/android/gallery3d/data/LocalVideo.java
@@ -33,7 +33,7 @@ import com.android.gallery3d.util.UpdateHelper;
// LocalVideo represents a video in the local storage.
public class LocalVideo extends LocalMediaItem {
-
+ private static final String TAG = "LocalVideo";
static final Path ITEM_PATH = Path.fromString("/local/video/item");
// Must preserve order between these indices and the order of the terms in
@@ -49,7 +49,8 @@ public class LocalVideo extends LocalMediaItem {
private static final int INDEX_DATA = 8;
private static final int INDEX_DURATION = 9;
private static final int INDEX_BUCKET_ID = 10;
- private static final int INDEX_SIZE_ID = 11;
+ private static final int INDEX_SIZE = 11;
+ private static final int INDEX_RESOLUTION = 12;
static final String[] PROJECTION = new String[] {
VideoColumns._ID,
@@ -63,7 +64,8 @@ public class LocalVideo extends LocalMediaItem {
VideoColumns.DATA,
VideoColumns.DURATION,
VideoColumns.BUCKET_ID,
- VideoColumns.SIZE
+ VideoColumns.SIZE,
+ VideoColumns.RESOLUTION,
};
private final GalleryApp mApplication;
@@ -106,7 +108,21 @@ public class LocalVideo extends LocalMediaItem {
filePath = cursor.getString(INDEX_DATA);
durationInSec = cursor.getInt(INDEX_DURATION) / 1000;
bucketId = cursor.getInt(INDEX_BUCKET_ID);
- fileSize = cursor.getLong(INDEX_SIZE_ID);
+ fileSize = cursor.getLong(INDEX_SIZE);
+ parseResolution(cursor.getString(INDEX_RESOLUTION));
+ }
+
+ private void parseResolution(String resolution) {
+ int m = resolution.indexOf('x');
+ if (m == -1) return;
+ try {
+ int w = Integer.parseInt(resolution.substring(0, m));
+ int h = Integer.parseInt(resolution.substring(m + 1));
+ width = w;
+ height = h;
+ } catch (Throwable t) {
+ Log.w(TAG, t);
+ }
}
@Override
@@ -127,7 +143,7 @@ public class LocalVideo extends LocalMediaItem {
durationInSec = uh.update(
durationInSec, cursor.getInt(INDEX_DURATION) / 1000);
bucketId = uh.update(bucketId, cursor.getInt(INDEX_BUCKET_ID));
- fileSize = uh.update(fileSize, cursor.getLong(INDEX_SIZE_ID));
+ fileSize = uh.update(fileSize, cursor.getLong(INDEX_SIZE));
return uh.isUpdated();
}
@@ -206,11 +222,11 @@ public class LocalVideo extends LocalMediaItem {
@Override
public int getWidth() {
- return 0;
+ return width;
}
@Override
public int getHeight() {
- return 0;
+ return height;
}
}
diff --git a/src/com/android/gallery3d/ui/BitmapScreenNail.java b/src/com/android/gallery3d/ui/BitmapScreenNail.java
index 7f654058e..14d3f1919 100644
--- a/src/com/android/gallery3d/ui/BitmapScreenNail.java
+++ b/src/com/android/gallery3d/ui/BitmapScreenNail.java
@@ -20,17 +20,32 @@ import android.graphics.Bitmap;
import android.graphics.RectF;
import android.util.Log;
+import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.MediaItem;
-// This is a ScreenNail wraps a Bitmap. It also includes the rotation
-// information. The getWidth() and getHeight() methods return the width/height
-// before rotation.
+// This is a ScreenNail wraps a Bitmap. There are some extra functions:
+//
+// - If we need to draw before the bitmap is available, we draw a rectange of
+// placeholder color (gray).
+//
+// - When the the bitmap is available, and we have drawn the placeholder color
+// before, we will do a fade-in animation.
public class BitmapScreenNail implements ScreenNail {
private static final String TAG = "BitmapScreenNail";
- private final int mWidth;
- private final int mHeight;
+ private static final int PLACEHOLDER_COLOR = 0xFF222222;
+ // The duration of the fading animation in milliseconds
+ private static final int DURATION = 180;
+ // These are special values for mAnimationStartTime
+ private static final long ANIMATION_NOT_NEEDED = -1;
+ private static final long ANIMATION_NEEDED = -2;
+ private static final long ANIMATION_DONE = -3;
+
+ private int mWidth;
+ private int mHeight;
private Bitmap mBitmap;
private BitmapTexture mTexture;
+ private FadeInTexture mFadeInTexture;
+ private long mAnimationStartTime = ANIMATION_NOT_NEEDED;
public BitmapScreenNail(Bitmap bitmap) {
mWidth = bitmap.getWidth();
@@ -40,6 +55,56 @@ public class BitmapScreenNail implements ScreenNail {
// actually need it.
}
+ public BitmapScreenNail(int width, int height) {
+ if (width == 0 || height == 0) {
+ width = 640;
+ height = 480;
+ }
+ mWidth = width;
+ mHeight = height;
+ }
+
+ // Combines the two ScreenNails.
+ // Returns the used one and recycle the unused one.
+ public ScreenNail combine(ScreenNail other) {
+ if (other == null) {
+ return this;
+ }
+
+ if (!(other instanceof BitmapScreenNail)) {
+ recycle();
+ return other;
+ }
+
+ // Now both are BitmapScreenNail. Move over the information about width,
+ // height, and Bitmap, then recycle the other.
+ BitmapScreenNail newer = (BitmapScreenNail) other;
+ mWidth = newer.mWidth;
+ mHeight = newer.mHeight;
+ if (newer.mBitmap != null) {
+ if (mBitmap != null) {
+ MediaItem.getThumbPool().recycle(mBitmap);
+ }
+ mBitmap = newer.mBitmap;
+ newer.mBitmap = null;
+
+ if (mTexture != null) {
+ mTexture.recycle();
+ mTexture = null;
+ }
+ }
+
+ newer.recycle();
+ return this;
+ }
+
+ public void updatePlaceholderSize(int width, int height) {
+ if (mBitmap != null) return;
+ if (width == 0 || height == 0) return;
+ mWidth = width;
+ mHeight = height;
+ }
+
@Override
public int getWidth() {
return mWidth;
@@ -68,17 +133,60 @@ public class BitmapScreenNail implements ScreenNail {
@Override
public void draw(GLCanvas canvas, int x, int y, int width, int height) {
+ if (mBitmap == null) {
+ if (mAnimationStartTime == ANIMATION_NOT_NEEDED) {
+ mAnimationStartTime = ANIMATION_NEEDED;
+ }
+ canvas.fillRect(x, y, width, height, PLACEHOLDER_COLOR);
+ return;
+ }
+
if (mTexture == null) {
mTexture = new BitmapTexture(mBitmap);
}
- mTexture.draw(canvas, x, y, width, height);
+
+ if (mAnimationStartTime == ANIMATION_NEEDED) {
+ mAnimationStartTime = now();
+ }
+
+ if (isAnimating()) {
+ canvas.drawMixed(mTexture, PLACEHOLDER_COLOR, getRatio(), x, y,
+ width, height);
+ } else {
+ mTexture.draw(canvas, x, y, width, height);
+ }
}
@Override
public void draw(GLCanvas canvas, RectF source, RectF dest) {
+ if (mBitmap == null) {
+ canvas.fillRect(dest.left, dest.top, dest.width(), dest.height(),
+ PLACEHOLDER_COLOR);
+ return;
+ }
+
if (mTexture == null) {
mTexture = new BitmapTexture(mBitmap);
}
+
canvas.drawTexture(mTexture, source, dest);
}
+
+ public boolean isAnimating() {
+ if (mAnimationStartTime < 0) return false;
+ if (now() - mAnimationStartTime >= DURATION) {
+ mAnimationStartTime = ANIMATION_DONE;
+ return false;
+ }
+ return true;
+ }
+
+ private static long now() {
+ return AnimationTime.get();
+ }
+
+ private float getRatio() {
+ float r = (float)(now() - mAnimationStartTime) / DURATION;
+ return Utils.clamp(1.0f - r, 0.0f, 1.0f);
+ }
}
diff --git a/src/com/android/gallery3d/ui/BitmapTileProvider.java b/src/com/android/gallery3d/ui/BitmapTileProvider.java
index be05b33ab..320118e89 100644
--- a/src/com/android/gallery3d/ui/BitmapTileProvider.java
+++ b/src/com/android/gallery3d/ui/BitmapTileProvider.java
@@ -99,8 +99,4 @@ public class BitmapTileProvider implements TileImageView.Model {
mScreenNail.recycle();
}
}
-
- public boolean isFailedToLoad() {
- return false;
- }
}
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index 46d7c9362..e7ec3a948 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -76,6 +76,12 @@ public class PhotoView extends GLView {
// Returns true if the item is a Video.
public boolean isVideo(int offset);
+
+ public static final int LOADING_INIT = 0;
+ public static final int LOADING_COMPLETE = 1;
+ public static final int LOADING_FAIL = 2;
+
+ public int getLoadingState(int offset);
}
public interface Listener {
@@ -111,18 +117,10 @@ public class PhotoView extends GLView {
// There are four transitions we need to check if we need to
// lock/unlock. Marked as A to D above and in the code.
- private static final int MSG_SHOW_LOADING = 1;
private static final int MSG_CANCEL_EXTRA_SCALING = 2;
private static final int MSG_SWITCH_FOCUS = 3;
private static final int MSG_CAPTURE_ANIMATION_DONE = 4;
- private static final long DELAY_SHOW_LOADING = 250; // 250ms;
-
- private static final int LOADING_INIT = 0;
- private static final int LOADING_TIMEOUT = 1;
- private static final int LOADING_COMPLETE = 2;
- private static final int LOADING_FAIL = 3;
-
private static final int MOVE_THRESHOLD = 256;
private static final float SWIPE_THRESHOLD = 300f;
@@ -160,12 +158,8 @@ public class PhotoView extends GLView {
private EdgeView mEdgeView;
private Texture mVideoPlayIcon;
- private ProgressSpinner mLoadingSpinner;
-
private SynchronizedHandler mHandler;
- private int mLoadingState = LOADING_COMPLETE;
-
private Point mImageCenter = new Point();
private boolean mCancelExtraScalingPending;
private boolean mFilmMode = false;
@@ -195,7 +189,6 @@ public class PhotoView extends GLView {
Context context = activity.getAndroidContext();
mEdgeView = new EdgeView(context);
addComponent(mEdgeView);
- mLoadingSpinner = new ProgressSpinner(context);
mLoadingText = StringTexture.newInstance(
context.getString(R.string.loading),
DEFAULT_TEXT_SIZE, Color.WHITE);
@@ -249,17 +242,6 @@ public class PhotoView extends GLView {
@Override
public void handleMessage(Message message) {
switch (message.what) {
- case MSG_SHOW_LOADING: {
- if (mLoadingState == LOADING_INIT) {
- // We don't need the opening animation
- mPositionController.setOpenAnimationRect(null);
-
- mLoadingSpinner.startAnimation();
- mLoadingState = LOADING_TIMEOUT;
- invalidate();
- }
- break;
- }
case MSG_CANCEL_EXTRA_SCALING: {
mGestureRecognizer.cancelScale();
mPositionController.setExtraScalingRange(false);
@@ -281,28 +263,6 @@ public class PhotoView extends GLView {
}
};
- private void updateLoadingState() {
- // Possible transitions of mLoadingState:
- // INIT --> TIMEOUT, COMPLETE, FAIL
- // TIMEOUT --> COMPLETE, FAIL, INIT
- // COMPLETE --> INIT
- // FAIL --> INIT
- if (mModel.getLevelCount() != 0 || mModel.getScreenNail() != null) {
- mHandler.removeMessages(MSG_SHOW_LOADING);
- mLoadingState = LOADING_COMPLETE;
- } else if (mModel.isFailedToLoad()) {
- mHandler.removeMessages(MSG_SHOW_LOADING);
- mLoadingState = LOADING_FAIL;
- // We don't want the opening animation after loading failure
- mPositionController.setOpenAnimationRect(null);
- } else if (mLoadingState != LOADING_INIT) {
- mLoadingState = LOADING_INIT;
- mHandler.removeMessages(MSG_SHOW_LOADING);
- mHandler.sendEmptyMessageDelayed(
- MSG_SHOW_LOADING, DELAY_SHOW_LOADING);
- }
- }
-
////////////////////////////////////////////////////////////////////////////
// Data/Image change notifications
////////////////////////////////////////////////////////////////////////////
@@ -427,6 +387,7 @@ public class PhotoView extends GLView {
private boolean mIsCamera;
private boolean mIsPanorama;
private boolean mIsVideo;
+ private int mLoadingState = Model.LOADING_INIT;
private boolean mWasCameraCenter;
public void FullPicture(TileImageView tileView) {
@@ -441,9 +402,9 @@ public class PhotoView extends GLView {
mIsCamera = mModel.isCamera(0);
mIsPanorama = mModel.isPanorama(0);
mIsVideo = mModel.isVideo(0);
+ mLoadingState = mModel.getLoadingState(0);
setScreenNail(mModel.getScreenNail(0));
updateSize(false);
- updateLoadingState();
}
@Override
@@ -466,13 +427,9 @@ public class PhotoView extends GLView {
@Override
public void draw(GLCanvas canvas, Rect r) {
- boolean isCenter = mPositionController.isCenter();
-
- if (mLoadingState == LOADING_COMPLETE) {
- drawTileView(canvas, r);
- }
- renderMessage(canvas, r.centerX(), r.centerY());
+ drawTileView(canvas, r);
+ boolean isCenter = mPositionController.isCenter();
if (mIsCamera) {
boolean full = !mFilmMode && isCenter
&& mPositionController.isAtMinimalScale();
@@ -573,13 +530,18 @@ public class PhotoView extends GLView {
setTileViewPosition(cx, cy, viewW, viewH, imageScale);
PhotoView.super.render(canvas);
- // Draw the play video icon.
- if (mIsVideo) {
- canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f));
- int s = (int) (scale * Math.min(r.width(), r.height()) + 0.5f);
- drawVideoPlayIcon(canvas, s);
+ // Draw the play video icon and the message.
+ canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f));
+ int s = (int) (scale * Math.min(r.width(), r.height()) + 0.5f);
+ if (mIsVideo) drawVideoPlayIcon(canvas, s);
+ if (mLoadingState == Model.LOADING_FAIL) {
+ drawLoadingFailMessage(canvas);
}
+ // Draw a debug indicator showing which picture has focus (index ==
+ // 0).
+ //canvas.fillRect(-10, -10, 20, 20, 0x80FF00FF);
+
canvas.restore();
}
@@ -605,33 +567,6 @@ public class PhotoView extends GLView {
}
mTileView.setPosition(x, y, scale, mRotation);
}
-
- private void renderMessage(GLCanvas canvas, int x, int y) {
- // Draw the progress spinner and the text below it
- //
- // (x, y) is where we put the center of the spinner.
- // s is the size of the video play icon, and we use s to layout text
- // because we want to keep the text at the same place when the video
- // play icon is shown instead of the spinner.
- int w = getWidth();
- int h = getHeight();
- int s = Math.min(w, h) / ICON_RATIO;
-
- if (mLoadingState == LOADING_TIMEOUT) {
- StringTexture m = mLoadingText;
- ProgressSpinner p = mLoadingSpinner;
- p.draw(canvas, x - p.getWidth() / 2, y - p.getHeight() / 2);
- m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5);
- invalidate(); // we need to keep the spinner rotating
- } else if (mLoadingState == LOADING_FAIL) {
- StringTexture m = mNoThumbnailText;
- m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5);
- }
-
- // Draw a debug indicator showing which picture has focus (index ==
- // 0).
- // canvas.fillRect(x - 10, y - 10, 20, 20, 0x80FF00FF);
- }
}
private class ScreenNailPicture implements Picture {
@@ -642,6 +577,7 @@ public class PhotoView extends GLView {
private boolean mIsCamera;
private boolean mIsPanorama;
private boolean mIsVideo;
+ private int mLoadingState = Model.LOADING_INIT;
public ScreenNailPicture(int index) {
mIndex = index;
@@ -652,17 +588,17 @@ public class PhotoView extends GLView {
mIsCamera = mModel.isCamera(mIndex);
mIsPanorama = mModel.isPanorama(mIndex);
mIsVideo = mModel.isVideo(mIndex);
+ mLoadingState = mModel.getLoadingState(mIndex);
setScreenNail(mModel.getScreenNail(mIndex));
}
@Override
public void draw(GLCanvas canvas, Rect r) {
if (mScreenNail == null) {
- // Draw a placeholder rectange if there will be a picture in
- // this position.
+ // Draw a placeholder rectange if there should be a picture in
+ // this position (but somehow there isn't).
if (mIndex >= mPrevBound && mIndex <= mNextBound) {
- canvas.fillRect(r.left, r.top, r.width(), r.height(),
- PLACEHOLDER_COLOR);
+ drawPlaceHolder(canvas, r);
}
return;
}
@@ -703,10 +639,22 @@ public class PhotoView extends GLView {
int drawW = getRotated(mRotation, r.width(), r.height());
int drawH = getRotated(mRotation, r.height(), r.width());
mScreenNail.draw(canvas, -drawW / 2, -drawH / 2, drawW, drawH);
- if (mIsVideo) drawVideoPlayIcon(canvas, Math.min(drawW, drawH));
+ if (isScreenNailAnimating()) {
+ invalidate();
+ }
+ int s = Math.min(drawW, drawH);
+ if (mIsVideo) drawVideoPlayIcon(canvas, s);
+ if (mLoadingState == Model.LOADING_FAIL) {
+ drawLoadingFailMessage(canvas);
+ }
canvas.restore();
}
+ private boolean isScreenNailAnimating() {
+ return (mScreenNail instanceof BitmapScreenNail)
+ && ((BitmapScreenNail) mScreenNail).isAnimating();
+ }
+
@Override
public void setScreenNail(ScreenNail s) {
if (mScreenNail == s) return;
@@ -750,6 +698,11 @@ public class PhotoView extends GLView {
}
}
+ // Draw a gray placeholder in the specified rectangle.
+ private void drawPlaceHolder(GLCanvas canvas, Rect r) {
+ canvas.fillRect(r.left, r.top, r.width(), r.height(), PLACEHOLDER_COLOR);
+ }
+
// Draw the video play icon (in the place where the spinner was)
private void drawVideoPlayIcon(GLCanvas canvas, int side) {
int s = side / ICON_RATIO;
@@ -757,6 +710,12 @@ public class PhotoView extends GLView {
mVideoPlayIcon.draw(canvas, -s / 2, -s / 2, s, s);
}
+ // Draw the "no thumbnail" message
+ private void drawLoadingFailMessage(GLCanvas canvas) {
+ StringTexture m = mNoThumbnailText;
+ m.draw(canvas, -m.getWidth() / 2, -m.getHeight() / 2);
+ }
+
private static int getRotated(int degree, int original, int theother) {
return (degree % 180 == 0) ? original : theother;
}
@@ -1239,7 +1198,7 @@ public class PhotoView extends GLView {
}
mHolding |= HOLD_CAPTURE_ANIMATION;
Message m = mHandler.obtainMessage(MSG_CAPTURE_ANIMATION_DONE, offset, 0);
- mHandler.sendMessageDelayed(m, 800);
+ mHandler.sendMessageDelayed(m, PositionController.CAPTURE_ANIMATION_TIME);
return true;
}
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index 9797ce903..226826da7 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -34,6 +34,8 @@ class PositionController {
public static final int IMAGE_AT_TOP_EDGE = 4;
public static final int IMAGE_AT_BOTTOM_EDGE = 8;
+ public static final int CAPTURE_ANIMATION_TIME = 600;
+
// Special values for animation time.
private static final long NO_ANIMATION = -1;
private static final long LAST_ANIMATION = -2;
@@ -56,7 +58,7 @@ class PositionController {
300, // ANIM_KIND_ZOOM
400, // ANIM_KIND_OPENING
0, // ANIM_KIND_FLING (the duration is calculated dynamically)
- 800, // ANIM_KIND_CAPTURE
+ CAPTURE_ANIMATION_TIME, // ANIM_KIND_CAPTURE
};
// We try to scale up the image to fill the screen. But in order not to
diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java
index 7ee203dd0..fb0e333a1 100644
--- a/src/com/android/gallery3d/ui/TileImageView.java
+++ b/src/com/android/gallery3d/ui/TileImageView.java
@@ -139,7 +139,6 @@ public class TileImageView extends GLView {
// The method would be called in another thread.
public Bitmap getTile(int level, int x, int y, int tileSize,
int borderSize);
- public boolean isFailedToLoad();
}
public TileImageView(GalleryContext context) {
@@ -407,7 +406,7 @@ public class TileImageView extends GLView {
}
}
try {
- if (level != mLevelCount) {
+ if (level != mLevelCount && !isScreenNailAnimating()) {
if (mScreenNail != null) {
mScreenNail.noDraw();
}
@@ -427,6 +426,9 @@ public class TileImageView extends GLView {
mScreenNail.draw(canvas, mOffsetX, mOffsetY,
Math.round(mImageWidth * mScale),
Math.round(mImageHeight * mScale));
+ if (isScreenNailAnimating()) {
+ invalidate();
+ }
}
} finally {
if (flags != 0) canvas.restore();
@@ -439,6 +441,11 @@ public class TileImageView extends GLView {
}
}
+ private boolean isScreenNailAnimating() {
+ return (mScreenNail instanceof BitmapScreenNail)
+ && ((BitmapScreenNail) mScreenNail).isAnimating();
+ }
+
private void uploadBackgroundTiles(GLCanvas canvas) {
mBackgroundTileUploaded = true;
int n = mActiveTiles.size();
diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
index 0400de6f3..5c9281202 100644
--- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java
+++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
@@ -33,7 +33,6 @@ public class TileImageViewAdapter implements TileImageView.Model {
protected int mImageWidth;
protected int mImageHeight;
protected int mLevelCount;
- protected boolean mFailedToLoad;
public TileImageViewAdapter() {
}
@@ -54,7 +53,6 @@ public class TileImageViewAdapter implements TileImageView.Model {
mImageHeight = 0;
mLevelCount = 0;
mRegionDecoder = null;
- mFailedToLoad = false;
}
public synchronized void setScreenNail(Bitmap bitmap, int width, int height) {
@@ -64,7 +62,6 @@ public class TileImageViewAdapter implements TileImageView.Model {
mImageHeight = height;
mRegionDecoder = null;
mLevelCount = 0;
- mFailedToLoad = false;
}
public synchronized void setScreenNail(
@@ -75,7 +72,6 @@ public class TileImageViewAdapter implements TileImageView.Model {
mImageHeight = height;
mRegionDecoder = null;
mLevelCount = 0;
- mFailedToLoad = false;
}
private void updateScreenNail(ScreenNail screenNail, boolean own) {
@@ -91,7 +87,6 @@ public class TileImageViewAdapter implements TileImageView.Model {
mImageWidth = decoder.getWidth();
mImageHeight = decoder.getHeight();
mLevelCount = calculateLevelCount();
- mFailedToLoad = false;
}
private int calculateLevelCount() {
@@ -184,13 +179,4 @@ public class TileImageViewAdapter implements TileImageView.Model {
public int getLevelCount() {
return mLevelCount;
}
-
- public void setFailedToLoad() {
- mFailedToLoad = true;
- }
-
- @Override
- public boolean isFailedToLoad() {
- return mFailedToLoad;
- }
}