package org.chromium.content.browser;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.SearchManager;
import android.content.ContentResolver;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
+import android.os.SystemClock;
import android.provider.Browser;
import android.provider.Settings;
import android.text.Editable;
+import android.text.Selection;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import android.view.ActionMode;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
-import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-import android.widget.AbsoluteLayout;
import android.widget.FrameLayout;
import com.google.common.annotations.VisibleForTesting;
import org.chromium.base.CalledByNative;
import org.chromium.base.CommandLine;
import org.chromium.base.JNINamespace;
+import org.chromium.base.ObserverList;
+import org.chromium.base.ObserverList.RewindableIterator;
import org.chromium.base.TraceEvent;
-import org.chromium.base.WeakContext;
import org.chromium.content.R;
-import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate;
+import org.chromium.content.browser.ScreenOrientationListener.ScreenOrientationObserver;
import org.chromium.content.browser.accessibility.AccessibilityInjector;
import org.chromium.content.browser.accessibility.BrowserAccessibilityManager;
import org.chromium.content.browser.input.AdapterInputConnection;
import org.chromium.content.browser.input.SelectPopupItem;
import org.chromium.content.browser.input.SelectionHandleController;
import org.chromium.content.common.ContentSwitches;
+import org.chromium.content_public.browser.GestureStateListener;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.ViewAndroid;
import org.chromium.ui.base.ViewAndroidDelegate;
*/
@JNINamespace("content")
public class ContentViewCore
- implements MotionEventDelegate, NavigationClient, AccessibilityStateChangeListener {
+ implements NavigationClient, AccessibilityStateChangeListener, ScreenOrientationObserver {
private static final String TAG = "ContentViewCore";
}
/**
- * An interface that allows the embedder to be notified of events and state changes related to
- * gesture processing.
- */
- public interface GestureStateListener {
- /**
- * Called when the pinch gesture starts.
- */
- void onPinchGestureStart();
-
- /**
- * Called when the pinch gesture ends.
- */
- void onPinchGestureEnd();
-
- /**
- * Called when the fling gesture is sent.
- */
- void onFlingStartGesture(int vx, int vy);
-
- /**
- * Called when the fling cancel gesture is sent.
- */
- void onFlingCancelGesture();
-
- /**
- * Called when a fling event was not handled by the renderer.
- */
- void onUnhandledFlingStartEvent();
-
- /**
- * Called to indicate that a scroll update gesture had been consumed by the page.
- * This callback is called whenever any layer is scrolled (like a frame or div). It is
- * not called when a JS touch handler consumes the event (preventDefault), it is not called
- * for JS-initiated scrolling.
- */
- void onScrollUpdateGestureConsumed();
- }
-
- /**
* An interface for controlling visibility and state of embedder-provided zoom controls.
*/
public interface ZoomControlsDelegate {
}
/**
- * An interface that allows the embedder to be notified of changes to the parameters of the
- * currently displayed contents.
- * These notifications are consistent with respect to the UI thread (the size is the size of
- * the contents currently displayed on screen).
- */
- public interface UpdateFrameInfoListener {
- /**
- * Called each time any of the parameters are changed.
- *
- * @param pageScaleFactor The page scale.
- */
- void onFrameInfoUpdated(float pageScaleFactor);
- }
-
- /**
* An interface that allows the embedder to be notified when the results of
* extractSmartClipData are available.
*/
private boolean mInForeground = false;
- private ContentViewGestureHandler mContentViewGestureHandler;
- private GestureStateListener mGestureStateListener;
- private ZoomManager mZoomManager;
+ private final ObserverList<GestureStateListener> mGestureStateListeners;
+ private final RewindableIterator<GestureStateListener> mGestureStateListenersIterator;
private ZoomControlsDelegate mZoomControlsDelegate;
private PopupZoomer mPopupZoomer;
+ private SelectPopupDialog mSelectPopupDialog;
private Runnable mFakeMouseMoveRunnable = null;
private ImeAdapter mImeAdapter;
private ImeAdapter.AdapterInputConnectionFactory mAdapterInputConnectionFactory;
private AdapterInputConnection mInputConnection;
+ private InputMethodManagerWrapper mInputMethodManagerWrapper;
private SelectionHandleController mSelectionHandleController;
private InsertionHandleController mInsertionHandleController;
// System accessibility service.
private final AccessibilityManager mAccessibilityManager;
+ // Accessibility touch exploration state.
+ private boolean mTouchExplorationEnabled;
+
// Allows us to dynamically respond when the accessibility script injection flag changes.
private ContentObserver mAccessibilityScriptInjectionObserver;
// Temporary notification to tell onSizeChanged to focus a form element,
// because the OSK was just brought up.
- private boolean mUnfocusOnNextSizeChanged = false;
private final Rect mFocusPreOSKViewportRect = new Rect();
- // Used to keep track of whether we should try to undo the last zoom-to-textfield operation.
- private boolean mScrolledAndZoomedFocusedEditableNode = false;
-
// Whether we received a new frame since consumePendingRendererFrame() was last called.
private boolean mPendingRendererFrame = false;
// vsync.
private boolean mRequestedVSyncForInput = false;
+ // On single tap this will store the x, y coordinates of the touch.
+ private int mSingleTapX;
+ private int mSingleTapY;
+
+ // Whether a touch scroll sequence is active, used to hide text selection
+ // handles. Note that a scroll sequence will *always* bound a pinch
+ // sequence, so this will also be true for the duration of a pinch gesture.
+ private boolean mTouchScrollInProgress;
+
+ // The outstanding fling start events that hasn't got fling end yet. It may be > 1 because
+ // onNativeFlingStopped() is called asynchronously.
+ private int mPotentiallyActiveFlingCount;
+
private ViewAndroid mViewAndroid;
private SmartClipDataListener mSmartClipDataListener = null;
- /** ActionAfterDoubleTap defined in tools/metrics/histograms/histograms.xml. */
- public static class UMAActionAfterDoubleTap {
- public static final int NAVIGATE_BACK = 0;
- public static final int NAVIGATE_STOP = 1;
- public static final int NO_ACTION = 2;
- public static final int COUNT = 3;
- }
+ // This holds the state of editable text (e.g. contents of <input>, contenteditable) of
+ // a focused element.
+ // Every time the user, IME, javascript (Blink), autofill etc. modifies the content, the new
+ // state must be reflected to this to keep consistency.
+ private Editable mEditable;
- /** TapDelayType defined in tools/metrics/histograms/histograms.xml. */
- public static class UMASingleTapType {
- public static final int DELAYED_TAP = 0;
- public static final int UNDELAYED_TAP = 1;
- public static final int COUNT = 2;
- }
+ /**
+ * PID used to indicate an invalid render process.
+ */
+ // Keep in sync with the value returned from ContentViewCoreImpl::GetCurrentRendererProcessId()
+ // if there is no render process.
+ public static final int INVALID_RENDER_PROCESS_PID = 0;
/**
* Constructs a new ContentViewCore. Embedders must call initialize() after constructing
public ContentViewCore(Context context) {
mContext = context;
- WeakContext.initializeWeakContext(context);
- HeapStatsLogger.init(mContext.getApplicationContext());
mAdapterInputConnectionFactory = new AdapterInputConnectionFactory();
+ mInputMethodManagerWrapper = new InputMethodManagerWrapper(mContext);
mRenderCoordinates = new RenderCoordinates();
- mRenderCoordinates.setDeviceScaleFactor(
- getContext().getResources().getDisplayMetrics().density);
+ float deviceScaleFactor = getContext().getResources().getDisplayMetrics().density;
+ String forceScaleFactor = CommandLine.getInstance().getSwitchValue(
+ ContentSwitches.FORCE_DEVICE_SCALE_FACTOR);
+ if (forceScaleFactor != null) {
+ deviceScaleFactor = Float.valueOf(forceScaleFactor);
+ }
+ mRenderCoordinates.setDeviceScaleFactor(deviceScaleFactor);
mStartHandlePoint = mRenderCoordinates.createNormalizedPoint();
mEndHandlePoint = mRenderCoordinates.createNormalizedPoint();
mInsertionHandlePoint = mRenderCoordinates.createNormalizedPoint();
mAccessibilityManager = (AccessibilityManager)
getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ mGestureStateListeners = new ObserverList<GestureStateListener>();
+ mGestureStateListenersIterator = mGestureStateListeners.rewindableIterator();
+
+ mEditable = Editable.Factory.getInstance().newEditable("");
+ Selection.setSelection(mEditable, 0);
}
/**
}
@Override
- @SuppressWarnings("deprecation") // AbsoluteLayout.LayoutParams
+ @SuppressWarnings("deprecation") // AbsoluteLayout
public void setAnchorViewPosition(
View view, float x, float y, float width, float height) {
assert view.getParent() == mContainerView;
lp.leftMargin = leftMargin;
lp.topMargin = topMargin;
view.setLayoutParams(lp);
- } else if (mContainerView instanceof AbsoluteLayout) {
+ } else if (mContainerView instanceof android.widget.AbsoluteLayout) {
// This fixes the offset due to a difference in
// scrolling model of WebView vs. Chrome.
// TODO(sgurun) fix this to use mContainerView.getScroll[X/Y]()
}
@VisibleForTesting
+ public void setImeAdapterForTest(ImeAdapter imeAdapter) {
+ mImeAdapter = imeAdapter;
+ }
+
+ @VisibleForTesting
public ImeAdapter getImeAdapterForTest() {
return mImeAdapter;
}
}
@VisibleForTesting
+ public void setInputMethodManagerWrapperForTest(InputMethodManagerWrapper immw) {
+ mInputMethodManagerWrapper = immw;
+ }
+
+ @VisibleForTesting
public AdapterInputConnection getInputConnectionForTest() {
return mInputConnection;
}
+ @VisibleForTesting
+ public void setContainerViewForTest(ViewGroup view) {
+ mContainerView = view;
+ }
+
private ImeAdapter createImeAdapter(Context context) {
- return new ImeAdapter(new InputMethodManagerWrapper(context),
+ return new ImeAdapter(mInputMethodManagerWrapper,
new ImeAdapter.ImeAdapterDelegate() {
@Override
public void onImeEvent(boolean isFinish) {
getContentViewClient().onImeEvent();
if (!isFinish) {
hideHandles();
- undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
}
}
InputMethodManager.RESULT_UNCHANGED_SHOWN) {
// If the OSK was already there, focus the form immediately.
scrollFocusedEditableNodeIntoView();
- } else {
- undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
}
}
};
viewAndroidNativePointer = mViewAndroid.getNativePointer();
}
- // Note ContentViewGestureHandler initialization must occur before nativeInit
- // because nativeInit may callback into hasTouchEventHandlers.
- mZoomManager = new ZoomManager(mContext, this);
- mContentViewGestureHandler = new ContentViewGestureHandler(mContext, this, mZoomManager);
mZoomControlsDelegate = new ZoomControlsDelegate() {
@Override
public void invokeZoomPicker() {}
@Override
public void didStartLoading(String url) {
hidePopupDialog();
+ resetScrollInProgress();
resetGestureDetectors();
}
};
-
- sendOrientationChangeEvent();
}
@CalledByNative
public boolean onSingleTap(View v, MotionEvent e) {
mContainerView.requestFocus();
if (mNativeContentViewCore != 0) {
- nativeSingleTap(mNativeContentViewCore, e.getEventTime(),
- e.getX(), e.getY(), true);
+ nativeSingleTap(mNativeContentViewCore, e.getEventTime(), e.getX(), e.getY());
}
return true;
}
@Override
public boolean onLongPress(View v, MotionEvent e) {
if (mNativeContentViewCore != 0) {
- nativeLongPress(mNativeContentViewCore, e.getEventTime(),
- e.getX(), e.getY(), true);
+ nativeLongPress(mNativeContentViewCore, e.getEventTime(), e.getX(), e.getY());
}
return true;
}
mJavaScriptInterfaces.clear();
mRetainedJavaScriptObjects.clear();
unregisterAccessibilityContentObserver();
+ mGestureStateListeners.clear();
+ ScreenOrientationListener.getInstance().removeObserver(this);
}
private void unregisterAccessibilityContentObserver() {
* Stops loading the current web contents.
*/
public void stopLoading() {
- reportActionAfterDoubleTapUMA(ContentViewCore.UMAActionAfterDoubleTap.NAVIGATE_STOP);
if (mNativeContentViewCore != 0) nativeStopLoading(mNativeContentViewCore);
}
return mRenderCoordinates.getContentWidthCss();
}
- public Bitmap getBitmap() {
- return getBitmap(getViewportWidthPix(), getViewportHeightPix());
- }
-
- public Bitmap getBitmap(int width, int height) {
- if (width == 0 || height == 0
- || getViewportWidthPix() == 0 || getViewportHeightPix() == 0) {
- return null;
- }
-
- Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-
- if (mNativeContentViewCore != 0 &&
- nativePopulateBitmapFromCompositor(mNativeContentViewCore, b)) {
- // If we successfully grabbed a bitmap, check if we have to draw the Android overlay
- // components as well.
- if (mContainerView.getChildCount() > 0) {
- Canvas c = new Canvas(b);
- c.scale(width / (float) getViewportWidthPix(),
- height / (float) getViewportHeightPix());
- mContainerView.draw(c);
- }
- return b;
- }
-
- return null;
- }
-
- /**
- * Generates a bitmap of the content that is performance optimized based on capture time.
- *
- * <p>
- * To have a consistent capture time across devices, we will scale down the captured bitmap
- * where necessary to reduce the time to generate the bitmap.
- *
- * @param width The width of the content to be captured.
- * @param height The height of the content to be captured.
- * @return A pair of the generated bitmap, and the scale that needs to be applied to return the
- * bitmap to it's original size (i.e. if the bitmap is scaled down 50%, this
- * will be 2).
- */
- public Pair<Bitmap, Float> getScaledPerformanceOptimizedBitmap(int width, int height) {
- float scale = 1f;
- // On tablets, always scale down to MDPI for performance reasons.
- if (DeviceUtils.isTablet(getContext())) {
- scale = getContext().getResources().getDisplayMetrics().density;
- }
- return Pair.create(
- getBitmap((int) (width / scale), (int) (height / scale)),
- scale);
- }
-
// TODO(teddchoc): Remove all these navigation controller methods from here and have the
// embedders manage it.
/**
* @see View#onTouchEvent(MotionEvent)
*/
public boolean onTouchEvent(MotionEvent event) {
- undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
+ cancelRequestToScrollFocusedEditableNodeIntoView();
+
if (!mRequestedVSyncForInput) {
mRequestedVSyncForInput = true;
addVSyncSubscriber();
}
- return mContentViewGestureHandler.onTouchEvent(event);
- }
- /** @see ContentViewGestureHandler#setIgnoreRemainingTouchEvents */
- public void setIgnoreRemainingTouchEvents() {
- mContentViewGestureHandler.setIgnoreRemainingTouchEvents();
+ final int eventAction = event.getActionMasked();
+
+ // Only these actions have any effect on gesture detection. Other
+ // actions have no corresponding WebTouchEvent type and may confuse the
+ // touch pipline, so we ignore them entirely.
+ if (eventAction != MotionEvent.ACTION_DOWN
+ && eventAction != MotionEvent.ACTION_UP
+ && eventAction != MotionEvent.ACTION_CANCEL
+ && eventAction != MotionEvent.ACTION_MOVE
+ && eventAction != MotionEvent.ACTION_POINTER_DOWN
+ && eventAction != MotionEvent.ACTION_POINTER_UP) {
+ return false;
+ }
+
+ if (mNativeContentViewCore == 0) return false;
+ final int pointerCount = event.getPointerCount();
+ return nativeOnTouchEvent(mNativeContentViewCore, event,
+ event.getEventTime(), eventAction,
+ pointerCount, event.getHistorySize(), event.getActionIndex(),
+ event.getX(), event.getY(),
+ pointerCount > 1 ? event.getX(1) : 0,
+ pointerCount > 1 ? event.getY(1) : 0,
+ event.getPointerId(0), pointerCount > 1 ? event.getPointerId(1) : -1,
+ event.getTouchMajor(), pointerCount > 1 ? event.getTouchMajor(1) : 0);
}
- /**
- * @return ContentViewGestureHandler for all MotionEvent and gesture related calls.
- */
- ContentViewGestureHandler getContentViewGestureHandler() {
- return mContentViewGestureHandler;
+ public void setIgnoreRemainingTouchEvents() {
+ if (mNativeContentViewCore == 0) return;
+ nativeIgnoreRemainingTouchEvents(mNativeContentViewCore);
}
- @Override
- public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts) {
- if (mNativeContentViewCore != 0) {
- return nativeSendTouchEvent(mNativeContentViewCore, timeMs, action, pts);
- }
- return false;
+ public boolean isScrollInProgress() {
+ return mTouchScrollInProgress || mPotentiallyActiveFlingCount > 0;
}
@SuppressWarnings("unused")
@CalledByNative
- private void hasTouchEventHandlers(boolean hasTouchHandlers) {
- mContentViewGestureHandler.hasTouchEventHandlers(hasTouchHandlers);
+ private void onFlingStartEventConsumed(int vx, int vy) {
+ mTouchScrollInProgress = false;
+ mPotentiallyActiveFlingCount++;
+ temporarilyHideTextHandles();
+ for (mGestureStateListenersIterator.rewind();
+ mGestureStateListenersIterator.hasNext();) {
+ mGestureStateListenersIterator.next().onFlingStartGesture(
+ vx, vy, computeVerticalScrollOffset(), computeVerticalScrollExtent());
+ }
}
@SuppressWarnings("unused")
@CalledByNative
- private void confirmTouchEvent(int ackResult) {
- mContentViewGestureHandler.confirmTouchEvent(ackResult);
+ private void onFlingStartEventHadNoConsumer(int vx, int vy) {
+ mTouchScrollInProgress = false;
+ for (mGestureStateListenersIterator.rewind();
+ mGestureStateListenersIterator.hasNext();) {
+ mGestureStateListenersIterator.next().onUnhandledFlingStartEvent(vx, vy);
+ }
}
@SuppressWarnings("unused")
@CalledByNative
- private void onFlingStartEventAck(int ackResult) {
- if (ackResult == ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
- && mGestureStateListener != null) {
- mGestureStateListener.onUnhandledFlingStartEvent();
- }
- if (ackResult != ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_CONSUMED) {
- // No fling happened for the fling start event.
- // Cancel the fling status set when sending GestureFlingStart.
- getContentViewClient().onFlingStopped();
- }
+ private void onFlingCancelEventAck() {
+ updateGestureStateListener(GestureEventType.FLING_CANCEL);
}
@SuppressWarnings("unused")
@CalledByNative
private void onScrollBeginEventAck() {
- getContentViewClient().onScrollBeginEvent();
+ mTouchScrollInProgress = true;
+ temporarilyHideTextHandles();
+ mZoomControlsDelegate.invokeZoomPicker();
+ updateGestureStateListener(GestureEventType.SCROLL_START);
}
@SuppressWarnings("unused")
@CalledByNative
private void onScrollUpdateGestureConsumed() {
- if (mGestureStateListener != null) {
- mGestureStateListener.onScrollUpdateGestureConsumed();
+ mZoomControlsDelegate.invokeZoomPicker();
+ for (mGestureStateListenersIterator.rewind();
+ mGestureStateListenersIterator.hasNext();) {
+ mGestureStateListenersIterator.next().onScrollUpdateGestureConsumed();
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onScrollEndEventAck() {
- getContentViewClient().onScrollEndEvent();
+ if (!mTouchScrollInProgress) return;
+ mTouchScrollInProgress = false;
+ updateGestureStateListener(GestureEventType.SCROLL_END);
}
- private void reportActionAfterDoubleTapUMA(int type) {
- mContentViewGestureHandler.reportActionAfterDoubleTapUMA(type);
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void onPinchBeginEventAck() {
+ temporarilyHideTextHandles();
+ updateGestureStateListener(GestureEventType.PINCH_BEGIN);
}
- @Override
- public boolean sendGesture(int type, long timeMs, int x, int y, Bundle b) {
- if (offerGestureToEmbedder(type)) return false;
- if (mNativeContentViewCore == 0) return false;
- updateTextHandlesForGesture(type);
- updateGestureStateListener(type, b);
- switch (type) {
- case ContentViewGestureHandler.GESTURE_SHOW_PRESSED_STATE:
- nativeShowPressState(mNativeContentViewCore, timeMs, x, y);
- return true;
- case ContentViewGestureHandler.GESTURE_TAP_CANCEL:
- nativeTapCancel(mNativeContentViewCore, timeMs, x, y);
- return true;
- case ContentViewGestureHandler.GESTURE_TAP_DOWN:
- nativeTapDown(mNativeContentViewCore, timeMs, x, y);
- return true;
- case ContentViewGestureHandler.GESTURE_DOUBLE_TAP:
- nativeDoubleTap(mNativeContentViewCore, timeMs, x, y);
- return true;
- case ContentViewGestureHandler.GESTURE_SINGLE_TAP_UP:
- nativeSingleTap(mNativeContentViewCore, timeMs, x, y, false);
- return true;
- case ContentViewGestureHandler.GESTURE_SINGLE_TAP_CONFIRMED:
- handleTapOrPress(timeMs, x, y, 0,
- b.getBoolean(ContentViewGestureHandler.SHOW_PRESS, false));
- return true;
- case ContentViewGestureHandler.GESTURE_SINGLE_TAP_UNCONFIRMED:
- nativeSingleTapUnconfirmed(mNativeContentViewCore, timeMs, x, y);
- return true;
- case ContentViewGestureHandler.GESTURE_LONG_PRESS:
- handleTapOrPress(timeMs, x, y, IS_LONG_PRESS, false);
- return true;
- case ContentViewGestureHandler.GESTURE_LONG_TAP:
- handleTapOrPress(timeMs, x, y, IS_LONG_TAP, false);
- return true;
- case ContentViewGestureHandler.GESTURE_SCROLL_START: {
- int dx = b.getInt(ContentViewGestureHandler.DELTA_HINT_X);
- int dy = b.getInt(ContentViewGestureHandler.DELTA_HINT_Y);
- nativeScrollBegin(mNativeContentViewCore, timeMs, x, y, dx, dy);
- return true;
- }
- case ContentViewGestureHandler.GESTURE_SCROLL_BY: {
- int dx = b.getInt(ContentViewGestureHandler.DISTANCE_X);
- int dy = b.getInt(ContentViewGestureHandler.DISTANCE_Y);
- nativeScrollBy(mNativeContentViewCore, timeMs, x, y, dx, dy);
- return true;
- }
- case ContentViewGestureHandler.GESTURE_SCROLL_END:
- nativeScrollEnd(mNativeContentViewCore, timeMs);
- return true;
- case ContentViewGestureHandler.GESTURE_FLING_START:
- mContentViewClient.onFlingStarted();
- nativeFlingStart(mNativeContentViewCore, timeMs, x, y,
- b.getInt(ContentViewGestureHandler.VELOCITY_X, 0),
- b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0));
- return true;
- case ContentViewGestureHandler.GESTURE_FLING_CANCEL:
- nativeFlingCancel(mNativeContentViewCore, timeMs);
- return true;
- case ContentViewGestureHandler.GESTURE_PINCH_BEGIN:
- nativePinchBegin(mNativeContentViewCore, timeMs, x, y);
- return true;
- case ContentViewGestureHandler.GESTURE_PINCH_BY:
- nativePinchBy(mNativeContentViewCore, timeMs, x, y,
- b.getFloat(ContentViewGestureHandler.DELTA, 0));
- return true;
- case ContentViewGestureHandler.GESTURE_PINCH_END:
- nativePinchEnd(mNativeContentViewCore, timeMs);
- return true;
- default:
- return false;
- }
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void onPinchEndEventAck() {
+ updateGestureStateListener(GestureEventType.PINCH_END);
}
- @VisibleForTesting
- public void sendDoubleTapForTest(long timeMs, int x, int y, Bundle b) {
- sendGesture(ContentViewGestureHandler.GESTURE_DOUBLE_TAP, timeMs, x, y, b);
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void onDoubleTapEventAck() {
+ temporarilyHideTextHandles();
}
- @Override
- public void sendSingleTapUMA(int type) {
- if (mNativeContentViewCore == 0) return;
- nativeSendSingleTapUma(
- mNativeContentViewCore,
- type,
- UMASingleTapType.COUNT);
+ /**
+ * Called just prior to a tap or press gesture being forwarded to the renderer.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private boolean filterTapOrPressEvent(int type, int x, int y) {
+ if (type == GestureEventType.LONG_PRESS && offerLongPressToEmbedder()) {
+ return true;
+ }
+ updateForTapOrPress(type, x, y);
+ return false;
}
- @Override
- public void sendActionAfterDoubleTapUMA(int type,
- boolean clickDelayEnabled) {
+ @VisibleForTesting
+ public void sendDoubleTapForTest(long timeMs, int x, int y) {
if (mNativeContentViewCore == 0) return;
- nativeSendActionAfterDoubleTapUma(
- mNativeContentViewCore,
- type,
- clickDelayEnabled,
- UMAActionAfterDoubleTap.COUNT);
+ nativeDoubleTap(mNativeContentViewCore, timeMs, x, y);
}
- public void setGestureStateListener(GestureStateListener pinchGestureStateListener) {
- mGestureStateListener = pinchGestureStateListener;
- }
-
- void updateGestureStateListener(int gestureType, Bundle b) {
- if (mGestureStateListener == null) return;
-
- switch (gestureType) {
- case ContentViewGestureHandler.GESTURE_PINCH_BEGIN:
- mGestureStateListener.onPinchGestureStart();
- break;
- case ContentViewGestureHandler.GESTURE_PINCH_END:
- mGestureStateListener.onPinchGestureEnd();
- break;
- case ContentViewGestureHandler.GESTURE_FLING_START:
- mGestureStateListener.onFlingStartGesture(
- b.getInt(ContentViewGestureHandler.VELOCITY_X, 0),
- b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0));
- break;
- case ContentViewGestureHandler.GESTURE_FLING_CANCEL:
- mGestureStateListener.onFlingCancelGesture();
- break;
- default:
- break;
+ @VisibleForTesting
+ public void flingForTest(long timeMs, int x, int y, int velocityX, int velocityY) {
+ if (mNativeContentViewCore == 0) return;
+ nativeFlingCancel(mNativeContentViewCore, timeMs);
+ nativeScrollBegin(mNativeContentViewCore, timeMs, x, y, velocityX, velocityY);
+ nativeFlingStart(mNativeContentViewCore, timeMs, x, y, velocityX, velocityY);
+ }
+
+ /**
+ * Add a listener that gets alerted on gesture state changes.
+ * @param listener Listener to add.
+ */
+ public void addGestureStateListener(GestureStateListener listener) {
+ mGestureStateListeners.addObserver(listener);
+ }
+
+ /**
+ * Removes a listener that was added to watch for gesture state changes.
+ * @param listener Listener to remove.
+ */
+ public void removeGestureStateListener(GestureStateListener listener) {
+ mGestureStateListeners.removeObserver(listener);
+ }
+
+ void updateGestureStateListener(int gestureType) {
+ for (mGestureStateListenersIterator.rewind();
+ mGestureStateListenersIterator.hasNext();) {
+ GestureStateListener listener = mGestureStateListenersIterator.next();
+ switch (gestureType) {
+ case GestureEventType.PINCH_BEGIN:
+ listener.onPinchStarted();
+ break;
+ case GestureEventType.PINCH_END:
+ listener.onPinchEnded();
+ break;
+ case GestureEventType.FLING_END:
+ listener.onFlingEndGesture(
+ computeVerticalScrollOffset(),
+ computeVerticalScrollExtent());
+ break;
+ case GestureEventType.FLING_CANCEL:
+ listener.onFlingCancelGesture();
+ break;
+ case GestureEventType.SCROLL_START:
+ listener.onScrollStarted(
+ computeVerticalScrollOffset(),
+ computeVerticalScrollExtent());
+ break;
+ case GestureEventType.SCROLL_END:
+ listener.onScrollEnded(
+ computeVerticalScrollOffset(),
+ computeVerticalScrollExtent());
+ break;
+ default:
+ break;
+ }
}
}
public void onShow() {
assert mNativeContentViewCore != 0;
if (!mInForeground) {
- int pid = nativeGetCurrentRenderProcessId(mNativeContentViewCore);
- ChildProcessLauncher.getBindingManager().setInForeground(pid, true);
+ ChildProcessLauncher.getBindingManager().setInForeground(getCurrentRenderProcessId(),
+ true);
}
mInForeground = true;
nativeOnShow(mNativeContentViewCore);
}
/**
+ * @return The ID of the renderer process that backs this tab or
+ * {@link #INVALID_RENDER_PROCESS_PID} if there is none.
+ */
+ public int getCurrentRenderProcessId() {
+ return nativeGetCurrentRenderProcessId(mNativeContentViewCore);
+ }
+
+ /**
* To be called when the ContentView is hidden.
*/
public void onHide() {
assert mNativeContentViewCore != 0;
if (mInForeground) {
- int pid = nativeGetCurrentRenderProcessId(mNativeContentViewCore);
- ChildProcessLauncher.getBindingManager().setInForeground(pid, false);
+ ChildProcessLauncher.getBindingManager().setInForeground(getCurrentRenderProcessId(),
+ false);
}
mInForeground = false;
hidePopupDialog();
}
private void onRenderCoordinatesUpdated() {
- if (mContentViewGestureHandler == null) return;
+ if (mNativeContentViewCore == 0) return;
// We disable double tap zoom for pages that have a width=device-width
// or narrower viewport (indicating that this is a mobile-optimized or
// responsive web design, so text will be legible without zooming).
// We also disable it for pages that disallow the user from zooming in
// or out (even if they don't have a device-width or narrower viewport).
- mContentViewGestureHandler.updateShouldDisableDoubleTap(
- mRenderCoordinates.hasMobileViewport() || mRenderCoordinates.hasFixedPageScale());
+ nativeSetDoubleTapSupportForPageEnabled(mNativeContentViewCore,
+ !mRenderCoordinates.hasMobileViewport() && !mRenderCoordinates.hasFixedPageScale());
}
private void hidePopupDialog() {
- SelectPopupDialog.hide(this);
+ if (mSelectPopupDialog != null) {
+ mSelectPopupDialog.hide();
+ mSelectPopupDialog = null;
+ }
hideHandles();
hideSelectActionBar();
}
}
private void resetGestureDetectors() {
- mContentViewGestureHandler.resetGestureHandlers();
+ if (mNativeContentViewCore == 0) return;
+ nativeResetGestureDetectors(mNativeContentViewCore);
}
/**
@SuppressWarnings("javadoc")
public void onAttachedToWindow() {
setAccessibilityState(mAccessibilityManager.isEnabled());
+
+ ScreenOrientationListener.getInstance().addObserver(this, mContext);
}
/**
* @see View#onDetachedFromWindow()
*/
@SuppressWarnings("javadoc")
+ @SuppressLint("MissingSuperCall")
public void onDetachedFromWindow() {
setInjectedAccessibility(false);
hidePopupDialog();
mZoomControlsDelegate.dismissZoomPicker();
unregisterAccessibilityContentObserver();
+
+ ScreenOrientationListener.getInstance().removeObserver(this);
}
/**
// enter fullscreen mode.
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
}
- mInputConnection =
- mAdapterInputConnectionFactory.get(mContainerView, mImeAdapter, outAttrs);
+ mInputConnection = mAdapterInputConnectionFactory.get(mContainerView, mImeAdapter,
+ mEditable, outAttrs);
+ return mInputConnection;
+ }
+
+ @VisibleForTesting
+ public AdapterInputConnection getAdapterInputConnectionForTest() {
return mInputConnection;
}
+ @VisibleForTesting
public Editable getEditableForTest() {
- return mInputConnection.getEditable();
+ return mEditable;
}
/**
if (newConfig.keyboard != Configuration.KEYBOARD_NOKEYS) {
mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore),
- ImeAdapter.getTextInputTypeNone(),
- AdapterInputConnection.INVALID_SELECTION,
- AdapterInputConnection.INVALID_SELECTION);
- InputMethodManager manager = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- manager.restartInput(mContainerView);
+ ImeAdapter.getTextInputTypeNone());
+ mInputMethodManagerWrapper.restartInput(mContainerView);
}
mContainerViewInternals.super_onConfigurationChanged(newConfig);
- // Make sure the size is up to date in JavaScript's window.onorientationchanged.
- mContainerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- mContainerView.removeOnLayoutChangeListener(this);
- sendOrientationChangeEvent();
- }
- });
+
// To request layout has side effect, but it seems OK as it only happen in
// onConfigurationChange and layout has to be changed in most case.
mContainerView.requestLayout();
if (rect.width() == mFocusPreOSKViewportRect.width()) {
scrollFocusedEditableNodeIntoView();
}
- mFocusPreOSKViewportRect.setEmpty();
+ cancelRequestToScrollFocusedEditableNodeIntoView();
}
- } else if (mUnfocusOnNextSizeChanged) {
- undoScrollFocusedEditableNodeIntoViewIfNeeded(true);
- mUnfocusOnNextSizeChanged = false;
}
}
- private void scrollFocusedEditableNodeIntoView() {
- if (mNativeContentViewCore != 0) {
- Runnable scrollTask = new Runnable() {
- @Override
- public void run() {
- if (mNativeContentViewCore != 0) {
- nativeScrollFocusedEditableNodeIntoView(mNativeContentViewCore);
- }
- }
- };
-
- scrollTask.run();
-
- // The native side keeps track of whether the zoom and scroll actually occurred. It is
- // more efficient to do it this way and sometimes fire an unnecessary message rather
- // than synchronize with the renderer and always have an additional message.
- mScrolledAndZoomedFocusedEditableNode = true;
- }
+ private void cancelRequestToScrollFocusedEditableNodeIntoView() {
+ // Zero-ing the rect will prevent |updateAfterSizeChanged()| from
+ // issuing the delayed form focus event.
+ mFocusPreOSKViewportRect.setEmpty();
}
- private void undoScrollFocusedEditableNodeIntoViewIfNeeded(boolean backButtonPressed) {
- // The only call to this function that matters is the first call after the
- // scrollFocusedEditableNodeIntoView function call.
- // If the first call to this function is a result of a back button press we want to undo the
- // preceding scroll. If the call is a result of some other action we don't want to perform
- // an undo.
- // All subsequent calls are ignored since only the scroll function sets
- // mScrolledAndZoomedFocusedEditableNode to true.
- if (mScrolledAndZoomedFocusedEditableNode && backButtonPressed &&
- mNativeContentViewCore != 0) {
- Runnable scrollTask = new Runnable() {
- @Override
- public void run() {
- if (mNativeContentViewCore != 0) {
- nativeUndoScrollFocusedEditableNodeIntoView(mNativeContentViewCore);
- }
- }
- };
-
- scrollTask.run();
- }
- mScrolledAndZoomedFocusedEditableNode = false;
+ private void scrollFocusedEditableNodeIntoView() {
+ if (mNativeContentViewCore == 0) return;
+ // The native side keeps track of whether the zoom and scroll actually occurred. It is
+ // more efficient to do it this way and sometimes fire an unnecessary message rather
+ // than synchronize with the renderer and always have an additional message.
+ nativeScrollFocusedEditableNodeIntoView(mNativeContentViewCore);
}
/**
*/
public void onWindowFocusChanged(boolean hasWindowFocus) {
if (!hasWindowFocus) {
- mContentViewGestureHandler.onWindowFocusLost();
+ if (mNativeContentViewCore == 0) return;
+ nativeOnWindowFocusLost(mNativeContentViewCore);
}
}
public void onFocusChanged(boolean gainFocus) {
- if (!gainFocus) getContentViewClient().onImeStateChangeRequested(false);
+ if (!gainFocus) {
+ hideImeIfNeeded();
+ }
if (mNativeContentViewCore != 0) nativeSetFocus(mNativeContentViewCore, gainFocus);
}
public boolean dispatchKeyEventPreIme(KeyEvent event) {
try {
TraceEvent.begin();
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && mImeAdapter.isActive()) {
- mUnfocusOnNextSizeChanged = true;
- } else {
- undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
- }
return mContainerViewInternals.super_dispatchKeyEventPreIme(event);
} finally {
TraceEvent.end();
*/
public boolean onHoverEvent(MotionEvent event) {
TraceEvent.begin("onHoverEvent");
- mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
+
if (mBrowserAccessibilityManager != null) {
return mBrowserAccessibilityManager.onHoverEvent(event);
}
+
+ // Work around Android bug where the x, y coordinates of a hover exit
+ // event are incorrect when touch exploration is on.
+ if (mTouchExplorationEnabled && event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
+ return true;
+ }
+
+ mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
if (mNativeContentViewCore != 0) {
nativeSendMouseMoveEvent(mNativeContentViewCore, event.getEventTime(),
event.getX(), event.getY());
public void scrollBy(int xPix, int yPix) {
if (mNativeContentViewCore != 0) {
nativeScrollBy(mNativeContentViewCore,
- System.currentTimeMillis(), 0, 0, xPix, yPix);
+ SystemClock.uptimeMillis(), 0, 0, xPix, yPix);
}
}
final float dxPix = xPix - xCurrentPix;
final float dyPix = yPix - yCurrentPix;
if (dxPix != 0 || dyPix != 0) {
- long time = System.currentTimeMillis();
+ long time = SystemClock.uptimeMillis();
nativeScrollBegin(mNativeContentViewCore, time,
xCurrentPix, yCurrentPix, -dxPix, -dyPix);
nativeScrollBy(mNativeContentViewCore,
}
}
- private void handleTapOrPress(
- long timeMs, float xPix, float yPix, int isLongPressOrTap, boolean showPress) {
+ private void updateForTapOrPress(int type, float xPix, float yPix) {
+ if (type != GestureEventType.SINGLE_TAP_CONFIRMED
+ && type != GestureEventType.SINGLE_TAP_UP
+ && type != GestureEventType.LONG_PRESS
+ && type != GestureEventType.LONG_TAP) {
+ return;
+ }
+
if (mContainerView.isFocusable() && mContainerView.isFocusableInTouchMode()
&& !mContainerView.isFocused()) {
mContainerView.requestFocus();
if (!mPopupZoomer.isShowing()) mPopupZoomer.setLastTouch(xPix, yPix);
- if (isLongPressOrTap == IS_LONG_PRESS) {
- getInsertionHandleController().allowAutomaticShowing();
- getSelectionHandleController().allowAutomaticShowing();
- if (mNativeContentViewCore != 0) {
- nativeLongPress(mNativeContentViewCore, timeMs, xPix, yPix, false);
- }
- } else if (isLongPressOrTap == IS_LONG_TAP) {
+ if (type == GestureEventType.LONG_PRESS
+ || type == GestureEventType.LONG_TAP) {
getInsertionHandleController().allowAutomaticShowing();
getSelectionHandleController().allowAutomaticShowing();
- if (mNativeContentViewCore != 0) {
- nativeLongTap(mNativeContentViewCore, timeMs, xPix, yPix, false);
- }
} else {
- if (!showPress && mNativeContentViewCore != 0) {
- nativeShowPressState(mNativeContentViewCore, timeMs, xPix, yPix);
- }
+ setClickXAndY((int) xPix, (int) yPix);
if (mSelectionEditable) getInsertionHandleController().allowAutomaticShowing();
- if (mNativeContentViewCore != 0) {
- nativeSingleTap(mNativeContentViewCore, timeMs, xPix, yPix, false);
- }
}
}
+ private void setClickXAndY(int x, int y) {
+ mSingleTapX = x;
+ mSingleTapY = y;
+ }
+
+ /**
+ * @return The x coordinate for the last point that a singleTap gesture was initiated from.
+ */
+ public int getSingleTapX() {
+ return mSingleTapX;
+ }
+
+ /**
+ * @return The y coordinate for the last point that a singleTap gesture was initiated from.
+ */
+ public int getSingleTapY() {
+ return mSingleTapY;
+ }
+
public void setZoomControlsDelegate(ZoomControlsDelegate zoomControlsDelegate) {
mZoomControlsDelegate = zoomControlsDelegate;
}
public void updateMultiTouchZoomSupport(boolean supportsMultiTouchZoom) {
- mZoomManager.updateMultiTouchSupport(supportsMultiTouchZoom);
+ if (mNativeContentViewCore == 0) return;
+ nativeSetMultiTouchZoomSupportEnabled(mNativeContentViewCore, supportsMultiTouchZoom);
}
public void updateDoubleTapSupport(boolean supportsDoubleTap) {
- mContentViewGestureHandler.updateDoubleTapSupport(supportsDoubleTap);
+ if (mNativeContentViewCore == 0) return;
+ nativeSetDoubleTapSupportEnabled(mNativeContentViewCore, supportsDoubleTap);
}
public void selectPopupMenuItems(int[] indices) {
if (mNativeContentViewCore != 0) {
nativeSelectPopupMenuItems(mNativeContentViewCore, indices);
}
+ mSelectPopupDialog = null;
}
/**
- * Get the screen orientation from the OS and push it to WebKit.
- *
- * TODO(husky): Add a hook for mock orientations.
+ * Send the screen orientation value to the renderer.
*/
- private void sendOrientationChangeEvent() {
+ @VisibleForTesting
+ void sendOrientationChangeEvent(int orientation) {
if (mNativeContentViewCore == 0) return;
- WindowManager windowManager =
- (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
- switch (windowManager.getDefaultDisplay().getRotation()) {
- case Surface.ROTATION_90:
- nativeSendOrientationChangeEvent(mNativeContentViewCore, 90);
- break;
- case Surface.ROTATION_180:
- nativeSendOrientationChangeEvent(mNativeContentViewCore, 180);
- break;
- case Surface.ROTATION_270:
- nativeSendOrientationChangeEvent(mNativeContentViewCore, -90);
- break;
- case Surface.ROTATION_0:
- nativeSendOrientationChangeEvent(mNativeContentViewCore, 0);
- break;
- default:
- Log.w(TAG, "Unknown rotation!");
- break;
- }
+ nativeSendOrientationChangeEvent(mNativeContentViewCore, orientation);
}
/**
return mInsertionHandleController != null && mInsertionHandleController.isShowing();
}
- private void updateTextHandlesForGesture(int type) {
- switch(type) {
- case ContentViewGestureHandler.GESTURE_DOUBLE_TAP:
- case ContentViewGestureHandler.GESTURE_SCROLL_START:
- case ContentViewGestureHandler.GESTURE_FLING_START:
- case ContentViewGestureHandler.GESTURE_PINCH_BEGIN:
- temporarilyHideTextHandles();
- break;
-
- default:
- break;
- }
- }
-
// Makes the insertion/selection handles invisible. They will fade back in shortly after the
// last call to scheduleTextHandleFadeIn (or temporarilyHideTextHandles).
private void temporarilyHideTextHandles() {
}
private boolean allowTextHandleFadeIn() {
- if (mContentViewGestureHandler.isNativeScrolling() ||
- mContentViewGestureHandler.isNativePinching()) {
- return false;
- }
+ if (mTouchScrollInProgress) return false;
if (mPopupZoomer.isShowing()) return false;
if (mNativeContentViewCore != 0) nativeShowImeIfNeeded(mNativeContentViewCore);
}
+ /**
+ * Hides the IME if the containerView is the active view for IME.
+ */
+ public void hideImeIfNeeded() {
+ // Hide input method window from the current view synchronously
+ // because ImeAdapter does so asynchronouly with a delay, and
+ // by the time when ImeAdapter dismisses the input, the
+ // containerView may have lost focus.
+ // We cannot trust ContentViewClient#onImeStateChangeRequested to
+ // hide the input window because it has an empty default implementation.
+ // So we need to explicitly hide the input method window here.
+ if (mInputMethodManagerWrapper.isActive(mContainerView)) {
+ mInputMethodManagerWrapper.hideSoftInputFromWindow(
+ mContainerView.getWindowToken(), 0, null);
+ }
+ getContentViewClient().onImeStateChangeRequested(false);
+ }
+
@SuppressWarnings("unused")
@CalledByNative
private void updateFrameInfo(
TraceEvent.instant("ContentViewCore:updateFrameInfo");
// Adjust contentWidth/Height to be always at least as big as
// the actual viewport (as set by onSizeChanged).
+ final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
contentWidth = Math.max(contentWidth,
- mRenderCoordinates.fromPixToLocalCss(mViewportWidthPix));
+ mViewportWidthPix / (deviceScale * pageScaleFactor));
contentHeight = Math.max(contentHeight,
- mRenderCoordinates.fromPixToLocalCss(mViewportHeightPix));
-
+ mViewportHeightPix / (deviceScale * pageScaleFactor));
final float contentOffsetYPix = mRenderCoordinates.fromDipToPix(contentOffsetYCss);
final boolean contentSizeChanged =
onRenderCoordinatesUpdated();
if (scrollChanged || contentOffsetChanged) {
- getContentViewClient().onScrollOrViewportChanged();
+ for (mGestureStateListenersIterator.rewind();
+ mGestureStateListenersIterator.hasNext();) {
+ mGestureStateListenersIterator.next().onScrollOffsetOrExtentChanged(
+ computeVerticalScrollOffset(),
+ computeVerticalScrollExtent());
+ }
}
if (needTemporarilyHideHandles) temporarilyHideTextHandles();
if (contentOffsetChanged) updateHandleScreenPositions();
// Update offsets for fullscreen.
- final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
final float controlsOffsetPix = controlsOffsetYCss * deviceScale;
final float overdrawBottomHeightPix = overdrawBottomHeightCss * deviceScale;
getContentViewClient().onOffsetsForFullscreenChanged(
if (mBrowserAccessibilityManager != null) {
mBrowserAccessibilityManager.notifyFrameInfoInitialized();
}
-
- // Update geometry for external video surface.
- getContentViewClient().onGeometryChanged(-1, null);
}
@CalledByNative
- private void updateImeAdapter(int nativeImeAdapterAndroid, int textInputType,
+ private void updateImeAdapter(long nativeImeAdapterAndroid, int textInputType,
String text, int selectionStart, int selectionEnd,
int compositionStart, int compositionEnd, boolean showImeIfNeeded, boolean requireAck) {
TraceEvent.begin();
if (mActionMode != null) mActionMode.invalidate();
- mImeAdapter.attachAndShowIfNeeded(nativeImeAdapterAndroid, textInputType,
- selectionStart, selectionEnd, showImeIfNeeded);
+ mImeAdapter.attachAndShowIfNeeded(nativeImeAdapterAndroid, textInputType, showImeIfNeeded);
if (mInputConnection != null) {
mInputConnection.updateState(text, selectionStart, selectionEnd, compositionStart,
@CalledByNative
private void showSelectPopup(String[] items, int[] enabled, boolean multiple,
int[] selectedIndices) {
+ if (mContainerView.getParent() == null || mContainerView.getVisibility() != View.VISIBLE) {
+ selectPopupMenuItems(null);
+ return;
+ }
+
+ if (mSelectPopupDialog != null) {
+ mSelectPopupDialog.hide();
+ mSelectPopupDialog = null;
+ }
assert items.length == enabled.length;
List<SelectPopupItem> popupItems = new ArrayList<SelectPopupItem>();
for (int i = 0; i < items.length; i++) {
popupItems.add(new SelectPopupItem(items[i], enabled[i]));
}
- SelectPopupDialog.show(this, popupItems, multiple, selectedIndices);
+ mSelectPopupDialog = SelectPopupDialog.show(this, popupItems, multiple, selectedIndices);
+ }
+
+ /**
+ * @return The visible select popup dialog being shown.
+ */
+ public SelectPopupDialog getSelectPopupForTest() {
+ return mSelectPopupDialog;
}
@SuppressWarnings("unused")
@CalledByNative
private void onSelectionChanged(String text) {
mLastSelectedText = text;
+ getContentViewClient().onSelectionChanged(text);
}
@SuppressWarnings("unused")
getInsertionHandleController().onCursorPositionChanged();
updateHandleScreenPositions();
- InputMethodManager manager = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- if (manager.isWatchingCursor(mContainerView)) {
+ if (mInputMethodManagerWrapper.isWatchingCursor(mContainerView)) {
final int xPix = (int) mInsertionHandlePoint.getXPix();
final int yPix = (int) mInsertionHandlePoint.getYPix();
- manager.updateCursor(mContainerView, xPix, yPix, xPix, yPix);
+ mInputMethodManagerWrapper.updateCursor(
+ mContainerView, xPix, yPix, xPix, yPix);
}
} else {
// Deselection
* @return whether the gesture was sent.
*/
public boolean pinchByDelta(float delta) {
- if (mNativeContentViewCore == 0) {
- return false;
- }
+ if (mNativeContentViewCore == 0) return false;
- long timeMs = System.currentTimeMillis();
+ long timeMs = SystemClock.uptimeMillis();
int xPix = getViewportWidthPix() / 2;
int yPix = getViewportHeightPix() / 2;
- getContentViewGestureHandler().pinchBegin(timeMs, xPix, yPix);
- getContentViewGestureHandler().pinchBy(timeMs, xPix, yPix, delta);
- getContentViewGestureHandler().pinchEnd(timeMs);
+ nativePinchBegin(mNativeContentViewCore, timeMs, xPix, yPix);
+ nativePinchBy(mNativeContentViewCore, timeMs, xPix, yPix, delta);
+ nativePinchEnd(mNativeContentViewCore, timeMs);
return true;
}
/**
* Invokes the graphical zoom picker widget for this ContentView.
*/
- @Override
public void invokeZoomPicker() {
mZoomControlsDelegate.invokeZoomPicker();
}
/**
+ * Enables or disables inspection of JavaScript objects added via
+ * {@link #addJavascriptInterface(Object, String)} by means of Object.keys() method and
+ * "for .. in" loop. Being able to inspect JavaScript objects is useful
+ * when debugging hybrid Android apps, but can't be enabled for legacy applications due
+ * to compatibility risks.
+ *
+ * @param allow Whether to allow JavaScript objects inspection.
+ */
+ public void setAllowJavascriptInterfacesInspection(boolean allow) {
+ nativeSetAllowJavascriptInterfacesInspection(mNativeContentViewCore, allow);
+ }
+
+ /**
* This will mimic {@link #addPossiblyUnsafeJavascriptInterface(Object, String, Class)}
* and automatically pass in {@link JavascriptInterface} as the required annotation.
*
*/
public boolean isDeviceAccessibilityScriptInjectionEnabled() {
try {
- if (CommandLine.getInstance().hasSwitch(
- ContentSwitches.DISABLE_ACCESSIBILITY_SCRIPT_INJECTION)) {
+ // On JellyBean and higher, native accessibility is the default so script
+ // injection is only allowed if enabled via a flag.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
+ !CommandLine.getInstance().hasSwitch(
+ ContentSwitches.ENABLE_ACCESSIBILITY_SCRIPT_INJECTION)) {
return false;
}
}
/**
+ * Returns true if accessibility is on and touch exploration is enabled.
+ */
+ public boolean isTouchExplorationEnabled() {
+ return mTouchExplorationEnabled;
+ }
+
+ /**
* Turns browser accessibility on or off.
* If |state| is |false|, this turns off both native and injected accessibility.
* Otherwise, if accessibility script injection is enabled, this will enable the injected
if (!state) {
setInjectedAccessibility(false);
mNativeAccessibilityAllowed = false;
+ mTouchExplorationEnabled = false;
} else {
boolean useScriptInjection = isDeviceAccessibilityScriptInjectionEnabled();
setInjectedAccessibility(useScriptInjection);
mNativeAccessibilityAllowed = !useScriptInjection;
+ mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
}
}
return new Rect(x, y, right, bottom);
}
- public void attachExternalVideoSurface(int playerId, Surface surface) {
- if (mNativeContentViewCore != 0) {
- nativeAttachExternalVideoSurface(mNativeContentViewCore, playerId, surface);
- }
- }
-
- public void detachExternalVideoSurface(int playerId) {
- if (mNativeContentViewCore != 0) {
- nativeDetachExternalVideoSurface(mNativeContentViewCore, playerId);
- }
- }
-
private boolean onAnimate(long frameTimeMicros) {
if (mNativeContentViewCore == 0) return false;
return nativeOnAnimate(mNativeContentViewCore, frameTimeMicros);
}
}
- @CalledByNative
- private void notifyExternalSurface(
- int playerId, boolean isRequest, float x, float y, float width, float height) {
- if (isRequest) getContentViewClient().onExternalVideoSurfaceRequested(playerId);
- getContentViewClient().onGeometryChanged(playerId, new RectF(x, y, x + width, y + height));
- }
-
public void extractSmartClipData(int x, int y, int width, int height) {
if (mNativeContentViewCore != 0) {
nativeExtractSmartClipData(mNativeContentViewCore, x, y, width, height);
}
/**
- * Offer a subset of gesture events to the embedding View,
- * primarily for WebView compatibility.
- *
- * @param type The type of the event.
+ * Offer a long press gesture to the embedding View, primarily for WebView compatibility.
*
* @return true if the embedder handled the event.
*/
- private boolean offerGestureToEmbedder(int type) {
- if (type == ContentViewGestureHandler.GESTURE_LONG_PRESS) {
- return mContainerView.performLongClick();
- }
- return false;
+ private boolean offerLongPressToEmbedder() {
+ return mContainerView.performLongClick();
+ }
+
+ /**
+ * Reset scroll and fling accounting, notifying listeners as appropriate.
+ * This is useful as a failsafe when the input stream may have been interruped.
+ */
+ private void resetScrollInProgress() {
+ if (!isScrollInProgress()) return;
+
+ final boolean touchScrollInProgress = mTouchScrollInProgress;
+ final int potentiallyActiveFlingCount = mPotentiallyActiveFlingCount;
+
+ mTouchScrollInProgress = false;
+ mPotentiallyActiveFlingCount = 0;
+
+ if (touchScrollInProgress) updateGestureStateListener(GestureEventType.SCROLL_END);
+ if (potentiallyActiveFlingCount > 0) updateGestureStateListener(GestureEventType.FLING_END);
}
private native long nativeInit(long webContentsPtr,
@CalledByNative
private void onNativeFlingStopped() {
- getContentViewClient().onFlingStopped();
+ // Note that mTouchScrollInProgress should normally be false at this
+ // point, but we reset it anyway as another failsafe.
+ mTouchScrollInProgress = false;
+ if (mPotentiallyActiveFlingCount <= 0) return;
+ mPotentiallyActiveFlingCount--;
+ updateGestureStateListener(GestureEventType.FLING_END);
+ }
+
+ @Override
+ public void onScreenOrientationChanged(int orientation) {
+ sendOrientationChangeEvent(orientation);
}
private native WebContents nativeGetWebContentsAndroid(long nativeContentViewCoreImpl);
long nativeContentViewCoreImpl, int orientation);
// All touch events (including flings, scrolls etc) accept coordinates in physical pixels.
- private native boolean nativeSendTouchEvent(
- long nativeContentViewCoreImpl, long timeMs, int action, TouchPoint[] pts);
+ private native boolean nativeOnTouchEvent(
+ long nativeContentViewCoreImpl, MotionEvent event,
+ long timeMs, int action, int pointerCount, int historySize, int actionIndex,
+ float x0, float y0, float x1, float y1,
+ int pointerId0, int pointerId1,
+ float touchMajor0, float touchMajor1);
private native int nativeSendMouseMoveEvent(
long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeFlingCancel(long nativeContentViewCoreImpl, long timeMs);
private native void nativeSingleTap(
- long nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap);
-
- private native void nativeSingleTapUnconfirmed(
- long nativeContentViewCoreImpl, long timeMs, float x, float y);
-
- private native void nativeShowPressState(
- long nativeContentViewCoreImpl, long timeMs, float x, float y);
-
- private native void nativeTapCancel(
- long nativeContentViewCoreImpl, long timeMs, float x, float y);
-
- private native void nativeTapDown(
long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeDoubleTap(
long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeLongPress(
- long nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap);
-
- private native void nativeLongTap(
- long nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap);
+ long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativePinchBegin(
long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeMoveCaret(long nativeContentViewCoreImpl, float x, float y);
+ private native void nativeResetGestureDetectors(long nativeContentViewCoreImpl);
+
+ private native void nativeIgnoreRemainingTouchEvents(long nativeContentViewCoreImpl);
+
+ private native void nativeOnWindowFocusLost(long nativeContentViewCoreImpl);
+
+ private native void nativeSetDoubleTapSupportForPageEnabled(
+ long nativeContentViewCoreImpl, boolean enabled);
+ private native void nativeSetDoubleTapSupportEnabled(
+ long nativeContentViewCoreImpl, boolean enabled);
+ private native void nativeSetMultiTouchZoomSupportEnabled(
+ long nativeContentViewCoreImpl, boolean enabled);
+
private native void nativeLoadIfNecessary(long nativeContentViewCoreImpl);
private native void nativeRequestRestoreLoad(long nativeContentViewCoreImpl);
private native void nativeSelectPopupMenuItems(long nativeContentViewCoreImpl, int[] indices);
private native void nativeScrollFocusedEditableNodeIntoView(long nativeContentViewCoreImpl);
- private native void nativeUndoScrollFocusedEditableNodeIntoView(long nativeContentViewCoreImpl);
private native void nativeClearHistory(long nativeContentViewCoreImpl);
private native void nativeEvaluateJavaScript(long nativeContentViewCoreImpl,
String script, JavaScriptCallback callback, boolean startRenderer);
- private native int nativeGetNativeImeAdapter(long nativeContentViewCoreImpl);
+ private native long nativeGetNativeImeAdapter(long nativeContentViewCoreImpl);
private native int nativeGetCurrentRenderProcessId(long nativeContentViewCoreImpl);
private native void nativeClearSslPreferences(long nativeContentViewCoreImpl);
+ private native void nativeSetAllowJavascriptInterfacesInspection(
+ long nativeContentViewCoreImpl, boolean allow);
+
private native void nativeAddJavascriptInterface(long nativeContentViewCoreImpl, Object object,
String name, Class requiredAnnotation, HashSet<Object> retainedObjectSet);
private native boolean nativeOnAnimate(long nativeContentViewCoreImpl, long frameTimeMicros);
- private native boolean nativePopulateBitmapFromCompositor(long nativeContentViewCoreImpl,
- Bitmap bitmap);
-
private native void nativeWasResized(long nativeContentViewCoreImpl);
private native boolean nativeIsRenderWidgetHostViewReady(long nativeContentViewCoreImpl);
private native void nativeShowImeIfNeeded(long nativeContentViewCoreImpl);
- private native void nativeAttachExternalVideoSurface(
- long nativeContentViewCoreImpl, int playerId, Surface surface);
-
- private native void nativeDetachExternalVideoSurface(
- long nativeContentViewCoreImpl, int playerId);
-
private native void nativeSetAccessibilityEnabled(
long nativeContentViewCoreImpl, boolean enabled);
- private native void nativeSendSingleTapUma(long nativeContentViewCoreImpl,
- int type, int count);
-
- private native void nativeSendActionAfterDoubleTapUma(long nativeContentViewCoreImpl,
- int type, boolean hasDelay, int count);
-
private native void nativeExtractSmartClipData(long nativeContentViewCoreImpl,
int x, int y, int w, int h);
}