blob: 4e2faa88ece5736825b507e3b5b7c7c7cfe4895f [file] [log] [blame]
// 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);
}
}