Merge "Recognizes the stylus scale gesture in ScaleGestureDetector"
diff --git a/api/current.txt b/api/current.txt
index ce6bbfa..0c55e56 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -34289,8 +34289,10 @@
     method public long getTimeDelta();
     method public boolean isInProgress();
     method public boolean isQuickScaleEnabled();
+    method public boolean isSecondaryButtonScaleEnabled();
     method public boolean onTouchEvent(android.view.MotionEvent);
     method public void setQuickScaleEnabled(boolean);
+    method public void setSecondaryButtonScaleEnabled(boolean);
   }
 
   public static abstract interface ScaleGestureDetector.OnScaleGestureListener {
diff --git a/api/system-current.txt b/api/system-current.txt
index 6053603..16c80ef 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -36831,8 +36831,10 @@
     method public long getTimeDelta();
     method public boolean isInProgress();
     method public boolean isQuickScaleEnabled();
+    method public boolean isSecondaryButtonScaleEnabled();
     method public boolean onTouchEvent(android.view.MotionEvent);
     method public void setQuickScaleEnabled(boolean);
+    method public void setSecondaryButtonScaleEnabled(boolean);
   }
 
   public static abstract interface ScaleGestureDetector.OnScaleGestureListener {
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 6508cca..5cf2c5c 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -130,6 +130,7 @@
     private float mFocusY;
 
     private boolean mQuickScaleEnabled;
+    private boolean mButtonScaleEnabled;
 
     private float mCurrSpan;
     private float mPrevSpan;
@@ -151,14 +152,17 @@
     private int mTouchHistoryDirection;
     private long mTouchHistoryLastAcceptedTime;
     private int mTouchMinMajor;
-    private MotionEvent mDoubleTapEvent;
-    private int mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
     private final Handler mHandler;
 
+    private float mAnchoredScaleStartX;
+    private float mAnchoredScaleStartY;
+    private int mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+
     private static final long TOUCH_STABILIZE_TIME = 128; // ms
-    private static final int DOUBLE_TAP_MODE_NONE = 0;
-    private static final int DOUBLE_TAP_MODE_IN_PROGRESS = 1;
     private static final float SCALE_FACTOR = .5f;
+    private static final int ANCHORED_SCALE_MODE_NONE = 0;
+    private static final int ANCHORED_SCALE_MODE_DOUBLE_TAP = 1;
+    private static final int ANCHORED_SCALE_MODE_BUTTON = 2;
 
 
     /**
@@ -310,8 +314,17 @@
             mGestureDetector.onTouchEvent(event);
         }
 
+        final int count = event.getPointerCount();
+        final int toolType = event.getToolType(0);
+        final boolean isButtonTool = toolType == MotionEvent.TOOL_TYPE_STYLUS
+                || toolType == MotionEvent.TOOL_TYPE_MOUSE;
+        final boolean isAnchoredScaleButtonDown = isButtonTool && (count == 1)
+                && (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0;
+
+        final boolean anchoredScaleCancelled =
+                mAnchoredScaleMode == ANCHORED_SCALE_MODE_BUTTON && !isAnchoredScaleButtonDown;
         final boolean streamComplete = action == MotionEvent.ACTION_UP ||
-                action == MotionEvent.ACTION_CANCEL;
+                action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
 
         if (action == MotionEvent.ACTION_DOWN || streamComplete) {
             // Reset any scale in progress with the listener.
@@ -321,11 +334,11 @@
                 mListener.onScaleEnd(this);
                 mInProgress = false;
                 mInitialSpan = 0;
-                mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
-            } else if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS && streamComplete) {
+                mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+            } else if (inAnchoredScaleMode() && streamComplete) {
                 mInProgress = false;
                 mInitialSpan = 0;
-                mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
+                mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
             }
 
             if (streamComplete) {
@@ -334,25 +347,32 @@
             }
         }
 
+        if (!mInProgress && mButtonScaleEnabled && !inAnchoredScaleMode()
+                && !streamComplete && isAnchoredScaleButtonDown) {
+            // Start of a button scale gesture
+            mAnchoredScaleStartX = event.getX();
+            mAnchoredScaleStartY = event.getY();
+            mAnchoredScaleMode = ANCHORED_SCALE_MODE_BUTTON;
+            mInitialSpan = 0;
+        }
+
         final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
                 action == MotionEvent.ACTION_POINTER_UP ||
-                action == MotionEvent.ACTION_POINTER_DOWN;
-
+                action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;
 
         final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
         final int skipIndex = pointerUp ? event.getActionIndex() : -1;
 
         // Determine focal point
         float sumX = 0, sumY = 0;
-        final int count = event.getPointerCount();
         final int div = pointerUp ? count - 1 : count;
         final float focusX;
         final float focusY;
-        if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS) {
-            // In double tap mode, the focal pt is always where the double tap
-            // gesture started
-            focusX = mDoubleTapEvent.getX();
-            focusY = mDoubleTapEvent.getY();
+        if (inAnchoredScaleMode()) {
+            // In anchored scale mode, the focal pt is always where the double tap
+            // or button down gesture started
+            focusX = mAnchoredScaleStartX;
+            focusY = mAnchoredScaleStartY;
             if (event.getY() < focusY) {
                 mEventBeforeOrAboveStartingGestureEvent = true;
             } else {
@@ -390,7 +410,7 @@
         final float spanX = devX * 2;
         final float spanY = devY * 2;
         final float span;
-        if (inDoubleTapMode()) {
+        if (inAnchoredScaleMode()) {
             span = spanY;
         } else {
             span = (float) Math.hypot(spanX, spanY);
@@ -402,11 +422,10 @@
         final boolean wasInProgress = mInProgress;
         mFocusX = focusX;
         mFocusY = focusY;
-        if (!inDoubleTapMode() && mInProgress && (span < mMinSpan || configChanged)) {
+        if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {
             mListener.onScaleEnd(this);
             mInProgress = false;
             mInitialSpan = span;
-            mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
         }
         if (configChanged) {
             mPrevSpanX = mCurrSpanX = spanX;
@@ -414,7 +433,7 @@
             mInitialSpan = mPrevSpan = mCurrSpan = span;
         }
 
-        final int minSpan = inDoubleTapMode() ? mSpanSlop : mMinSpan;
+        final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
         if (!mInProgress && span >=  minSpan &&
                 (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
             mPrevSpanX = mCurrSpanX = spanX;
@@ -447,9 +466,8 @@
         return true;
     }
 
-
-    private boolean inDoubleTapMode() {
-        return mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS;
+    private boolean inAnchoredScaleMode() {
+        return mAnchoredScaleMode != ANCHORED_SCALE_MODE_NONE;
     }
 
     /**
@@ -466,8 +484,9 @@
                         @Override
                         public boolean onDoubleTap(MotionEvent e) {
                             // Double tap: start watching for a swipe
-                            mDoubleTapEvent = e;
-                            mDoubleTapMode = DOUBLE_TAP_MODE_IN_PROGRESS;
+                            mAnchoredScaleStartX = e.getX();
+                            mAnchoredScaleStartY = e.getY();
+                            mAnchoredScaleMode = ANCHORED_SCALE_MODE_DOUBLE_TAP;
                             return true;
                         }
                     };
@@ -484,6 +503,27 @@
     }
 
     /**
+     * Sets whether the associates {@link OnScaleGestureListener} should receive onScale callbacks
+     * when the user presses a {@value MotionEvent#BUTTON_SECONDARY} (right mouse button, stylus
+     * first button) and drags the pointer on the screen. Note that this is enabled by default if
+     * the app targets API 23 and newer.
+     *
+     * @param scales true to enable stylus or mouse scaling, false to disable.
+     */
+    public void setSecondaryButtonScaleEnabled(boolean scales) {
+        mButtonScaleEnabled = scales;
+    }
+
+    /**
+     * Return whether the button scale gesture, in which the user presses a
+     * {@value MotionEvent#BUTTON_SECONDARY} (right mouse button, stylus first button) and drags the
+     * pointer on the screen, should perform scaling. {@see #setButtonScaleEnabled(boolean)}.
+     */
+    public boolean isSecondaryButtonScaleEnabled() {
+        return mButtonScaleEnabled;
+    }
+
+    /**
      * Returns {@code true} if a scale gesture is in progress.
      */
     public boolean isInProgress() {
@@ -586,7 +626,7 @@
      * @return The current scaling factor.
      */
     public float getScaleFactor() {
-        if (inDoubleTapMode()) {
+        if (inAnchoredScaleMode()) {
             // Drag is moving up; the further away from the gesture
             // start, the smaller the span should be, the closer,
             // the larger the span, and therefore the larger the scale