Improve scale gesture snapback.

If the picture is zoomed-in, we want to keep the focus point stay in
the same position on screen, so we need to adjust the target mCurrentX
value.

Bug: 6563619
Change-Id: I87b23da99aced1490187e550bd43fad733122577
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index b673722..65334d5 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -490,7 +490,7 @@
         //
         // (focusX' - currentX') / scale' = (focusX - currentX) / scale
         //
-        s *= getTargetScale(b);
+        s = b.clampScale(s * getTargetScale(b));
         int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
         int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
         startAnimation(x, y, s, ANIM_KIND_SCALE);
@@ -531,8 +531,20 @@
         Box b = mBoxes.get(0);
         Platform p = mPlatform;
 
-        int x = getTargetX(p) + (int) (dx + 0.5f);
-        int y = getTargetY(b) + (int) (dy + 0.5f);
+        // Only allow scrolling when we are not currently in an animation or we
+        // are in some animation with can be interrupted.
+        if (b.mAnimationStartTime != NO_ANIMATION) {
+            switch (b.mAnimationKind) {
+                case ANIM_KIND_SCROLL:
+                case ANIM_KIND_FLING:
+                    break;
+                default:
+                    return;
+            }
+        }
+
+        int x = p.mCurrentX + (int) (dx + 0.5f);
+        int y = b.mCurrentY + (int) (dy + 0.5f);
 
         if (mFilmMode) {
             scrollToFilm(x, y);
@@ -1193,21 +1205,8 @@
     }
 
     private float getTargetScale(Box b) {
-        return useCurrentValueAsTarget(b) ? b.mCurrentScale : b.mToScale;
-    }
-
-    private int getTargetX(Platform p) {
-        return useCurrentValueAsTarget(p) ? p.mCurrentX : p.mToX;
-    }
-
-    private int getTargetY(Box b) {
-        return useCurrentValueAsTarget(b) ? b.mCurrentY : b.mToY;
-    }
-
-    private boolean useCurrentValueAsTarget(Animatable a) {
-        return a.mAnimationStartTime == NO_ANIMATION ||
-                a.mAnimationKind == ANIM_KIND_SNAPBACK ||
-                a.mAnimationKind == ANIM_KIND_FLING;
+        return b.mAnimationStartTime == NO_ANIMATION
+                ? b.mCurrentScale : b.mToScale;
     }
 
     ////////////////////////////////////////////////////////////////////////////
@@ -1295,6 +1294,7 @@
             if (mAnimationStartTime != NO_ANIMATION) return false;
             if (mAnimationKind == ANIM_KIND_SCROLL
                     && mListener.isHolding()) return false;
+            if (mInScale) return false;
 
             Box b = mBoxes.get(0);
             float scaleMin = mExtraScalingRange ?
@@ -1310,6 +1310,19 @@
                 if (!mHasPrev) x = Math.min(x, defaultX);
             } else {
                 calculateStableBound(scale, HORIZONTAL_SLACK);
+                // If the picture is zoomed-in, we want to keep the focus point
+                // stay in the same position on screen, so we need to adjust
+                // target mCurrentX (which is the center of the focused
+                // box). The position of the focus point on screen (relative the
+                // the center of the view) is:
+                //
+                // mCurrentX + scale * mFocusX = mCurrentX' + scale' * mFocusX
+                // => mCurrentX' = mCurrentX + (scale - scale') * mFocusX
+                //
+                if (!viewWiderThanScaledImage(scale)) {
+                    float scaleDiff = b.mCurrentScale - scale;
+                    x += (int) (mFocusX * scaleDiff + 0.5f);
+                }
                 x = Utils.clamp(x, mBoundLeft, mBoundRight);
             }
             if (mCurrentX != x || mCurrentY != y) {
@@ -1459,7 +1472,7 @@
                     && mListener.isHolding()) return false;
             if (mInScale && this == mBoxes.get(0)) return false;
 
-            int y;
+            int y = mCurrentY;
             float scale;
 
             if (this == mBoxes.get(0)) {
@@ -1472,7 +1485,14 @@
                     y = 0;
                 } else {
                     calculateStableBound(scale, HORIZONTAL_SLACK);
-                    y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
+                    // If the picture is zoomed-in, we want to keep the focus
+                    // point stay in the same position on screen. See the
+                    // comment in Platform.startSnapback for details.
+                    if (!viewTallerThanScaledImage(scale)) {
+                        float scaleDiff = mCurrentScale - scale;
+                        y += (int) (mFocusY * scaleDiff + 0.5f);
+                    }
+                    y = Utils.clamp(y, mBoundTop, mBoundBottom);
                 }
             } else {
                 y = 0;
@@ -1486,9 +1506,7 @@
         }
 
         private boolean doAnimation(int targetY, float targetScale, int kind) {
-            targetScale = Utils.clamp(targetScale,
-                    SCALE_MIN_EXTRA * mScaleMin,
-                    SCALE_MAX_EXTRA * mScaleMax);
+            targetScale = clampScale(targetScale);
 
             // If the scaled height is smaller than the view height, force it to be
             // in the center.  (We do this for height only, not width, because the
@@ -1514,6 +1532,13 @@
             return true;
         }
 
+        // Clamps the input scale to the range that doAnimation() can reach.
+        public float clampScale(float s) {
+            return Utils.clamp(s,
+                    SCALE_MIN_EXTRA * mScaleMin,
+                    SCALE_MAX_EXTRA * mScaleMax);
+        }
+
         @Override
         protected boolean interpolate(float progress) {
             if (mAnimationKind == ANIM_KIND_FLING) {