blob: b5bdeaa766d7e9f73204643ceeb0659a62766ae7 [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.annotation.SuppressLint;
import android.app.Activity;
import android.app.SearchManager;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
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.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import org.chromium.base.ApiCompatibilityUtils;
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.VisibleForTesting;
import org.chromium.content.R;
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.GamepadList;
import org.chromium.content.browser.input.ImeAdapter;
import org.chromium.content.browser.input.ImeAdapter.AdapterInputConnectionFactory;
import org.chromium.content.browser.input.InputMethodManagerWrapper;
import org.chromium.content.browser.input.PastePopupMenu;
import org.chromium.content.browser.input.PastePopupMenu.PastePopupMenuDelegate;
import org.chromium.content.browser.input.PopupTouchHandleDrawable;
import org.chromium.content.browser.input.PopupTouchHandleDrawable.PopupTouchHandleDrawableDelegate;
import org.chromium.content.browser.input.SelectPopup;
import org.chromium.content.browser.input.SelectPopupDialog;
import org.chromium.content.browser.input.SelectPopupDropdown;
import org.chromium.content.browser.input.SelectPopupItem;
import org.chromium.content.browser.input.SelectionEventType;
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.DeviceFormFactor;
import org.chromium.ui.base.ViewAndroid;
import org.chromium.ui.base.ViewAndroidDelegate;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.gfx.DeviceDisplayInfo;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Provides a Java-side 'wrapper' around a WebContent (native) instance.
* Contains all the major functionality necessary to manage the lifecycle of a ContentView without
* being tied to the view system.
*/
@JNINamespace("content")
public class ContentViewCore
implements AccessibilityStateChangeListener, ScreenOrientationObserver {
private static final String TAG = "ContentViewCore";
// Used to avoid enabling zooming in / out if resulting zooming will
// produce little visible difference.
private static final float ZOOM_CONTROLS_EPSILON = 0.007f;
// Used to represent gestures for long press and long tap.
private static final int IS_LONG_PRESS = 1;
private static final int IS_LONG_TAP = 2;
private static final ZoomControlsDelegate NO_OP_ZOOM_CONTROLS_DELEGATE =
new ZoomControlsDelegate() {
@Override
public void invokeZoomPicker() {}
@Override
public void dismissZoomPicker() {}
@Override
public void updateZoomControls() {}
};
// If the embedder adds a JavaScript interface object that contains an indirect reference to
// the ContentViewCore, then storing a strong ref to the interface object on the native
// side would prevent garbage collection of the ContentViewCore (as that strong ref would
// create a new GC root).
// For that reason, we store only a weak reference to the interface object on the
// native side. However we still need a strong reference on the Java side to
// prevent garbage collection if the embedder doesn't maintain their own ref to the
// interface object - the Java side ref won't create a new GC root.
// This map stores those references. We put into the map on addJavaScriptInterface()
// and remove from it in removeJavaScriptInterface(). The annotation class is stored for
// the purpose of migrating injected objects from one instance of CVC to another, which
// is used by Android WebView to support WebChromeClient.onCreateWindow scenario.
private final Map<String, Pair<Object, Class>> mJavaScriptInterfaces =
new HashMap<String, Pair<Object, Class>>();
// Additionally, we keep track of all Java bound JS objects that are in use on the
// current page to ensure that they are not garbage collected until the page is
// navigated. This includes interface objects that have been removed
// via the removeJavaScriptInterface API and transient objects returned from methods
// on the interface object. Note we use HashSet rather than Set as the native side
// expects HashSet (no bindings for interfaces).
private final HashSet<Object> mRetainedJavaScriptObjects = new HashSet<Object>();
/**
* A {@link ViewAndroidDelegate} that delegates to the current container view.
*
* <p>This delegate handles the replacement of container views transparently so
* that clients can safely hold to instances of this class.
*/
private class ContentViewAndroidDelegate implements ViewAndroidDelegate {
/**
* Represents the position of an anchor view.
*/
@VisibleForTesting
private class Position {
private final float mX;
private final float mY;
private final float mWidth;
private final float mHeight;
public Position(float x, float y, float width, float height) {
mX = x;
mY = y;
mWidth = width;
mHeight = height;
}
}
/**
* The current container view. This view can be updated with
* {@link #updateCurrentContainerView()}.
*/
private ViewGroup mCurrentContainerView;
/**
* List of anchor views stored in the order in which they were acquired mapped
* to their position.
*/
private Map<View, Position> mAnchorViews = new LinkedHashMap<View, Position>();
@Override
public View acquireAnchorView() {
View anchorView = new View(mContext);
mAnchorViews.put(anchorView, null);
mCurrentContainerView.addView(anchorView);
return anchorView;
}
@Override
public void setAnchorViewPosition(
View view, float x, float y, float width, float height) {
mAnchorViews.put(view, new Position(x, y, width, height));
doSetAnchorViewPosition(view, x, y, width, height);
}
@SuppressWarnings("deprecation") // AbsoluteLayout
private void doSetAnchorViewPosition(
View view, float x, float y, float width, float height) {
if (view.getParent() == null) {
// Ignore. setAnchorViewPosition has been called after the anchor view has
// already been released.
return;
}
assert view.getParent() == mCurrentContainerView;
float scale = (float) DeviceDisplayInfo.create(mContext).getDIPScale();
// The anchor view should not go outside the bounds of the ContainerView.
int leftMargin = Math.round(x * scale);
int topMargin = Math.round(mRenderCoordinates.getContentOffsetYPix() + y * scale);
int scaledWidth = Math.round(width * scale);
// ContentViewCore currently only supports these two container view types.
if (mCurrentContainerView instanceof FrameLayout) {
int startMargin;
if (ApiCompatibilityUtils.isLayoutRtl(mCurrentContainerView)) {
startMargin = mCurrentContainerView.getMeasuredWidth()
- Math.round((width + x) * scale);
} else {
startMargin = leftMargin;
}
if (scaledWidth + startMargin > mCurrentContainerView.getWidth()) {
scaledWidth = mCurrentContainerView.getWidth() - startMargin;
}
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
scaledWidth, Math.round(height * scale));
ApiCompatibilityUtils.setMarginStart(lp, startMargin);
lp.topMargin = topMargin;
view.setLayoutParams(lp);
} else if (mCurrentContainerView 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 mContainerViewAtCreation.getScroll[X/Y]()
// as it naturally accounts for scroll differences between
// these models.
leftMargin += mRenderCoordinates.getScrollXPixInt();
topMargin += mRenderCoordinates.getScrollYPixInt();
android.widget.AbsoluteLayout.LayoutParams lp =
new android.widget.AbsoluteLayout.LayoutParams(
scaledWidth, (int) (height * scale), leftMargin, topMargin);
view.setLayoutParams(lp);
} else {
Log.e(TAG, "Unknown layout " + mCurrentContainerView.getClass().getName());
}
}
@Override
public void releaseAnchorView(View anchorView) {
mAnchorViews.remove(anchorView);
mCurrentContainerView.removeView(anchorView);
}
/**
* Updates (or sets for the first time) the current container view to which
* this class delegates. Existing anchor views are transferred from the old to
* the new container view.
*/
void updateCurrentContainerView() {
ViewGroup oldContainerView = mCurrentContainerView;
mCurrentContainerView = mContainerView;
for (Entry<View, Position> entry : mAnchorViews.entrySet()) {
View anchorView = entry.getKey();
Position position = entry.getValue();
oldContainerView.removeView(anchorView);
mCurrentContainerView.addView(anchorView);
if (position != null) {
doSetAnchorViewPosition(anchorView,
position.mX, position.mY, position.mWidth, position.mHeight);
}
}
}
}
/**
* Interface that consumers of {@link ContentViewCore} must implement to allow the proper
* dispatching of view methods through the containing view.
*
* <p>
* All methods with the "super_" prefix should be routed to the parent of the
* implementing container view.
*/
@SuppressWarnings("javadoc")
public interface InternalAccessDelegate {
/**
* @see View#drawChild(Canvas, View, long)
*/
boolean drawChild(Canvas canvas, View child, long drawingTime);
/**
* @see View#onKeyUp(keyCode, KeyEvent)
*/
boolean super_onKeyUp(int keyCode, KeyEvent event);
/**
* @see View#dispatchKeyEventPreIme(KeyEvent)
*/
boolean super_dispatchKeyEventPreIme(KeyEvent event);
/**
* @see View#dispatchKeyEvent(KeyEvent)
*/
boolean super_dispatchKeyEvent(KeyEvent event);
/**
* @see View#onGenericMotionEvent(MotionEvent)
*/
boolean super_onGenericMotionEvent(MotionEvent event);
/**
* @see View#onConfigurationChanged(Configuration)
*/
void super_onConfigurationChanged(Configuration newConfig);
/**
* @see View#onScrollChanged(int, int, int, int)
*/
void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix);
/**
* @see View#awakenScrollBars()
*/
boolean awakenScrollBars();
/**
* @see View#awakenScrollBars(int, boolean)
*/
boolean super_awakenScrollBars(int startDelay, boolean invalidate);
}
/**
* An interface for controlling visibility and state of embedder-provided zoom controls.
*/
public interface ZoomControlsDelegate {
/**
* Called when it's reasonable to show zoom controls.
*/
void invokeZoomPicker();
/**
* Called when zoom controls need to be hidden (e.g. when the view hides).
*/
void dismissZoomPicker();
/**
* Called when page scale has been changed, so the controls can update their state.
*/
void updateZoomControls();
}
/**
* An interface that allows the embedder to be notified when the results of
* extractSmartClipData are available.
*/
public interface SmartClipDataListener {
public void onSmartClipDataExtracted(String text, String html, Rect clipRect);
}
/**
* Cast from Context to Activity taking ContextWrapper into account.
*/
public static Activity activityFromContext(Context context) {
// Only retrieve the base context if the supplied context is a ContextWrapper but not
// an Activity, given that Activity is already a subclass of ContextWrapper.
if (context instanceof Activity) {
return ((Activity) context);
} else if (context instanceof ContextWrapper) {
context = ((ContextWrapper) context).getBaseContext();
return activityFromContext(context);
} else {
return null;
}
}
private final Context mContext;
private ViewGroup mContainerView;
private InternalAccessDelegate mContainerViewInternals;
private WebContents mWebContents;
private WebContentsObserver mWebContentsObserver;
private ContentViewClient mContentViewClient;
private ContentSettings mContentSettings;
// Native pointer to C++ ContentViewCoreImpl object which will be set by nativeInit().
private long mNativeContentViewCore = 0;
private final ObserverList<GestureStateListener> mGestureStateListeners;
private final RewindableIterator<GestureStateListener> mGestureStateListenersIterator;
private ZoomControlsDelegate mZoomControlsDelegate;
private PopupZoomer mPopupZoomer;
private SelectPopup mSelectPopup;
private long mNativeSelectPopupSourceFrame = 0;
private Runnable mFakeMouseMoveRunnable = null;
// Only valid when focused on a text / password field.
private ImeAdapter mImeAdapter;
private ImeAdapter.AdapterInputConnectionFactory mAdapterInputConnectionFactory;
private AdapterInputConnection mInputConnection;
private InputMethodManagerWrapper mInputMethodManagerWrapper;
// Lazily created paste popup menu, triggered either via long press in an
// editable region or from tapping the insertion handle.
private PastePopupMenu mPastePopupMenu;
private boolean mWasPastePopupShowingOnInsertionDragStart;
private PopupTouchHandleDrawableDelegate mTouchHandleDelegate;
private PositionObserver mPositionObserver;
// Size of the viewport in physical pixels as set from onSizeChanged.
private int mViewportWidthPix;
private int mViewportHeightPix;
private int mPhysicalBackingWidthPix;
private int mPhysicalBackingHeightPix;
private int mTopControlsLayoutHeightPix;
// Cached copy of all positions and scales as reported by the renderer.
private final RenderCoordinates mRenderCoordinates;
// Tracks whether a selection is currently active. When applied to selected text, indicates
// whether the last selected text is still highlighted.
private boolean mHasSelection;
private boolean mHasInsertion;
private String mLastSelectedText;
private boolean mFocusedNodeEditable;
private ActionMode mActionMode;
private boolean mUnselectAllOnActionModeDismiss;
private boolean mPreserveSelectionOnNextLossOfFocus;
private SelectActionModeCallback.ActionHandler mActionHandler;
// Delegate that will handle GET downloads, and be notified of completion of POST downloads.
private ContentViewDownloadDelegate mDownloadDelegate;
// The AccessibilityInjector that handles loading Accessibility scripts into the web page.
private AccessibilityInjector mAccessibilityInjector;
// Whether native accessibility, i.e. without any script injection, is allowed.
private boolean mNativeAccessibilityAllowed;
// Whether native accessibility, i.e. without any script injection, has been enabled.
private boolean mNativeAccessibilityEnabled;
// Handles native accessibility, i.e. without any script injection.
private BrowserAccessibilityManager mBrowserAccessibilityManager;
// System accessibility service.
private final AccessibilityManager mAccessibilityManager;
// Accessibility touch exploration state.
private boolean mTouchExplorationEnabled;
// Whether accessibility focus should be set to the page when it finishes loading.
// This only applies if an accessibility service like TalkBack is running.
// This is desirable behavior for a browser window, but not for an embedded
// WebView.
private boolean mShouldSetAccessibilityFocusOnPageLoad;
// 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 final Rect mFocusPreOSKViewportRect = new Rect();
// On tap this will store the x, y coordinates of the touch.
private int mLastTapX;
private int mLastTapY;
// 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;
// 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 final Editable mEditable;
/**
* 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;
// Offsets for the events that passes through this ContentViewCore.
private float mCurrentTouchOffsetX;
private float mCurrentTouchOffsetY;
// Offsets for smart clip
private int mSmartClipOffsetX;
private int mSmartClipOffsetY;
// Whether the ContentViewCore requires the WebContents to be fullscreen in order to lock the
// screen orientation.
private boolean mFullscreenRequiredForOrientationLock = true;
// A ViewAndroidDelegate that delegates to the current container view.
private ContentViewAndroidDelegate mViewAndroidDelegate;
/**
* Constructs a new ContentViewCore. Embedders must call initialize() after constructing
* a ContentViewCore and before using it.
*
* @param context The context used to create this.
*/
public ContentViewCore(Context context) {
mContext = context;
mAdapterInputConnectionFactory = new AdapterInputConnectionFactory();
mInputMethodManagerWrapper = new InputMethodManagerWrapper(mContext);
mRenderCoordinates = new RenderCoordinates();
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);
mAccessibilityManager = (AccessibilityManager)
getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
mGestureStateListeners = new ObserverList<GestureStateListener>();
mGestureStateListenersIterator = mGestureStateListeners.rewindableIterator();
mEditable = Editable.Factory.getInstance().newEditable("");
Selection.setSelection(mEditable, 0);
}
/**
* @return The context used for creating this ContentViewCore.
*/
@CalledByNative
public Context getContext() {
return mContext;
}
/**
* @return The ViewGroup that all view actions of this ContentViewCore should interact with.
*/
public ViewGroup getContainerView() {
return mContainerView;
}
/**
* @return The WebContents currently being rendered.
*/
public WebContents getWebContents() {
return mWebContents;
}
/* TODO(aelias): Remove this after downstream callers switch to setTopControlsLayoutHeight. */
public void setViewportSizeOffset(int offsetXPix, int offsetYPix) {
setTopControlsLayoutHeight(offsetYPix);
}
/**
* Specifies how much smaller the Blink layout size should be relative to the size of this
* view.
* @param topControlsLayoutHeightPix The Y amount in pixels to shrink the viewport by.
*/
public void setTopControlsLayoutHeight(int topControlsLayoutHeightPix) {
if (topControlsLayoutHeightPix != mTopControlsLayoutHeightPix) {
mTopControlsLayoutHeightPix = topControlsLayoutHeightPix;
if (mNativeContentViewCore != 0) nativeWasResized(mNativeContentViewCore);
}
}
/**
* Returns a delegate that can be used to add and remove views from the current
* container view. Clients can safely hold to instances of this class as it handles the
* replacement of container views transparently.
*
* NOTE: Use with care, as not all ContentViewCore users setup their container view in the same
* way. In particular, the Android WebView has limitations on what implementation details can
* be provided via a child view, as they are visible in the API and could introduce
* compatibility breaks with existing applications. If in doubt, contact the
* android_webview/OWNERS
*
* @return A ViewAndroidDelegate that can be used to add and remove views.
*/
public ViewAndroidDelegate getViewAndroidDelegate() {
return mViewAndroidDelegate;
}
@VisibleForTesting
public void setImeAdapterForTest(ImeAdapter imeAdapter) {
mImeAdapter = imeAdapter;
}
@VisibleForTesting
public ImeAdapter getImeAdapterForTest() {
return mImeAdapter;
}
@VisibleForTesting
public void setAdapterInputConnectionFactory(AdapterInputConnectionFactory factory) {
mAdapterInputConnectionFactory = factory;
}
@VisibleForTesting
public void setInputMethodManagerWrapperForTest(InputMethodManagerWrapper immw) {
mInputMethodManagerWrapper = immw;
}
@VisibleForTesting
public AdapterInputConnection getInputConnectionForTest() {
return mInputConnection;
}
@VisibleForTesting
ViewAndroid getViewAndroid() {
return mViewAndroid;
}
private ImeAdapter createImeAdapter(Context context) {
return new ImeAdapter(mInputMethodManagerWrapper,
new ImeAdapter.ImeAdapterDelegate() {
@Override
public void onImeEvent() {
mPopupZoomer.hide(true);
getContentViewClient().onImeEvent();
if (mFocusedNodeEditable) dismissTextHandles();
}
@Override
public void onDismissInput() {
getContentViewClient().onImeStateChangeRequested(false);
}
@Override
public View getAttachedView() {
return mContainerView;
}
@Override
public ResultReceiver getNewShowKeyboardReceiver() {
return new ResultReceiver(new Handler()) {
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
getContentViewClient().onImeStateChangeRequested(
resultCode == InputMethodManager.RESULT_SHOWN
|| resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN);
if (resultCode == InputMethodManager.RESULT_SHOWN) {
// If OSK is newly shown, delay the form focus until
// the onSizeChanged (in order to adjust relative to the
// new size).
// TODO(jdduke): We should not assume that onSizeChanged will
// always be called, crbug.com/294908.
getContainerView().getWindowVisibleDisplayFrame(
mFocusPreOSKViewportRect);
} else if (hasFocus() && resultCode
== InputMethodManager.RESULT_UNCHANGED_SHOWN) {
// If the OSK was already there, focus the form immediately.
assert mWebContents != null;
mWebContents.scrollFocusedEditableNodeIntoView();
}
}
};
}
}
);
}
/**
*
* @param containerView The view that will act as a container for all views created by this.
* @param internalDispatcher Handles dispatching all hidden or super methods to the
* containerView.
* @param nativeWebContents A pointer to the native web contents.
* @param windowAndroid An instance of the WindowAndroid.
*/
// Perform important post-construction set up of the ContentViewCore.
// We do not require the containing view in the constructor to allow embedders to create a
// ContentViewCore without having fully created its containing view. The containing view
// is a vital component of the ContentViewCore, so embedders must exercise caution in what
// they do with the ContentViewCore before calling initialize().
// We supply the nativeWebContents pointer here rather than in the constructor to allow us
// to set the private browsing mode at a later point for the WebView implementation.
// Note that the caller remains the owner of the nativeWebContents and is responsible for
// deleting it after destroying the ContentViewCore.
public void initialize(ViewGroup containerView, InternalAccessDelegate internalDispatcher,
long nativeWebContents, WindowAndroid windowAndroid) {
createContentViewAndroidDelegate();
setContainerView(containerView);
long windowNativePointer = windowAndroid.getNativePointer();
assert windowNativePointer != 0;
createViewAndroid(windowAndroid);
long viewAndroidNativePointer = mViewAndroid.getNativePointer();
assert viewAndroidNativePointer != 0;
mZoomControlsDelegate = NO_OP_ZOOM_CONTROLS_DELEGATE;
mNativeContentViewCore = nativeInit(
nativeWebContents, viewAndroidNativePointer, windowNativePointer,
mRetainedJavaScriptObjects);
mWebContents = nativeGetWebContentsAndroid(mNativeContentViewCore);
mContentSettings = new ContentSettings(this, mNativeContentViewCore);
setContainerViewInternals(internalDispatcher);
mRenderCoordinates.reset();
initPopupZoomer(mContext);
mImeAdapter = createImeAdapter(mContext);
attachImeAdapter();
mAccessibilityInjector = AccessibilityInjector.newInstance(this);
mWebContentsObserver = new WebContentsObserver(mWebContents) {
@Override
public void didFailLoad(boolean isProvisionalLoad, boolean isMainFrame, int errorCode,
String description, String failingUrl) {
// Navigation that fails the provisional load will have the strong binding removed
// here. One for which the provisional load is commited will have the strong binding
// removed in navigationEntryCommitted() below.
if (isProvisionalLoad) determinedProcessVisibility();
}
@Override
public void didNavigateMainFrame(String url, String baseUrl,
boolean isNavigationToDifferentPage, boolean isFragmentNavigation) {
if (!isNavigationToDifferentPage) return;
hidePopupsAndClearSelection();
resetScrollInProgress();
resetGestureDetection();
}
@Override
public void renderProcessGone(boolean wasOomProtected) {
hidePopupsAndClearSelection();
resetScrollInProgress();
// No need to reset gesture detection as the detector will have
// been destroyed in the RenderWidgetHostView.
}
@Override
public void navigationEntryCommitted() {
determinedProcessVisibility();
}
private void determinedProcessVisibility() {
// Signal to the process management logic that we can now rely on the process
// visibility signal for binding management. Before the navigation commits, its
// renderer is considered background even if the pending navigation happens in the
// foreground renderer.
ChildProcessLauncher.determinedVisibility(getCurrentRenderProcessId());
}
};
}
@VisibleForTesting
void createContentViewAndroidDelegate() {
mViewAndroidDelegate = new ContentViewAndroidDelegate();
}
@VisibleForTesting
void createViewAndroid(WindowAndroid windowAndroid) {
mViewAndroid = new ViewAndroid(windowAndroid, mViewAndroidDelegate);
}
/**
* Sets a new container view for this {@link ContentViewCore}.
*
* <p>WARNING: This method can also be used to replace the existing container view,
* but you should only do it if you have a very good reason to. Replacing the
* container view has been designed to support fullscreen in the Webview so it
* might not be appropriate for other use cases.
*
* <p>This method only performs a small part of replacing the container view and
* embedders are responsible for:
* <ul>
* <li>Disconnecting the old container view from this ContentViewCore</li>
* <li>Updating the InternalAccessDelegate</li>
* <li>Reconciling the state of this ContentViewCore with the new container view</li>
* <li>Tearing down and recreating the native GL rendering where appropriate</li>
* <li>etc.</li>
* </ul>
*/
public void setContainerView(ViewGroup containerView) {
TraceEvent.begin();
if (mContainerView != null) {
mPastePopupMenu = null;
mInputConnection = null;
hidePopupsAndClearSelection();
}
mContainerView = containerView;
mPositionObserver = new ViewPositionObserver(mContainerView);
mContainerView.setClickable(true);
mViewAndroidDelegate.updateCurrentContainerView();
TraceEvent.end();
}
@CalledByNative
void onNativeContentViewCoreDestroyed(long nativeContentViewCore) {
assert nativeContentViewCore == mNativeContentViewCore;
mNativeContentViewCore = 0;
}
/**
* Set the Container view Internals.
* @param internalDispatcher Handles dispatching all hidden or super methods to the
* containerView.
*/
public void setContainerViewInternals(InternalAccessDelegate internalDispatcher) {
mContainerViewInternals = internalDispatcher;
}
@VisibleForTesting
void initPopupZoomer(Context context) {
mPopupZoomer = new PopupZoomer(context);
mPopupZoomer.setOnVisibilityChangedListener(new PopupZoomer.OnVisibilityChangedListener() {
// mContainerView can change, but this OnVisibilityChangedListener can only be used
// to add and remove views from the mContainerViewAtCreation.
private final ViewGroup mContainerViewAtCreation = mContainerView;
@Override
public void onPopupZoomerShown(final PopupZoomer zoomer) {
mContainerViewAtCreation.post(new Runnable() {
@Override
public void run() {
if (mContainerViewAtCreation.indexOfChild(zoomer) == -1) {
mContainerViewAtCreation.addView(zoomer);
}
}
});
}
@Override
public void onPopupZoomerHidden(final PopupZoomer zoomer) {
mContainerViewAtCreation.post(new Runnable() {
@Override
public void run() {
if (mContainerViewAtCreation.indexOfChild(zoomer) != -1) {
mContainerViewAtCreation.removeView(zoomer);
mContainerViewAtCreation.invalidate();
}
}
});
}
});
// TODO(yongsheng): LONG_TAP is not enabled in PopupZoomer. So need to dispatch a LONG_TAP
// gesture if a user completes a tap on PopupZoomer UI after a LONG_PRESS gesture.
PopupZoomer.OnTapListener listener = new PopupZoomer.OnTapListener() {
// mContainerView can change, but this OnTapListener can only be used
// with the mContainerViewAtCreation.
private final ViewGroup mContainerViewAtCreation = mContainerView;
@Override
public boolean onSingleTap(View v, MotionEvent e) {
mContainerViewAtCreation.requestFocus();
if (mNativeContentViewCore != 0) {
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());
}
return true;
}
};
mPopupZoomer.setOnTapListener(listener);
}
@VisibleForTesting
public void setPopupZoomerForTest(PopupZoomer popupZoomer) {
mPopupZoomer = popupZoomer;
}
/**
* Destroy the internal state of the ContentView. This method may only be
* called after the ContentView has been removed from the view system. No
* other methods may be called on this ContentView after this method has
* been called.
*/
public void destroy() {
if (mNativeContentViewCore != 0) {
nativeOnJavaContentViewCoreDestroyed(mNativeContentViewCore);
}
mWebContentsObserver.detachFromWebContents();
mWebContentsObserver = null;
setSmartClipDataListener(null);
setZoomControlsDelegate(null);
// TODO(igsolla): address TODO in ContentViewClient because ContentViewClient is not
// currently a real Null Object.
//
// Instead of deleting the client we use the Null Object pattern to avoid null checks
// in this class.
mContentViewClient = new ContentViewClient();
mWebContents = null;
if (mViewAndroid != null) mViewAndroid.destroy();
mNativeContentViewCore = 0;
mContentSettings = null;
mJavaScriptInterfaces.clear();
mRetainedJavaScriptObjects.clear();
unregisterAccessibilityContentObserver();
mGestureStateListeners.clear();
ScreenOrientationListener.getInstance().removeObserver(this);
mPositionObserver.clearListener();
}
private void unregisterAccessibilityContentObserver() {
if (mAccessibilityScriptInjectionObserver == null) {
return;
}
getContext().getContentResolver().unregisterContentObserver(
mAccessibilityScriptInjectionObserver);
mAccessibilityScriptInjectionObserver = null;
}
/**
* Returns true initially, false after destroy() has been called.
* It is illegal to call any other public method after destroy().
*/
public boolean isAlive() {
return mNativeContentViewCore != 0;
}
/**
* This is only useful for passing over JNI to native code that requires ContentViewCore*.
* @return native ContentViewCore pointer.
*/
@CalledByNative
public long getNativeContentViewCore() {
return mNativeContentViewCore;
}
public void setContentViewClient(ContentViewClient client) {
if (client == null) {
throw new IllegalArgumentException("The client can't be null.");
}
mContentViewClient = client;
}
@VisibleForTesting
public ContentViewClient getContentViewClient() {
if (mContentViewClient == null) {
// We use the Null Object pattern to avoid having to perform a null check in this class.
// We create it lazily because most of the time a client will be set almost immediately
// after ContentView is created.
mContentViewClient = new ContentViewClient();
// We don't set the native ContentViewClient pointer here on purpose. The native
// implementation doesn't mind a null delegate and using one is better than passing a
// Null Object, since we cut down on the number of JNI calls.
}
return mContentViewClient;
}
@CalledByNative
private void onBackgroundColorChanged(int color) {
getContentViewClient().onBackgroundColorChanged(color);
}
/**
* @return Viewport width in physical pixels as set from onSizeChanged.
*/
@CalledByNative
public int getViewportWidthPix() {
return mViewportWidthPix;
}
/**
* @return Viewport height in physical pixels as set from onSizeChanged.
*/
@CalledByNative
public int getViewportHeightPix() {
return mViewportHeightPix;
}
/**
* @return Width of underlying physical surface.
*/
@CalledByNative
public int getPhysicalBackingWidthPix() {
return mPhysicalBackingWidthPix;
}
/**
* @return Height of underlying physical surface.
*/
@CalledByNative
public int getPhysicalBackingHeightPix() {
return mPhysicalBackingHeightPix;
}
/* TODO(aelias): Remove these when downstream callers disappear. */
@VisibleForTesting
public int getViewportSizeOffsetWidthPix() {
return 0;
}
@VisibleForTesting
public int getViewportSizeOffsetHeightPix() {
return getTopControlsLayoutHeightPix();
}
/**
* @return The amount that the viewport size given to Blink is shrunk by the URL-bar..
*/
@CalledByNative
public int getTopControlsLayoutHeightPix() {
return mTopControlsLayoutHeightPix;
}
/**
* @see android.webkit.WebView#getContentHeight()
*/
public float getContentHeightCss() {
return mRenderCoordinates.getContentHeightCss();
}
/**
* @see android.webkit.WebView#getContentWidth()
*/
public float getContentWidthCss() {
return mRenderCoordinates.getContentWidthCss();
}
/**
* @return The selected text (empty if no text selected).
*/
public String getSelectedText() {
return mHasSelection ? mLastSelectedText : "";
}
/**
* @return Whether the current selection is editable (false if no text selected).
*/
public boolean isSelectionEditable() {
return mHasSelection ? mFocusedNodeEditable : false;
}
/**
* @return Whether the current focused node is editable.
*/
public boolean isFocusedNodeEditable() {
return mFocusedNodeEditable;
}
// End FrameLayout overrides.
/**
* @see View#onTouchEvent(MotionEvent)
*/
public boolean onTouchEvent(MotionEvent event) {
final boolean isTouchHandleEvent = false;
return onTouchEventImpl(event, isTouchHandleEvent);
}
private boolean onTouchEventImpl(MotionEvent event, boolean isTouchHandleEvent) {
TraceEvent.begin("onTouchEvent");
try {
int eventAction = event.getActionMasked();
if (eventAction == MotionEvent.ACTION_DOWN) {
cancelRequestToScrollFocusedEditableNodeIntoView();
}
if (SPenSupport.isSPenSupported(mContext))
eventAction = SPenSupport.convertSPenEventAction(eventAction);
if (!isValidTouchEventActionForNative(eventAction)) return false;
if (mNativeContentViewCore == 0) return false;
// A zero offset is quite common, in which case the unnecessary copy should be avoided.
MotionEvent offset = null;
if (mCurrentTouchOffsetX != 0 || mCurrentTouchOffsetY != 0) {
offset = createOffsetMotionEvent(event);
event = offset;
}
final int pointerCount = event.getPointerCount();
final boolean consumed = 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,
event.getTouchMinor(), pointerCount > 1 ? event.getTouchMinor(1) : 0,
event.getOrientation(), pointerCount > 1 ? event.getOrientation(1) : 0,
event.getRawX(), event.getRawY(),
event.getToolType(0),
pointerCount > 1 ? event.getToolType(1) : MotionEvent.TOOL_TYPE_UNKNOWN,
event.getButtonState(),
event.getMetaState(),
isTouchHandleEvent);
if (offset != null) offset.recycle();
return consumed;
} finally {
TraceEvent.end("onTouchEvent");
}
}
private static boolean isValidTouchEventActionForNative(int eventAction) {
// 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.
return 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;
}
public void setIgnoreRemainingTouchEvents() {
resetGestureDetection();
}
public boolean isScrollInProgress() {
return mTouchScrollInProgress || mPotentiallyActiveFlingCount > 0;
}
@SuppressWarnings("unused")
@CalledByNative
private void onFlingStartEventConsumed(int vx, int vy) {
mTouchScrollInProgress = false;
mPotentiallyActiveFlingCount++;
for (mGestureStateListenersIterator.rewind();
mGestureStateListenersIterator.hasNext();) {
mGestureStateListenersIterator.next().onFlingStartGesture(
vx, vy, computeVerticalScrollOffset(), computeVerticalScrollExtent());
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onFlingStartEventHadNoConsumer(int vx, int vy) {
mTouchScrollInProgress = false;
for (mGestureStateListenersIterator.rewind();
mGestureStateListenersIterator.hasNext();) {
mGestureStateListenersIterator.next().onUnhandledFlingStartEvent(vx, vy);
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onFlingCancelEventAck() {
updateGestureStateListener(GestureEventType.FLING_CANCEL);
}
@SuppressWarnings("unused")
@CalledByNative
private void onScrollBeginEventAck() {
mTouchScrollInProgress = true;
hidePastePopup();
mZoomControlsDelegate.invokeZoomPicker();
updateGestureStateListener(GestureEventType.SCROLL_START);
}
@SuppressWarnings("unused")
@CalledByNative
private void onScrollUpdateGestureConsumed() {
mZoomControlsDelegate.invokeZoomPicker();
for (mGestureStateListenersIterator.rewind();
mGestureStateListenersIterator.hasNext();) {
mGestureStateListenersIterator.next().onScrollUpdateGestureConsumed();
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onScrollEndEventAck() {
if (!mTouchScrollInProgress) return;
mTouchScrollInProgress = false;
updateGestureStateListener(GestureEventType.SCROLL_END);
}
@SuppressWarnings("unused")
@CalledByNative
private void onPinchBeginEventAck() {
updateGestureStateListener(GestureEventType.PINCH_BEGIN);
}
@SuppressWarnings("unused")
@CalledByNative
private void onPinchEndEventAck() {
updateGestureStateListener(GestureEventType.PINCH_END);
}
@SuppressWarnings("unused")
@CalledByNative
private void onSingleTapEventAck(boolean consumed, int x, int y) {
for (mGestureStateListenersIterator.rewind();
mGestureStateListenersIterator.hasNext();) {
mGestureStateListenersIterator.next().onSingleTap(consumed, x, y);
}
}
/**
* 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;
}
@VisibleForTesting
public void sendDoubleTapForTest(long timeMs, int x, int y) {
if (mNativeContentViewCore == 0) return;
nativeDoubleTap(mNativeContentViewCore, timeMs, x, y);
}
@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);
}
/**
* Cancel any fling gestures active.
* @param timeMs Current time (in milliseconds).
*/
public void cancelFling(long timeMs) {
if (mNativeContentViewCore == 0) return;
nativeFlingCancel(mNativeContentViewCore, timeMs);
}
/**
* 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;
}
}
}
/**
* To be called when the ContentView is shown.
*/
public void onShow() {
assert mWebContents != null;
mWebContents.onShow();
setAccessibilityState(mAccessibilityManager.isEnabled());
restoreSelectionPopupsIfNecessary();
}
/**
* @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 mWebContents != null;
hidePopupsAndPreserveSelection();
setInjectedAccessibility(false);
mWebContents.onHide();
}
/**
* Return the ContentSettings object used to retrieve the settings for this
* ContentViewCore. For modifications, ChromeNativePreferences is to be used.
* @return A ContentSettings object that can be used to retrieve this
* ContentViewCore's settings.
*/
public ContentSettings getContentSettings() {
return mContentSettings;
}
private void hidePopupsAndClearSelection() {
mUnselectAllOnActionModeDismiss = true;
hidePopups();
// Clear the selection. The selection is cleared on destroying IME
// and also here since we may receive destroy first, for example
// when focus is lost in webview.
clearUserSelection();
}
private void hidePopupsAndPreserveSelection() {
mUnselectAllOnActionModeDismiss = false;
hidePopups();
}
private void clearUserSelection() {
if (mFocusedNodeEditable) {
if (mInputConnection != null) {
int selectionEnd = Selection.getSelectionEnd(mEditable);
mInputConnection.setSelection(selectionEnd, selectionEnd);
}
} else if (mImeAdapter != null) {
mImeAdapter.unselect();
}
}
private void hidePopups() {
hideSelectActionBar();
hidePastePopup();
hideSelectPopup();
mPopupZoomer.hide(false);
if (mUnselectAllOnActionModeDismiss) dismissTextHandles();
}
private void restoreSelectionPopupsIfNecessary() {
if (mHasSelection && mActionMode == null) showSelectActionBar();
}
public void hideSelectActionBar() {
if (mActionMode != null) {
mActionMode.finish();
mActionMode = null;
}
}
public boolean isSelectActionBarShowing() {
return mActionMode != null;
}
private void resetGestureDetection() {
if (mNativeContentViewCore == 0) return;
nativeResetGestureDetection(mNativeContentViewCore);
}
/**
* @see View#onAttachedToWindow()
*/
@SuppressWarnings("javadoc")
public void onAttachedToWindow() {
setAccessibilityState(mAccessibilityManager.isEnabled());
setTextHandlesTemporarilyHidden(false);
restoreSelectionPopupsIfNecessary();
ScreenOrientationListener.getInstance().addObserver(this, mContext);
GamepadList.onAttachedToWindow(mContext);
}
/**
* @see View#onDetachedFromWindow()
*/
@SuppressWarnings("javadoc")
@SuppressLint("MissingSuperCall")
public void onDetachedFromWindow() {
setInjectedAccessibility(false);
mZoomControlsDelegate.dismissZoomPicker();
unregisterAccessibilityContentObserver();
ScreenOrientationListener.getInstance().removeObserver(this);
GamepadList.onDetachedFromWindow();
// WebView uses PopupWindows for handle rendering, which may remain
// unintentionally visible even after the WebView has been detached.
// Override the handle visibility explicitly to address this, but
// preserve the underlying selection for detachment cases like screen
// locking and app switching.
setTextHandlesTemporarilyHidden(true);
hidePopupsAndPreserveSelection();
}
/**
* @see View#onVisibilityChanged(android.view.View, int)
*/
public void onVisibilityChanged(View changedView, int visibility) {
if (visibility != View.VISIBLE) {
mZoomControlsDelegate.dismissZoomPicker();
}
}
/**
* @see View#onCreateInputConnection(EditorInfo)
*/
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
if (!mImeAdapter.hasTextInputType()) {
// Although onCheckIsTextEditor will return false in this case, the EditorInfo
// is still used by the InputMethodService. Need to make sure the IME doesn't
// enter fullscreen mode.
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
}
mInputConnection = mAdapterInputConnectionFactory.get(mContainerView, mImeAdapter,
mEditable, outAttrs);
return mInputConnection;
}
@VisibleForTesting
public AdapterInputConnection getAdapterInputConnectionForTest() {
return mInputConnection;
}
@VisibleForTesting
public Editable getEditableForTest() {
return mEditable;
}
/**
* @see View#onCheckIsTextEditor()
*/
public boolean onCheckIsTextEditor() {
return mImeAdapter.hasTextInputType();
}
/**
* @see View#onConfigurationChanged(Configuration)
*/
@SuppressWarnings("javadoc")
public void onConfigurationChanged(Configuration newConfig) {
TraceEvent.begin();
if (newConfig.keyboard != Configuration.KEYBOARD_NOKEYS) {
if (mNativeContentViewCore != 0) {
mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore),
ImeAdapter.getTextInputTypeNone(), 0 /* no flags */);
}
mInputMethodManagerWrapper.restartInput(mContainerView);
}
mContainerViewInternals.super_onConfigurationChanged(newConfig);
// 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();
TraceEvent.end();
}
/**
* @see View#onSizeChanged(int, int, int, int)
*/
@SuppressWarnings("javadoc")
public void onSizeChanged(int wPix, int hPix, int owPix, int ohPix) {
if (getViewportWidthPix() == wPix && getViewportHeightPix() == hPix) return;
mViewportWidthPix = wPix;
mViewportHeightPix = hPix;
if (mNativeContentViewCore != 0) {
nativeWasResized(mNativeContentViewCore);
}
updateAfterSizeChanged();
}
/**
* Called when the underlying surface the compositor draws to changes size.
* This may be larger than the viewport size.
*/
public void onPhysicalBackingSizeChanged(int wPix, int hPix) {
if (mPhysicalBackingWidthPix == wPix && mPhysicalBackingHeightPix == hPix) return;
mPhysicalBackingWidthPix = wPix;
mPhysicalBackingHeightPix = hPix;
if (mNativeContentViewCore != 0) {
nativeWasResized(mNativeContentViewCore);
}
}
/* TODO(aelias): Remove this after downstream callers disappear. */
public void onOverdrawBottomHeightChanged(int overdrawHeightPix) {
}
private void updateAfterSizeChanged() {
mPopupZoomer.hide(false);
// Execute a delayed form focus operation because the OSK was brought
// up earlier.
if (!mFocusPreOSKViewportRect.isEmpty()) {
Rect rect = new Rect();
getContainerView().getWindowVisibleDisplayFrame(rect);
if (!rect.equals(mFocusPreOSKViewportRect)) {
// Only assume the OSK triggered the onSizeChanged if width was preserved.
if (rect.width() == mFocusPreOSKViewportRect.width()) {
assert mWebContents != null;
mWebContents.scrollFocusedEditableNodeIntoView();
}
cancelRequestToScrollFocusedEditableNodeIntoView();
}
}
}
private void cancelRequestToScrollFocusedEditableNodeIntoView() {
// Zero-ing the rect will prevent |updateAfterSizeChanged()| from
// issuing the delayed form focus event.
mFocusPreOSKViewportRect.setEmpty();
}
/**
* @see View#onWindowFocusChanged(boolean)
*/
public void onWindowFocusChanged(boolean hasWindowFocus) {
if (!hasWindowFocus) resetGestureDetection();
}
public void onFocusChanged(boolean gainFocus) {
if (gainFocus) {
restoreSelectionPopupsIfNecessary();
} else {
hideImeIfNeeded();
cancelRequestToScrollFocusedEditableNodeIntoView();
if (mPreserveSelectionOnNextLossOfFocus) {
mPreserveSelectionOnNextLossOfFocus = false;
hidePopupsAndPreserveSelection();
} else {
hidePopupsAndClearSelection();
}
}
if (mNativeContentViewCore != 0) nativeSetFocus(mNativeContentViewCore, gainFocus);
}
/**
* @see View#onKeyUp(int, KeyEvent)
*/
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (mPopupZoomer.isShowing() && keyCode == KeyEvent.KEYCODE_BACK) {
mPopupZoomer.hide(true);
return true;
}
return mContainerViewInternals.super_onKeyUp(keyCode, event);
}
/**
* @see View#dispatchKeyEventPreIme(KeyEvent)
*/
public boolean dispatchKeyEventPreIme(KeyEvent event) {
try {
TraceEvent.begin();
return mContainerViewInternals.super_dispatchKeyEventPreIme(event);
} finally {
TraceEvent.end();
}
}
/**
* @see View#dispatchKeyEvent(KeyEvent)
*/
public boolean dispatchKeyEvent(KeyEvent event) {
if (GamepadList.dispatchKeyEvent(event)) return true;
if (getContentViewClient().shouldOverrideKeyEvent(event)) {
return mContainerViewInternals.super_dispatchKeyEvent(event);
}
if (mImeAdapter.dispatchKeyEvent(event)) return true;
return mContainerViewInternals.super_dispatchKeyEvent(event);
}
/**
* @see View#onHoverEvent(MotionEvent)
* Mouse move events are sent on hover enter, hover move and hover exit.
* They are sent on hover exit because sometimes it acts as both a hover
* move and hover exit.
*/
public boolean onHoverEvent(MotionEvent event) {
TraceEvent.begin("onHoverEvent");
MotionEvent offset = createOffsetMotionEvent(event);
try {
if (mBrowserAccessibilityManager != null) {
return mBrowserAccessibilityManager.onHoverEvent(offset);
}
// Work around Android bug where the x, y coordinates of a hover exit
// event are incorrect when touch exploration is on.
if (mTouchExplorationEnabled && offset.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
return true;
}
mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
if (mNativeContentViewCore != 0) {
nativeSendMouseMoveEvent(mNativeContentViewCore, offset.getEventTime(),
offset.getX(), offset.getY());
}
return true;
} finally {
offset.recycle();
TraceEvent.end("onHoverEvent");
}
}
/**
* @see View#onGenericMotionEvent(MotionEvent)
*/
public boolean onGenericMotionEvent(MotionEvent event) {
if (GamepadList.onGenericMotionEvent(event)) return true;
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL:
if (mNativeContentViewCore == 0) return false;
nativeSendMouseWheelEvent(mNativeContentViewCore, event.getEventTime(),
event.getX(), event.getY(),
event.getAxisValue(MotionEvent.AXIS_VSCROLL));
mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
// Send a delayed onMouseMove event so that we end
// up hovering over the right position after the scroll.
final MotionEvent eventFakeMouseMove = MotionEvent.obtain(event);
mFakeMouseMoveRunnable = new Runnable() {
@Override
public void run() {
onHoverEvent(eventFakeMouseMove);
eventFakeMouseMove.recycle();
}
};
mContainerView.postDelayed(mFakeMouseMoveRunnable, 250);
return true;
}
}
return mContainerViewInternals.super_onGenericMotionEvent(event);
}
/**
* Sets the current amount to offset incoming touch events by. This is used to handle content
* moving and not lining up properly with the android input system.
* @param dx The X offset in pixels to shift touch events.
* @param dy The Y offset in pixels to shift touch events.
*/
public void setCurrentMotionEventOffsets(float dx, float dy) {
mCurrentTouchOffsetX = dx;
mCurrentTouchOffsetY = dy;
}
private MotionEvent createOffsetMotionEvent(MotionEvent src) {
MotionEvent dst = MotionEvent.obtain(src);
dst.offsetLocation(mCurrentTouchOffsetX, mCurrentTouchOffsetY);
return dst;
}
/**
* @see View#scrollBy(int, int)
* Currently the ContentView scrolling happens in the native side. In
* the Java view system, it is always pinned at (0, 0). scrollBy() and scrollTo()
* are overridden, so that View's mScrollX and mScrollY will be unchanged at
* (0, 0). This is critical for drawing ContentView correctly.
*/
public void scrollBy(int xPix, int yPix) {
if (mNativeContentViewCore != 0) {
nativeScrollBy(mNativeContentViewCore,
SystemClock.uptimeMillis(), 0, 0, xPix, yPix);
}
}
/**
* @see View#scrollTo(int, int)
*/
public void scrollTo(int xPix, int yPix) {
if (mNativeContentViewCore == 0) return;
final float xCurrentPix = mRenderCoordinates.getScrollXPix();
final float yCurrentPix = mRenderCoordinates.getScrollYPix();
final float dxPix = xPix - xCurrentPix;
final float dyPix = yPix - yCurrentPix;
if (dxPix != 0 || dyPix != 0) {
long time = SystemClock.uptimeMillis();
nativeScrollBegin(mNativeContentViewCore, time,
xCurrentPix, yCurrentPix, -dxPix, -dyPix);
nativeScrollBy(mNativeContentViewCore,
time, xCurrentPix, yCurrentPix, dxPix, dyPix);
nativeScrollEnd(mNativeContentViewCore, time);
}
}
// NOTE: this can go away once ContentView.getScrollX() reports correct values.
// see: b/6029133
public int getNativeScrollXForTest() {
return mRenderCoordinates.getScrollXPixInt();
}
// NOTE: this can go away once ContentView.getScrollY() reports correct values.
// see: b/6029133
public int getNativeScrollYForTest() {
return mRenderCoordinates.getScrollYPixInt();
}
/**
* @see View#computeHorizontalScrollExtent()
*/
@SuppressWarnings("javadoc")
public int computeHorizontalScrollExtent() {
return mRenderCoordinates.getLastFrameViewportWidthPixInt();
}
/**
* @see View#computeHorizontalScrollOffset()
*/
@SuppressWarnings("javadoc")
public int computeHorizontalScrollOffset() {
return mRenderCoordinates.getScrollXPixInt();
}
/**
* @see View#computeHorizontalScrollRange()
*/
@SuppressWarnings("javadoc")
public int computeHorizontalScrollRange() {
return mRenderCoordinates.getContentWidthPixInt();
}
/**
* @see View#computeVerticalScrollExtent()
*/
@SuppressWarnings("javadoc")
public int computeVerticalScrollExtent() {
return mRenderCoordinates.getLastFrameViewportHeightPixInt();
}
/**
* @see View#computeVerticalScrollOffset()
*/
@SuppressWarnings("javadoc")
public int computeVerticalScrollOffset() {
return mRenderCoordinates.getScrollYPixInt();
}
/**
* @see View#computeVerticalScrollRange()
*/
@SuppressWarnings("javadoc")
public int computeVerticalScrollRange() {
return mRenderCoordinates.getContentHeightPixInt();
}
// End FrameLayout overrides.
/**
* @see View#awakenScrollBars(int, boolean)
*/
@SuppressWarnings("javadoc")
public boolean awakenScrollBars(int startDelay, boolean invalidate) {
// For the default implementation of ContentView which draws the scrollBars on the native
// side, calling this function may get us into a bad state where we keep drawing the
// scrollBars, so disable it by always returning false.
if (mContainerView.getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) {
return false;
} else {
return mContainerViewInternals.super_awakenScrollBars(startDelay, invalidate);
}
}
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);
mLastTapX = (int) xPix;
mLastTapY = (int) yPix;
}
/**
* @return The x coordinate for the last point that a tap or press gesture was initiated from.
*/
public int getLastTapX() {
return mLastTapX;
}
/**
* @return The y coordinate for the last point that a tap or press gesture was initiated from.
*/
public int getLastTapY() {
return mLastTapY;
}
public void setZoomControlsDelegate(ZoomControlsDelegate zoomControlsDelegate) {
if (zoomControlsDelegate == null) {
mZoomControlsDelegate = NO_OP_ZOOM_CONTROLS_DELEGATE;
return;
}
mZoomControlsDelegate = zoomControlsDelegate;
}
public void updateMultiTouchZoomSupport(boolean supportsMultiTouchZoom) {
if (mNativeContentViewCore == 0) return;
nativeSetMultiTouchZoomSupportEnabled(mNativeContentViewCore, supportsMultiTouchZoom);
}
public void updateDoubleTapSupport(boolean supportsDoubleTap) {
if (mNativeContentViewCore == 0) return;
nativeSetDoubleTapSupportEnabled(mNativeContentViewCore, supportsDoubleTap);
}
public void selectPopupMenuItems(int[] indices) {
if (mNativeContentViewCore != 0) {
nativeSelectPopupMenuItems(mNativeContentViewCore, mNativeSelectPopupSourceFrame,
indices);
}
mNativeSelectPopupSourceFrame = 0;
mSelectPopup = null;
}
/**
* Send the screen orientation value to the renderer.
*/
@VisibleForTesting
void sendOrientationChangeEvent(int orientation) {
if (mNativeContentViewCore == 0) return;
nativeSendOrientationChangeEvent(mNativeContentViewCore, orientation);
}
/**
* Register the delegate to be used when content can not be handled by
* the rendering engine, and should be downloaded instead. This will replace
* the current delegate, if any.
* @param delegate An implementation of ContentViewDownloadDelegate.
*/
public void setDownloadDelegate(ContentViewDownloadDelegate delegate) {
mDownloadDelegate = delegate;
}
// Called by DownloadController.
ContentViewDownloadDelegate getDownloadDelegate() {
return mDownloadDelegate;
}
@VisibleForTesting
public SelectActionModeCallback.ActionHandler getSelectActionHandler() {
return mActionHandler;
}
private void showSelectActionBar() {
if (mActionMode != null) {
mActionMode.invalidate();
return;
}
// Start a new action mode with a SelectActionModeCallback.
if (mActionHandler == null) {
mActionHandler = new SelectActionModeCallback.ActionHandler() {
@Override
public void selectAll() {
mImeAdapter.selectAll();
}
@Override
public void cut() {
mImeAdapter.cut();
}
@Override
public void copy() {
mImeAdapter.copy();
}
@Override
public void paste() {
mImeAdapter.paste();
}
@Override
public void share() {
final String query = getSelectedText();
if (TextUtils.isEmpty(query)) return;
Intent send = new Intent(Intent.ACTION_SEND);
send.setType("text/plain");
send.putExtra(Intent.EXTRA_TEXT, query);
try {
Intent i = Intent.createChooser(send, getContext().getString(
R.string.actionbar_share));
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(i);
} catch (android.content.ActivityNotFoundException ex) {
// If no app handles it, do nothing.
}
}
@Override
public void search() {
final String query = getSelectedText();
if (TextUtils.isEmpty(query)) return;
// See if ContentViewClient wants to override
if (getContentViewClient().doesPerformWebSearch()) {
getContentViewClient().performWebSearch(query);
return;
}
Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
i.putExtra(SearchManager.QUERY, query);
i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
if (activityFromContext(getContext()) == null) {
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
try {
getContext().startActivity(i);
} catch (android.content.ActivityNotFoundException ex) {
// If no app handles it, do nothing.
}
}
@Override
public boolean isSelectionPassword() {
return mImeAdapter.isSelectionPassword();
}
@Override
public boolean isSelectionEditable() {
return mFocusedNodeEditable;
}
@Override
public void onDestroyActionMode() {
mActionMode = null;
if (mUnselectAllOnActionModeDismiss) {
dismissTextHandles();
clearUserSelection();
}
getContentViewClient().onContextualActionBarHidden();
}
@Override
public boolean isShareAvailable() {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
return getContext().getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
}
@Override
public boolean isWebSearchAvailable() {
if (getContentViewClient().doesPerformWebSearch()) return true;
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
return getContext().getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
}
};
}
mActionMode = null;
// On ICS, startActionMode throws an NPE when getParent() is null.
if (mContainerView.getParent() != null) {
assert mWebContents != null;
mActionMode = mContainerView.startActionMode(
getContentViewClient().getSelectActionModeCallback(getContext(), mActionHandler,
mWebContents.isIncognito()));
}
mUnselectAllOnActionModeDismiss = true;
if (mActionMode == null) {
// There is no ActionMode, so remove the selection.
mImeAdapter.unselect();
} else {
getContentViewClient().onContextualActionBarShown();
}
}
/**
* Clears the current text selection.
*/
public void clearSelection() {
mImeAdapter.unselect();
}
/**
* Ensure the selection is preserved the next time the view loses focus.
*/
public void preserveSelectionOnNextLossOfFocus() {
mPreserveSelectionOnNextLossOfFocus = true;
}
/**
* @return Whether the page has an active, touch-controlled selection region.
*/
@VisibleForTesting
public boolean hasSelection() {
return mHasSelection;
}
/**
* @return Whether the page has an active, touch-controlled insertion handle.
*/
@VisibleForTesting
protected boolean hasInsertion() {
return mHasInsertion;
}
private void hidePastePopup() {
if (mPastePopupMenu == null) return;
mPastePopupMenu.hide();
}
@CalledByNative
private void onSelectionEvent(int eventType, float posXDip, float posYDip) {
switch (eventType) {
case SelectionEventType.SELECTION_SHOWN:
mHasSelection = true;
mUnselectAllOnActionModeDismiss = true;
// TODO(cjhopman): Remove this when there is a better signal that long press caused
// a selection. See http://crbug.com/150151.
mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
showSelectActionBar();
break;
case SelectionEventType.SELECTION_CLEARED:
mHasSelection = false;
mUnselectAllOnActionModeDismiss = false;
hideSelectActionBar();
break;
case SelectionEventType.SELECTION_DRAG_STARTED:
break;
case SelectionEventType.SELECTION_DRAG_STOPPED:
break;
case SelectionEventType.INSERTION_SHOWN:
mHasInsertion = true;
break;
case SelectionEventType.INSERTION_MOVED:
if (mPastePopupMenu == null) break;
if (!isScrollInProgress() && mPastePopupMenu.isShowing()) {
showPastePopup((int) posXDip, (int) posYDip);
} else {
hidePastePopup();
}
break;
case SelectionEventType.INSERTION_TAPPED:
if (mWasPastePopupShowingOnInsertionDragStart)
hidePastePopup();
else
showPastePopup((int) posXDip, (int) posYDip);
break;
case SelectionEventType.INSERTION_CLEARED:
mHasInsertion = false;
hidePastePopup();
break;
case SelectionEventType.INSERTION_DRAG_STARTED:
mWasPastePopupShowingOnInsertionDragStart =
mPastePopupMenu != null && mPastePopupMenu.isShowing();
hidePastePopup();
break;
default:
assert false : "Invalid selection event type.";
}
final float scale = mRenderCoordinates.getDeviceScaleFactor();
getContentViewClient().onSelectionEvent(eventType, posXDip * scale, posYDip * scale);
}
private void dismissTextHandles() {
mHasSelection = false;
mHasInsertion = false;
if (mNativeContentViewCore != 0) nativeDismissTextHandles(mNativeContentViewCore);
}
private void setTextHandlesTemporarilyHidden(boolean hide) {
if (mNativeContentViewCore == 0) return;
nativeSetTextHandlesTemporarilyHidden(mNativeContentViewCore, hide);
}
/**
* 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(
float scrollOffsetX, float scrollOffsetY,
float pageScaleFactor, float minPageScaleFactor, float maxPageScaleFactor,
float contentWidth, float contentHeight,
float viewportWidth, float viewportHeight,
float controlsOffsetYCss, float contentOffsetYCss) {
TraceEvent.begin("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,
mViewportWidthPix / (deviceScale * pageScaleFactor));
contentHeight = Math.max(contentHeight,
mViewportHeightPix / (deviceScale * pageScaleFactor));
final float contentOffsetYPix = mRenderCoordinates.fromDipToPix(contentOffsetYCss);
final boolean contentSizeChanged =
contentWidth != mRenderCoordinates.getContentWidthCss()
|| contentHeight != mRenderCoordinates.getContentHeightCss();
final boolean scaleLimitsChanged =
minPageScaleFactor != mRenderCoordinates.getMinPageScaleFactor()
|| maxPageScaleFactor != mRenderCoordinates.getMaxPageScaleFactor();
final boolean pageScaleChanged =
pageScaleFactor != mRenderCoordinates.getPageScaleFactor();
final boolean scrollChanged =
pageScaleChanged
|| scrollOffsetX != mRenderCoordinates.getScrollX()
|| scrollOffsetY != mRenderCoordinates.getScrollY();
final boolean contentOffsetChanged =
contentOffsetYPix != mRenderCoordinates.getContentOffsetYPix();
final boolean needHidePopupZoomer = contentSizeChanged || scrollChanged;
final boolean needUpdateZoomControls = scaleLimitsChanged || scrollChanged;
if (needHidePopupZoomer) mPopupZoomer.hide(true);
if (scrollChanged) {
mContainerViewInternals.onScrollChanged(
(int) mRenderCoordinates.fromLocalCssToPix(scrollOffsetX),
(int) mRenderCoordinates.fromLocalCssToPix(scrollOffsetY),
(int) mRenderCoordinates.getScrollXPix(),
(int) mRenderCoordinates.getScrollYPix());
}
mRenderCoordinates.updateFrameInfo(
scrollOffsetX, scrollOffsetY,
contentWidth, contentHeight,
viewportWidth, viewportHeight,
pageScaleFactor, minPageScaleFactor, maxPageScaleFactor,
contentOffsetYPix);
if (scrollChanged || contentOffsetChanged) {
for (mGestureStateListenersIterator.rewind();
mGestureStateListenersIterator.hasNext();) {
mGestureStateListenersIterator.next().onScrollOffsetOrExtentChanged(
computeVerticalScrollOffset(),
computeVerticalScrollExtent());
}
}
if (needUpdateZoomControls) mZoomControlsDelegate.updateZoomControls();
// Update offsets for fullscreen.
final float controlsOffsetPix = controlsOffsetYCss * deviceScale;
// TODO(aelias): Remove last argument after downstream removes it.
getContentViewClient().onOffsetsForFullscreenChanged(
controlsOffsetPix, contentOffsetYPix, 0);
if (mBrowserAccessibilityManager != null) {
mBrowserAccessibilityManager.notifyFrameInfoInitialized();
}
TraceEvent.end("ContentViewCore:updateFrameInfo");
}
@CalledByNative
private void updateImeAdapter(long nativeImeAdapterAndroid, int textInputType,
int textInputFlags, String text, int selectionStart, int selectionEnd,
int compositionStart, int compositionEnd, boolean showImeIfNeeded,
boolean isNonImeChange) {
TraceEvent.begin();
mFocusedNodeEditable = (textInputType != ImeAdapter.getTextInputTypeNone());
if (!mFocusedNodeEditable) hidePastePopup();
mImeAdapter.updateKeyboardVisibility(
nativeImeAdapterAndroid, textInputType, textInputFlags, showImeIfNeeded);
if (mInputConnection != null) {
mInputConnection.updateState(text, selectionStart, selectionEnd, compositionStart,
compositionEnd, isNonImeChange);
}
if (mActionMode != null) mActionMode.invalidate();
TraceEvent.end();
}
@SuppressWarnings("unused")
@CalledByNative
private void setTitle(String title) {
getContentViewClient().onUpdateTitle(title);
}
/**
* Called (from native) when the <select> popup needs to be shown.
* @param nativeSelectPopupSourceFrame The native RenderFrameHost that owns the popup.
* @param items Items to show.
* @param enabled POPUP_ITEM_TYPEs for items.
* @param multiple Whether the popup menu should support multi-select.
* @param selectedIndices Indices of selected items.
*/
@SuppressWarnings("unused")
@CalledByNative
private void showSelectPopup(long nativeSelectPopupSourceFrame, Rect bounds, String[] items,
int[] enabled, boolean multiple, int[] selectedIndices) {
if (mContainerView.getParent() == null || mContainerView.getVisibility() != View.VISIBLE) {
mNativeSelectPopupSourceFrame = nativeSelectPopupSourceFrame;
selectPopupMenuItems(null);
return;
}
hidePopupsAndClearSelection();
assert mNativeSelectPopupSourceFrame == 0 : "Zombie popup did not clear the frame source";
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]));
}
if (DeviceFormFactor.isTablet(mContext) && !multiple && !isTouchExplorationEnabled()) {
mSelectPopup = new SelectPopupDropdown(this, popupItems, bounds, selectedIndices);
} else {
mSelectPopup = new SelectPopupDialog(this, popupItems, multiple, selectedIndices);
}
mNativeSelectPopupSourceFrame = nativeSelectPopupSourceFrame;
mSelectPopup.show();
}
/**
* Called when the <select> popup needs to be hidden.
*/
@CalledByNative
private void hideSelectPopup() {
if (mSelectPopup != null) mSelectPopup.hide();
}
/**
* @return The visible select popup being shown.
*/
public SelectPopup getSelectPopupForTest() {
return mSelectPopup;
}
@SuppressWarnings("unused")
@CalledByNative
private void showDisambiguationPopup(Rect targetRect, Bitmap zoomedBitmap) {
mPopupZoomer.setBitmap(zoomedBitmap);
mPopupZoomer.show(targetRect);
}
@SuppressWarnings("unused")
@CalledByNative
private TouchEventSynthesizer createTouchEventSynthesizer() {
return new TouchEventSynthesizer(this);
}
@SuppressWarnings("unused")
@CalledByNative
private PopupTouchHandleDrawable createPopupTouchHandleDrawable() {
if (mTouchHandleDelegate == null) {
mTouchHandleDelegate = new PopupTouchHandleDrawableDelegate() {
@Override
public View getParent() {
return getContainerView();
}
@Override
public PositionObserver getParentPositionObserver() {
return mPositionObserver;
}
@Override
public boolean onTouchHandleEvent(MotionEvent event) {
final boolean isTouchHandleEvent = true;
return onTouchEventImpl(event, isTouchHandleEvent);
}
@Override
public boolean isScrollInProgress() {
return ContentViewCore.this.isScrollInProgress();
}
};
}
return new PopupTouchHandleDrawable(mTouchHandleDelegate);
}
@SuppressWarnings("unused")
@CalledByNative
private void onSelectionChanged(String text) {
mLastSelectedText = text;
getContentViewClient().onSelectionChanged(text);
}
@SuppressWarnings("unused")
@CalledByNative
private void showPastePopupWithFeedback(int xDip, int yDip) {
// TODO(jdduke): Remove this when there is a better signal that long press caused
// showing of the paste popup. See http://crbug.com/150151.
if (showPastePopup(xDip, yDip)) {
mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
}
private boolean showPastePopup(int xDip, int yDip) {
if (!mHasInsertion || !canPaste()) return false;
final float contentOffsetYPix = mRenderCoordinates.getContentOffsetYPix();
getPastePopup().showAt(
(int) mRenderCoordinates.fromDipToPix(xDip),
(int) (mRenderCoordinates.fromDipToPix(yDip) + contentOffsetYPix));
return true;
}
private PastePopupMenu getPastePopup() {
if (mPastePopupMenu == null) {
mPastePopupMenu = new PastePopupMenu(getContainerView(),
new PastePopupMenuDelegate() {
@Override
public void paste() {
mImeAdapter.paste();
dismissTextHandles();
}
});
}
return mPastePopupMenu;
}
@VisibleForTesting
public PastePopupMenu getPastePopupForTest() {
return getPastePopup();
}
private boolean canPaste() {
if (!mFocusedNodeEditable) return false;
return ((ClipboardManager) mContext.getSystemService(
Context.CLIPBOARD_SERVICE)).hasPrimaryClip();
}
@SuppressWarnings("unused")
@CalledByNative
private void onRenderProcessChange() {
attachImeAdapter();
}
/**
* Attaches the native ImeAdapter object to the java ImeAdapter to allow communication via JNI.
*/
public void attachImeAdapter() {
if (mImeAdapter != null && mNativeContentViewCore != 0) {
mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore));
}
}
/**
* @see View#hasFocus()
*/
@CalledByNative
private boolean hasFocus() {
// If the container view is not focusable, we consider it always focused from
// Chromium's point of view.
if (!mContainerView.isFocusable()) return true;
return mContainerView.hasFocus();
}
/**
* Checks whether the ContentViewCore can be zoomed in.
*
* @return True if the ContentViewCore can be zoomed in.
*/
// This method uses the term 'zoom' for legacy reasons, but relates
// to what chrome calls the 'page scale factor'.
public boolean canZoomIn() {
final float zoomInExtent = mRenderCoordinates.getMaxPageScaleFactor()
- mRenderCoordinates.getPageScaleFactor();
return zoomInExtent > ZOOM_CONTROLS_EPSILON;
}
/**
* Checks whether the ContentViewCore can be zoomed out.
*
* @return True if the ContentViewCore can be zoomed out.
*/
// This method uses the term 'zoom' for legacy reasons, but relates
// to what chrome calls the 'page scale factor'.
public boolean canZoomOut() {
final float zoomOutExtent = mRenderCoordinates.getPageScaleFactor()
- mRenderCoordinates.getMinPageScaleFactor();
return zoomOutExtent > ZOOM_CONTROLS_EPSILON;
}
/**
* Zooms in the ContentViewCore by 25% (or less if that would result in
* zooming in more than possible).
*
* @return True if there was a zoom change, false otherwise.
*/
// This method uses the term 'zoom' for legacy reasons, but relates
// to what chrome calls the 'page scale factor'.
public boolean zoomIn() {
if (!canZoomIn()) {
return false;
}
return pinchByDelta(1.25f);
}
/**
* Zooms out the ContentViewCore by 20% (or less if that would result in
* zooming out more than possible).
*
* @return True if there was a zoom change, false otherwise.
*/
// This method uses the term 'zoom' for legacy reasons, but relates
// to what chrome calls the 'page scale factor'.
public boolean zoomOut() {
if (!canZoomOut()) {
return false;
}
return pinchByDelta(0.8f);
}
/**
* Resets the zoom factor of the ContentViewCore.
*
* @return True if there was a zoom change, false otherwise.
*/
// This method uses the term 'zoom' for legacy reasons, but relates
// to what chrome calls the 'page scale factor'.
public boolean zoomReset() {
// The page scale factor is initialized to mNativeMinimumScale when
// the page finishes loading. Thus sets it back to mNativeMinimumScale.
if (!canZoomOut()) return false;
return pinchByDelta(
mRenderCoordinates.getMinPageScaleFactor()
/ mRenderCoordinates.getPageScaleFactor());
}
/**
* Simulate a pinch zoom gesture.
*
* @param delta the factor by which the current page scale should be multiplied by.
* @return whether the gesture was sent.
*/
public boolean pinchByDelta(float delta) {
if (mNativeContentViewCore == 0) return false;
long timeMs = SystemClock.uptimeMillis();
int xPix = getViewportWidthPix() / 2;
int yPix = getViewportHeightPix() / 2;
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.
*/
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
* &quot;for .. in&quot; 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);
}
/**
* Returns JavaScript interface objects previously injected via
* {@link #addJavascriptInterface(Object, String)}.
*
* @return the mapping of names to interface objects and corresponding annotation classes
*/
public Map<String, Pair<Object, Class>> getJavascriptInterfaces() {
return mJavaScriptInterfaces;
}
/**
* This will mimic {@link #addPossiblyUnsafeJavascriptInterface(Object, String, Class)}
* and automatically pass in {@link JavascriptInterface} as the required annotation.
*
* @param object The Java object to inject into the ContentViewCore's JavaScript context. Null
* values are ignored.
* @param name The name used to expose the instance in JavaScript.
*/
public void addJavascriptInterface(Object object, String name) {
addPossiblyUnsafeJavascriptInterface(object, name, JavascriptInterface.class);
}
/**
* This method injects the supplied Java object into the ContentViewCore.
* The object is injected into the JavaScript context of the main frame,
* using the supplied name. This allows the Java object to be accessed from
* JavaScript. Note that that injected objects will not appear in
* JavaScript until the page is next (re)loaded. For example:
* <pre> view.addJavascriptInterface(new Object(), "injectedObject");
* view.loadData("<!DOCTYPE html><title></title>", "text/html", null);
* view.loadUrl("javascript:alert(injectedObject.toString())");</pre>
* <p><strong>IMPORTANT:</strong>
* <ul>
* <li> addJavascriptInterface() can be used to allow JavaScript to control
* the host application. This is a powerful feature, but also presents a
* security risk. Use of this method in a ContentViewCore containing
* untrusted content could allow an attacker to manipulate the host
* application in unintended ways, executing Java code with the permissions
* of the host application. Use extreme care when using this method in a
* ContentViewCore which could contain untrusted content. Particular care
* should be taken to avoid unintentional access to inherited methods, such
* as {@link Object#getClass()}. To prevent access to inherited methods,
* pass an annotation for {@code requiredAnnotation}. This will ensure
* that only methods with {@code requiredAnnotation} are exposed to the
* Javascript layer. {@code requiredAnnotation} will be passed to all
* subsequently injected Java objects if any methods return an object. This
* means the same restrictions (or lack thereof) will apply. Alternatively,
* {@link #addJavascriptInterface(Object, String)} can be called, which
* automatically uses the {@link JavascriptInterface} annotation.
* <li> JavaScript interacts with Java objects on a private, background
* thread of the ContentViewCore. Care is therefore required to maintain
* thread safety.</li>
* </ul></p>
*
* @param object The Java object to inject into the
* ContentViewCore's JavaScript context. Null
* values are ignored.
* @param name The name used to expose the instance in
* JavaScript.
* @param requiredAnnotation Restrict exposed methods to ones with this
* annotation. If {@code null} all methods are
* exposed.
*
*/
public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
Class<? extends Annotation> requiredAnnotation) {
if (mNativeContentViewCore != 0 && object != null) {
mJavaScriptInterfaces.put(name, new Pair<Object, Class>(object, requiredAnnotation));
nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation);
}
}
/**
* Removes a previously added JavaScript interface with the given name.
*
* @param name The name of the interface to remove.
*/
public void removeJavascriptInterface(String name) {
mJavaScriptInterfaces.remove(name);
if (mNativeContentViewCore != 0) {
nativeRemoveJavascriptInterface(mNativeContentViewCore, name);
}
}
/**
* Return the current scale of the ContentView.
* @return The current page scale factor.
*/
@VisibleForTesting
public float getScale() {
return mRenderCoordinates.getPageScaleFactor();
}
@CalledByNative
private void startContentIntent(String contentUrl) {
getContentViewClient().onStartContentIntent(getContext(), contentUrl);
}
@Override
public void onAccessibilityStateChanged(boolean enabled) {
setAccessibilityState(enabled);
}
/**
* Determines whether or not this ContentViewCore can handle this accessibility action.
* @param action The action to perform.
* @return Whether or not this action is supported.
*/
public boolean supportsAccessibilityAction(int action) {
return mAccessibilityInjector.supportsAccessibilityAction(action);
}
/**
* Attempts to perform an accessibility action on the web content. If the accessibility action
* cannot be processed, it returns {@code null}, allowing the caller to know to call the
* super {@link View#performAccessibilityAction(int, Bundle)} method and use that return value.
* Otherwise the return value from this method should be used.
* @param action The action to perform.
* @param arguments Optional action arguments.
* @return Whether the action was performed or {@code null} if the call should be delegated to
* the super {@link View} class.
*/
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (mAccessibilityInjector.supportsAccessibilityAction(action)) {
return mAccessibilityInjector.performAccessibilityAction(action, arguments);
}
return false;
}
/**
* Set the BrowserAccessibilityManager, used for native accessibility
* (not script injection). This is only set when system accessibility
* has been enabled.
* @param manager The new BrowserAccessibilityManager.
*/
public void setBrowserAccessibilityManager(BrowserAccessibilityManager manager) {
mBrowserAccessibilityManager = manager;
}
/**
* Get the BrowserAccessibilityManager, used for native accessibility
* (not script injection). This will return null when system accessibility
* is not enabled.
* @return This view's BrowserAccessibilityManager.
*/
public BrowserAccessibilityManager getBrowserAccessibilityManager() {
return mBrowserAccessibilityManager;
}
/**
* If native accessibility (not script injection) is enabled, and if this is
* running on JellyBean or later, returns an AccessibilityNodeProvider that
* implements native accessibility for this view. Returns null otherwise.
* Lazily initializes native accessibility here if it's allowed.
* @return The AccessibilityNodeProvider, if available, or null otherwise.
*/
public AccessibilityNodeProvider getAccessibilityNodeProvider() {
if (mBrowserAccessibilityManager != null) {
return mBrowserAccessibilityManager.getAccessibilityNodeProvider();
}
if (mNativeAccessibilityAllowed && !mNativeAccessibilityEnabled
&& mNativeContentViewCore != 0
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mNativeAccessibilityEnabled = true;
nativeSetAccessibilityEnabled(mNativeContentViewCore, true);
}
return null;
}
/**
* @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
*/
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
// Note: this is only used by the script-injecting accessibility code.
mAccessibilityInjector.onInitializeAccessibilityNodeInfo(info);
}
/**
* @see View#onInitializeAccessibilityEvent(AccessibilityEvent)
*/
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
// Note: this is only used by the script-injecting accessibility code.
event.setClassName(this.getClass().getName());
// Identify where the top-left of the screen currently points to.
event.setScrollX(mRenderCoordinates.getScrollXPixInt());
event.setScrollY(mRenderCoordinates.getScrollYPixInt());
// The maximum scroll values are determined by taking the content dimensions and
// subtracting off the actual dimensions of the ChromeView.
int maxScrollXPix = Math.max(0, mRenderCoordinates.getMaxHorizontalScrollPixInt());
int maxScrollYPix = Math.max(0, mRenderCoordinates.getMaxVerticalScrollPixInt());
event.setScrollable(maxScrollXPix > 0 || maxScrollYPix > 0);
// Setting the maximum scroll values requires API level 15 or higher.
final int sdkVersionRequiredToSetScroll = 15;
if (Build.VERSION.SDK_INT >= sdkVersionRequiredToSetScroll) {
event.setMaxScrollX(maxScrollXPix);
event.setMaxScrollY(maxScrollYPix);
}
}
/**
* Returns whether accessibility script injection is enabled on the device
*/
public boolean isDeviceAccessibilityScriptInjectionEnabled() {
try {
// 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;
}
if (!mContentSettings.getJavaScriptEnabled()) {
return false;
}
int result = getContext().checkCallingOrSelfPermission(
android.Manifest.permission.INTERNET);
if (result != PackageManager.PERMISSION_GRANTED) {
return false;
}
Field field = Settings.Secure.class.getField("ACCESSIBILITY_SCRIPT_INJECTION");
field.setAccessible(true);
String accessibilityScriptInjection = (String) field.get(null);
ContentResolver contentResolver = getContext().getContentResolver();
if (mAccessibilityScriptInjectionObserver == null) {
ContentObserver contentObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange, Uri uri) {
setAccessibilityState(mAccessibilityManager.isEnabled());
}
};
contentResolver.registerContentObserver(
Settings.Secure.getUriFor(accessibilityScriptInjection),
false,
contentObserver);
mAccessibilityScriptInjectionObserver = contentObserver;
}
return Settings.Secure.getInt(contentResolver, accessibilityScriptInjection, 0) == 1;
} catch (NoSuchFieldException e) {
// Do nothing, default to false.
} catch (IllegalAccessException e) {
// Do nothing, default to false.
}
return false;
}
/**
* Returns whether or not accessibility injection is being used.
*/
public boolean isInjectingAccessibilityScript() {
return mAccessibilityInjector.accessibilityIsAvailable();
}
/**
* 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
* accessibility scripts. Native accessibility is enabled on demand.
*/
public void setAccessibilityState(boolean state) {
if (!state) {
setInjectedAccessibility(false);
mNativeAccessibilityAllowed = false;
mTouchExplorationEnabled = false;
} else {
boolean useScriptInjection = isDeviceAccessibilityScriptInjectionEnabled();
setInjectedAccessibility(useScriptInjection);
mNativeAccessibilityAllowed = !useScriptInjection;
mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
}
}
/**
* Enable or disable injected accessibility features
*/
public void setInjectedAccessibility(boolean enabled) {
mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
mAccessibilityInjector.setScriptEnabled(enabled);
}
/**
* Stop any TTS notifications that are currently going on.
*/
public void stopCurrentAccessibilityNotifications() {
mAccessibilityInjector.onPageLostFocus();
}
/**
* Return whether or not we should set accessibility focus on page load.
*/
public boolean shouldSetAccessibilityFocusOnPageLoad() {
return mShouldSetAccessibilityFocusOnPageLoad;
}
/**
* Sets whether or not we should set accessibility focus on page load.
* This only applies if an accessibility service like TalkBack is running.
* This is desirable behavior for a browser window, but not for an embedded
* WebView.
*/
public void setShouldSetAccessibilityFocusOnPageLoad(boolean on) {
mShouldSetAccessibilityFocusOnPageLoad = on;
}
/**
*
* @return The cached copy of render positions and scales.
*/
public RenderCoordinates getRenderCoordinates() {
return mRenderCoordinates;
}
@CalledByNative
private static Rect createRect(int x, int y, int right, int bottom) {
return new Rect(x, y, right, bottom);
}
public void extractSmartClipData(int x, int y, int width, int height) {
if (mNativeContentViewCore != 0) {
x += mSmartClipOffsetX;
y += mSmartClipOffsetY;
nativeExtractSmartClipData(mNativeContentViewCore, x, y, width, height);
}
}
/**
* Set offsets for smart clip.
*
* <p>This should be called if there is a viewport change introduced by,
* e.g., show and hide of a location bar.
*
* @param offsetX Offset for X position.
* @param offsetY Offset for Y position.
*/
public void setSmartClipOffsets(int offsetX, int offsetY) {
mSmartClipOffsetX = offsetX;
mSmartClipOffsetY = offsetY;
}
@CalledByNative
private void onSmartClipDataExtracted(String text, String html, Rect clipRect) {
// Translate the positions by the offsets introduced by location bar. Note that the
// coordinates are in dp scale, and that this definitely has the potential to be
// different from the offsets when extractSmartClipData() was called. However,
// as long as OEM has a UI that consumes all the inputs and waits until the
// callback is called, then there shouldn't be any difference.
// TODO(changwan): once crbug.com/416432 is resolved, try to pass offsets as
// separate params for extractSmartClipData(), and apply them not the new offset
// values in the callback.
final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
final int offsetXInDp = (int) (mSmartClipOffsetX / deviceScale);
final int offsetYInDp = (int) (mSmartClipOffsetY / deviceScale);
clipRect.offset(-offsetXInDp, -offsetYInDp);
if (mSmartClipDataListener != null) {
mSmartClipDataListener.onSmartClipDataExtracted(text, html, clipRect);
}
}
public void setSmartClipDataListener(SmartClipDataListener listener) {
mSmartClipDataListener = listener;
}
public void setBackgroundOpaque(boolean opaque) {
if (mNativeContentViewCore != 0) {
nativeSetBackgroundOpaque(mNativeContentViewCore, opaque);
}
}
/**
* Offer a long press gesture to the embedding View, primarily for WebView compatibility.
*
* @return true if the embedder handled the event.
*/
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,
long viewAndroidPtr, long windowAndroidPtr, HashSet<Object> retainedObjectSet);
ContentVideoViewClient getContentVideoViewClient() {
return getContentViewClient().getContentVideoViewClient();
}
@CalledByNative
private boolean shouldBlockMediaRequest(String url) {
return getContentViewClient().shouldBlockMediaRequest(url);
}
@CalledByNative
private void onNativeFlingStopped() {
// 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);
}
/**
* Set whether the ContentViewCore requires the WebContents to be fullscreen in order to lock
* the screen orientation.
*/
public void setFullscreenRequiredForOrientationLock(boolean value) {
mFullscreenRequiredForOrientationLock = value;
}
@CalledByNative
private boolean isFullscreenRequiredForOrientationLock() {
return mFullscreenRequiredForOrientationLock;
}
private native WebContents nativeGetWebContentsAndroid(long nativeContentViewCoreImpl);
private native void nativeOnJavaContentViewCoreDestroyed(long nativeContentViewCoreImpl);
private native void nativeSetFocus(long nativeContentViewCoreImpl, boolean focused);
private native void nativeSendOrientationChangeEvent(
long nativeContentViewCoreImpl, int orientation);
// All touch events (including flings, scrolls etc) accept coordinates in physical pixels.
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,
float touchMinor0, float touchMinor1,
float orientation0, float orientation1,
float rawX, float rawY,
int androidToolType0, int androidToolType1,
int androidButtonState, int androidMetaState,
boolean isTouchHandleEvent);
private native int nativeSendMouseMoveEvent(
long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native int nativeSendMouseWheelEvent(
long nativeContentViewCoreImpl, long timeMs, float x, float y, float verticalAxis);
private native void nativeScrollBegin(
long nativeContentViewCoreImpl, long timeMs, float x, float y, float hintX,
float hintY);
private native void nativeScrollEnd(long nativeContentViewCoreImpl, long timeMs);
private native void nativeScrollBy(
long nativeContentViewCoreImpl, long timeMs, float x, float y,
float deltaX, float deltaY);
private native void nativeFlingStart(
long nativeContentViewCoreImpl, long timeMs, float x, float y, float vx, float vy);
private native void nativeFlingCancel(long nativeContentViewCoreImpl, long timeMs);
private native void nativeSingleTap(
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);
private native void nativePinchBegin(
long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativePinchEnd(long nativeContentViewCoreImpl, long timeMs);
private native void nativePinchBy(long nativeContentViewCoreImpl, long timeMs,
float anchorX, float anchorY, float deltaScale);
private native void nativeSelectBetweenCoordinates(
long nativeContentViewCoreImpl, float x1, float y1, float x2, float y2);
private native void nativeMoveCaret(long nativeContentViewCoreImpl, float x, float y);
private native void nativeDismissTextHandles(long nativeContentViewCoreImpl);
private native void nativeSetTextHandlesTemporarilyHidden(
long nativeContentViewCoreImpl, boolean hidden);
private native void nativeResetGestureDetection(long nativeContentViewCoreImpl);
private native void nativeSetDoubleTapSupportEnabled(
long nativeContentViewCoreImpl, boolean enabled);
private native void nativeSetMultiTouchZoomSupportEnabled(
long nativeContentViewCoreImpl, boolean enabled);
private native void nativeSelectPopupMenuItems(long nativeContentViewCoreImpl,
long nativeSelectPopupSourceFrame, int[] indices);
private native long nativeGetNativeImeAdapter(long nativeContentViewCoreImpl);
private native int nativeGetCurrentRenderProcessId(long nativeContentViewCoreImpl);
private native void nativeSetAllowJavascriptInterfacesInspection(
long nativeContentViewCoreImpl, boolean allow);
private native void nativeAddJavascriptInterface(long nativeContentViewCoreImpl, Object object,
String name, Class requiredAnnotation);
private native void nativeRemoveJavascriptInterface(long nativeContentViewCoreImpl,
String name);
private native void nativeWasResized(long nativeContentViewCoreImpl);
private native void nativeSetAccessibilityEnabled(
long nativeContentViewCoreImpl, boolean enabled);
private native void nativeExtractSmartClipData(long nativeContentViewCoreImpl,
int x, int y, int w, int h);
private native void nativeSetBackgroundOpaque(long nativeContentViewCoreImpl, boolean opaque);
}