Patch 2 for master.

Change-Id: I8b7c9fd326c4f247a1f2129b1d64388a223d79e0
diff --git a/src/com/android/gallery3d/ui/GestureRecognizer.java b/src/com/android/gallery3d/ui/GestureRecognizer.java
index 492f993..4a17d43 100644
--- a/src/com/android/gallery3d/ui/GestureRecognizer.java
+++ b/src/com/android/gallery3d/ui/GestureRecognizer.java
@@ -17,6 +17,7 @@
 package com.android.gallery3d.ui;
 
 import android.content.Context;
+import android.os.SystemClock;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
@@ -62,6 +63,14 @@
         return mDownUpDetector.isDown();
     }
 
+    public void cancelScale() {
+        long now = SystemClock.uptimeMillis();
+        MotionEvent cancelEvent = MotionEvent.obtain(
+                now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+        mScaleDetector.onTouchEvent(cancelEvent);
+        cancelEvent.recycle();
+    }
+
     private class MyGestureListener
                 extends GestureDetector.SimpleOnGestureListener {
         @Override
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index c2c750b..252f348 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -38,6 +38,7 @@
 
     private static final int MSG_TRANSITION_COMPLETE = 1;
     private static final int MSG_SHOW_LOADING = 2;
+    private static final int MSG_CANCEL_EXTRA_SCALING = 3;
 
     private static final long DELAY_SHOW_LOADING = 250; // 250ms;
 
@@ -103,6 +104,7 @@
 
     private Rect mOpenAnimationRect;
     private Point mImageCenter = new Point();
+    private boolean mCancelExtraScalingPending;
 
     public PhotoView(GalleryActivity activity) {
         mTileView = new TileImageView(activity);
@@ -137,6 +139,12 @@
                         }
                         break;
                     }
+                    case MSG_CANCEL_EXTRA_SCALING: {
+                        mGestureRecognizer.cancelScale();
+                        mPositionController.setExtraScalingRange(false);
+                        mCancelExtraScalingPending = false;
+                        break;
+                    }
                     default: throw new AssertionError(message.what);
                 }
             }
@@ -570,7 +578,22 @@
         public boolean onScale(float focusX, float focusY, float scale) {
             if (Float.isNaN(scale) || Float.isInfinite(scale)
                     || mTransitionMode != TRANS_NONE) return true;
-            mPositionController.scaleBy(scale, focusX, focusY);
+            boolean outOfRange = mPositionController.scaleBy(
+                    scale, focusX, focusY);
+            if (outOfRange) {
+                if (!mCancelExtraScalingPending) {
+                    mHandler.sendEmptyMessageDelayed(
+                            MSG_CANCEL_EXTRA_SCALING, 700);
+                    mPositionController.setExtraScalingRange(true);
+                    mCancelExtraScalingPending = true;
+                }
+            } else {
+                if (mCancelExtraScalingPending) {
+                    mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING);
+                    mPositionController.setExtraScalingRange(false);
+                    mCancelExtraScalingPending = false;
+                }
+            }
             return true;
         }
 
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index 09fbc17..625505f 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -59,6 +59,9 @@
     private static final float SCALE_LIMIT = 4;
     private static final int sHorizontalSlack = GalleryUtils.dpToPixel(12);
 
+    private static final float SCALE_MIN_EXTRA = 0.6f;
+    private static final float SCALE_MAX_EXTRA = 1.4f;
+
     private PhotoView mViewer;
     private EdgeView mEdgeView;
     private int mImageW, mImageH;
@@ -78,6 +81,7 @@
 
     // The minimum and maximum scale we allow.
     private float mScaleMin, mScaleMax = SCALE_LIMIT;
+    private boolean mExtraScalingRange = false;
 
     // This is used by the fling animation
     private FlingScroller mScroller;
@@ -269,7 +273,8 @@
                 (focusY - mViewH / 2f) / mCurrentScale);
     }
 
-    public void scaleBy(float s, float focusX, float focusY) {
+    // Returns true if the result scale is outside the stable range.
+    public boolean scaleBy(float s, float focusX, float focusY) {
 
         // We want to keep the focus point (on the bitmap) the same as when
         // we begin the scale guesture, that is,
@@ -281,6 +286,7 @@
         int y = Math.round(mFocusBitmapY - (focusY - mViewH / 2f) / s);
 
         startAnimation(x, y, s, ANIM_KIND_SCALE);
+        return (s < mScaleMin || s > mScaleMax);
     }
 
     public void endScale() {
@@ -288,6 +294,13 @@
         startSnapbackIfNeeded();
     }
 
+    public void setExtraScalingRange(boolean enabled) {
+        mExtraScalingRange = enabled;
+        if (!enabled) {
+            startSnapbackIfNeeded();
+        }
+    }
+
     public float getCurrentScale() {
         return mCurrentScale;
     }
@@ -419,7 +432,8 @@
 
         mToX = targetX;
         mToY = targetY;
-        mToScale = Utils.clamp(scale, 0.6f * mScaleMin, 1.4f * mScaleMax);
+        mToScale = Utils.clamp(scale, SCALE_MIN_EXTRA * mScaleMin,
+                SCALE_MAX_EXTRA * mScaleMax);
 
         // If the scaled height is smaller than the view height,
         // force it to be in the center.
@@ -561,9 +575,14 @@
         boolean needAnimation = false;
         float scale = mCurrentScale;
 
-        if (mCurrentScale < mScaleMin || mCurrentScale > mScaleMax) {
+        float scaleMin = mExtraScalingRange ?
+                mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
+        float scaleMax = mExtraScalingRange ?
+                mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
+
+        if (mCurrentScale < scaleMin || mCurrentScale > scaleMax) {
             needAnimation = true;
-            scale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax);
+            scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
         }
 
         calculateStableBound(scale, sHorizontalSlack);