In filmstrip, show placeholders for pictures not loaded yet.

Change-Id: I037f1f054da4a3800045d5b89724341ac22272a5
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java
index ec59997..096e781 100644
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java
@@ -210,20 +210,14 @@
         for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
             mChanges[i + SCREEN_NAIL_MAX] = getVersion(mCurrentIndex + i);
         }
-        mPhotoView.notifyDataChange(mChanges, mCurrentIndex > 0,
-                mCurrentIndex < mSize - 1);
+        mPhotoView.notifyDataChange(mChanges, -mCurrentIndex,
+                mSize - 1 - mCurrentIndex);
     }
 
     public void setDataListener(DataListener listener) {
         mDataListener = listener;
     }
 
-    @Override
-    public void setNeedFullImage(boolean enabled) {
-        mNeedFullImage = enabled;
-        mMainHandler.sendEmptyMessage(MSG_UPDATE_IMAGE_REQUESTS);
-    }
-
     private void updateScreenNail(long version, Future<ScreenNail> future) {
         ImageEntry entry = mImageCache.get(version);
         ScreenNail screenNail = future.get();
@@ -307,8 +301,14 @@
         return entry == null ? null : entry.screenNail;
     }
 
-    public ScreenNail getScreenNail(int offset) {
-        return getImage(mCurrentIndex + offset);
+    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) {
@@ -329,14 +329,45 @@
         fireDataChange();
     }
 
+    @Override
     public void next() {
         updateCurrentIndex(mCurrentIndex + 1);
     }
 
+    @Override
     public void previous() {
         updateCurrentIndex(mCurrentIndex - 1);
     }
 
+    @Override
+    public ScreenNail getScreenNail(int offset) {
+        return getImage(mCurrentIndex + offset);
+    }
+
+    @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);
+    }
+
     public ScreenNail getScreenNail() {
         return mTileProvider.getScreenNail();
     }
@@ -349,11 +380,6 @@
         return mTileProvider.getImageWidth();
     }
 
-    public int getImageRotation() {
-        ImageEntry entry = mImageCache.get(getVersion(mCurrentIndex));
-        return entry == null ? 0 : entry.rotation;
-    }
-
     public int getLevelCount() {
         return mTileProvider.getLevelCount();
     }
@@ -505,7 +531,7 @@
                 bitmap = BitmapUtils.rotateBitmap(bitmap,
                     mItem.getRotation() - mItem.getFullImageRotation(), true);
             }
-            return new BitmapScreenNail(bitmap, mItem.getFullImageRotation());
+            return new BitmapScreenNail(bitmap);
         }
     }
 
diff --git a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
index 59959cf..47f6acb 100644
--- a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
@@ -109,15 +109,6 @@
         return false;
     }
 
-    public int getImageRotation() {
-        return mItem.getRotation();
-    }
-
-    @Override
-    public void setNeedFullImage(boolean enabled) {
-        // currently not necessary.
-    }
-
     private void onDecodeLargeComplete(ImageBundle bundle) {
         try {
             setScreenNail(bundle.backupImage,
@@ -162,18 +153,43 @@
         }
     }
 
-    public ScreenNail getScreenNail(int offset) {
-        return (offset == 0) ? getScreenNail() : null;
-    }
-
+    @Override
     public void next() {
         throw new UnsupportedOperationException();
     }
 
+    @Override
     public void previous() {
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public void getImageSize(int offset, PhotoView.Size size) {
+        if (offset == 0) {
+            size.width = mItem.getWidth();
+            size.height = mItem.getHeight();
+        } else {
+            size.width = 0;
+            size.height = 0;
+        }
+    }
+
+    @Override
+    public int getImageRotation(int offset) {
+        return (offset == 0) ? mItem.getFullImageRotation() : 0;
+    }
+
+    @Override
+    public ScreenNail getScreenNail(int offset) {
+        return (offset == 0) ? getScreenNail() : null;
+    }
+
+    @Override
+    public void setNeedFullImage(boolean enabled) {
+        // currently not necessary.
+    }
+
+
     public MediaItem getCurrentMediaItem() {
         return mItem;
     }
diff --git a/src/com/android/gallery3d/ui/BitmapScreenNail.java b/src/com/android/gallery3d/ui/BitmapScreenNail.java
index 064e1af..7f65405 100644
--- a/src/com/android/gallery3d/ui/BitmapScreenNail.java
+++ b/src/com/android/gallery3d/ui/BitmapScreenNail.java
@@ -29,14 +29,12 @@
     private static final String TAG = "BitmapScreenNail";
     private final int mWidth;
     private final int mHeight;
-    private final int mRotation;
     private Bitmap mBitmap;
     private BitmapTexture mTexture;
 
-    public BitmapScreenNail(Bitmap bitmap, int rotation) {
+    public BitmapScreenNail(Bitmap bitmap) {
         mWidth = bitmap.getWidth();
         mHeight = bitmap.getHeight();
-        mRotation = rotation;
         mBitmap = bitmap;
         // We create mTexture lazily, so we don't incur the cost if we don't
         // actually need it.
@@ -53,11 +51,6 @@
     }
 
     @Override
-    public int getRotation() {
-        return mRotation;
-    }
-
-    @Override
     public void noDraw() {
     }
 
diff --git a/src/com/android/gallery3d/ui/BitmapTileProvider.java b/src/com/android/gallery3d/ui/BitmapTileProvider.java
index 99b64d4..be05b33 100644
--- a/src/com/android/gallery3d/ui/BitmapTileProvider.java
+++ b/src/com/android/gallery3d/ui/BitmapTileProvider.java
@@ -44,7 +44,7 @@
             list.add(bitmap);
         }
 
-        mScreenNail = new BitmapScreenNail(list.remove(list.size() - 1), 0);
+        mScreenNail = new BitmapScreenNail(list.remove(list.size() - 1));
         mMipmaps = list.toArray(new Bitmap[list.size()]);
         mConfig = Config.ARGB_8888;
     }
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index 306da81..109a5d9 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -38,20 +38,34 @@
 public class PhotoView extends GLView {
     @SuppressWarnings("unused")
     private static final String TAG = "PhotoView";
+    private static final int PLACEHOLDER_COLOR = 0xFF222222;
 
     public static final int INVALID_SIZE = -1;
     public static final long INVALID_DATA_VERSION =
             MediaObject.INVALID_DATA_VERSION;
 
-    public static interface Model extends TileImageView.Model {
+    public static class Size {
+        public int width;
+        public int height;
+    }
+
+    public interface Model extends TileImageView.Model {
         public void next();
         public void previous();
-        public int getImageRotation();
+
+        // Returns the size for the specified picture. If the size information is
+        // not avaiable, width = height = 0.
+        public void getImageSize(int offset, Size size);
+
+        // Returns the rotation for the specified picture.
+        public int getImageRotation(int offset);
 
         // This amends the getScreenNail() method of TileImageView.Model to get
         // ScreenNail at previous (negative offset) or next (positive offset)
         // positions. Returns null if the specified ScreenNail is unavailable.
         public ScreenNail getScreenNail(int offset);
+
+        // Set this to true if we need the model to provide full images.
         public void setNeedFullImage(boolean enabled);
     }
 
@@ -119,6 +133,13 @@
     private boolean mCancelExtraScalingPending;
     private boolean mFilmMode = false;
 
+    // [mPrevBound, mNextBound] is the range of index for all pictures in the
+    // model, if we assume the index of current focused picture is 0.  So if
+    // there are some previous pictures, mPrevBound < 0, and if there are some
+    // next pictures, mNextBound > 0.
+    private int mPrevBound;
+    private int mNextBound;
+
     public PhotoView(GalleryActivity activity) {
         mTileView = new TileImageView(activity);
         addComponent(mTileView);
@@ -232,8 +253,10 @@
     //  Data/Image change notifications
     ////////////////////////////////////////////////////////////////////////////
 
-    public void notifyDataChange(long[] versions, boolean hasPrev,
-            boolean hasNext) {
+    public void notifyDataChange(long[] versions, int prevBound, int nextBound) {
+        mPrevBound = prevBound;
+        mNextBound = nextBound;
+
         // Check if the data version actually changed.
         boolean changed = false;
         int N = 2 * SCREEN_NAIL_MAX + 1;
@@ -270,7 +293,7 @@
         }
 
         // Move the boxes
-        mPositionController.moveBox(mFromIndex, hasPrev, hasNext);
+        mPositionController.moveBox(mFromIndex, mPrevBound < 0, mNextBound > 0);
 
         // Update the ScreenNails.
         for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
@@ -312,18 +335,14 @@
             mTileView.notifyModelInvalidated();
             if (CARD_EFFECT) mTileView.setAlpha(1.0f);
 
-            if (mModel == null) {
-                mRotation = 0;
-                mPositionController.setImageSize(0, 0, 0);
-            } else {
-                mRotation = mModel.getImageRotation();
-                int w = mTileView.mImageWidth;
-                int h = mTileView.mImageHeight;
-                mPositionController.setImageSize(0,
-                        getRotated(mRotation, w, h),
-                        getRotated(mRotation, h, w));
-            }
-            setScreenNail(mModel == null ? null : mModel.getScreenNail(0));
+            mRotation = mModel.getImageRotation(0);
+            int w = mTileView.mImageWidth;
+            int h = mTileView.mImageHeight;
+            mPositionController.setImageSize(0,
+                    getRotated(mRotation, w, h),
+                    getRotated(mRotation, h, w));
+
+            setScreenNail(mModel.getScreenNail(0));
             updateLoadingState();
         }
 
@@ -447,6 +466,7 @@
         private boolean mEnabled;
         private int mRotation;
         private ScreenNail mScreenNail;
+        private Size mSize = new Size();
 
         public ScreenNailPicture(int index) {
             mIndex = index;
@@ -454,12 +474,18 @@
 
         @Override
         public void reload() {
-            setScreenNail(mModel == null ? null : mModel.getScreenNail(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.
+                if (mIndex >= mPrevBound && mIndex <= mNextBound) {
+                    canvas.fillRect(r.left, r.top, r.width(), r.height(),
+                            PLACEHOLDER_COLOR);
+                }
                 return;
             }
             if (r.left >= getWidth() || r.right <= 0 ||
@@ -501,12 +527,21 @@
             mEnabled = (s != null);
             if (mScreenNail == s) return;
             mScreenNail = s;
+            mRotation = mModel.getImageRotation(mIndex);
+
+            int w = 0, h = 0;
             if (mScreenNail != null) {
-                mRotation = mScreenNail.getRotation();
+                w = s.getWidth();
+                h = s.getHeight();
+            } else if (mModel != null) {
+                // If we don't have ScreenNail available, we can still try to
+                // get the size information of it.
+                mModel.getImageSize(mIndex, mSize);
+                w = mSize.width;
+                h = mSize.height;
             }
-            if (mScreenNail != null) {
-                int w = s.getWidth();
-                int h = s.getHeight();
+
+            if (w != 0 && h != 0)  {
                 mPositionController.setImageSize(mIndex,
                         getRotated(mRotation, w, h),
                         getRotated(mRotation, h, w));
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index f087c6b..46f0121 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -533,7 +533,10 @@
         mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
                 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
         int targetX = mFilmScroller.getFinalX();
-        ANIM_TIME[ANIM_KIND_FLING] = mFilmScroller.getDuration();
+        // This value doesn't matter because we use mFilmScroller.isFinished()
+        // to decide when to stop. We set this to 0 so it's faster for
+        // Animatable.advanceAnimation() to calculate the progress (always 1).
+        ANIM_TIME[ANIM_KIND_FLING] = 0;
         startAnimation(targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING);
         return true;
     }
@@ -1123,8 +1126,8 @@
             float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
             int x = mCurrentX;
             if (mFilmMode) {
-                if (mHasNext) x = Math.max(x, mViewW / 2);
-                if (mHasPrev) x = Math.min(x, mViewW / 2);
+                if (!mHasNext) x = Math.max(x, mViewW / 2);
+                if (!mHasPrev) x = Math.min(x, mViewW / 2);
             } else {
                 calculateStableBound(scale, HORIZONTAL_SLACK);
                 x = Utils.clamp(x, mBoundLeft, mBoundRight);
diff --git a/src/com/android/gallery3d/ui/ScreenNail.java b/src/com/android/gallery3d/ui/ScreenNail.java
index 25adc77..0a16ab8 100644
--- a/src/com/android/gallery3d/ui/ScreenNail.java
+++ b/src/com/android/gallery3d/ui/ScreenNail.java
@@ -20,7 +20,6 @@
 public interface ScreenNail {
     public int getWidth();
     public int getHeight();
-    public int getRotation();
     public void draw(GLCanvas canvas, int x, int y, int width, int height);
 
     // We do not need to draw this ScreenNail in this frame.
diff --git a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
index f8442d0..555def8 100644
--- a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
+++ b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
@@ -77,11 +77,6 @@
     }
 
     @Override
-    public int getRotation() {
-        return 0;
-    }
-
-    @Override
     public void draw(GLCanvas canvas, int x, int y, int width, int height) {
         synchronized (this) {
             if (!mHasTexture) return;
diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
index 8061657..f4c6e65 100644
--- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java
+++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
@@ -41,7 +41,7 @@
     public TileImageViewAdapter(
             Bitmap bitmap, BitmapRegionDecoder regionDecoder) {
         Utils.checkNotNull(bitmap);
-        updateScreenNail(new BitmapScreenNail(bitmap, 0), true);
+        updateScreenNail(new BitmapScreenNail(bitmap), true);
         mRegionDecoder = regionDecoder;
         mImageWidth = regionDecoder.getWidth();
         mImageHeight = regionDecoder.getHeight();
@@ -59,7 +59,7 @@
 
     public synchronized void setScreenNail(Bitmap bitmap, int width, int height) {
         Utils.checkNotNull(bitmap);
-        updateScreenNail(new BitmapScreenNail(bitmap, 0), true);
+        updateScreenNail(new BitmapScreenNail(bitmap), true);
         mImageWidth = width;
         mImageHeight = height;
         mRegionDecoder = null;