1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.content.browser;
7 import android.content.Context;
8 import android.util.Log;
9 import android.view.MotionEvent;
10 import android.view.ScaleGestureDetector;
13 * ZoomManager is responsible for maintaining the ContentView's current zoom
14 * level state and process scaling-related gestures.
17 private static final String TAG = "ContentViewZoom";
19 private ContentViewCore mContentViewCore;
21 // ScaleGestureDetector previous to 4.2.2 failed to record touch event times (b/7626515),
22 // so we record them manually for use when synthesizing pinch gestures.
23 private long mCurrentEventTime;
25 private class ScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
26 // Completely silence scaling events. Used in WebView when zoom support
28 private boolean mPermanentlyIgnoreDetectorEvents = false;
29 // Bypass events through the detector to maintain its state. Used when
30 // renderes already handles the touch event.
31 private boolean mTemporarilyIgnoreDetectorEvents = false;
33 // Whether any pinch zoom event has been sent to native.
34 private boolean mPinchEventSent;
36 long getEventTime(ScaleGestureDetector detector) {
37 // Workaround for b/7626515, fixed in 4.2.2.
38 assert mCurrentEventTime != 0;
39 assert detector.getEventTime() == 0 || detector.getEventTime() == mCurrentEventTime;
40 return mCurrentEventTime;
43 boolean getPermanentlyIgnoreDetectorEvents() {
44 return mPermanentlyIgnoreDetectorEvents;
47 void setPermanentlyIgnoreDetectorEvents(boolean value) {
48 // Note that returning false from onScaleBegin / onScale makes the
49 // gesture detector not to emit further scaling notifications
50 // related to this gesture. Thus, if detector events are enabled in
51 // the middle of the gesture, we don't need to do anything.
52 mPermanentlyIgnoreDetectorEvents = value;
55 void setTemporarilyIgnoreDetectorEvents(boolean value) {
56 mTemporarilyIgnoreDetectorEvents = value;
60 public boolean onScaleBegin(ScaleGestureDetector detector) {
61 if (ignoreDetectorEvents()) return false;
62 mPinchEventSent = false;
63 mContentViewCore.getContentViewGestureHandler().setIgnoreSingleTap(true);
68 public void onScaleEnd(ScaleGestureDetector detector) {
69 if (!mPinchEventSent || !mContentViewCore.isAlive()) return;
70 mContentViewCore.getContentViewGestureHandler().pinchEnd(getEventTime(detector));
71 mPinchEventSent = false;
75 public boolean onScale(ScaleGestureDetector detector) {
76 if (ignoreDetectorEvents()) return false;
77 // It is possible that pinchBegin() was never called when we reach here.
78 // This happens when webkit handles the 2nd touch down event. That causes
79 // ContentView to ignore the onScaleBegin() call. And if webkit does not
80 // handle the touch move events afterwards, we will face a situation
81 // that pinchBy() is called without any pinchBegin().
82 // To solve this problem, we call pinchBegin() here if it is never called.
83 if (!mPinchEventSent) {
84 mContentViewCore.getContentViewGestureHandler().pinchBegin(getEventTime(detector),
85 (int) detector.getFocusX(), (int) detector.getFocusY());
86 mPinchEventSent = true;
88 mContentViewCore.getContentViewGestureHandler().pinchBy(
89 getEventTime(detector), (int) detector.getFocusX(), (int) detector.getFocusY(),
90 detector.getScaleFactor());
94 private boolean ignoreDetectorEvents() {
95 return mPermanentlyIgnoreDetectorEvents ||
96 mTemporarilyIgnoreDetectorEvents ||
97 !mContentViewCore.isAlive();
101 private ScaleGestureDetector mMultiTouchDetector;
102 private ScaleGestureListener mMultiTouchListener;
104 ZoomManager(final Context context, ContentViewCore contentViewCore) {
105 mContentViewCore = contentViewCore;
106 mMultiTouchListener = new ScaleGestureListener();
107 mMultiTouchDetector = new ScaleGestureDetector(context, mMultiTouchListener);
110 boolean isScaleGestureDetectionInProgress() {
111 return !mMultiTouchListener.getPermanentlyIgnoreDetectorEvents()
112 && mMultiTouchDetector.isInProgress();
115 // Passes the touch event to ScaleGestureDetector so that its internal
116 // state won't go wrong, but instructs the listener to ignore the result
117 // of processing, if any.
118 void passTouchEventThrough(MotionEvent event) {
119 mMultiTouchListener.setTemporarilyIgnoreDetectorEvents(true);
120 mCurrentEventTime = event.getEventTime();
122 mMultiTouchDetector.onTouchEvent(event);
123 } catch (Exception e) {
124 Log.e(TAG, "ScaleGestureDetector got into a bad state!", e);
129 // Passes the touch event to ScaleGestureDetector so that its internal state
130 // won't go wrong. ScaleGestureDetector needs two pointers in a MotionEvent
131 // to recognize a zoom gesture.
132 boolean processTouchEvent(MotionEvent event) {
133 // TODO: Need to deal with multi-touch transition
134 mMultiTouchListener.setTemporarilyIgnoreDetectorEvents(false);
135 mCurrentEventTime = event.getEventTime();
137 boolean inGesture = isScaleGestureDetectionInProgress();
138 boolean retVal = mMultiTouchDetector.onTouchEvent(event);
139 if (!inGesture && (event.getActionMasked() == MotionEvent.ACTION_UP
140 || event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
144 } catch (Exception e) {
145 Log.e(TAG, "ScaleGestureDetector got into a bad state!", e);
151 void updateMultiTouchSupport(boolean supportsMultiTouchZoom) {
152 mMultiTouchListener.setPermanentlyIgnoreDetectorEvents(!supportsMultiTouchZoom);