| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.content.browser; |
| |
| import android.content.Context; |
| import android.util.Log; |
| import android.view.MotionEvent; |
| import android.view.ScaleGestureDetector; |
| |
| /** |
| * ZoomManager is responsible for maintaining the ContentView's current zoom |
| * level state and process scaling-related gestures. |
| */ |
| class ZoomManager { |
| private static final String TAG = "ContentViewZoom"; |
| |
| private final ContentViewCore mContentViewCore; |
| |
| // ScaleGestureDetector previous to 4.2.2 failed to record touch event times (b/7626515), |
| // so we record them manually for use when synthesizing pinch gestures. |
| private long mCurrentEventTime; |
| |
| private class ScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener { |
| // Completely silence scaling events. Used in WebView when zoom support |
| // is turned off. |
| private boolean mPermanentlyIgnoreDetectorEvents = false; |
| // Bypass events through the detector to maintain its state. Used when |
| // renderes already handles the touch event. |
| private boolean mTemporarilyIgnoreDetectorEvents = false; |
| |
| // Whether any pinch zoom event has been sent to native. |
| private boolean mPinchEventSent; |
| |
| long getEventTime(ScaleGestureDetector detector) { |
| // Workaround for b/7626515, fixed in 4.2.2. |
| assert mCurrentEventTime != 0; |
| assert detector.getEventTime() == 0 || detector.getEventTime() == mCurrentEventTime; |
| return mCurrentEventTime; |
| } |
| |
| boolean getPermanentlyIgnoreDetectorEvents() { |
| return mPermanentlyIgnoreDetectorEvents; |
| } |
| |
| void setPermanentlyIgnoreDetectorEvents(boolean value) { |
| // Note that returning false from onScaleBegin / onScale makes the |
| // gesture detector not to emit further scaling notifications |
| // related to this gesture. Thus, if detector events are enabled in |
| // the middle of the gesture, we don't need to do anything. |
| mPermanentlyIgnoreDetectorEvents = value; |
| } |
| |
| void setTemporarilyIgnoreDetectorEvents(boolean value) { |
| mTemporarilyIgnoreDetectorEvents = value; |
| } |
| |
| @Override |
| public boolean onScaleBegin(ScaleGestureDetector detector) { |
| if (ignoreDetectorEvents()) return false; |
| mPinchEventSent = false; |
| mContentViewCore.getContentViewGestureHandler().setIgnoreSingleTap(true); |
| return true; |
| } |
| |
| @Override |
| public void onScaleEnd(ScaleGestureDetector detector) { |
| if (!mPinchEventSent || !mContentViewCore.isAlive()) return; |
| mContentViewCore.getContentViewGestureHandler().pinchEnd(getEventTime(detector)); |
| mPinchEventSent = false; |
| } |
| |
| @Override |
| public boolean onScale(ScaleGestureDetector detector) { |
| if (ignoreDetectorEvents()) return false; |
| // It is possible that pinchBegin() was never called when we reach here. |
| // This happens when webkit handles the 2nd touch down event. That causes |
| // ContentView to ignore the onScaleBegin() call. And if webkit does not |
| // handle the touch move events afterwards, we will face a situation |
| // that pinchBy() is called without any pinchBegin(). |
| // To solve this problem, we call pinchBegin() here if it is never called. |
| if (!mPinchEventSent) { |
| mContentViewCore.getContentViewGestureHandler().pinchBegin(getEventTime(detector), |
| (int) detector.getFocusX(), (int) detector.getFocusY()); |
| mPinchEventSent = true; |
| } |
| mContentViewCore.getContentViewGestureHandler().pinchBy( |
| getEventTime(detector), (int) detector.getFocusX(), (int) detector.getFocusY(), |
| detector.getScaleFactor()); |
| return true; |
| } |
| |
| private boolean ignoreDetectorEvents() { |
| return mPermanentlyIgnoreDetectorEvents || |
| mTemporarilyIgnoreDetectorEvents || |
| !mContentViewCore.isAlive(); |
| } |
| } |
| |
| private final ScaleGestureDetector mMultiTouchDetector; |
| private final ScaleGestureListener mMultiTouchListener; |
| |
| ZoomManager(final Context context, ContentViewCore contentViewCore) { |
| mContentViewCore = contentViewCore; |
| mMultiTouchListener = new ScaleGestureListener(); |
| mMultiTouchDetector = new ScaleGestureDetector(context, mMultiTouchListener); |
| } |
| |
| boolean isScaleGestureDetectionInProgress() { |
| return !mMultiTouchListener.getPermanentlyIgnoreDetectorEvents() |
| && mMultiTouchDetector.isInProgress(); |
| } |
| |
| // Passes the touch event to ScaleGestureDetector so that its internal |
| // state won't go wrong, but instructs the listener to ignore the result |
| // of processing, if any. |
| void passTouchEventThrough(MotionEvent event) { |
| mMultiTouchListener.setTemporarilyIgnoreDetectorEvents(true); |
| mCurrentEventTime = event.getEventTime(); |
| try { |
| mMultiTouchDetector.onTouchEvent(event); |
| } catch (Exception e) { |
| Log.e(TAG, "ScaleGestureDetector got into a bad state!", e); |
| assert false; |
| } |
| } |
| |
| // Passes the touch event to ScaleGestureDetector so that its internal state |
| // won't go wrong. ScaleGestureDetector needs two pointers in a MotionEvent |
| // to recognize a zoom gesture. |
| boolean processTouchEvent(MotionEvent event) { |
| // TODO: Need to deal with multi-touch transition |
| mMultiTouchListener.setTemporarilyIgnoreDetectorEvents(false); |
| mCurrentEventTime = event.getEventTime(); |
| try { |
| boolean inGesture = isScaleGestureDetectionInProgress(); |
| boolean retVal = mMultiTouchDetector.onTouchEvent(event); |
| if (!inGesture && (event.getActionMasked() == MotionEvent.ACTION_UP |
| || event.getActionMasked() == MotionEvent.ACTION_CANCEL)) { |
| return false; |
| } |
| return retVal; |
| } catch (Exception e) { |
| Log.e(TAG, "ScaleGestureDetector got into a bad state!", e); |
| assert false; |
| } |
| return false; |
| } |
| |
| void updateMultiTouchSupport(boolean supportsMultiTouchZoom) { |
| mMultiTouchListener.setPermanentlyIgnoreDetectorEvents(!supportsMultiTouchZoom); |
| } |
| } |