11e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
21e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
31e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)// found in the LICENSE file.
41e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
51e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)package org.chromium.chromoting;
61e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
71e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)import android.content.Context;
81e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)import android.graphics.Matrix;
91e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)import android.graphics.PointF;
101e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)import android.view.GestureDetector;
111e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)import android.view.MotionEvent;
121e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)import android.view.ScaleGestureDetector;
13f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import android.widget.Scroller;
141e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)/**
161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * This class implements the cursor-tracking behavior and gestures.
171e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) */
181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)public class TrackingInputHandler implements TouchInputHandler {
191e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    /**
201e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * Minimum change to the scaling factor to be recognized as a zoom gesture. Setting lower
211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * values here will result in more frequent canvas redraws during zooming.
221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     */
231e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private static final double MIN_ZOOM_DELTA = 0.05;
241e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
251e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    /**
261e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * Maximum allowed zoom level - see {@link #repositionImageWithZoom()}.
271e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     */
281e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private static final float MAX_ZOOM_FACTOR = 100.0f;
291e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
301e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private DesktopViewInterface mViewer;
311e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private RenderData mRenderData;
321e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
331e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private GestureDetector mScroller;
341e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private ScaleGestureDetector mZoomer;
350f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    private TapGestureDetector mTapDetector;
361e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
37f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    /** Used to calculate the physics for flinging the cursor. */
38f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private Scroller mFlingScroller;
39f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
40f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    /** Used to disambiguate a 2-finger gesture as a swipe or a pinch. */
41f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private SwipePinchDetector mSwipePinchDetector;
42f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    /**
441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * The current cursor position is stored here as floats, so that the desktop image can be
451e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * positioned with sub-pixel accuracy, to give a smoother panning animation at high zoom levels.
461e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     */
471e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private PointF mCursorPosition;
481e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
490f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    /**
500f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)     * Used for tracking swipe gestures. Only the Y-direction is needed for responding to swipe-up
510f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)     * or swipe-down.
520f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)     */
530f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    private float mTotalMotionY = 0;
540f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
550f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    /**
560f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)     * Distance in pixels beyond which a motion gesture is considered to be a swipe. This is
570f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)     * initialized using the Context passed into the ctor.
581e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     */
590f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    private float mSwipeThreshold;
601e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
61f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    /** Mouse-button currently held down, or BUTTON_UNDEFINED otherwise. */
62f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private int mHeldButton = BUTTON_UNDEFINED;
63f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
64f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    /**
65f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)     * Set to true to prevent any further movement of the cursor, for example, when showing the
66f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)     * keyboard to prevent the cursor wandering from the area where keystrokes should be sent.
67f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)     */
68f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private boolean mSuppressCursorMovement = false;
69f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
70f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    /**
71f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)     * Set to true to suppress the fling animation at the end of a gesture, for example, when
72f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)     * dragging whilst a button is held down.
73f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)     */
74f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private boolean mSuppressFling = false;
75f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
76f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    /**
77f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)     * Set to true when 3-finger swipe gesture is complete, so that further movement doesn't
78f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)     * trigger more swipe actions.
79f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)     */
80f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private boolean mSwipeCompleted = false;
81f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
821e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    public TrackingInputHandler(DesktopViewInterface viewer, Context context,
831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                                RenderData renderData) {
841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mViewer = viewer;
851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mRenderData = renderData;
861e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
871e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        GestureListener listener = new GestureListener();
881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mScroller = new GestureDetector(context, listener, null, false);
891e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
901e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        // If long-press is enabled, the gesture-detector will not emit any further onScroll
911e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        // notifications after the onLongPress notification. Since onScroll is being used for
921e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        // moving the cursor, it means that the cursor would become stuck if the finger were held
931e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        // down too long.
941e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mScroller.setIsLongpressEnabled(false);
951e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
961e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mZoomer = new ScaleGestureDetector(context, listener);
970f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        mTapDetector = new TapGestureDetector(context, listener);
98f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        mFlingScroller = new Scroller(context);
99f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        mSwipePinchDetector = new SwipePinchDetector(context);
1001e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1011e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mCursorPosition = new PointF();
1021e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1030f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        // The threshold needs to be bigger than the ScaledTouchSlop used by the gesture-detectors,
1040f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        // so that a gesture cannot be both a tap and a swipe. It also needs to be small enough so
1050f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        // that intentional swipes are usually detected.
1060f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        float density = context.getResources().getDisplayMetrics().density;
1070f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        mSwipeThreshold = 40 * density;
1081e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
1091e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1101e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    /**
1111e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * Moves the mouse-cursor, injects a mouse-move event and repositions the image.
1121e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     */
1131e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private void moveCursor(float newX, float newY) {
1141e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        synchronized (mRenderData) {
1151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // Constrain cursor to the image area.
1161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (newX < 0) newX = 0;
1171e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (newY < 0) newY = 0;
1181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (newX > mRenderData.imageWidth) newX = mRenderData.imageWidth;
1191e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (newY > mRenderData.imageHeight) newY = mRenderData.imageHeight;
1201e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            mCursorPosition.set(newX, newY);
1211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            repositionImage();
1221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
1231e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        mViewer.injectMouseEvent((int) newX, (int) newY, BUTTON_UNDEFINED, false);
1251e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
1261e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1271e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    /**
1281e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * Repositions the image by translating it (without affecting the zoom level) to place the
1291e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * cursor close to the center of the screen.
1301e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     */
1311e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private void repositionImage() {
1321e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        synchronized (mRenderData) {
1331e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // Get the current cursor position in screen coordinates.
1341e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            float[] cursorScreen = {mCursorPosition.x, mCursorPosition.y};
1351e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            mRenderData.transform.mapPoints(cursorScreen);
1361e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1371e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // Translate so the cursor is displayed in the middle of the screen.
1381e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            mRenderData.transform.postTranslate(
1391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    (float) mRenderData.screenWidth / 2 - cursorScreen[0],
1401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    (float) mRenderData.screenHeight / 2 - cursorScreen[1]);
1411e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1421e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // Now the cursor is displayed in the middle of the screen, see if the image can be
1431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // panned so that more of it is visible. The primary goal is to show as much of the
1441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // image as possible. The secondary goal is to keep the cursor in the middle.
1451e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1461e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // Get the coordinates of the desktop rectangle (top-left/bottom-right corners) in
1471e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // screen coordinates. Order is: left, top, right, bottom.
1481e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            float[] rectScreen = {0, 0, mRenderData.imageWidth, mRenderData.imageHeight};
1491e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            mRenderData.transform.mapPoints(rectScreen);
1501e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1511e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            float leftDelta = rectScreen[0];
1521e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            float rightDelta = rectScreen[2] - mRenderData.screenWidth;
1531e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            float topDelta = rectScreen[1];
1541e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            float bottomDelta = rectScreen[3] - mRenderData.screenHeight;
1551e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            float xAdjust = 0;
1561e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            float yAdjust = 0;
1571e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1581e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (rectScreen[2] - rectScreen[0] < mRenderData.screenWidth) {
1591e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                // Image is narrower than the screen, so center it.
1601e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                xAdjust = -(rightDelta + leftDelta) / 2;
1611e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            } else if (leftDelta > 0 && rightDelta > 0) {
1621e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                // Panning the image left will show more of it.
1631e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                xAdjust = -Math.min(leftDelta, rightDelta);
1641e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            } else if (leftDelta < 0 && rightDelta < 0) {
1651e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                // Pan the image right.
1661e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                xAdjust = Math.min(-leftDelta, -rightDelta);
1671e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            }
1681e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1691e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // Apply similar logic for yAdjust.
1701e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (rectScreen[3] - rectScreen[1] < mRenderData.screenHeight) {
1711e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                yAdjust = -(bottomDelta + topDelta) / 2;
1721e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            } else if (topDelta > 0 && bottomDelta > 0) {
1731e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                yAdjust = -Math.min(topDelta, bottomDelta);
1741e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            } else if (topDelta < 0 && bottomDelta < 0) {
1751e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                yAdjust = Math.min(-topDelta, -bottomDelta);
1761e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            }
1771e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1781e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            mRenderData.transform.postTranslate(xAdjust, yAdjust);
1791e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
1801e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mViewer.transformationChanged();
1811e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
1821e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    /**
1841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * Repositions the image by translating and zooming it, to keep the zoom level within sensible
1851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * limits. The minimum zoom level is chosen to avoid black space around all 4 sides. The
1861e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * maximum zoom level is set arbitrarily, so that the user can zoom out again in a reasonable
1871e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * time, and to prevent arithmetic overflow problems from displaying the image.
1881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     */
1891e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private void repositionImageWithZoom() {
1901e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        synchronized (mRenderData) {
1911e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // Avoid division by zero in case this gets called before the image size is initialized.
1921e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (mRenderData.imageWidth == 0 || mRenderData.imageHeight == 0) {
1931e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                return;
1941e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            }
1951e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1961e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // Zoom out if the zoom level is too high.
1971e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            float currentZoomLevel = mRenderData.transform.mapRadius(1.0f);
1981e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (currentZoomLevel > MAX_ZOOM_FACTOR) {
1991e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                mRenderData.transform.setScale(MAX_ZOOM_FACTOR, MAX_ZOOM_FACTOR);
2001e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            }
2011e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
2021e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // Get image size scaled to screen coordinates.
2031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            float[] imageSize = {mRenderData.imageWidth, mRenderData.imageHeight};
2041e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            mRenderData.transform.mapVectors(imageSize);
2051e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
2061e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (imageSize[0] < mRenderData.screenWidth && imageSize[1] < mRenderData.screenHeight) {
2071e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                // Displayed image is too small in both directions, so apply the minimum zoom
2081e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                // level needed to fit either the width or height.
2091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                float scale = Math.min((float) mRenderData.screenWidth / mRenderData.imageWidth,
2101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                       (float) mRenderData.screenHeight / mRenderData.imageHeight);
2111e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                mRenderData.transform.setScale(scale, scale);
2121e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            }
2131e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
2141e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            repositionImage();
2151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
2161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
2171e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
2181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    /** Injects a button event using the current cursor location. */
2191e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private void injectButtonEvent(int button, boolean pressed) {
2201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        mViewer.injectMouseEvent((int) mCursorPosition.x, (int) mCursorPosition.y, button, pressed);
2211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
2221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
2230f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    /** Processes a (multi-finger) swipe gesture. */
2240f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    private boolean onSwipe() {
2250f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        if (mTotalMotionY > mSwipeThreshold) {
2260f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)            // Swipe down occurred.
2270f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)            mViewer.showActionBar();
2280f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        } else if (mTotalMotionY < -mSwipeThreshold) {
2290f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)            // Swipe up occurred.
2300f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)            mViewer.showKeyboard();
231f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        } else {
232f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            return false;
233f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
234f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
235f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        mSuppressCursorMovement = true;
236f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        mSuppressFling = true;
237f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        mSwipeCompleted = true;
238f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        return true;
239f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    }
240f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
241f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    /** Injects a button-up event if the button is currently held down (during a drag event). */
242f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private void releaseAnyHeldButton() {
243f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        if (mHeldButton != BUTTON_UNDEFINED) {
244f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            injectButtonEvent(mHeldButton, false);
245f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            mHeldButton = BUTTON_UNDEFINED;
2460f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        }
2470f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    }
2480f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
2491e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    @Override
2501e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    public boolean onTouchEvent(MotionEvent event) {
251f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        // Avoid short-circuit logic evaluation - ensure all gesture detectors see all events so
2521e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        // that they generate correct notifications.
2531e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        boolean handled = mScroller.onTouchEvent(event);
2540f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        handled |= mZoomer.onTouchEvent(event);
2550f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        handled |= mTapDetector.onTouchEvent(event);
256f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        mSwipePinchDetector.onTouchEvent(event);
257f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
258f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        switch (event.getActionMasked()) {
259f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            case MotionEvent.ACTION_DOWN:
260f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                mViewer.setAnimationEnabled(false);
261f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                mSuppressCursorMovement = false;
262f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                mSuppressFling = false;
263f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                mSwipeCompleted = false;
264f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                break;
265f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
266f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            case MotionEvent.ACTION_POINTER_DOWN:
267f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                mTotalMotionY = 0;
268f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                break;
269f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
270f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            case MotionEvent.ACTION_UP:
271f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                releaseAnyHeldButton();
272f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                break;
273f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
274f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            default:
275f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                break;
2761e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
2771e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        return handled;
2781e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
2791e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
2801e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    @Override
2811e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    public void onScreenConfigurationChanged() {
2821e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
2831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
2841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    @Override
2851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    public void onClientSizeChanged(int width, int height) {
2861e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        repositionImageWithZoom();
2871e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
2881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
2891e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    @Override
2901e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    public void onHostSizeChanged(int width, int height) {
2911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        moveCursor((float) width / 2, (float) height / 2);
2921e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        repositionImageWithZoom();
2931e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
2941e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
295f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    @Override
296f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    public void processAnimation() {
297f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        int previousX = mFlingScroller.getCurrX();
298f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        int previousY = mFlingScroller.getCurrY();
299f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        if (!mFlingScroller.computeScrollOffset()) {
300f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            mViewer.setAnimationEnabled(false);
301f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            return;
302f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
303f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        int deltaX = mFlingScroller.getCurrX() - previousX;
304f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        int deltaY = mFlingScroller.getCurrY() - previousY;
3051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        float[] delta = {deltaX, deltaY};
306f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        synchronized (mRenderData) {
307f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            Matrix canvasToImage = new Matrix();
308f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            mRenderData.transform.invert(canvasToImage);
309f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            canvasToImage.mapVectors(delta);
310f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
311f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
312f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        moveCursor(mCursorPosition.x + delta[0], mCursorPosition.y + delta[1]);
313f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    }
314f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
3151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    /** Responds to touch events filtered by the gesture detectors. */
3161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private class GestureListener extends GestureDetector.SimpleOnGestureListener
3170f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)            implements ScaleGestureDetector.OnScaleGestureListener,
3180f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)                       TapGestureDetector.OnTapListener {
3191e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        /**
3201e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)         * Called when the user drags one or more fingers across the touchscreen.
3211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)         */
3221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        @Override
3231e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
324f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            int pointerCount = e2.getPointerCount();
325f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            if (pointerCount == 3 && !mSwipeCompleted) {
3260f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)                // Note that distance values are reversed. For example, dragging a finger in the
3270f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)                // direction of increasing Y coordinate (downwards) results in distanceY being
3280f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)                // negative.
3290f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)                mTotalMotionY -= distanceY;
330f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                return onSwipe();
331f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            }
332f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
333f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            if (pointerCount == 2 && mSwipePinchDetector.isSwiping()) {
3341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                mViewer.injectMouseWheelDeltaEvent(-(int) distanceX, -(int) distanceY);
335f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
336f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                // Prevent the cursor being moved or flung by the gesture.
337f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                mSuppressCursorMovement = true;
338f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                return true;
3390f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)            }
3400f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
341f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            if (pointerCount != 1 || mSuppressCursorMovement) {
3421e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                return false;
3431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            }
3441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
3451e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            float[] delta = {distanceX, distanceY};
3461e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            synchronized (mRenderData) {
3471e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                Matrix canvasToImage = new Matrix();
3481e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                mRenderData.transform.invert(canvasToImage);
3491e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                canvasToImage.mapVectors(delta);
3501e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            }
3511e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
3521e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            moveCursor(mCursorPosition.x - delta[0], mCursorPosition.y - delta[1]);
3531e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            return true;
3541e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
3551e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
356f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        /**
357f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)         * Called when a fling gesture is recognized.
358f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)         */
359f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        @Override
360f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
361f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // If cursor movement is suppressed, fling also needs to be suppressed, as the
362f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // gesture-detector will still generate onFling() notifications based on movement of
363f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // the fingers, which would result in unwanted cursor movement.
364f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            if (mSuppressCursorMovement || mSuppressFling) {
365f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                return false;
366f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            }
367f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
368f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // The fling physics calculation is based on screen coordinates, so that it will
369f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // behave consistently at different zoom levels (and will work nicely at high zoom
370f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // levels, since |mFlingScroller| outputs integer coordinates). However, the desktop
371f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // will usually be panned as the cursor is moved across the desktop, which means the
372f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // transformation mapping from screen to desktop coordinates will change. To deal with
373f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // this, the cursor movement is computed from relative coordinate changes from
374f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // |mFlingScroller|. This means the fling can be started at (0, 0) with no bounding
375f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // constraints - the cursor is already constrained by the desktop size.
3761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            mFlingScroller.fling(0, 0, (int) velocityX, (int) velocityY, Integer.MIN_VALUE,
377f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
378f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // Initialize the scroller's current offset coordinates, since they are used for
379f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // calculating the delta values.
380f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            mFlingScroller.computeScrollOffset();
381f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            mViewer.setAnimationEnabled(true);
382f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            return true;
383f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
384f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
3851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        /** Called when the user is in the process of pinch-zooming. */
3861e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        @Override
3871e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        public boolean onScale(ScaleGestureDetector detector) {
388f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            if (!mSwipePinchDetector.isPinching()) {
389f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                return false;
390f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            }
391f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
3921e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (Math.abs(detector.getScaleFactor() - 1) < MIN_ZOOM_DELTA) {
3931e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                return false;
3941e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            }
3951e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
3961e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            float scaleFactor = detector.getScaleFactor();
3971e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            synchronized (mRenderData) {
3981e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                mRenderData.transform.postScale(
3991e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                        scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
4001e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            }
4011e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            repositionImageWithZoom();
4021e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            return true;
4031e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
4041e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
4051e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        /** Called whenever a gesture starts. Always accepts the gesture so it isn't ignored. */
4061e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        @Override
4071e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        public boolean onDown(MotionEvent e) {
4081e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            return true;
4091e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
4101e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
4111e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        /**
4121e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)         * Called when the user starts to zoom. Always accepts the zoom so that
4131e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)         * onScale() can decide whether to respond to it.
4141e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)         */
4151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        @Override
4161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        public boolean onScaleBegin(ScaleGestureDetector detector) {
4171e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            return true;
4181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
4191e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
4201e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        /** Called when the user is done zooming. Defers to onScale()'s judgement. */
4211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        @Override
4221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        public void onScaleEnd(ScaleGestureDetector detector) {
4231e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            onScale(detector);
4241e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
4250f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
426f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        /** Maps the number of fingers in a tap or long-press gesture to a mouse-button. */
427f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        private int mouseButtonFromPointerCount(int pointerCount) {
4280f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)            switch (pointerCount) {
4290f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)                case 1:
430f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    return BUTTON_LEFT;
4310f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)                case 2:
432f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    return BUTTON_RIGHT;
4330f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)                case 3:
434f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    return BUTTON_MIDDLE;
4350f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)                default:
436f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    return BUTTON_UNDEFINED;
4370f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)            }
438f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
4390f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
440f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        /** Called when the user taps the screen with one or more fingers. */
441f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        @Override
442f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        public boolean onTap(int pointerCount) {
443f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            int button = mouseButtonFromPointerCount(pointerCount);
444f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            if (button == BUTTON_UNDEFINED) {
445f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                return false;
446f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            } else {
447f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                injectButtonEvent(button, true);
448f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                injectButtonEvent(button, false);
449f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                return true;
450f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            }
451f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
452f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
453f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        /** Called when a long-press is triggered for one or more fingers. */
454f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        @Override
455f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        public void onLongPress(int pointerCount) {
456f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            mHeldButton = mouseButtonFromPointerCount(pointerCount);
457f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            if (mHeldButton != BUTTON_UNDEFINED) {
458f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                injectButtonEvent(mHeldButton, true);
459f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                mViewer.showLongPressFeedback();
460f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                mSuppressFling = true;
461f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            }
4620f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        }
4631e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
4641e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)}
465