1 // Copyright 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.annotation.TargetApi;
8 import android.content.Context;
9 import android.os.Build;
10 import android.util.Log;
11 import android.view.MotionEvent;
12 import android.view.ScaleGestureDetector;
15 * ZoomManager is responsible for maintaining the ContentView's current zoom
16 * level state and process scaling-related gestures.
19 private static final String TAG = "ContentViewZoom";
21 private final ContentViewCore mContentViewCore;
23 // ScaleGestureDetector previous to 4.2.2 failed to record touch event times (b/7626515),
24 // so we record them manually for use when synthesizing pinch gestures.
25 private long mCurrentEventTime;
27 private class ScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
28 // Completely silence scaling events. Used in WebView when zoom support
30 private boolean mPermanentlyIgnoreDetectorEvents = false;
31 // Bypass events through the detector to maintain its state. Used when
32 // renderes already handles the touch event.
33 private boolean mTemporarilyIgnoreDetectorEvents = false;
35 // Whether any pinch zoom event has been sent to native.
36 private boolean mPinchEventSent;
38 long getEventTime(ScaleGestureDetector detector) {
39 // Workaround for b/7626515, fixed in 4.2.2.
40 assert mCurrentEventTime != 0;
41 assert detector.getEventTime() == 0 || detector.getEventTime() == mCurrentEventTime;
42 return mCurrentEventTime;
45 boolean getPermanentlyIgnoreDetectorEvents() {
46 return mPermanentlyIgnoreDetectorEvents;
49 void setPermanentlyIgnoreDetectorEvents(boolean value) {
50 // Note that returning false from onScaleBegin / onScale makes the
51 // gesture detector not to emit further scaling notifications
52 // related to this gesture. Thus, if detector events are enabled in
53 // the middle of the gesture, we don't need to do anything.
54 mPermanentlyIgnoreDetectorEvents = value;
57 void setTemporarilyIgnoreDetectorEvents(boolean value) {
58 mTemporarilyIgnoreDetectorEvents = value;
62 public boolean onScaleBegin(ScaleGestureDetector detector) {
63 if (ignoreDetectorEvents()) return false;
64 mPinchEventSent = false;
65 mContentViewCore.getContentViewGestureHandler().setIgnoreSingleTap(true);
70 public void onScaleEnd(ScaleGestureDetector detector) {
71 if (!mPinchEventSent || !mContentViewCore.isAlive()) return;
72 mContentViewCore.getContentViewGestureHandler().pinchEnd(getEventTime(detector));
73 mPinchEventSent = false;
77 public boolean onScale(ScaleGestureDetector detector) {
78 if (ignoreDetectorEvents()) return false;
79 // It is possible that pinchBegin() was never called when we reach here.
80 // This happens when webkit handles the 2nd touch down event. That causes
81 // ContentView to ignore the onScaleBegin() call. And if webkit does not
82 // handle the touch move events afterwards, we will face a situation
83 // that pinchBy() is called without any pinchBegin().
84 // To solve this problem, we call pinchBegin() here if it is never called.
85 if (!mPinchEventSent) {
86 mContentViewCore.getContentViewGestureHandler().pinchBegin(getEventTime(detector),
87 (int) detector.getFocusX(), (int) detector.getFocusY());
88 mPinchEventSent = true;
90 mContentViewCore.getContentViewGestureHandler().pinchBy(
91 getEventTime(detector), (int) detector.getFocusX(), (int) detector.getFocusY(),
92 detector.getScaleFactor());
96 private boolean ignoreDetectorEvents() {
97 return mPermanentlyIgnoreDetectorEvents ||
98 mTemporarilyIgnoreDetectorEvents ||
99 !mContentViewCore.isAlive();
103 private final ScaleGestureDetector mMultiTouchDetector;
104 private final ScaleGestureListener mMultiTouchListener;
106 ZoomManager(final Context context, ContentViewCore contentViewCore) {
107 mContentViewCore = contentViewCore;
108 mMultiTouchListener = new ScaleGestureListener();
109 mMultiTouchDetector = new ScaleGestureDetector(context, mMultiTouchListener);
111 // ScaleGestureDetector's "QuickScale" feature was introduced in KitKat.
112 // As ContentViewGestureHandler already implements this feature,
113 // explicitly disable it to prevent double-handling of the gesture.
114 disableQuickScale(mMultiTouchDetector);
117 boolean isScaleGestureDetectionInProgress() {
118 return !mMultiTouchListener.getPermanentlyIgnoreDetectorEvents()
119 && mMultiTouchDetector.isInProgress();
122 // Passes the touch event to ScaleGestureDetector so that its internal
123 // state won't go wrong, but instructs the listener to ignore the result
124 // of processing, if any.
125 void passTouchEventThrough(MotionEvent event) {
126 mMultiTouchListener.setTemporarilyIgnoreDetectorEvents(true);
127 mCurrentEventTime = event.getEventTime();
129 mMultiTouchDetector.onTouchEvent(event);
130 } catch (Exception e) {
131 Log.e(TAG, "ScaleGestureDetector got into a bad state!", e);
136 // Passes the touch event to ScaleGestureDetector so that its internal state
137 // won't go wrong. ScaleGestureDetector needs two pointers in a MotionEvent
138 // to recognize a zoom gesture.
139 boolean processTouchEvent(MotionEvent event) {
140 // TODO: Need to deal with multi-touch transition
141 mMultiTouchListener.setTemporarilyIgnoreDetectorEvents(false);
142 mCurrentEventTime = event.getEventTime();
144 boolean inGesture = isScaleGestureDetectionInProgress();
145 boolean retVal = mMultiTouchDetector.onTouchEvent(event);
146 if (!inGesture && (event.getActionMasked() == MotionEvent.ACTION_UP
147 || event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
151 } catch (Exception e) {
152 Log.e(TAG, "ScaleGestureDetector got into a bad state!", e);
158 void updateMultiTouchSupport(boolean supportsMultiTouchZoom) {
159 mMultiTouchListener.setPermanentlyIgnoreDetectorEvents(!supportsMultiTouchZoom);
162 @TargetApi(Build.VERSION_CODES.KITKAT)
163 private static void disableQuickScale(ScaleGestureDetector scaleGestureDetector) {
164 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
165 scaleGestureDetector.setQuickScaleEnabled(false);