// 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.os.Build;
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);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // TODO(jdduke): Enable this and remove the custom double-tap drag
            // zoom code from ContentViewGestureHandler, crbug/331092.
            mMultiTouchDetector.setQuickScaleEnabled(false);
        }
    }

    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);
    }
}

@@ -198,16 +198,53 @@
 owser;%0A%0A
+import android.annotation.TargetApi;%0A
 import a
@@ -4787,24 +4787,25 @@
 tener);%0A
+%0A
         
 if (Buil
@@ -4800,287 +4800,277 @@
     
-if (Build.VERSION.SDK_INT %3E= Build.VERSION_CODES.KITKAT) %7B%0A            // TODO(jdduke): Enable this and remove the custom double-tap drag%0A            // zoom code from ContentViewGestureHandler, crbug/331092.%0A            mMultiTouchDetector.setQuickScaleEnabled(false);%0A        %7D
+// ScaleGestureDetector's %22QuickScale%22 feature was introduced in KitKat.%0A        // As ContentViewGestureHandler already implements this feature,%0A        // explicitly disable it to prevent double-handling of the gesture.%0A        disableQuickScale(mMultiTouchDetector);
 %0A   
@@ -6969,11 +6969,277 @@
 );%0A    %7D
+%0A%0A    @TargetApi(Build.VERSION_CODES.KITKAT)%0A    private static void disableQuickScale(ScaleGestureDetector scaleGestureDetector) %7B%0A       if (Build.VERSION.SDK_INT %3C Build.VERSION_CODES.KITKAT) return;%0A       scaleGestureDetector.setQuickScaleEnabled(false);%0A     %7D
 %0A%7D%0A

Fix lint issues with ScaleGestureDetector.setQuickScaleEnabled