ContentViewCore.java revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'o// Copyright 2012 The Chromium Authors. All rights reserved.
2b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'o// Use of this source code is governed by a BSD-style license that can be
3b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'o// found in the LICENSE file.
4b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'o
5b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'opackage org.chromium.content.browser;
6d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'o
7d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport android.annotation.SuppressLint;
8e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.app.Activity;
9e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.app.SearchManager;
10b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.content.ClipboardManager;
11e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.content.ContentResolver;
12e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.content.Context;
13e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.content.Intent;
14e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.content.pm.FeatureInfo;
15e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.content.pm.PackageManager;
16e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.content.res.Configuration;
17e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.database.ContentObserver;
18e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.graphics.Bitmap;
19e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.graphics.Canvas;
20e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.graphics.Rect;
21e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.net.Uri;
22e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.os.Build;
23e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.os.Bundle;
24e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.os.Handler;
25e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.os.ResultReceiver;
26e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.os.SystemClock;
27e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.provider.Browser;
28e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.provider.Settings;
29e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.text.Editable;
30e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.text.Selection;
31e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.text.TextUtils;
32e0ed7404719a9ddd2ba427a80db5365c8bad18c0JP Abgrallimport android.util.Log;
33b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.util.Pair;
34b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.ActionMode;
35b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.HapticFeedbackConstants;
36b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.InputDevice;
37b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.KeyEvent;
38b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.MotionEvent;
39b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.View;
40b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.ViewGroup;
41b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.accessibility.AccessibilityEvent;
42b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.accessibility.AccessibilityManager;
43b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
44b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.accessibility.AccessibilityNodeInfo;
45b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.accessibility.AccessibilityNodeProvider;
46b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.inputmethod.EditorInfo;
47b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.inputmethod.InputConnection;
48b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.view.inputmethod.InputMethodManager;
49b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport android.widget.FrameLayout;
50b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'o
51e7d32fcb567b2a716de87204542ecc1d5e9ae1aeTheodore Ts'oimport com.google.common.annotations.VisibleForTesting;
52b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'o
53b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport org.chromium.base.ApiCompatibilityUtils;
54b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport org.chromium.base.CalledByNative;
55e7d32fcb567b2a716de87204542ecc1d5e9ae1aeTheodore Ts'oimport org.chromium.base.CommandLine;
56b16fb628bf9035363002ca7ab72992624f4bb3cfTheodore Ts'oimport org.chromium.base.JNINamespace;
57d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.base.ObserverList;
5844104b2b377c9107bbfe3cd1124f0e783e143dd9Theodore Ts'oimport org.chromium.base.ObserverList.RewindableIterator;
59d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.base.TraceEvent;
60d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.content.R;
61d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.content.browser.ScreenOrientationListener.ScreenOrientationObserver;
62d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.content.browser.accessibility.AccessibilityInjector;
63d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.content.browser.accessibility.BrowserAccessibilityManager;
64d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.content.browser.input.AdapterInputConnection;
65d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.content.browser.input.GamepadList;
66d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.content.browser.input.ImeAdapter;
67e7d32fcb567b2a716de87204542ecc1d5e9ae1aeTheodore Ts'oimport org.chromium.content.browser.input.ImeAdapter.AdapterInputConnectionFactory;
68e7d32fcb567b2a716de87204542ecc1d5e9ae1aeTheodore Ts'oimport org.chromium.content.browser.input.InputMethodManagerWrapper;
69e7d32fcb567b2a716de87204542ecc1d5e9ae1aeTheodore Ts'oimport org.chromium.content.browser.input.PastePopupMenu;
70e7d32fcb567b2a716de87204542ecc1d5e9ae1aeTheodore Ts'oimport org.chromium.content.browser.input.PastePopupMenu.PastePopupMenuDelegate;
71e7d32fcb567b2a716de87204542ecc1d5e9ae1aeTheodore Ts'oimport org.chromium.content.browser.input.PopupTouchHandleDrawable;
72e7d32fcb567b2a716de87204542ecc1d5e9ae1aeTheodore Ts'oimport org.chromium.content.browser.input.PopupTouchHandleDrawable.PopupTouchHandleDrawableDelegate;
73d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.content.browser.input.SelectPopup;
74d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.content.browser.input.SelectPopupDialog;
75d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.content.browser.input.SelectPopupDropdown;
76d03550970c57100dbafd8941f34acafd208ec52eTheodore Ts'oimport org.chromium.content.browser.input.SelectPopupItem;
77import org.chromium.content.browser.input.SelectionEventType;
78import org.chromium.content.common.ContentSwitches;
79import org.chromium.content_public.browser.GestureStateListener;
80import org.chromium.content_public.browser.WebContents;
81import org.chromium.ui.base.DeviceFormFactor;
82import org.chromium.ui.base.ViewAndroid;
83import org.chromium.ui.base.ViewAndroidDelegate;
84import org.chromium.ui.base.WindowAndroid;
85import org.chromium.ui.gfx.DeviceDisplayInfo;
86
87import java.lang.annotation.Annotation;
88import java.lang.reflect.Field;
89import java.util.ArrayList;
90import java.util.HashMap;
91import java.util.HashSet;
92import java.util.List;
93import java.util.Map;
94
95/**
96 * Provides a Java-side 'wrapper' around a WebContent (native) instance.
97 * Contains all the major functionality necessary to manage the lifecycle of a ContentView without
98 * being tied to the view system.
99 */
100@JNINamespace("content")
101public class ContentViewCore
102        implements NavigationClient, AccessibilityStateChangeListener, ScreenOrientationObserver {
103
104    private static final String TAG = "ContentViewCore";
105
106    // Used to avoid enabling zooming in / out if resulting zooming will
107    // produce little visible difference.
108    private static final float ZOOM_CONTROLS_EPSILON = 0.007f;
109
110    // Used to represent gestures for long press and long tap.
111    private static final int IS_LONG_PRESS = 1;
112    private static final int IS_LONG_TAP = 2;
113
114    // These values are obtained from Samsung.
115    // TODO(changwan): refactor SPen related code into a separate class. See
116    // http://crbug.com/398169.
117    private static final int SPEN_ACTION_DOWN = 211;
118    private static final int SPEN_ACTION_UP = 212;
119    private static final int SPEN_ACTION_MOVE = 213;
120    private static final int SPEN_ACTION_CANCEL = 214;
121    private static Boolean sIsSPenSupported;
122
123    // If the embedder adds a JavaScript interface object that contains an indirect reference to
124    // the ContentViewCore, then storing a strong ref to the interface object on the native
125    // side would prevent garbage collection of the ContentViewCore (as that strong ref would
126    // create a new GC root).
127    // For that reason, we store only a weak reference to the interface object on the
128    // native side. However we still need a strong reference on the Java side to
129    // prevent garbage collection if the embedder doesn't maintain their own ref to the
130    // interface object - the Java side ref won't create a new GC root.
131    // This map stores those references. We put into the map on addJavaScriptInterface()
132    // and remove from it in removeJavaScriptInterface(). The annotation class is stored for
133    // the purpose of migrating injected objects from one instance of CVC to another, which
134    // is used by Android WebView to support WebChromeClient.onCreateWindow scenario.
135    private final Map<String, Pair<Object, Class>> mJavaScriptInterfaces =
136            new HashMap<String, Pair<Object, Class>>();
137
138    // Additionally, we keep track of all Java bound JS objects that are in use on the
139    // current page to ensure that they are not garbage collected until the page is
140    // navigated. This includes interface objects that have been removed
141    // via the removeJavaScriptInterface API and transient objects returned from methods
142    // on the interface object. Note we use HashSet rather than Set as the native side
143    // expects HashSet (no bindings for interfaces).
144    private final HashSet<Object> mRetainedJavaScriptObjects = new HashSet<Object>();
145
146    /**
147     * Interface that consumers of {@link ContentViewCore} must implement to allow the proper
148     * dispatching of view methods through the containing view.
149     *
150     * <p>
151     * All methods with the "super_" prefix should be routed to the parent of the
152     * implementing container view.
153     */
154    @SuppressWarnings("javadoc")
155    public interface InternalAccessDelegate {
156        /**
157         * @see View#drawChild(Canvas, View, long)
158         */
159        boolean drawChild(Canvas canvas, View child, long drawingTime);
160
161        /**
162         * @see View#onKeyUp(keyCode, KeyEvent)
163         */
164        boolean super_onKeyUp(int keyCode, KeyEvent event);
165
166        /**
167         * @see View#dispatchKeyEventPreIme(KeyEvent)
168         */
169        boolean super_dispatchKeyEventPreIme(KeyEvent event);
170
171        /**
172         * @see View#dispatchKeyEvent(KeyEvent)
173         */
174        boolean super_dispatchKeyEvent(KeyEvent event);
175
176        /**
177         * @see View#onGenericMotionEvent(MotionEvent)
178         */
179        boolean super_onGenericMotionEvent(MotionEvent event);
180
181        /**
182         * @see View#onConfigurationChanged(Configuration)
183         */
184        void super_onConfigurationChanged(Configuration newConfig);
185
186        /**
187         * @see View#onScrollChanged(int, int, int, int)
188         */
189        void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix);
190
191        /**
192         * @see View#awakenScrollBars()
193         */
194        boolean awakenScrollBars();
195
196        /**
197         * @see View#awakenScrollBars(int, boolean)
198         */
199        boolean super_awakenScrollBars(int startDelay, boolean invalidate);
200    }
201
202    /**
203     * An interface for controlling visibility and state of embedder-provided zoom controls.
204     */
205    public interface ZoomControlsDelegate {
206        /**
207         * Called when it's reasonable to show zoom controls.
208         */
209        void invokeZoomPicker();
210
211        /**
212         * Called when zoom controls need to be hidden (e.g. when the view hides).
213         */
214        void dismissZoomPicker();
215
216        /**
217         * Called when page scale has been changed, so the controls can update their state.
218         */
219        void updateZoomControls();
220    }
221
222    /**
223     * An interface that allows the embedder to be notified when the results of
224     * extractSmartClipData are available.
225     */
226    public interface SmartClipDataListener {
227        public void onSmartClipDataExtracted(String text, String html, Rect clipRect);
228    }
229
230    /**
231     * An interface that allows the embedder to be notified of navigation transition
232     * related events and respond to them.
233     */
234    public interface NavigationTransitionDelegate {
235        /**
236         * Called when the navigation is deferred immediately after the response started.
237         *
238         * @param enteringColor The background color of the entering document, as a String
239         *                      representing a legal CSS color value. This is inserted into
240         *                      the transition layer's markup after the entering stylesheets
241         *                      have been applied.
242         */
243        public void didDeferAfterResponseStarted(String enteringColor);
244
245        /**
246         * Called when a navigation transition has been detected, and we need to check
247         * if it's supported.
248         */
249        public boolean willHandleDeferAfterResponseStarted();
250
251        /**
252         * Called when the navigation is deferred immediately after the response
253         * started.
254         */
255        public void addEnteringStylesheetToTransition(String stylesheet);
256
257        /**
258         * Notifies that a navigation transition is started for a given frame.
259         * @param frameId A positive, non-zero integer identifying the navigating frame.
260         */
261        public void didStartNavigationTransitionForFrame(long frameId);
262    }
263
264    private final Context mContext;
265    private ViewGroup mContainerView;
266    private InternalAccessDelegate mContainerViewInternals;
267    private WebContents mWebContents;
268    private WebContentsObserverAndroid mWebContentsObserver;
269
270    private ContentViewClient mContentViewClient;
271
272    private ContentSettings mContentSettings;
273
274    // Native pointer to C++ ContentViewCoreImpl object which will be set by nativeInit().
275    private long mNativeContentViewCore = 0;
276
277    private final ObserverList<GestureStateListener> mGestureStateListeners;
278    private final RewindableIterator<GestureStateListener> mGestureStateListenersIterator;
279    private ZoomControlsDelegate mZoomControlsDelegate;
280
281    private PopupZoomer mPopupZoomer;
282    private SelectPopup mSelectPopup;
283
284    private Runnable mFakeMouseMoveRunnable = null;
285
286    // Only valid when focused on a text / password field.
287    private ImeAdapter mImeAdapter;
288    private ImeAdapter.AdapterInputConnectionFactory mAdapterInputConnectionFactory;
289    private AdapterInputConnection mInputConnection;
290    private InputMethodManagerWrapper mInputMethodManagerWrapper;
291
292    // Lazily created paste popup menu, triggered either via long press in an
293    // editable region or from tapping the insertion handle.
294    private PastePopupMenu mPastePopupMenu;
295
296    private PopupTouchHandleDrawableDelegate mTouchHandleDelegate;
297
298    private PositionObserver mPositionObserver;
299
300    // Size of the viewport in physical pixels as set from onSizeChanged.
301    private int mViewportWidthPix;
302    private int mViewportHeightPix;
303    private int mPhysicalBackingWidthPix;
304    private int mPhysicalBackingHeightPix;
305    private int mOverdrawBottomHeightPix;
306    private int mViewportSizeOffsetWidthPix;
307    private int mViewportSizeOffsetHeightPix;
308
309    // Cached copy of all positions and scales as reported by the renderer.
310    private final RenderCoordinates mRenderCoordinates;
311
312    // Tracks whether a selection is currently active.  When applied to selected text, indicates
313    // whether the last selected text is still highlighted.
314    private boolean mHasSelection;
315    private boolean mHasInsertion;
316    private String mLastSelectedText;
317    private boolean mFocusedNodeEditable;
318    private ActionMode mActionMode;
319    private boolean mUnselectAllOnActionModeDismiss;
320
321    // Delegate that will handle GET downloads, and be notified of completion of POST downloads.
322    private ContentViewDownloadDelegate mDownloadDelegate;
323
324    // The AccessibilityInjector that handles loading Accessibility scripts into the web page.
325    private AccessibilityInjector mAccessibilityInjector;
326
327    // Whether native accessibility, i.e. without any script injection, is allowed.
328    private boolean mNativeAccessibilityAllowed;
329
330    // Whether native accessibility, i.e. without any script injection, has been enabled.
331    private boolean mNativeAccessibilityEnabled;
332
333    // Handles native accessibility, i.e. without any script injection.
334    private BrowserAccessibilityManager mBrowserAccessibilityManager;
335
336    // System accessibility service.
337    private final AccessibilityManager mAccessibilityManager;
338
339    // Accessibility touch exploration state.
340    private boolean mTouchExplorationEnabled;
341
342    // Allows us to dynamically respond when the accessibility script injection flag changes.
343    private ContentObserver mAccessibilityScriptInjectionObserver;
344
345    // Temporary notification to tell onSizeChanged to focus a form element,
346    // because the OSK was just brought up.
347    private final Rect mFocusPreOSKViewportRect = new Rect();
348
349    // On tap this will store the x, y coordinates of the touch.
350    private int mLastTapX;
351    private int mLastTapY;
352
353    // Whether a touch scroll sequence is active, used to hide text selection
354    // handles. Note that a scroll sequence will *always* bound a pinch
355    // sequence, so this will also be true for the duration of a pinch gesture.
356    private boolean mTouchScrollInProgress;
357
358    // The outstanding fling start events that hasn't got fling end yet. It may be > 1 because
359    // onNativeFlingStopped() is called asynchronously.
360    private int mPotentiallyActiveFlingCount;
361
362    private ViewAndroid mViewAndroid;
363
364    private SmartClipDataListener mSmartClipDataListener = null;
365
366    private NavigationTransitionDelegate mNavigationTransitionDelegate = null;
367
368    // This holds the state of editable text (e.g. contents of <input>, contenteditable) of
369    // a focused element.
370    // Every time the user, IME, javascript (Blink), autofill etc. modifies the content, the new
371    //  state must be reflected to this to keep consistency.
372    private final Editable mEditable;
373
374    /**
375     * PID used to indicate an invalid render process.
376     */
377    // Keep in sync with the value returned from ContentViewCoreImpl::GetCurrentRendererProcessId()
378    // if there is no render process.
379    public static final int INVALID_RENDER_PROCESS_PID = 0;
380
381    // Offsets for the events that passes through this ContentViewCore.
382    private float mCurrentTouchOffsetX;
383    private float mCurrentTouchOffsetY;
384
385    // Offsets for smart clip
386    private int mSmartClipOffsetX;
387    private int mSmartClipOffsetY;
388
389    // Whether the ContentViewCore requires the WebContents to be fullscreen in order to lock the
390    // screen orientation.
391    private boolean mFullscreenRequiredForOrientationLock = true;
392
393    /**
394     * Constructs a new ContentViewCore. Embedders must call initialize() after constructing
395     * a ContentViewCore and before using it.
396     *
397     * @param context The context used to create this.
398     */
399    public ContentViewCore(Context context) {
400        mContext = context;
401
402        mAdapterInputConnectionFactory = new AdapterInputConnectionFactory();
403        mInputMethodManagerWrapper = new InputMethodManagerWrapper(mContext);
404
405        mRenderCoordinates = new RenderCoordinates();
406        float deviceScaleFactor = getContext().getResources().getDisplayMetrics().density;
407        String forceScaleFactor = CommandLine.getInstance().getSwitchValue(
408                ContentSwitches.FORCE_DEVICE_SCALE_FACTOR);
409        if (forceScaleFactor != null) {
410            deviceScaleFactor = Float.valueOf(forceScaleFactor);
411        }
412        mRenderCoordinates.setDeviceScaleFactor(deviceScaleFactor);
413        mAccessibilityManager = (AccessibilityManager)
414                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
415        mGestureStateListeners = new ObserverList<GestureStateListener>();
416        mGestureStateListenersIterator = mGestureStateListeners.rewindableIterator();
417
418        mEditable = Editable.Factory.getInstance().newEditable("");
419        Selection.setSelection(mEditable, 0);
420    }
421
422    /**
423     * @return The context used for creating this ContentViewCore.
424     */
425    @CalledByNative
426    public Context getContext() {
427        return mContext;
428    }
429
430    /**
431     * @return The ViewGroup that all view actions of this ContentViewCore should interact with.
432     */
433    public ViewGroup getContainerView() {
434        return mContainerView;
435    }
436
437    /**
438     * @return The WebContents currently being rendered.
439     */
440    public WebContents getWebContents() {
441        return mWebContents;
442    }
443
444    /**
445     * Specifies how much smaller the WebKit layout size should be relative to the size of this
446     * view.
447     * @param offsetXPix The X amount in pixels to shrink the viewport by.
448     * @param offsetYPix The Y amount in pixels to shrink the viewport by.
449     */
450    public void setViewportSizeOffset(int offsetXPix, int offsetYPix) {
451        if (offsetXPix != mViewportSizeOffsetWidthPix ||
452                offsetYPix != mViewportSizeOffsetHeightPix) {
453            mViewportSizeOffsetWidthPix = offsetXPix;
454            mViewportSizeOffsetHeightPix = offsetYPix;
455            if (mNativeContentViewCore != 0) nativeWasResized(mNativeContentViewCore);
456        }
457    }
458
459    /**
460     * Returns a delegate that can be used to add and remove views from the ContainerView.
461     *
462     * NOTE: Use with care, as not all ContentViewCore users setup their ContainerView in the same
463     * way. In particular, the Android WebView has limitations on what implementation details can
464     * be provided via a child view, as they are visible in the API and could introduce
465     * compatibility breaks with existing applications. If in doubt, contact the
466     * android_webview/OWNERS
467     *
468     * @return A ViewAndroidDelegate that can be used to add and remove views.
469     */
470    @VisibleForTesting
471    public ViewAndroidDelegate getViewAndroidDelegate() {
472        return new ViewAndroidDelegate() {
473            // mContainerView can change, but this ViewAndroidDelegate can only be used to
474            // add and remove views from the mContainerViewAtCreation.
475            private final ViewGroup mContainerViewAtCreation = mContainerView;
476
477            @Override
478            public View acquireAnchorView() {
479                View anchorView = new View(mContext);
480                mContainerViewAtCreation.addView(anchorView);
481                return anchorView;
482            }
483
484            @Override
485            @SuppressWarnings("deprecation")  // AbsoluteLayout
486            public void setAnchorViewPosition(
487                    View view, float x, float y, float width, float height) {
488                assert view.getParent() == mContainerViewAtCreation;
489
490                float scale = (float) DeviceDisplayInfo.create(mContext).getDIPScale();
491
492                // The anchor view should not go outside the bounds of the ContainerView.
493                int leftMargin = Math.round(x * scale);
494                int topMargin = Math.round(mRenderCoordinates.getContentOffsetYPix() + y * scale);
495                int scaledWidth = Math.round(width * scale);
496                // ContentViewCore currently only supports these two container view types.
497                if (mContainerViewAtCreation instanceof FrameLayout) {
498                    int startMargin;
499                    if (ApiCompatibilityUtils.isLayoutRtl(mContainerViewAtCreation)) {
500                        startMargin = mContainerViewAtCreation.getMeasuredWidth()
501                                - Math.round((width + x) * scale);
502                    } else {
503                        startMargin = leftMargin;
504                    }
505                    if (scaledWidth + startMargin > mContainerViewAtCreation.getWidth()) {
506                        scaledWidth = mContainerViewAtCreation.getWidth() - startMargin;
507                    }
508                    FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
509                        scaledWidth, Math.round(height * scale));
510                    ApiCompatibilityUtils.setMarginStart(lp, startMargin);
511                    lp.topMargin = topMargin;
512                    view.setLayoutParams(lp);
513                } else if (mContainerViewAtCreation instanceof android.widget.AbsoluteLayout) {
514                    // This fixes the offset due to a difference in
515                    // scrolling model of WebView vs. Chrome.
516                    // TODO(sgurun) fix this to use mContainerViewAtCreation.getScroll[X/Y]()
517                    // as it naturally accounts for scroll differences between
518                    // these models.
519                    leftMargin += mRenderCoordinates.getScrollXPixInt();
520                    topMargin += mRenderCoordinates.getScrollYPixInt();
521
522                    android.widget.AbsoluteLayout.LayoutParams lp =
523                            new android.widget.AbsoluteLayout.LayoutParams(
524                                scaledWidth, (int) (height * scale), leftMargin, topMargin);
525                    view.setLayoutParams(lp);
526                } else {
527                    Log.e(TAG, "Unknown layout " + mContainerViewAtCreation.getClass().getName());
528                }
529            }
530
531            @Override
532            public void releaseAnchorView(View anchorView) {
533                mContainerViewAtCreation.removeView(anchorView);
534            }
535        };
536    }
537
538    @VisibleForTesting
539    public void setImeAdapterForTest(ImeAdapter imeAdapter) {
540        mImeAdapter = imeAdapter;
541    }
542
543    @VisibleForTesting
544    public ImeAdapter getImeAdapterForTest() {
545        return mImeAdapter;
546    }
547
548    @VisibleForTesting
549    public void setAdapterInputConnectionFactory(AdapterInputConnectionFactory factory) {
550        mAdapterInputConnectionFactory = factory;
551    }
552
553    @VisibleForTesting
554    public void setInputMethodManagerWrapperForTest(InputMethodManagerWrapper immw) {
555        mInputMethodManagerWrapper = immw;
556    }
557
558    @VisibleForTesting
559    public AdapterInputConnection getInputConnectionForTest() {
560        return mInputConnection;
561    }
562
563    private ImeAdapter createImeAdapter(Context context) {
564        return new ImeAdapter(mInputMethodManagerWrapper,
565                new ImeAdapter.ImeAdapterDelegate() {
566                    @Override
567                    public void onImeEvent() {
568                        if (mPopupZoomer.isShowing()) {
569                            mPopupZoomer.hide(true);
570                        }
571                        getContentViewClient().onImeEvent();
572                        hideTextHandles();
573                    }
574
575                    @Override
576                    public void onDismissInput() {
577                        getContentViewClient().onImeStateChangeRequested(false);
578                    }
579
580                    @Override
581                    public View getAttachedView() {
582                        return mContainerView;
583                    }
584
585                    @Override
586                    public ResultReceiver getNewShowKeyboardReceiver() {
587                        return new ResultReceiver(new Handler()) {
588                            @Override
589                            public void onReceiveResult(int resultCode, Bundle resultData) {
590                                getContentViewClient().onImeStateChangeRequested(
591                                        resultCode == InputMethodManager.RESULT_SHOWN ||
592                                        resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN);
593                                if (resultCode == InputMethodManager.RESULT_SHOWN) {
594                                    // If OSK is newly shown, delay the form focus until
595                                    // the onSizeChanged (in order to adjust relative to the
596                                    // new size).
597                                    // TODO(jdduke): We should not assume that onSizeChanged will
598                                    // always be called, crbug.com/294908.
599                                    getContainerView().getWindowVisibleDisplayFrame(
600                                            mFocusPreOSKViewportRect);
601                                } else if (hasFocus() && resultCode ==
602                                        InputMethodManager.RESULT_UNCHANGED_SHOWN) {
603                                    // If the OSK was already there, focus the form immediately.
604                                    scrollFocusedEditableNodeIntoView();
605                                }
606                            }
607                        };
608                    }
609                }
610        );
611    }
612
613    /**
614     *
615     * @param containerView The view that will act as a container for all views created by this.
616     * @param internalDispatcher Handles dispatching all hidden or super methods to the
617     *                           containerView.
618     * @param nativeWebContents A pointer to the native web contents.
619     * @param windowAndroid An instance of the WindowAndroid.
620     */
621    // Perform important post-construction set up of the ContentViewCore.
622    // We do not require the containing view in the constructor to allow embedders to create a
623    // ContentViewCore without having fully created its containing view. The containing view
624    // is a vital component of the ContentViewCore, so embedders must exercise caution in what
625    // they do with the ContentViewCore before calling initialize().
626    // We supply the nativeWebContents pointer here rather than in the constructor to allow us
627    // to set the private browsing mode at a later point for the WebView implementation.
628    // Note that the caller remains the owner of the nativeWebContents and is responsible for
629    // deleting it after destroying the ContentViewCore.
630    public void initialize(ViewGroup containerView, InternalAccessDelegate internalDispatcher,
631            long nativeWebContents, WindowAndroid windowAndroid) {
632        setContainerView(containerView);
633
634        long windowNativePointer = windowAndroid.getNativePointer();
635        assert windowNativePointer != 0;
636        mViewAndroid = new ViewAndroid(windowAndroid, getViewAndroidDelegate());
637        long viewAndroidNativePointer = mViewAndroid.getNativePointer();
638        assert viewAndroidNativePointer != 0;
639
640        mZoomControlsDelegate = new ZoomControlsDelegate() {
641            @Override
642            public void invokeZoomPicker() {}
643            @Override
644            public void dismissZoomPicker() {}
645            @Override
646            public void updateZoomControls() {}
647        };
648
649        mNativeContentViewCore = nativeInit(
650                nativeWebContents, viewAndroidNativePointer, windowNativePointer,
651                mRetainedJavaScriptObjects);
652        mWebContents = nativeGetWebContentsAndroid(mNativeContentViewCore);
653        mContentSettings = new ContentSettings(this, mNativeContentViewCore);
654
655        setContainerViewInternals(internalDispatcher);
656        mRenderCoordinates.reset();
657        initPopupZoomer(mContext);
658        mImeAdapter = createImeAdapter(mContext);
659
660        mAccessibilityInjector = AccessibilityInjector.newInstance(this);
661
662        mWebContentsObserver = new WebContentsObserverAndroid(mWebContents) {
663            @Override
664            public void didNavigateMainFrame(String url, String baseUrl,
665                    boolean isNavigationToDifferentPage, boolean isFragmentNavigation) {
666                if (!isNavigationToDifferentPage) return;
667                hidePopups();
668                resetScrollInProgress();
669                resetGestureDetection();
670            }
671
672            @Override
673            public void renderProcessGone(boolean wasOomProtected) {
674                hidePopups();
675                resetScrollInProgress();
676                // No need to reset gesture detection as the detector will have
677                // been destroyed in the RenderWidgetHostView.
678            }
679        };
680    }
681
682    /**
683     * Sets a new container view for this {@link ContentViewCore}.
684     *
685     * <p>WARNING: This is not a general purpose method and has been designed with WebView
686     * fullscreen in mind. Please be aware that it might not be appropriate for other use cases
687     * and that it has a number of limitations. For example the PopupZoomer only works with the
688     * container view with which this ContentViewCore has been initialized.
689     *
690     * <p>This method only performs a small part of replacing the container view and
691     * embedders are responsible for:
692     * <ul>
693     *     <li>Disconnecting the old container view from this ContentViewCore</li>
694     *     <li>Updating the InternalAccessDelegate</li>
695     *     <li>Reconciling the state of this ContentViewCore with the new container view</li>
696     *     <li>Tearing down and recreating the native GL rendering where appropriate</li>
697     *     <li>etc.</li>
698     * </ul>
699     */
700    public void setContainerView(ViewGroup containerView) {
701        TraceEvent.begin();
702        if (mContainerView != null) {
703            mPastePopupMenu = null;
704            mInputConnection = null;
705            hidePopups();
706        }
707
708        mContainerView = containerView;
709        mPositionObserver = new ViewPositionObserver(mContainerView);
710        String contentDescription = "Web View";
711        if (R.string.accessibility_content_view == 0) {
712            Log.w(TAG, "Setting contentDescription to 'Web View' as no value was specified.");
713        } else {
714            contentDescription = mContext.getResources().getString(
715                    R.string.accessibility_content_view);
716        }
717        mContainerView.setContentDescription(contentDescription);
718        mContainerView.setWillNotDraw(false);
719        mContainerView.setClickable(true);
720        TraceEvent.end();
721    }
722
723    @CalledByNative
724    void onNativeContentViewCoreDestroyed(long nativeContentViewCore) {
725        assert nativeContentViewCore == mNativeContentViewCore;
726        mNativeContentViewCore = 0;
727    }
728
729    /**
730     * Set the Container view Internals.
731     * @param internalDispatcher Handles dispatching all hidden or super methods to the
732     *                           containerView.
733     */
734    public void setContainerViewInternals(InternalAccessDelegate internalDispatcher) {
735        mContainerViewInternals = internalDispatcher;
736    }
737
738    private void initPopupZoomer(Context context) {
739        mPopupZoomer = new PopupZoomer(context);
740        mPopupZoomer.setOnVisibilityChangedListener(new PopupZoomer.OnVisibilityChangedListener() {
741            // mContainerView can change, but this OnVisibilityChangedListener can only be used
742            // to add and remove views from the mContainerViewAtCreation.
743            private final ViewGroup mContainerViewAtCreation = mContainerView;
744
745            @Override
746            public void onPopupZoomerShown(final PopupZoomer zoomer) {
747                mContainerViewAtCreation.post(new Runnable() {
748                    @Override
749                    public void run() {
750                        if (mContainerViewAtCreation.indexOfChild(zoomer) == -1) {
751                            mContainerViewAtCreation.addView(zoomer);
752                        } else {
753                            assert false : "PopupZoomer should never be shown without being hidden";
754                        }
755                    }
756                });
757            }
758
759            @Override
760            public void onPopupZoomerHidden(final PopupZoomer zoomer) {
761                mContainerViewAtCreation.post(new Runnable() {
762                    @Override
763                    public void run() {
764                        if (mContainerViewAtCreation.indexOfChild(zoomer) != -1) {
765                            mContainerViewAtCreation.removeView(zoomer);
766                            mContainerViewAtCreation.invalidate();
767                        } else {
768                            assert false : "PopupZoomer should never be hidden without being shown";
769                        }
770                    }
771                });
772            }
773        });
774        // TODO(yongsheng): LONG_TAP is not enabled in PopupZoomer. So need to dispatch a LONG_TAP
775        // gesture if a user completes a tap on PopupZoomer UI after a LONG_PRESS gesture.
776        PopupZoomer.OnTapListener listener = new PopupZoomer.OnTapListener() {
777            // mContainerView can change, but this OnTapListener can only be used
778            // with the mContainerViewAtCreation.
779            private final ViewGroup mContainerViewAtCreation = mContainerView;
780
781            @Override
782            public boolean onSingleTap(View v, MotionEvent e) {
783                mContainerViewAtCreation.requestFocus();
784                if (mNativeContentViewCore != 0) {
785                    nativeSingleTap(mNativeContentViewCore, e.getEventTime(), e.getX(), e.getY());
786                }
787                return true;
788            }
789
790            @Override
791            public boolean onLongPress(View v, MotionEvent e) {
792                if (mNativeContentViewCore != 0) {
793                    nativeLongPress(mNativeContentViewCore, e.getEventTime(), e.getX(), e.getY());
794                }
795                return true;
796            }
797        };
798        mPopupZoomer.setOnTapListener(listener);
799    }
800
801    /**
802     * Destroy the internal state of the ContentView. This method may only be
803     * called after the ContentView has been removed from the view system. No
804     * other methods may be called on this ContentView after this method has
805     * been called.
806     */
807    public void destroy() {
808        if (mNativeContentViewCore != 0) {
809            nativeOnJavaContentViewCoreDestroyed(mNativeContentViewCore);
810        }
811        mWebContents = null;
812        if (mViewAndroid != null) mViewAndroid.destroy();
813        mNativeContentViewCore = 0;
814        mContentSettings = null;
815        mJavaScriptInterfaces.clear();
816        mRetainedJavaScriptObjects.clear();
817        unregisterAccessibilityContentObserver();
818        mGestureStateListeners.clear();
819        ScreenOrientationListener.getInstance().removeObserver(this);
820        mPositionObserver.clearListener();
821    }
822
823    private void unregisterAccessibilityContentObserver() {
824        if (mAccessibilityScriptInjectionObserver == null) {
825            return;
826        }
827        getContext().getContentResolver().unregisterContentObserver(
828                mAccessibilityScriptInjectionObserver);
829        mAccessibilityScriptInjectionObserver = null;
830    }
831
832    /**
833     * Returns true initially, false after destroy() has been called.
834     * It is illegal to call any other public method after destroy().
835     */
836    public boolean isAlive() {
837        return mNativeContentViewCore != 0;
838    }
839
840    /**
841     * This is only useful for passing over JNI to native code that requires ContentViewCore*.
842     * @return native ContentViewCore pointer.
843     */
844    @CalledByNative
845    public long getNativeContentViewCore() {
846        return mNativeContentViewCore;
847    }
848
849    public void setContentViewClient(ContentViewClient client) {
850        if (client == null) {
851            throw new IllegalArgumentException("The client can't be null.");
852        }
853        mContentViewClient = client;
854    }
855
856    @VisibleForTesting
857    public ContentViewClient getContentViewClient() {
858        if (mContentViewClient == null) {
859            // We use the Null Object pattern to avoid having to perform a null check in this class.
860            // We create it lazily because most of the time a client will be set almost immediately
861            // after ContentView is created.
862            mContentViewClient = new ContentViewClient();
863            // We don't set the native ContentViewClient pointer here on purpose. The native
864            // implementation doesn't mind a null delegate and using one is better than passing a
865            // Null Object, since we cut down on the number of JNI calls.
866        }
867        return mContentViewClient;
868    }
869
870    public int getBackgroundColor() {
871        assert mWebContents != null;
872        return mWebContents.getBackgroundColor();
873    }
874
875    @CalledByNative
876    private void onBackgroundColorChanged(int color) {
877        getContentViewClient().onBackgroundColorChanged(color);
878    }
879
880    /**
881     * Load url without fixing up the url string. Consumers of ContentView are responsible for
882     * ensuring the URL passed in is properly formatted (i.e. the scheme has been added if left
883     * off during user input).
884     *
885     * @param params Parameters for this load.
886     */
887    public void loadUrl(LoadUrlParams params) {
888        if (mNativeContentViewCore == 0) return;
889
890        nativeLoadUrl(mNativeContentViewCore,
891                params.mUrl,
892                params.mLoadUrlType,
893                params.mTransitionType,
894                params.getReferrer() != null ? params.getReferrer().getUrl() : null,
895                params.getReferrer() != null ? params.getReferrer().getPolicy() : 0,
896                params.mUaOverrideOption,
897                params.getExtraHeadersString(),
898                params.mPostData,
899                params.mBaseUrlForDataUrl,
900                params.mVirtualUrlForDataUrl,
901                params.mCanLoadLocalResources,
902                params.mIsRendererInitiated);
903    }
904
905    /**
906     * Stops loading the current web contents.
907     */
908    public void stopLoading() {
909        if (mWebContents != null) mWebContents.stop();
910    }
911
912    /**
913     * Get the URL of the current page.
914     *
915     * @return The URL of the current page.
916     */
917    public String getUrl() {
918        if (mNativeContentViewCore != 0) return nativeGetURL(mNativeContentViewCore);
919        return null;
920    }
921
922    /**
923     * Get the title of the current page.
924     *
925     * @return The title of the current page.
926     */
927    public String getTitle() {
928        return mWebContents == null ? null : mWebContents.getTitle();
929    }
930
931    /**
932     * Shows an interstitial page driven by the passed in delegate.
933     *
934     * @param url The URL being blocked by the interstitial.
935     * @param delegate The delegate handling the interstitial.
936     */
937    @VisibleForTesting
938    public void showInterstitialPage(
939            String url, InterstitialPageDelegateAndroid delegate) {
940        assert mWebContents != null;
941        mWebContents.showInterstitialPage(url, delegate.getNative());
942    }
943
944    /**
945     * @return Whether the page is currently showing an interstitial, such as a bad HTTPS page.
946     */
947    public boolean isShowingInterstitialPage() {
948        assert mWebContents != null;
949        return mWebContents.isShowingInterstitialPage();
950    }
951
952    /**
953     * @return Viewport width in physical pixels as set from onSizeChanged.
954     */
955    @CalledByNative
956    public int getViewportWidthPix() { return mViewportWidthPix; }
957
958    /**
959     * @return Viewport height in physical pixels as set from onSizeChanged.
960     */
961    @CalledByNative
962    public int getViewportHeightPix() { return mViewportHeightPix; }
963
964    /**
965     * @return Width of underlying physical surface.
966     */
967    @CalledByNative
968    public int getPhysicalBackingWidthPix() { return mPhysicalBackingWidthPix; }
969
970    /**
971     * @return Height of underlying physical surface.
972     */
973    @CalledByNative
974    public int getPhysicalBackingHeightPix() { return mPhysicalBackingHeightPix; }
975
976    /**
977     * @return Amount the output surface extends past the bottom of the window viewport.
978     */
979    @CalledByNative
980    public int getOverdrawBottomHeightPix() { return mOverdrawBottomHeightPix; }
981
982    /**
983     * @return The amount to shrink the viewport relative to {@link #getViewportWidthPix()}.
984     */
985    @CalledByNative
986    public int getViewportSizeOffsetWidthPix() { return mViewportSizeOffsetWidthPix; }
987
988    /**
989     * @return The amount to shrink the viewport relative to {@link #getViewportHeightPix()}.
990     */
991    @CalledByNative
992    public int getViewportSizeOffsetHeightPix() { return mViewportSizeOffsetHeightPix; }
993
994    /**
995     * @see android.webkit.WebView#getContentHeight()
996     */
997    public float getContentHeightCss() {
998        return mRenderCoordinates.getContentHeightCss();
999    }
1000
1001    /**
1002     * @see android.webkit.WebView#getContentWidth()
1003     */
1004    public float getContentWidthCss() {
1005        return mRenderCoordinates.getContentWidthCss();
1006    }
1007
1008    // TODO(teddchoc): Remove all these navigation controller methods from here and have the
1009    //                 embedders manage it.
1010    /**
1011     * @return Whether the current WebContents has a previous navigation entry.
1012     */
1013    public boolean canGoBack() {
1014        return mWebContents != null && mWebContents.getNavigationController().canGoBack();
1015    }
1016
1017    /**
1018     * @return Whether the current WebContents has a navigation entry after the current one.
1019     */
1020    public boolean canGoForward() {
1021        return mWebContents != null && mWebContents.getNavigationController().canGoForward();
1022    }
1023
1024    /**
1025     * @param offset The offset into the navigation history.
1026     * @return Whether we can move in history by given offset
1027     */
1028    public boolean canGoToOffset(int offset) {
1029        return mWebContents != null &&
1030                mWebContents.getNavigationController().canGoToOffset(offset);
1031    }
1032
1033    /**
1034     * Navigates to the specified offset from the "current entry". Does nothing if the offset is out
1035     * of bounds.
1036     * @param offset The offset into the navigation history.
1037     */
1038    public void goToOffset(int offset) {
1039        if (mWebContents != null) mWebContents.getNavigationController().goToOffset(offset);
1040    }
1041
1042    @Override
1043    public void goToNavigationIndex(int index) {
1044        if (mWebContents != null) {
1045            mWebContents.getNavigationController().goToNavigationIndex(index);
1046        }
1047    }
1048
1049    /**
1050     * Goes to the navigation entry before the current one.
1051     */
1052    public void goBack() {
1053        if (mWebContents != null) mWebContents.getNavigationController().goBack();
1054    }
1055
1056    /**
1057     * Goes to the navigation entry following the current one.
1058     */
1059    public void goForward() {
1060        if (mWebContents != null) mWebContents.getNavigationController().goForward();
1061    }
1062
1063    /**
1064     * Loads the current navigation if there is a pending lazy load (after tab restore).
1065     */
1066    public void loadIfNecessary() {
1067        if (mWebContents != null) mWebContents.getNavigationController().loadIfNecessary();
1068    }
1069
1070    /**
1071     * Requests the current navigation to be loaded upon the next call to loadIfNecessary().
1072     */
1073    public void requestRestoreLoad() {
1074        if (mWebContents != null) mWebContents.getNavigationController().requestRestoreLoad();
1075    }
1076
1077    /**
1078     * Reload the current page.
1079     */
1080    public void reload(boolean checkForRepost) {
1081        mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
1082        if (mWebContents != null) mWebContents.getNavigationController().reload(checkForRepost);
1083    }
1084
1085    /**
1086     * Reload the current page, ignoring the contents of the cache.
1087     */
1088    public void reloadIgnoringCache(boolean checkForRepost) {
1089        mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
1090        if (mWebContents != null) mWebContents.getNavigationController().reloadIgnoringCache(
1091                checkForRepost);
1092    }
1093
1094    /**
1095     * Cancel the pending reload.
1096     */
1097    public void cancelPendingReload() {
1098        if (mWebContents != null) mWebContents.getNavigationController().cancelPendingReload();
1099    }
1100
1101    /**
1102     * Continue the pending reload.
1103     */
1104    public void continuePendingReload() {
1105        if (mWebContents != null) mWebContents.getNavigationController().continuePendingReload();
1106    }
1107
1108    /**
1109     * Clears the ContentViewCore's page history in both the backwards and
1110     * forwards directions.
1111     */
1112    public void clearHistory() {
1113        if (mNativeContentViewCore != 0) nativeClearHistory(mNativeContentViewCore);
1114    }
1115
1116    /**
1117     * @return The selected text (empty if no text selected).
1118     */
1119    public String getSelectedText() {
1120        return mHasSelection ? mLastSelectedText : "";
1121    }
1122
1123    /**
1124     * @return Whether the current selection is editable (false if no text selected).
1125     */
1126    public boolean isSelectionEditable() {
1127        return mHasSelection ? mFocusedNodeEditable : false;
1128    }
1129
1130    /**
1131     * @return Whether the current focused node is editable.
1132     */
1133    public boolean isFocusedNodeEditable() {
1134        return mFocusedNodeEditable;
1135    }
1136
1137    // End FrameLayout overrides.
1138
1139    /**
1140     * TODO(changwan): refactor SPen related code into a separate class. See
1141     * http://crbug.com/398169.
1142     * @return Whether SPen is supported on the device.
1143     */
1144    public static boolean isSPenSupported(Context context) {
1145        if (sIsSPenSupported == null)
1146            sIsSPenSupported = detectSPenSupport(context);
1147        return sIsSPenSupported.booleanValue();
1148    }
1149
1150    private static boolean detectSPenSupport(Context context) {
1151        if (!"SAMSUNG".equalsIgnoreCase(Build.MANUFACTURER))
1152            return false;
1153
1154        final FeatureInfo[] infos = context.getPackageManager().getSystemAvailableFeatures();
1155        for (FeatureInfo info : infos) {
1156            if ("com.sec.feature.spen_usp".equalsIgnoreCase(info.name)) {
1157                return true;
1158            }
1159        }
1160        return false;
1161    }
1162
1163    /**
1164     * Convert SPen event action into normal event action.
1165     * TODO(changwan): refactor SPen related code into a separate class. See
1166     * http://crbug.com/398169.
1167     *
1168     * @param eventActionMasked Input event action. It is assumed that it is masked as the values
1169                                cannot be ORed.
1170     * @return Event action after the conversion
1171     */
1172    public static int convertSPenEventAction(int eventActionMasked) {
1173        // S-Pen support: convert to normal stylus event handling
1174        switch (eventActionMasked) {
1175            case SPEN_ACTION_DOWN:
1176                return MotionEvent.ACTION_DOWN;
1177            case SPEN_ACTION_UP:
1178                return MotionEvent.ACTION_UP;
1179            case SPEN_ACTION_MOVE:
1180                return MotionEvent.ACTION_MOVE;
1181            case SPEN_ACTION_CANCEL:
1182                return MotionEvent.ACTION_CANCEL;
1183            default:
1184                return eventActionMasked;
1185        }
1186    }
1187
1188    /**
1189     * @see View#onTouchEvent(MotionEvent)
1190     */
1191    public boolean onTouchEvent(MotionEvent event) {
1192        final boolean isTouchHandleEvent = false;
1193        return onTouchEventImpl(event, isTouchHandleEvent);
1194    }
1195
1196    private boolean onTouchEventImpl(MotionEvent event, boolean isTouchHandleEvent) {
1197        TraceEvent.begin("onTouchEvent");
1198        try {
1199            int eventAction = event.getActionMasked();
1200
1201            if (eventAction == MotionEvent.ACTION_DOWN) {
1202                cancelRequestToScrollFocusedEditableNodeIntoView();
1203            }
1204
1205            if (isSPenSupported(mContext))
1206                eventAction = convertSPenEventAction(eventAction);
1207            if (!isValidTouchEventActionForNative(eventAction)) return false;
1208
1209            if (mNativeContentViewCore == 0) return false;
1210
1211            // A zero offset is quite common, in which case the unnecessary copy should be avoided.
1212            MotionEvent offset = null;
1213            if (mCurrentTouchOffsetX != 0 || mCurrentTouchOffsetY != 0) {
1214                offset = createOffsetMotionEvent(event);
1215                event = offset;
1216            }
1217
1218            final int pointerCount = event.getPointerCount();
1219            final boolean consumed = nativeOnTouchEvent(mNativeContentViewCore, event,
1220                    event.getEventTime(), eventAction,
1221                    pointerCount, event.getHistorySize(), event.getActionIndex(),
1222                    event.getX(), event.getY(),
1223                    pointerCount > 1 ? event.getX(1) : 0,
1224                    pointerCount > 1 ? event.getY(1) : 0,
1225                    event.getPointerId(0), pointerCount > 1 ? event.getPointerId(1) : -1,
1226                    event.getTouchMajor(), pointerCount > 1 ? event.getTouchMajor(1) : 0,
1227                    event.getRawX(), event.getRawY(),
1228                    event.getToolType(0),
1229                    pointerCount > 1 ? event.getToolType(1) : MotionEvent.TOOL_TYPE_UNKNOWN,
1230                    event.getButtonState(),
1231                    isTouchHandleEvent);
1232
1233            if (offset != null) offset.recycle();
1234            return consumed;
1235        } finally {
1236            TraceEvent.end("onTouchEvent");
1237        }
1238    }
1239
1240    private static boolean isValidTouchEventActionForNative(int eventAction) {
1241        // Only these actions have any effect on gesture detection.  Other
1242        // actions have no corresponding WebTouchEvent type and may confuse the
1243        // touch pipline, so we ignore them entirely.
1244        return eventAction == MotionEvent.ACTION_DOWN
1245                || eventAction == MotionEvent.ACTION_UP
1246                || eventAction == MotionEvent.ACTION_CANCEL
1247                || eventAction == MotionEvent.ACTION_MOVE
1248                || eventAction == MotionEvent.ACTION_POINTER_DOWN
1249                || eventAction == MotionEvent.ACTION_POINTER_UP;
1250    }
1251
1252    public void setIgnoreRemainingTouchEvents() {
1253        resetGestureDetection();
1254    }
1255
1256    public boolean isScrollInProgress() {
1257        return mTouchScrollInProgress || mPotentiallyActiveFlingCount > 0;
1258    }
1259
1260    @SuppressWarnings("unused")
1261    @CalledByNative
1262    private void onFlingStartEventConsumed(int vx, int vy) {
1263        mTouchScrollInProgress = false;
1264        mPotentiallyActiveFlingCount++;
1265        for (mGestureStateListenersIterator.rewind();
1266                    mGestureStateListenersIterator.hasNext();) {
1267            mGestureStateListenersIterator.next().onFlingStartGesture(
1268                    vx, vy, computeVerticalScrollOffset(), computeVerticalScrollExtent());
1269        }
1270    }
1271
1272    @SuppressWarnings("unused")
1273    @CalledByNative
1274    private void onFlingStartEventHadNoConsumer(int vx, int vy) {
1275        mTouchScrollInProgress = false;
1276        for (mGestureStateListenersIterator.rewind();
1277                    mGestureStateListenersIterator.hasNext();) {
1278            mGestureStateListenersIterator.next().onUnhandledFlingStartEvent(vx, vy);
1279        }
1280    }
1281
1282    @SuppressWarnings("unused")
1283    @CalledByNative
1284    private void onFlingCancelEventAck() {
1285        updateGestureStateListener(GestureEventType.FLING_CANCEL);
1286    }
1287
1288    @SuppressWarnings("unused")
1289    @CalledByNative
1290    private void onScrollBeginEventAck() {
1291        mTouchScrollInProgress = true;
1292        hidePastePopup();
1293        mZoomControlsDelegate.invokeZoomPicker();
1294        updateGestureStateListener(GestureEventType.SCROLL_START);
1295    }
1296
1297    @SuppressWarnings("unused")
1298    @CalledByNative
1299    private void onScrollUpdateGestureConsumed() {
1300        mZoomControlsDelegate.invokeZoomPicker();
1301        for (mGestureStateListenersIterator.rewind();
1302                mGestureStateListenersIterator.hasNext();) {
1303            mGestureStateListenersIterator.next().onScrollUpdateGestureConsumed();
1304        }
1305    }
1306
1307    @SuppressWarnings("unused")
1308    @CalledByNative
1309    private void onScrollEndEventAck() {
1310        if (!mTouchScrollInProgress) return;
1311        mTouchScrollInProgress = false;
1312        updateGestureStateListener(GestureEventType.SCROLL_END);
1313    }
1314
1315    @SuppressWarnings("unused")
1316    @CalledByNative
1317    private void onPinchBeginEventAck() {
1318        updateGestureStateListener(GestureEventType.PINCH_BEGIN);
1319    }
1320
1321    @SuppressWarnings("unused")
1322    @CalledByNative
1323    private void onPinchEndEventAck() {
1324        updateGestureStateListener(GestureEventType.PINCH_END);
1325    }
1326
1327    @SuppressWarnings("unused")
1328    @CalledByNative
1329    private void onSingleTapEventAck(boolean consumed, int x, int y) {
1330        for (mGestureStateListenersIterator.rewind();
1331                mGestureStateListenersIterator.hasNext();) {
1332            mGestureStateListenersIterator.next().onSingleTap(consumed, x, y);
1333        }
1334    }
1335
1336    /**
1337     * Called just prior to a tap or press gesture being forwarded to the renderer.
1338     */
1339    @SuppressWarnings("unused")
1340    @CalledByNative
1341    private boolean filterTapOrPressEvent(int type, int x, int y) {
1342        if (type == GestureEventType.LONG_PRESS && offerLongPressToEmbedder()) {
1343            return true;
1344        }
1345        updateForTapOrPress(type, x, y);
1346        return false;
1347    }
1348
1349    @VisibleForTesting
1350    public void sendDoubleTapForTest(long timeMs, int x, int y) {
1351        if (mNativeContentViewCore == 0) return;
1352        nativeDoubleTap(mNativeContentViewCore, timeMs, x, y);
1353    }
1354
1355    @VisibleForTesting
1356    public void flingForTest(long timeMs, int x, int y, int velocityX, int velocityY) {
1357        if (mNativeContentViewCore == 0) return;
1358        nativeFlingCancel(mNativeContentViewCore, timeMs);
1359        nativeScrollBegin(mNativeContentViewCore, timeMs, x, y, velocityX, velocityY);
1360        nativeFlingStart(mNativeContentViewCore, timeMs, x, y, velocityX, velocityY);
1361    }
1362
1363    /**
1364     * Cancel any fling gestures active.
1365     * @param timeMs Current time (in milliseconds).
1366     */
1367    public void cancelFling(long timeMs) {
1368        if (mNativeContentViewCore == 0) return;
1369        nativeFlingCancel(mNativeContentViewCore, timeMs);
1370    }
1371
1372    /**
1373     * Add a listener that gets alerted on gesture state changes.
1374     * @param listener Listener to add.
1375     */
1376    public void addGestureStateListener(GestureStateListener listener) {
1377        mGestureStateListeners.addObserver(listener);
1378    }
1379
1380    /**
1381     * Removes a listener that was added to watch for gesture state changes.
1382     * @param listener Listener to remove.
1383     */
1384    public void removeGestureStateListener(GestureStateListener listener) {
1385        mGestureStateListeners.removeObserver(listener);
1386    }
1387
1388    void updateGestureStateListener(int gestureType) {
1389        for (mGestureStateListenersIterator.rewind();
1390                mGestureStateListenersIterator.hasNext();) {
1391            GestureStateListener listener = mGestureStateListenersIterator.next();
1392            switch (gestureType) {
1393                case GestureEventType.PINCH_BEGIN:
1394                    listener.onPinchStarted();
1395                    break;
1396                case GestureEventType.PINCH_END:
1397                    listener.onPinchEnded();
1398                    break;
1399                case GestureEventType.FLING_END:
1400                    listener.onFlingEndGesture(
1401                            computeVerticalScrollOffset(),
1402                            computeVerticalScrollExtent());
1403                    break;
1404                case GestureEventType.FLING_CANCEL:
1405                    listener.onFlingCancelGesture();
1406                    break;
1407                case GestureEventType.SCROLL_START:
1408                    listener.onScrollStarted(
1409                            computeVerticalScrollOffset(),
1410                            computeVerticalScrollExtent());
1411                    break;
1412                case GestureEventType.SCROLL_END:
1413                    listener.onScrollEnded(
1414                            computeVerticalScrollOffset(),
1415                            computeVerticalScrollExtent());
1416                    break;
1417                default:
1418                    break;
1419            }
1420        }
1421    }
1422
1423    /**
1424     * Requests the renderer insert a link to the specified stylesheet in the
1425     * main frame's document.
1426     */
1427    void addStyleSheetByURL(String url) {
1428        assert mWebContents != null;
1429        mWebContents.addStyleSheetByURL(url);
1430    }
1431
1432    /** Callback interface for evaluateJavaScript(). */
1433    public interface JavaScriptCallback {
1434        void handleJavaScriptResult(String jsonResult);
1435    }
1436
1437    /**
1438     * Injects the passed Javascript code in the current page and evaluates it.
1439     * If a result is required, pass in a callback.
1440     * Used in automation tests.
1441     *
1442     * @param script The Javascript to execute.
1443     * @param callback The callback to be fired off when a result is ready. The script's
1444     *                 result will be json encoded and passed as the parameter, and the call
1445     *                 will be made on the main thread.
1446     *                 If no result is required, pass null.
1447     */
1448    public void evaluateJavaScript(String script, JavaScriptCallback callback) {
1449        if (mNativeContentViewCore == 0) return;
1450        nativeEvaluateJavaScript(mNativeContentViewCore, script, callback, false);
1451    }
1452
1453    /**
1454     * Injects the passed Javascript code in the current page and evaluates it.
1455     * If there is no page existing, a new one will be created.
1456     *
1457     * @param script The Javascript to execute.
1458     */
1459    public void evaluateJavaScriptEvenIfNotYetNavigated(String script) {
1460        if (mNativeContentViewCore == 0) return;
1461        nativeEvaluateJavaScript(mNativeContentViewCore, script, null, true);
1462    }
1463
1464    /**
1465     * Post a message to a frame.
1466     * TODO(sgurun) also add support for transferring a message channel port.
1467     *
1468     * @param frameName The name of the frame. If the name is null the message is posted
1469     *                  to the main frame.
1470     * @param message   The message
1471     * @param sourceOrigin  The source origin
1472     * @param targetOrigin  The target origin
1473     */
1474    public void postMessageToFrame(String frameName, String message,
1475            String sourceOrigin, String targetOrigin) {
1476        if (mNativeContentViewCore == 0) return;
1477        nativePostMessageToFrame(mNativeContentViewCore, frameName, message, sourceOrigin,
1478            targetOrigin);
1479    }
1480
1481    /**
1482     * To be called when the ContentView is shown.
1483     */
1484    public void onShow() {
1485        assert mWebContents != null;
1486        mWebContents.onShow();
1487        setAccessibilityState(mAccessibilityManager.isEnabled());
1488    }
1489
1490    /**
1491     * @return The ID of the renderer process that backs this tab or
1492     *         {@link #INVALID_RENDER_PROCESS_PID} if there is none.
1493     */
1494    public int getCurrentRenderProcessId() {
1495        return nativeGetCurrentRenderProcessId(mNativeContentViewCore);
1496    }
1497
1498    /**
1499     * To be called when the ContentView is hidden.
1500     */
1501    public void onHide() {
1502        assert mWebContents != null;
1503        hidePopups();
1504        setInjectedAccessibility(false);
1505        mWebContents.onHide();
1506    }
1507
1508    /**
1509     * Return the ContentSettings object used to retrieve the settings for this
1510     * ContentViewCore. For modifications, ChromeNativePreferences is to be used.
1511     * @return A ContentSettings object that can be used to retrieve this
1512     *         ContentViewCore's settings.
1513     */
1514    public ContentSettings getContentSettings() {
1515        return mContentSettings;
1516    }
1517
1518    private void hidePopups() {
1519        mUnselectAllOnActionModeDismiss = true;
1520        hideSelectActionBar();
1521        hidePastePopup();
1522        hideSelectPopup();
1523        hideTextHandles();
1524    }
1525
1526    public void hideSelectActionBar() {
1527        if (mActionMode != null) {
1528            mActionMode.finish();
1529            mActionMode = null;
1530        }
1531    }
1532
1533    public boolean isSelectActionBarShowing() {
1534        return mActionMode != null;
1535    }
1536
1537    private void resetGestureDetection() {
1538        if (mNativeContentViewCore == 0) return;
1539        nativeResetGestureDetection(mNativeContentViewCore);
1540    }
1541
1542    /**
1543     * @see View#onAttachedToWindow()
1544     */
1545    @SuppressWarnings("javadoc")
1546    public void onAttachedToWindow() {
1547        setAccessibilityState(mAccessibilityManager.isEnabled());
1548
1549        ScreenOrientationListener.getInstance().addObserver(this, mContext);
1550        GamepadList.onAttachedToWindow(mContext);
1551    }
1552
1553    /**
1554     * @see View#onDetachedFromWindow()
1555     */
1556    @SuppressWarnings("javadoc")
1557    @SuppressLint("MissingSuperCall")
1558    public void onDetachedFromWindow() {
1559        setInjectedAccessibility(false);
1560        hidePopups();
1561        mZoomControlsDelegate.dismissZoomPicker();
1562        unregisterAccessibilityContentObserver();
1563
1564        ScreenOrientationListener.getInstance().removeObserver(this);
1565        GamepadList.onDetachedFromWindow();
1566    }
1567
1568    /**
1569     * @see View#onVisibilityChanged(android.view.View, int)
1570     */
1571    public void onVisibilityChanged(View changedView, int visibility) {
1572        if (visibility != View.VISIBLE) {
1573            mZoomControlsDelegate.dismissZoomPicker();
1574        }
1575    }
1576
1577    /**
1578     * @see View#onCreateInputConnection(EditorInfo)
1579     */
1580    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
1581        if (!mImeAdapter.hasTextInputType()) {
1582            // Although onCheckIsTextEditor will return false in this case, the EditorInfo
1583            // is still used by the InputMethodService. Need to make sure the IME doesn't
1584            // enter fullscreen mode.
1585            outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
1586        }
1587        mInputConnection = mAdapterInputConnectionFactory.get(mContainerView, mImeAdapter,
1588                mEditable, outAttrs);
1589        return mInputConnection;
1590    }
1591
1592    @VisibleForTesting
1593    public AdapterInputConnection getAdapterInputConnectionForTest() {
1594        return mInputConnection;
1595    }
1596
1597    @VisibleForTesting
1598    public Editable getEditableForTest() {
1599        return mEditable;
1600    }
1601
1602    /**
1603     * @see View#onCheckIsTextEditor()
1604     */
1605    public boolean onCheckIsTextEditor() {
1606        return mImeAdapter.hasTextInputType();
1607    }
1608
1609    /**
1610     * @see View#onConfigurationChanged(Configuration)
1611     */
1612    @SuppressWarnings("javadoc")
1613    public void onConfigurationChanged(Configuration newConfig) {
1614        TraceEvent.begin();
1615
1616        if (newConfig.keyboard != Configuration.KEYBOARD_NOKEYS) {
1617            if (mNativeContentViewCore != 0) {
1618                mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore),
1619                        ImeAdapter.getTextInputTypeNone());
1620            }
1621            mInputMethodManagerWrapper.restartInput(mContainerView);
1622        }
1623        mContainerViewInternals.super_onConfigurationChanged(newConfig);
1624
1625        // To request layout has side effect, but it seems OK as it only happen in
1626        // onConfigurationChange and layout has to be changed in most case.
1627        mContainerView.requestLayout();
1628        TraceEvent.end();
1629    }
1630
1631    /**
1632     * @see View#onSizeChanged(int, int, int, int)
1633     */
1634    @SuppressWarnings("javadoc")
1635    public void onSizeChanged(int wPix, int hPix, int owPix, int ohPix) {
1636        if (getViewportWidthPix() == wPix && getViewportHeightPix() == hPix) return;
1637
1638        mViewportWidthPix = wPix;
1639        mViewportHeightPix = hPix;
1640        if (mNativeContentViewCore != 0) {
1641            nativeWasResized(mNativeContentViewCore);
1642        }
1643
1644        updateAfterSizeChanged();
1645    }
1646
1647    /**
1648     * Called when the underlying surface the compositor draws to changes size.
1649     * This may be larger than the viewport size.
1650     */
1651    public void onPhysicalBackingSizeChanged(int wPix, int hPix) {
1652        if (mPhysicalBackingWidthPix == wPix && mPhysicalBackingHeightPix == hPix) return;
1653
1654        mPhysicalBackingWidthPix = wPix;
1655        mPhysicalBackingHeightPix = hPix;
1656
1657        if (mNativeContentViewCore != 0) {
1658            nativeWasResized(mNativeContentViewCore);
1659        }
1660    }
1661
1662    /**
1663     * Called when the amount the surface is overdrawing off the bottom has changed.
1664     * @param overdrawHeightPix The overdraw height.
1665     */
1666    public void onOverdrawBottomHeightChanged(int overdrawHeightPix) {
1667        if (mOverdrawBottomHeightPix == overdrawHeightPix) return;
1668
1669        mOverdrawBottomHeightPix = overdrawHeightPix;
1670
1671        if (mNativeContentViewCore != 0) {
1672            nativeWasResized(mNativeContentViewCore);
1673        }
1674    }
1675
1676    private void updateAfterSizeChanged() {
1677        mPopupZoomer.hide(false);
1678
1679        // Execute a delayed form focus operation because the OSK was brought
1680        // up earlier.
1681        if (!mFocusPreOSKViewportRect.isEmpty()) {
1682            Rect rect = new Rect();
1683            getContainerView().getWindowVisibleDisplayFrame(rect);
1684            if (!rect.equals(mFocusPreOSKViewportRect)) {
1685                // Only assume the OSK triggered the onSizeChanged if width was preserved.
1686                if (rect.width() == mFocusPreOSKViewportRect.width()) {
1687                    scrollFocusedEditableNodeIntoView();
1688                }
1689                cancelRequestToScrollFocusedEditableNodeIntoView();
1690            }
1691        }
1692    }
1693
1694    private void cancelRequestToScrollFocusedEditableNodeIntoView() {
1695        // Zero-ing the rect will prevent |updateAfterSizeChanged()| from
1696        // issuing the delayed form focus event.
1697        mFocusPreOSKViewportRect.setEmpty();
1698    }
1699
1700    private void scrollFocusedEditableNodeIntoView() {
1701        assert mWebContents != null;
1702        mWebContents.scrollFocusedEditableNodeIntoView();
1703    }
1704
1705    /**
1706     * Selects the word around the caret, if any.
1707     * The caller can check if selection actually occurred by listening to OnSelectionChanged.
1708     */
1709    public void selectWordAroundCaret() {
1710        assert mWebContents != null;
1711        mWebContents.selectWordAroundCaret();
1712    }
1713
1714    /**
1715     * @see View#onWindowFocusChanged(boolean)
1716     */
1717    public void onWindowFocusChanged(boolean hasWindowFocus) {
1718        if (!hasWindowFocus) resetGestureDetection();
1719    }
1720
1721    public void onFocusChanged(boolean gainFocus) {
1722        if (!gainFocus) {
1723            hideImeIfNeeded();
1724            cancelRequestToScrollFocusedEditableNodeIntoView();
1725            hidePastePopup();
1726            hideTextHandles();
1727        }
1728        if (mNativeContentViewCore != 0) nativeSetFocus(mNativeContentViewCore, gainFocus);
1729    }
1730
1731    /**
1732     * @see View#onKeyUp(int, KeyEvent)
1733     */
1734    public boolean onKeyUp(int keyCode, KeyEvent event) {
1735        if (mPopupZoomer.isShowing() && keyCode == KeyEvent.KEYCODE_BACK) {
1736            mPopupZoomer.hide(true);
1737            return true;
1738        }
1739        return mContainerViewInternals.super_onKeyUp(keyCode, event);
1740    }
1741
1742    /**
1743     * @see View#dispatchKeyEventPreIme(KeyEvent)
1744     */
1745    public boolean dispatchKeyEventPreIme(KeyEvent event) {
1746        try {
1747            TraceEvent.begin();
1748            return mContainerViewInternals.super_dispatchKeyEventPreIme(event);
1749        } finally {
1750            TraceEvent.end();
1751        }
1752    }
1753
1754    /**
1755     * @see View#dispatchKeyEvent(KeyEvent)
1756     */
1757    public boolean dispatchKeyEvent(KeyEvent event) {
1758        if (GamepadList.dispatchKeyEvent(event)) return true;
1759        if (getContentViewClient().shouldOverrideKeyEvent(event)) {
1760            return mContainerViewInternals.super_dispatchKeyEvent(event);
1761        }
1762
1763        if (mImeAdapter.dispatchKeyEvent(event)) return true;
1764
1765        return mContainerViewInternals.super_dispatchKeyEvent(event);
1766    }
1767
1768    /**
1769     * @see View#onHoverEvent(MotionEvent)
1770     * Mouse move events are sent on hover enter, hover move and hover exit.
1771     * They are sent on hover exit because sometimes it acts as both a hover
1772     * move and hover exit.
1773     */
1774    public boolean onHoverEvent(MotionEvent event) {
1775        TraceEvent.begin("onHoverEvent");
1776        MotionEvent offset = createOffsetMotionEvent(event);
1777        try {
1778            if (mBrowserAccessibilityManager != null) {
1779                return mBrowserAccessibilityManager.onHoverEvent(offset);
1780            }
1781
1782            // Work around Android bug where the x, y coordinates of a hover exit
1783            // event are incorrect when touch exploration is on.
1784            if (mTouchExplorationEnabled && offset.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
1785                return true;
1786            }
1787
1788            mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
1789            if (mNativeContentViewCore != 0) {
1790                nativeSendMouseMoveEvent(mNativeContentViewCore, offset.getEventTime(),
1791                        offset.getX(), offset.getY());
1792            }
1793            return true;
1794        } finally {
1795            offset.recycle();
1796            TraceEvent.end("onHoverEvent");
1797        }
1798    }
1799
1800    /**
1801     * @see View#onGenericMotionEvent(MotionEvent)
1802     */
1803    public boolean onGenericMotionEvent(MotionEvent event) {
1804        if (GamepadList.onGenericMotionEvent(event)) return true;
1805        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1806            switch (event.getAction()) {
1807                case MotionEvent.ACTION_SCROLL:
1808                    if (mNativeContentViewCore == 0) return false;
1809
1810                    nativeSendMouseWheelEvent(mNativeContentViewCore, event.getEventTime(),
1811                            event.getX(), event.getY(),
1812                            event.getAxisValue(MotionEvent.AXIS_VSCROLL));
1813
1814                    mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
1815                    // Send a delayed onMouseMove event so that we end
1816                    // up hovering over the right position after the scroll.
1817                    final MotionEvent eventFakeMouseMove = MotionEvent.obtain(event);
1818                    mFakeMouseMoveRunnable = new Runnable() {
1819                        @Override
1820                        public void run() {
1821                            onHoverEvent(eventFakeMouseMove);
1822                            eventFakeMouseMove.recycle();
1823                        }
1824                    };
1825                    mContainerView.postDelayed(mFakeMouseMoveRunnable, 250);
1826                    return true;
1827            }
1828        }
1829        return mContainerViewInternals.super_onGenericMotionEvent(event);
1830    }
1831
1832    /**
1833     * Sets the current amount to offset incoming touch events by.  This is used to handle content
1834     * moving and not lining up properly with the android input system.
1835     * @param dx The X offset in pixels to shift touch events.
1836     * @param dy The Y offset in pixels to shift touch events.
1837     */
1838    public void setCurrentMotionEventOffsets(float dx, float dy) {
1839        mCurrentTouchOffsetX = dx;
1840        mCurrentTouchOffsetY = dy;
1841    }
1842
1843    private MotionEvent createOffsetMotionEvent(MotionEvent src) {
1844        MotionEvent dst = MotionEvent.obtain(src);
1845        dst.offsetLocation(mCurrentTouchOffsetX, mCurrentTouchOffsetY);
1846        return dst;
1847    }
1848
1849    /**
1850     * @see View#scrollBy(int, int)
1851     * Currently the ContentView scrolling happens in the native side. In
1852     * the Java view system, it is always pinned at (0, 0). scrollBy() and scrollTo()
1853     * are overridden, so that View's mScrollX and mScrollY will be unchanged at
1854     * (0, 0). This is critical for drawing ContentView correctly.
1855     */
1856    public void scrollBy(int xPix, int yPix) {
1857        if (mNativeContentViewCore != 0) {
1858            nativeScrollBy(mNativeContentViewCore,
1859                    SystemClock.uptimeMillis(), 0, 0, xPix, yPix);
1860        }
1861    }
1862
1863    /**
1864     * @see View#scrollTo(int, int)
1865     */
1866    public void scrollTo(int xPix, int yPix) {
1867        if (mNativeContentViewCore == 0) return;
1868        final float xCurrentPix = mRenderCoordinates.getScrollXPix();
1869        final float yCurrentPix = mRenderCoordinates.getScrollYPix();
1870        final float dxPix = xPix - xCurrentPix;
1871        final float dyPix = yPix - yCurrentPix;
1872        if (dxPix != 0 || dyPix != 0) {
1873            long time = SystemClock.uptimeMillis();
1874            nativeScrollBegin(mNativeContentViewCore, time,
1875                    xCurrentPix, yCurrentPix, -dxPix, -dyPix);
1876            nativeScrollBy(mNativeContentViewCore,
1877                    time, xCurrentPix, yCurrentPix, dxPix, dyPix);
1878            nativeScrollEnd(mNativeContentViewCore, time);
1879        }
1880    }
1881
1882    // NOTE: this can go away once ContentView.getScrollX() reports correct values.
1883    //       see: b/6029133
1884    public int getNativeScrollXForTest() {
1885        return mRenderCoordinates.getScrollXPixInt();
1886    }
1887
1888    // NOTE: this can go away once ContentView.getScrollY() reports correct values.
1889    //       see: b/6029133
1890    public int getNativeScrollYForTest() {
1891        return mRenderCoordinates.getScrollYPixInt();
1892    }
1893
1894    /**
1895     * @see View#computeHorizontalScrollExtent()
1896     */
1897    @SuppressWarnings("javadoc")
1898    public int computeHorizontalScrollExtent() {
1899        return mRenderCoordinates.getLastFrameViewportWidthPixInt();
1900    }
1901
1902    /**
1903     * @see View#computeHorizontalScrollOffset()
1904     */
1905    @SuppressWarnings("javadoc")
1906    public int computeHorizontalScrollOffset() {
1907        return mRenderCoordinates.getScrollXPixInt();
1908    }
1909
1910    /**
1911     * @see View#computeHorizontalScrollRange()
1912     */
1913    @SuppressWarnings("javadoc")
1914    public int computeHorizontalScrollRange() {
1915        return mRenderCoordinates.getContentWidthPixInt();
1916    }
1917
1918    /**
1919     * @see View#computeVerticalScrollExtent()
1920     */
1921    @SuppressWarnings("javadoc")
1922    public int computeVerticalScrollExtent() {
1923        return mRenderCoordinates.getLastFrameViewportHeightPixInt();
1924    }
1925
1926    /**
1927     * @see View#computeVerticalScrollOffset()
1928     */
1929    @SuppressWarnings("javadoc")
1930    public int computeVerticalScrollOffset() {
1931        return mRenderCoordinates.getScrollYPixInt();
1932    }
1933
1934    /**
1935     * @see View#computeVerticalScrollRange()
1936     */
1937    @SuppressWarnings("javadoc")
1938    public int computeVerticalScrollRange() {
1939        return mRenderCoordinates.getContentHeightPixInt();
1940    }
1941
1942    // End FrameLayout overrides.
1943
1944    /**
1945     * @see View#awakenScrollBars(int, boolean)
1946     */
1947    @SuppressWarnings("javadoc")
1948    public boolean awakenScrollBars(int startDelay, boolean invalidate) {
1949        // For the default implementation of ContentView which draws the scrollBars on the native
1950        // side, calling this function may get us into a bad state where we keep drawing the
1951        // scrollBars, so disable it by always returning false.
1952        if (mContainerView.getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) {
1953            return false;
1954        } else {
1955            return mContainerViewInternals.super_awakenScrollBars(startDelay, invalidate);
1956        }
1957    }
1958
1959    private void updateForTapOrPress(int type, float xPix, float yPix) {
1960        if (type != GestureEventType.SINGLE_TAP_CONFIRMED
1961                && type != GestureEventType.SINGLE_TAP_UP
1962                && type != GestureEventType.LONG_PRESS
1963                && type != GestureEventType.LONG_TAP) {
1964            return;
1965        }
1966
1967        if (mContainerView.isFocusable() && mContainerView.isFocusableInTouchMode()
1968                && !mContainerView.isFocused())  {
1969            mContainerView.requestFocus();
1970        }
1971
1972        if (!mPopupZoomer.isShowing()) mPopupZoomer.setLastTouch(xPix, yPix);
1973
1974        mLastTapX = (int) xPix;
1975        mLastTapY = (int) yPix;
1976    }
1977
1978    /**
1979     * @return The x coordinate for the last point that a tap or press gesture was initiated from.
1980     */
1981    public int getLastTapX()  {
1982        return mLastTapX;
1983    }
1984
1985    /**
1986     * @return The y coordinate for the last point that a tap or press gesture was initiated from.
1987     */
1988    public int getLastTapY()  {
1989        return mLastTapY;
1990    }
1991
1992    public void setZoomControlsDelegate(ZoomControlsDelegate zoomControlsDelegate) {
1993        mZoomControlsDelegate = zoomControlsDelegate;
1994    }
1995
1996    public void updateMultiTouchZoomSupport(boolean supportsMultiTouchZoom) {
1997        if (mNativeContentViewCore == 0) return;
1998        nativeSetMultiTouchZoomSupportEnabled(mNativeContentViewCore, supportsMultiTouchZoom);
1999    }
2000
2001    public void updateDoubleTapSupport(boolean supportsDoubleTap) {
2002        if (mNativeContentViewCore == 0) return;
2003        nativeSetDoubleTapSupportEnabled(mNativeContentViewCore, supportsDoubleTap);
2004    }
2005
2006    public void selectPopupMenuItems(int[] indices) {
2007        if (mNativeContentViewCore != 0) {
2008            nativeSelectPopupMenuItems(mNativeContentViewCore, indices);
2009        }
2010        mSelectPopup = null;
2011    }
2012
2013    /**
2014     * Send the screen orientation value to the renderer.
2015     */
2016    @VisibleForTesting
2017    void sendOrientationChangeEvent(int orientation) {
2018        if (mNativeContentViewCore == 0) return;
2019
2020        nativeSendOrientationChangeEvent(mNativeContentViewCore, orientation);
2021    }
2022
2023    /**
2024     * Register the delegate to be used when content can not be handled by
2025     * the rendering engine, and should be downloaded instead. This will replace
2026     * the current delegate, if any.
2027     * @param delegate An implementation of ContentViewDownloadDelegate.
2028     */
2029    public void setDownloadDelegate(ContentViewDownloadDelegate delegate) {
2030        mDownloadDelegate = delegate;
2031    }
2032
2033    // Called by DownloadController.
2034    ContentViewDownloadDelegate getDownloadDelegate() {
2035        return mDownloadDelegate;
2036    }
2037
2038    private void showSelectActionBar() {
2039        if (mActionMode != null) {
2040            mActionMode.invalidate();
2041            return;
2042        }
2043
2044        // Start a new action mode with a SelectActionModeCallback.
2045        SelectActionModeCallback.ActionHandler actionHandler =
2046                new SelectActionModeCallback.ActionHandler() {
2047            @Override
2048            public void selectAll() {
2049                mImeAdapter.selectAll();
2050            }
2051
2052            @Override
2053            public void cut() {
2054                mImeAdapter.cut();
2055            }
2056
2057            @Override
2058            public void copy() {
2059                mImeAdapter.copy();
2060            }
2061
2062            @Override
2063            public void paste() {
2064                mImeAdapter.paste();
2065            }
2066
2067            @Override
2068            public void share() {
2069                final String query = getSelectedText();
2070                if (TextUtils.isEmpty(query)) return;
2071
2072                Intent send = new Intent(Intent.ACTION_SEND);
2073                send.setType("text/plain");
2074                send.putExtra(Intent.EXTRA_TEXT, query);
2075                try {
2076                    Intent i = Intent.createChooser(send, getContext().getString(
2077                            R.string.actionbar_share));
2078                    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2079                    getContext().startActivity(i);
2080                } catch (android.content.ActivityNotFoundException ex) {
2081                    // If no app handles it, do nothing.
2082                }
2083            }
2084
2085            @Override
2086            public void search() {
2087                final String query = getSelectedText();
2088                if (TextUtils.isEmpty(query)) return;
2089
2090                // See if ContentViewClient wants to override
2091                if (getContentViewClient().doesPerformWebSearch()) {
2092                    getContentViewClient().performWebSearch(query);
2093                    return;
2094                }
2095
2096                Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
2097                i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
2098                i.putExtra(SearchManager.QUERY, query);
2099                i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
2100                if (!(getContext() instanceof Activity)) {
2101                    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2102                }
2103                try {
2104                    getContext().startActivity(i);
2105                } catch (android.content.ActivityNotFoundException ex) {
2106                    // If no app handles it, do nothing.
2107                }
2108            }
2109
2110            @Override
2111            public boolean isSelectionPassword() {
2112                return mImeAdapter.isSelectionPassword();
2113            }
2114
2115            @Override
2116            public boolean isSelectionEditable() {
2117                return mFocusedNodeEditable;
2118            }
2119
2120            @Override
2121            public void onDestroyActionMode() {
2122                mActionMode = null;
2123                if (mUnselectAllOnActionModeDismiss) {
2124                    hideTextHandles();
2125                    if (isSelectionEditable()) {
2126                        int selectionEnd = Selection.getSelectionEnd(mEditable);
2127                        mInputConnection.setSelection(selectionEnd, selectionEnd);
2128                    } else {
2129                        mImeAdapter.unselect();
2130                    }
2131                }
2132                getContentViewClient().onContextualActionBarHidden();
2133            }
2134
2135            @Override
2136            public boolean isShareAvailable() {
2137                Intent intent = new Intent(Intent.ACTION_SEND);
2138                intent.setType("text/plain");
2139                return getContext().getPackageManager().queryIntentActivities(intent,
2140                        PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
2141            }
2142
2143            @Override
2144            public boolean isWebSearchAvailable() {
2145                if (getContentViewClient().doesPerformWebSearch()) return true;
2146                Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
2147                intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
2148                return getContext().getPackageManager().queryIntentActivities(intent,
2149                        PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
2150            }
2151        };
2152        mActionMode = null;
2153        // On ICS, startActionMode throws an NPE when getParent() is null.
2154        if (mContainerView.getParent() != null) {
2155            mActionMode = mContainerView.startActionMode(
2156                    getContentViewClient().getSelectActionModeCallback(getContext(), actionHandler,
2157                            nativeIsIncognito(mNativeContentViewCore)));
2158        }
2159        mUnselectAllOnActionModeDismiss = true;
2160        if (mActionMode == null) {
2161            // There is no ActionMode, so remove the selection.
2162            mImeAdapter.unselect();
2163        } else {
2164            getContentViewClient().onContextualActionBarShown();
2165        }
2166    }
2167
2168    private void hidePastePopup() {
2169        if (mPastePopupMenu == null) return;
2170        mPastePopupMenu.hide();
2171    }
2172
2173    @CalledByNative
2174    private void onSelectionEvent(int eventType, float posXDip, float posYDip) {
2175        switch (eventType) {
2176            case SelectionEventType.SELECTION_SHOWN:
2177                mHasSelection = true;
2178                // TODO(cjhopman): Remove this when there is a better signal that long press caused
2179                // a selection. See http://crbug.com/150151.
2180                mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2181                showSelectActionBar();
2182                break;
2183
2184            case SelectionEventType.SELECTION_CLEARED:
2185                mHasSelection = false;
2186                mUnselectAllOnActionModeDismiss = false;
2187                hideSelectActionBar();
2188                break;
2189
2190            case SelectionEventType.INSERTION_SHOWN:
2191                mHasInsertion = true;
2192                break;
2193
2194            case SelectionEventType.INSERTION_MOVED:
2195                // TODO(jdduke): Handle case where movement triggered by focus.
2196                hidePastePopup();
2197                break;
2198
2199            case SelectionEventType.INSERTION_TAPPED:
2200                if (getPastePopup().isShowing())
2201                    mPastePopupMenu.hide();
2202                else
2203                    showPastePopup((int) posXDip, (int) posYDip);
2204                break;
2205
2206            case SelectionEventType.INSERTION_CLEARED:
2207                mHasInsertion = false;
2208                hidePastePopup();
2209                break;
2210
2211            default:
2212                assert false : "Invalid selection event type.";
2213        }
2214    }
2215
2216    public boolean getUseDesktopUserAgent() {
2217        if (mNativeContentViewCore != 0) {
2218            return nativeGetUseDesktopUserAgent(mNativeContentViewCore);
2219        }
2220        return false;
2221    }
2222
2223    /**
2224     * Set whether or not we're using a desktop user agent for the currently loaded page.
2225     * @param override If true, use a desktop user agent.  Use a mobile one otherwise.
2226     * @param reloadOnChange Reload the page if the UA has changed.
2227     */
2228    public void setUseDesktopUserAgent(boolean override, boolean reloadOnChange) {
2229        if (mNativeContentViewCore != 0) {
2230            nativeSetUseDesktopUserAgent(mNativeContentViewCore, override, reloadOnChange);
2231        }
2232    }
2233
2234    public void clearSslPreferences() {
2235        if (mNativeContentViewCore != 0) nativeClearSslPreferences(mNativeContentViewCore);
2236    }
2237
2238    private void hideTextHandles() {
2239        mHasSelection = false;
2240        mHasInsertion = false;
2241        if (mNativeContentViewCore != 0) nativeHideTextHandles(mNativeContentViewCore);
2242    }
2243
2244    /**
2245     * Shows the IME if the focused widget could accept text input.
2246     */
2247    public void showImeIfNeeded() {
2248        assert mWebContents != null;
2249        mWebContents.showImeIfNeeded();
2250    }
2251
2252    /**
2253     * Hides the IME if the containerView is the active view for IME.
2254     */
2255    public void hideImeIfNeeded() {
2256        // Hide input method window from the current view synchronously
2257        // because ImeAdapter does so asynchronouly with a delay, and
2258        // by the time when ImeAdapter dismisses the input, the
2259        // containerView may have lost focus.
2260        // We cannot trust ContentViewClient#onImeStateChangeRequested to
2261        // hide the input window because it has an empty default implementation.
2262        // So we need to explicitly hide the input method window here.
2263        if (mInputMethodManagerWrapper.isActive(mContainerView)) {
2264            mInputMethodManagerWrapper.hideSoftInputFromWindow(
2265                    mContainerView.getWindowToken(), 0, null);
2266        }
2267        getContentViewClient().onImeStateChangeRequested(false);
2268    }
2269
2270    @SuppressWarnings("unused")
2271    @CalledByNative
2272    private void updateFrameInfo(
2273            float scrollOffsetX, float scrollOffsetY,
2274            float pageScaleFactor, float minPageScaleFactor, float maxPageScaleFactor,
2275            float contentWidth, float contentHeight,
2276            float viewportWidth, float viewportHeight,
2277            float controlsOffsetYCss, float contentOffsetYCss,
2278            float overdrawBottomHeightCss) {
2279        TraceEvent.begin("ContentViewCore:updateFrameInfo");
2280        // Adjust contentWidth/Height to be always at least as big as
2281        // the actual viewport (as set by onSizeChanged).
2282        final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
2283        contentWidth = Math.max(contentWidth,
2284                mViewportWidthPix / (deviceScale * pageScaleFactor));
2285        contentHeight = Math.max(contentHeight,
2286                mViewportHeightPix / (deviceScale * pageScaleFactor));
2287        final float contentOffsetYPix = mRenderCoordinates.fromDipToPix(contentOffsetYCss);
2288
2289        final boolean contentSizeChanged =
2290                contentWidth != mRenderCoordinates.getContentWidthCss()
2291                || contentHeight != mRenderCoordinates.getContentHeightCss();
2292        final boolean scaleLimitsChanged =
2293                minPageScaleFactor != mRenderCoordinates.getMinPageScaleFactor()
2294                || maxPageScaleFactor != mRenderCoordinates.getMaxPageScaleFactor();
2295        final boolean pageScaleChanged =
2296                pageScaleFactor != mRenderCoordinates.getPageScaleFactor();
2297        final boolean scrollChanged =
2298                pageScaleChanged
2299                || scrollOffsetX != mRenderCoordinates.getScrollX()
2300                || scrollOffsetY != mRenderCoordinates.getScrollY();
2301        final boolean contentOffsetChanged =
2302                contentOffsetYPix != mRenderCoordinates.getContentOffsetYPix();
2303
2304        final boolean needHidePopupZoomer = contentSizeChanged || scrollChanged;
2305        final boolean needUpdateZoomControls = scaleLimitsChanged || scrollChanged;
2306
2307        if (needHidePopupZoomer) mPopupZoomer.hide(true);
2308
2309        if (scrollChanged) {
2310            mContainerViewInternals.onScrollChanged(
2311                    (int) mRenderCoordinates.fromLocalCssToPix(scrollOffsetX),
2312                    (int) mRenderCoordinates.fromLocalCssToPix(scrollOffsetY),
2313                    (int) mRenderCoordinates.getScrollXPix(),
2314                    (int) mRenderCoordinates.getScrollYPix());
2315        }
2316
2317        mRenderCoordinates.updateFrameInfo(
2318                scrollOffsetX, scrollOffsetY,
2319                contentWidth, contentHeight,
2320                viewportWidth, viewportHeight,
2321                pageScaleFactor, minPageScaleFactor, maxPageScaleFactor,
2322                contentOffsetYPix);
2323
2324        if (scrollChanged || contentOffsetChanged) {
2325            for (mGestureStateListenersIterator.rewind();
2326                    mGestureStateListenersIterator.hasNext();) {
2327                mGestureStateListenersIterator.next().onScrollOffsetOrExtentChanged(
2328                        computeVerticalScrollOffset(),
2329                        computeVerticalScrollExtent());
2330            }
2331        }
2332
2333        if (needUpdateZoomControls) mZoomControlsDelegate.updateZoomControls();
2334
2335        // Update offsets for fullscreen.
2336        final float controlsOffsetPix = controlsOffsetYCss * deviceScale;
2337        final float overdrawBottomHeightPix = overdrawBottomHeightCss * deviceScale;
2338        getContentViewClient().onOffsetsForFullscreenChanged(
2339                controlsOffsetPix, contentOffsetYPix, overdrawBottomHeightPix);
2340
2341        if (mBrowserAccessibilityManager != null) {
2342            mBrowserAccessibilityManager.notifyFrameInfoInitialized();
2343        }
2344        TraceEvent.end("ContentViewCore:updateFrameInfo");
2345    }
2346
2347    @CalledByNative
2348    private void updateImeAdapter(long nativeImeAdapterAndroid, int textInputType,
2349            String text, int selectionStart, int selectionEnd,
2350            int compositionStart, int compositionEnd, boolean showImeIfNeeded,
2351            boolean isNonImeChange) {
2352        TraceEvent.begin();
2353        mFocusedNodeEditable = (textInputType != ImeAdapter.getTextInputTypeNone());
2354        if (!mFocusedNodeEditable) hidePastePopup();
2355
2356        mImeAdapter.updateKeyboardVisibility(
2357                nativeImeAdapterAndroid, textInputType, showImeIfNeeded);
2358
2359        if (mInputConnection != null) {
2360            mInputConnection.updateState(text, selectionStart, selectionEnd, compositionStart,
2361                    compositionEnd, isNonImeChange);
2362        }
2363
2364        if (mActionMode != null) mActionMode.invalidate();
2365        TraceEvent.end();
2366    }
2367
2368    @SuppressWarnings("unused")
2369    @CalledByNative
2370    private void setTitle(String title) {
2371        getContentViewClient().onUpdateTitle(title);
2372    }
2373
2374    /**
2375     * Called (from native) when the <select> popup needs to be shown.
2376     * @param items           Items to show.
2377     * @param enabled         POPUP_ITEM_TYPEs for items.
2378     * @param multiple        Whether the popup menu should support multi-select.
2379     * @param selectedIndices Indices of selected items.
2380     */
2381    @SuppressWarnings("unused")
2382    @CalledByNative
2383    private void showSelectPopup(Rect bounds, String[] items, int[] enabled, boolean multiple,
2384            int[] selectedIndices) {
2385        if (mContainerView.getParent() == null || mContainerView.getVisibility() != View.VISIBLE) {
2386            selectPopupMenuItems(null);
2387            return;
2388        }
2389
2390        assert items.length == enabled.length;
2391        List<SelectPopupItem> popupItems = new ArrayList<SelectPopupItem>();
2392        for (int i = 0; i < items.length; i++) {
2393            popupItems.add(new SelectPopupItem(items[i], enabled[i]));
2394        }
2395        hidePopups();
2396        if (DeviceFormFactor.isTablet(mContext) && !multiple) {
2397            mSelectPopup = new SelectPopupDropdown(this, popupItems, bounds, selectedIndices);
2398        } else {
2399            mSelectPopup = new SelectPopupDialog(this, popupItems, multiple, selectedIndices);
2400        }
2401        mSelectPopup.show();
2402    }
2403
2404    /**
2405     * Called when the <select> popup needs to be hidden.
2406     */
2407    @CalledByNative
2408    private void hideSelectPopup() {
2409        if (mSelectPopup != null) mSelectPopup.hide();
2410    }
2411
2412    /**
2413     * @return The visible select popup being shown.
2414     */
2415    public SelectPopup getSelectPopupForTest() {
2416        return mSelectPopup;
2417    }
2418
2419    @SuppressWarnings("unused")
2420    @CalledByNative
2421    private void showDisambiguationPopup(Rect targetRect, Bitmap zoomedBitmap) {
2422        mPopupZoomer.setBitmap(zoomedBitmap);
2423        mPopupZoomer.show(targetRect);
2424    }
2425
2426    @SuppressWarnings("unused")
2427    @CalledByNative
2428    private TouchEventSynthesizer createTouchEventSynthesizer() {
2429        return new TouchEventSynthesizer(this);
2430    }
2431
2432    @SuppressWarnings("unused")
2433    @CalledByNative
2434    private PopupTouchHandleDrawable createPopupTouchHandleDrawable() {
2435        if (mTouchHandleDelegate == null) {
2436            mTouchHandleDelegate = new PopupTouchHandleDrawableDelegate() {
2437                public View getParent() {
2438                    return getContainerView();
2439                }
2440
2441                public PositionObserver getParentPositionObserver() {
2442                    return mPositionObserver;
2443                }
2444
2445                public boolean onTouchHandleEvent(MotionEvent event) {
2446                    final boolean isTouchHandleEvent = true;
2447                    return onTouchEventImpl(event, isTouchHandleEvent);
2448                }
2449            };
2450        }
2451        return new PopupTouchHandleDrawable(mTouchHandleDelegate);
2452    }
2453
2454    @SuppressWarnings("unused")
2455    @CalledByNative
2456    private void onSelectionChanged(String text) {
2457        mLastSelectedText = text;
2458        getContentViewClient().onSelectionChanged(text);
2459    }
2460
2461    @SuppressWarnings("unused")
2462    @CalledByNative
2463    private static void onEvaluateJavaScriptResult(
2464            String jsonResult, JavaScriptCallback callback) {
2465        callback.handleJavaScriptResult(jsonResult);
2466    }
2467
2468    @SuppressWarnings("unused")
2469    @CalledByNative
2470    private void showPastePopup(int xDip, int yDip) {
2471        if (!mHasInsertion) return;
2472        final float contentOffsetYPix = mRenderCoordinates.getContentOffsetYPix();
2473        getPastePopup().showAt(
2474            (int) mRenderCoordinates.fromDipToPix(xDip),
2475            (int) (mRenderCoordinates.fromDipToPix(yDip) + contentOffsetYPix));
2476    }
2477
2478    private PastePopupMenu getPastePopup() {
2479        if (mPastePopupMenu == null) {
2480            mPastePopupMenu = new PastePopupMenu(getContainerView(),
2481                new PastePopupMenuDelegate() {
2482                    public void paste() {
2483                        mImeAdapter.paste();
2484                        hideTextHandles();
2485                    }
2486                    public boolean canPaste() {
2487                        if (!mFocusedNodeEditable) return false;
2488                        return ((ClipboardManager) mContext.getSystemService(
2489                                Context.CLIPBOARD_SERVICE)).hasPrimaryClip();
2490                    }
2491                });
2492        }
2493        return mPastePopupMenu;
2494    }
2495
2496    @SuppressWarnings("unused")
2497    @CalledByNative
2498    private void onRenderProcessChange() {
2499        attachImeAdapter();
2500    }
2501
2502    /**
2503     * Attaches the native ImeAdapter object to the java ImeAdapter to allow communication via JNI.
2504     */
2505    public void attachImeAdapter() {
2506        if (mImeAdapter != null && mNativeContentViewCore != 0) {
2507            mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore));
2508        }
2509    }
2510
2511    /**
2512     * @see View#hasFocus()
2513     */
2514    @CalledByNative
2515    public boolean hasFocus() {
2516        return mContainerView.hasFocus();
2517    }
2518
2519    /**
2520     * Checks whether the ContentViewCore can be zoomed in.
2521     *
2522     * @return True if the ContentViewCore can be zoomed in.
2523     */
2524    // This method uses the term 'zoom' for legacy reasons, but relates
2525    // to what chrome calls the 'page scale factor'.
2526    public boolean canZoomIn() {
2527        final float zoomInExtent = mRenderCoordinates.getMaxPageScaleFactor()
2528                - mRenderCoordinates.getPageScaleFactor();
2529        return zoomInExtent > ZOOM_CONTROLS_EPSILON;
2530    }
2531
2532    /**
2533     * Checks whether the ContentViewCore can be zoomed out.
2534     *
2535     * @return True if the ContentViewCore can be zoomed out.
2536     */
2537    // This method uses the term 'zoom' for legacy reasons, but relates
2538    // to what chrome calls the 'page scale factor'.
2539    public boolean canZoomOut() {
2540        final float zoomOutExtent = mRenderCoordinates.getPageScaleFactor()
2541                - mRenderCoordinates.getMinPageScaleFactor();
2542        return zoomOutExtent > ZOOM_CONTROLS_EPSILON;
2543    }
2544
2545    /**
2546     * Zooms in the ContentViewCore by 25% (or less if that would result in
2547     * zooming in more than possible).
2548     *
2549     * @return True if there was a zoom change, false otherwise.
2550     */
2551    // This method uses the term 'zoom' for legacy reasons, but relates
2552    // to what chrome calls the 'page scale factor'.
2553    public boolean zoomIn() {
2554        if (!canZoomIn()) {
2555            return false;
2556        }
2557        return pinchByDelta(1.25f);
2558    }
2559
2560    /**
2561     * Zooms out the ContentViewCore by 20% (or less if that would result in
2562     * zooming out more than possible).
2563     *
2564     * @return True if there was a zoom change, false otherwise.
2565     */
2566    // This method uses the term 'zoom' for legacy reasons, but relates
2567    // to what chrome calls the 'page scale factor'.
2568    public boolean zoomOut() {
2569        if (!canZoomOut()) {
2570            return false;
2571        }
2572        return pinchByDelta(0.8f);
2573    }
2574
2575    /**
2576     * Resets the zoom factor of the ContentViewCore.
2577     *
2578     * @return True if there was a zoom change, false otherwise.
2579     */
2580    // This method uses the term 'zoom' for legacy reasons, but relates
2581    // to what chrome calls the 'page scale factor'.
2582    public boolean zoomReset() {
2583        // The page scale factor is initialized to mNativeMinimumScale when
2584        // the page finishes loading. Thus sets it back to mNativeMinimumScale.
2585        if (!canZoomOut()) return false;
2586        return pinchByDelta(
2587                mRenderCoordinates.getMinPageScaleFactor()
2588                        / mRenderCoordinates.getPageScaleFactor());
2589    }
2590
2591    /**
2592     * Simulate a pinch zoom gesture.
2593     *
2594     * @param delta the factor by which the current page scale should be multiplied by.
2595     * @return whether the gesture was sent.
2596     */
2597    public boolean pinchByDelta(float delta) {
2598        if (mNativeContentViewCore == 0) return false;
2599
2600        long timeMs = SystemClock.uptimeMillis();
2601        int xPix = getViewportWidthPix() / 2;
2602        int yPix = getViewportHeightPix() / 2;
2603
2604        nativePinchBegin(mNativeContentViewCore, timeMs, xPix, yPix);
2605        nativePinchBy(mNativeContentViewCore, timeMs, xPix, yPix, delta);
2606        nativePinchEnd(mNativeContentViewCore, timeMs);
2607
2608        return true;
2609    }
2610
2611    /**
2612     * Invokes the graphical zoom picker widget for this ContentView.
2613     */
2614    public void invokeZoomPicker() {
2615        mZoomControlsDelegate.invokeZoomPicker();
2616    }
2617
2618    /**
2619     * Enables or disables inspection of JavaScript objects added via
2620     * {@link #addJavascriptInterface(Object, String)} by means of Object.keys() method and
2621     * &quot;for .. in&quot; loop. Being able to inspect JavaScript objects is useful
2622     * when debugging hybrid Android apps, but can't be enabled for legacy applications due
2623     * to compatibility risks.
2624     *
2625     * @param allow Whether to allow JavaScript objects inspection.
2626     */
2627    public void setAllowJavascriptInterfacesInspection(boolean allow) {
2628        nativeSetAllowJavascriptInterfacesInspection(mNativeContentViewCore, allow);
2629    }
2630
2631    /**
2632     * Returns JavaScript interface objects previously injected via
2633     * {@link #addJavascriptInterface(Object, String)}.
2634     *
2635     * @return the mapping of names to interface objects and corresponding annotation classes
2636     */
2637    public Map<String, Pair<Object, Class>> getJavascriptInterfaces() {
2638        return mJavaScriptInterfaces;
2639    }
2640
2641    /**
2642     * This will mimic {@link #addPossiblyUnsafeJavascriptInterface(Object, String, Class)}
2643     * and automatically pass in {@link JavascriptInterface} as the required annotation.
2644     *
2645     * @param object The Java object to inject into the ContentViewCore's JavaScript context.  Null
2646     *               values are ignored.
2647     * @param name   The name used to expose the instance in JavaScript.
2648     */
2649    public void addJavascriptInterface(Object object, String name) {
2650        addPossiblyUnsafeJavascriptInterface(object, name, JavascriptInterface.class);
2651    }
2652
2653    /**
2654     * This method injects the supplied Java object into the ContentViewCore.
2655     * The object is injected into the JavaScript context of the main frame,
2656     * using the supplied name. This allows the Java object to be accessed from
2657     * JavaScript. Note that that injected objects will not appear in
2658     * JavaScript until the page is next (re)loaded. For example:
2659     * <pre> view.addJavascriptInterface(new Object(), "injectedObject");
2660     * view.loadData("<!DOCTYPE html><title></title>", "text/html", null);
2661     * view.loadUrl("javascript:alert(injectedObject.toString())");</pre>
2662     * <p><strong>IMPORTANT:</strong>
2663     * <ul>
2664     * <li> addJavascriptInterface() can be used to allow JavaScript to control
2665     * the host application. This is a powerful feature, but also presents a
2666     * security risk. Use of this method in a ContentViewCore containing
2667     * untrusted content could allow an attacker to manipulate the host
2668     * application in unintended ways, executing Java code with the permissions
2669     * of the host application. Use extreme care when using this method in a
2670     * ContentViewCore which could contain untrusted content. Particular care
2671     * should be taken to avoid unintentional access to inherited methods, such
2672     * as {@link Object#getClass()}. To prevent access to inherited methods,
2673     * pass an annotation for {@code requiredAnnotation}.  This will ensure
2674     * that only methods with {@code requiredAnnotation} are exposed to the
2675     * Javascript layer.  {@code requiredAnnotation} will be passed to all
2676     * subsequently injected Java objects if any methods return an object.  This
2677     * means the same restrictions (or lack thereof) will apply.  Alternatively,
2678     * {@link #addJavascriptInterface(Object, String)} can be called, which
2679     * automatically uses the {@link JavascriptInterface} annotation.
2680     * <li> JavaScript interacts with Java objects on a private, background
2681     * thread of the ContentViewCore. Care is therefore required to maintain
2682     * thread safety.</li>
2683     * </ul></p>
2684     *
2685     * @param object             The Java object to inject into the
2686     *                           ContentViewCore's JavaScript context. Null
2687     *                           values are ignored.
2688     * @param name               The name used to expose the instance in
2689     *                           JavaScript.
2690     * @param requiredAnnotation Restrict exposed methods to ones with this
2691     *                           annotation.  If {@code null} all methods are
2692     *                           exposed.
2693     *
2694     */
2695    public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
2696            Class<? extends Annotation> requiredAnnotation) {
2697        if (mNativeContentViewCore != 0 && object != null) {
2698            mJavaScriptInterfaces.put(name, new Pair<Object, Class>(object, requiredAnnotation));
2699            nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation);
2700        }
2701    }
2702
2703    /**
2704     * Removes a previously added JavaScript interface with the given name.
2705     *
2706     * @param name The name of the interface to remove.
2707     */
2708    public void removeJavascriptInterface(String name) {
2709        mJavaScriptInterfaces.remove(name);
2710        if (mNativeContentViewCore != 0) {
2711            nativeRemoveJavascriptInterface(mNativeContentViewCore, name);
2712        }
2713    }
2714
2715    /**
2716     * Return the current scale of the ContentView.
2717     * @return The current page scale factor.
2718     */
2719    public float getScale() {
2720        return mRenderCoordinates.getPageScaleFactor();
2721    }
2722
2723    /**
2724     * If the view is ready to draw contents to the screen. In hardware mode,
2725     * the initialization of the surface texture may not occur until after the
2726     * view has been added to the layout. This method will return {@code true}
2727     * once the texture is actually ready.
2728     */
2729    public boolean isReady() {
2730        assert mWebContents != null;
2731        return mWebContents.isReady();
2732    }
2733
2734    @CalledByNative
2735    private void startContentIntent(String contentUrl) {
2736        getContentViewClient().onStartContentIntent(getContext(), contentUrl);
2737    }
2738
2739    @Override
2740    public void onAccessibilityStateChanged(boolean enabled) {
2741        setAccessibilityState(enabled);
2742    }
2743
2744    /**
2745     * Determines whether or not this ContentViewCore can handle this accessibility action.
2746     * @param action The action to perform.
2747     * @return Whether or not this action is supported.
2748     */
2749    public boolean supportsAccessibilityAction(int action) {
2750        return mAccessibilityInjector.supportsAccessibilityAction(action);
2751    }
2752
2753    /**
2754     * Attempts to perform an accessibility action on the web content.  If the accessibility action
2755     * cannot be processed, it returns {@code null}, allowing the caller to know to call the
2756     * super {@link View#performAccessibilityAction(int, Bundle)} method and use that return value.
2757     * Otherwise the return value from this method should be used.
2758     * @param action The action to perform.
2759     * @param arguments Optional action arguments.
2760     * @return Whether the action was performed or {@code null} if the call should be delegated to
2761     *         the super {@link View} class.
2762     */
2763    public boolean performAccessibilityAction(int action, Bundle arguments) {
2764        if (mAccessibilityInjector.supportsAccessibilityAction(action)) {
2765            return mAccessibilityInjector.performAccessibilityAction(action, arguments);
2766        }
2767
2768        return false;
2769    }
2770
2771    /**
2772     * Set the BrowserAccessibilityManager, used for native accessibility
2773     * (not script injection). This is only set when system accessibility
2774     * has been enabled.
2775     * @param manager The new BrowserAccessibilityManager.
2776     */
2777    public void setBrowserAccessibilityManager(BrowserAccessibilityManager manager) {
2778        mBrowserAccessibilityManager = manager;
2779    }
2780
2781    /**
2782     * Get the BrowserAccessibilityManager, used for native accessibility
2783     * (not script injection). This will return null when system accessibility
2784     * is not enabled.
2785     * @return This view's BrowserAccessibilityManager.
2786     */
2787    public BrowserAccessibilityManager getBrowserAccessibilityManager() {
2788        return mBrowserAccessibilityManager;
2789    }
2790
2791    /**
2792     * If native accessibility (not script injection) is enabled, and if this is
2793     * running on JellyBean or later, returns an AccessibilityNodeProvider that
2794     * implements native accessibility for this view. Returns null otherwise.
2795     * Lazily initializes native accessibility here if it's allowed.
2796     * @return The AccessibilityNodeProvider, if available, or null otherwise.
2797     */
2798    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
2799        if (mBrowserAccessibilityManager != null) {
2800            return mBrowserAccessibilityManager.getAccessibilityNodeProvider();
2801        }
2802
2803        if (mNativeAccessibilityAllowed &&
2804                !mNativeAccessibilityEnabled &&
2805                mNativeContentViewCore != 0 &&
2806                Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
2807            mNativeAccessibilityEnabled = true;
2808            nativeSetAccessibilityEnabled(mNativeContentViewCore, true);
2809        }
2810
2811        return null;
2812    }
2813
2814    /**
2815     * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
2816     */
2817    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2818        // Note: this is only used by the script-injecting accessibility code.
2819        mAccessibilityInjector.onInitializeAccessibilityNodeInfo(info);
2820    }
2821
2822    /**
2823     * @see View#onInitializeAccessibilityEvent(AccessibilityEvent)
2824     */
2825    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2826        // Note: this is only used by the script-injecting accessibility code.
2827        event.setClassName(this.getClass().getName());
2828
2829        // Identify where the top-left of the screen currently points to.
2830        event.setScrollX(mRenderCoordinates.getScrollXPixInt());
2831        event.setScrollY(mRenderCoordinates.getScrollYPixInt());
2832
2833        // The maximum scroll values are determined by taking the content dimensions and
2834        // subtracting off the actual dimensions of the ChromeView.
2835        int maxScrollXPix = Math.max(0, mRenderCoordinates.getMaxHorizontalScrollPixInt());
2836        int maxScrollYPix = Math.max(0, mRenderCoordinates.getMaxVerticalScrollPixInt());
2837        event.setScrollable(maxScrollXPix > 0 || maxScrollYPix > 0);
2838
2839        // Setting the maximum scroll values requires API level 15 or higher.
2840        final int SDK_VERSION_REQUIRED_TO_SET_SCROLL = 15;
2841        if (Build.VERSION.SDK_INT >= SDK_VERSION_REQUIRED_TO_SET_SCROLL) {
2842            event.setMaxScrollX(maxScrollXPix);
2843            event.setMaxScrollY(maxScrollYPix);
2844        }
2845    }
2846
2847    /**
2848     * Returns whether accessibility script injection is enabled on the device
2849     */
2850    public boolean isDeviceAccessibilityScriptInjectionEnabled() {
2851        try {
2852            // On JellyBean and higher, native accessibility is the default so script
2853            // injection is only allowed if enabled via a flag.
2854            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
2855                    !CommandLine.getInstance().hasSwitch(
2856                            ContentSwitches.ENABLE_ACCESSIBILITY_SCRIPT_INJECTION)) {
2857                return false;
2858            }
2859
2860            if (!mContentSettings.getJavaScriptEnabled()) {
2861                return false;
2862            }
2863
2864            int result = getContext().checkCallingOrSelfPermission(
2865                    android.Manifest.permission.INTERNET);
2866            if (result != PackageManager.PERMISSION_GRANTED) {
2867                return false;
2868            }
2869
2870            Field field = Settings.Secure.class.getField("ACCESSIBILITY_SCRIPT_INJECTION");
2871            field.setAccessible(true);
2872            String accessibilityScriptInjection = (String) field.get(null);
2873            ContentResolver contentResolver = getContext().getContentResolver();
2874
2875            if (mAccessibilityScriptInjectionObserver == null) {
2876                ContentObserver contentObserver = new ContentObserver(new Handler()) {
2877                    @Override
2878                    public void onChange(boolean selfChange, Uri uri) {
2879                        setAccessibilityState(mAccessibilityManager.isEnabled());
2880                    }
2881                };
2882                contentResolver.registerContentObserver(
2883                    Settings.Secure.getUriFor(accessibilityScriptInjection),
2884                    false,
2885                    contentObserver);
2886                mAccessibilityScriptInjectionObserver = contentObserver;
2887            }
2888
2889            return Settings.Secure.getInt(contentResolver, accessibilityScriptInjection, 0) == 1;
2890        } catch (NoSuchFieldException e) {
2891            // Do nothing, default to false.
2892        } catch (IllegalAccessException e) {
2893            // Do nothing, default to false.
2894        }
2895        return false;
2896    }
2897
2898    /**
2899     * Returns whether or not accessibility injection is being used.
2900     */
2901    public boolean isInjectingAccessibilityScript() {
2902        return mAccessibilityInjector.accessibilityIsAvailable();
2903    }
2904
2905    /**
2906     * Returns true if accessibility is on and touch exploration is enabled.
2907     */
2908    public boolean isTouchExplorationEnabled() {
2909        return mTouchExplorationEnabled;
2910    }
2911
2912    /**
2913     * Turns browser accessibility on or off.
2914     * If |state| is |false|, this turns off both native and injected accessibility.
2915     * Otherwise, if accessibility script injection is enabled, this will enable the injected
2916     * accessibility scripts. Native accessibility is enabled on demand.
2917     */
2918    public void setAccessibilityState(boolean state) {
2919        if (!state) {
2920            setInjectedAccessibility(false);
2921            mNativeAccessibilityAllowed = false;
2922            mTouchExplorationEnabled = false;
2923        } else {
2924            boolean useScriptInjection = isDeviceAccessibilityScriptInjectionEnabled();
2925            setInjectedAccessibility(useScriptInjection);
2926            mNativeAccessibilityAllowed = !useScriptInjection;
2927            mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
2928        }
2929    }
2930
2931    /**
2932     * Enable or disable injected accessibility features
2933     */
2934    public void setInjectedAccessibility(boolean enabled) {
2935        mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
2936        mAccessibilityInjector.setScriptEnabled(enabled);
2937    }
2938
2939    /**
2940     * Stop any TTS notifications that are currently going on.
2941     */
2942    public void stopCurrentAccessibilityNotifications() {
2943        mAccessibilityInjector.onPageLostFocus();
2944    }
2945
2946    /**
2947     * Inform WebKit that Fullscreen mode has been exited by the user.
2948     */
2949    public void exitFullscreen() {
2950        assert mWebContents != null;
2951        mWebContents.exitFullscreen();
2952    }
2953
2954    /**
2955     * Changes whether hiding the top controls is enabled.
2956     *
2957     * @param enableHiding Whether hiding the top controls should be enabled or not.
2958     * @param enableShowing Whether showing the top controls should be enabled or not.
2959     * @param animate Whether the transition should be animated or not.
2960     */
2961    public void updateTopControlsState(boolean enableHiding, boolean enableShowing,
2962            boolean animate) {
2963        assert mWebContents != null;
2964        mWebContents.updateTopControlsState(
2965                enableHiding, enableShowing, animate);
2966    }
2967
2968    /**
2969     * Callback factory method for nativeGetNavigationHistory().
2970     */
2971    @CalledByNative
2972    private void addToNavigationHistory(Object history, int index, String url, String virtualUrl,
2973            String originalUrl, String title, Bitmap favicon) {
2974        NavigationEntry entry = new NavigationEntry(
2975                index, url, virtualUrl, originalUrl, title, favicon);
2976        ((NavigationHistory) history).addEntry(entry);
2977    }
2978
2979    /**
2980     * Get a copy of the navigation history of the view.
2981     */
2982    public NavigationHistory getNavigationHistory() {
2983        NavigationHistory history = new NavigationHistory();
2984        if (mNativeContentViewCore != 0) {
2985            int currentIndex = nativeGetNavigationHistory(mNativeContentViewCore, history);
2986            history.setCurrentEntryIndex(currentIndex);
2987        }
2988        return history;
2989    }
2990
2991    @Override
2992    public NavigationHistory getDirectedNavigationHistory(boolean isForward, int itemLimit) {
2993        NavigationHistory history = new NavigationHistory();
2994        if (mNativeContentViewCore != 0) {
2995            nativeGetDirectedNavigationHistory(
2996                mNativeContentViewCore, history, isForward, itemLimit);
2997        }
2998        return history;
2999    }
3000
3001    /**
3002     * @return The original request URL for the current navigation entry, or null if there is no
3003     *         current entry.
3004     */
3005    public String getOriginalUrlForActiveNavigationEntry() {
3006        if (mNativeContentViewCore != 0) {
3007            return nativeGetOriginalUrlForActiveNavigationEntry(mNativeContentViewCore);
3008        }
3009        return "";
3010    }
3011
3012    /**
3013     * @return The cached copy of render positions and scales.
3014     */
3015    public RenderCoordinates getRenderCoordinates() {
3016        return mRenderCoordinates;
3017    }
3018
3019    @CalledByNative
3020    private static Rect createRect(int x, int y, int right, int bottom) {
3021        return new Rect(x, y, right, bottom);
3022    }
3023
3024    public void extractSmartClipData(int x, int y, int width, int height) {
3025        if (mNativeContentViewCore != 0) {
3026            x += mSmartClipOffsetX;
3027            y += mSmartClipOffsetY;
3028            nativeExtractSmartClipData(mNativeContentViewCore, x, y, width, height);
3029        }
3030    }
3031
3032    /**
3033     * Set offsets for smart clip.
3034     *
3035     * <p>This should be called if there is a viewport change introduced by,
3036     * e.g., show and hide of a location bar.
3037     *
3038     * @param offsetX Offset for X position.
3039     * @param offsetY Offset for Y position.
3040     */
3041    public void setSmartClipOffsets(int offsetX, int offsetY) {
3042        mSmartClipOffsetX = offsetX;
3043        mSmartClipOffsetY = offsetY;
3044    }
3045
3046    @CalledByNative
3047    private void onSmartClipDataExtracted(String text, String html, Rect clipRect) {
3048        if (mSmartClipDataListener != null ) {
3049            mSmartClipDataListener.onSmartClipDataExtracted(text, html, clipRect);
3050        }
3051    }
3052
3053    public void setSmartClipDataListener(SmartClipDataListener listener) {
3054        mSmartClipDataListener = listener;
3055    }
3056
3057    public void setBackgroundOpaque(boolean opaque) {
3058        if (mNativeContentViewCore != 0) {
3059            nativeSetBackgroundOpaque(mNativeContentViewCore, opaque);
3060        }
3061    }
3062
3063    @CalledByNative
3064    private void didDeferAfterResponseStarted(String enteringColor) {
3065        if (mNavigationTransitionDelegate != null ) {
3066            mNavigationTransitionDelegate.didDeferAfterResponseStarted(enteringColor);
3067        }
3068    }
3069
3070    @CalledByNative
3071    public void didStartNavigationTransitionForFrame(long frameId) {
3072        if (mNavigationTransitionDelegate != null ) {
3073            mNavigationTransitionDelegate.didStartNavigationTransitionForFrame(frameId);
3074        }
3075    }
3076
3077    @CalledByNative
3078    private boolean willHandleDeferAfterResponseStarted() {
3079        if (mNavigationTransitionDelegate == null) return false;
3080        return mNavigationTransitionDelegate.willHandleDeferAfterResponseStarted();
3081    }
3082
3083    @VisibleForTesting
3084    void setHasPendingNavigationTransitionForTesting() {
3085        if (mNativeContentViewCore == 0) return;
3086        nativeSetHasPendingNavigationTransitionForTesting(mNativeContentViewCore);
3087    }
3088
3089    public void setNavigationTransitionDelegate(NavigationTransitionDelegate delegate) {
3090        mNavigationTransitionDelegate = delegate;
3091    }
3092
3093    @CalledByNative
3094    private void addEnteringStylesheetToTransition(String stylesheet) {
3095        if (mNavigationTransitionDelegate != null ) {
3096            mNavigationTransitionDelegate.addEnteringStylesheetToTransition(stylesheet);
3097        }
3098    }
3099
3100    /**
3101     * Offer a long press gesture to the embedding View, primarily for WebView compatibility.
3102     *
3103     * @return true if the embedder handled the event.
3104     */
3105    private boolean offerLongPressToEmbedder() {
3106        return mContainerView.performLongClick();
3107    }
3108
3109    /**
3110     * Reset scroll and fling accounting, notifying listeners as appropriate.
3111     * This is useful as a failsafe when the input stream may have been interruped.
3112     */
3113    private void resetScrollInProgress() {
3114        if (!isScrollInProgress()) return;
3115
3116        final boolean touchScrollInProgress = mTouchScrollInProgress;
3117        final int potentiallyActiveFlingCount = mPotentiallyActiveFlingCount;
3118
3119        mTouchScrollInProgress = false;
3120        mPotentiallyActiveFlingCount = 0;
3121
3122        if (touchScrollInProgress) updateGestureStateListener(GestureEventType.SCROLL_END);
3123        if (potentiallyActiveFlingCount > 0) updateGestureStateListener(GestureEventType.FLING_END);
3124    }
3125
3126    private native long nativeInit(long webContentsPtr,
3127            long viewAndroidPtr, long windowAndroidPtr, HashSet<Object> retainedObjectSet);
3128
3129    @CalledByNative
3130    private ContentVideoViewClient getContentVideoViewClient() {
3131        return getContentViewClient().getContentVideoViewClient();
3132    }
3133
3134    @CalledByNative
3135    private boolean shouldBlockMediaRequest(String url) {
3136        return getContentViewClient().shouldBlockMediaRequest(url);
3137    }
3138
3139    @CalledByNative
3140    private void onNativeFlingStopped() {
3141        // Note that mTouchScrollInProgress should normally be false at this
3142        // point, but we reset it anyway as another failsafe.
3143        mTouchScrollInProgress = false;
3144        if (mPotentiallyActiveFlingCount <= 0) return;
3145        mPotentiallyActiveFlingCount--;
3146        updateGestureStateListener(GestureEventType.FLING_END);
3147    }
3148
3149    @Override
3150    public void onScreenOrientationChanged(int orientation) {
3151        sendOrientationChangeEvent(orientation);
3152    }
3153
3154    public void resumeResponseDeferredAtStart() {
3155        if (mNativeContentViewCore == 0) return;
3156        nativeResumeResponseDeferredAtStart(mNativeContentViewCore);
3157    }
3158
3159    /**
3160     * Set whether the ContentViewCore requires the WebContents to be fullscreen in order to lock
3161     * the screen orientation.
3162     */
3163    public void setFullscreenRequiredForOrientationLock(boolean value) {
3164        mFullscreenRequiredForOrientationLock = value;
3165    }
3166
3167    @CalledByNative
3168    private boolean isFullscreenRequiredForOrientationLock() {
3169        return mFullscreenRequiredForOrientationLock;
3170    }
3171
3172    private native WebContents nativeGetWebContentsAndroid(long nativeContentViewCoreImpl);
3173
3174    private native void nativeOnJavaContentViewCoreDestroyed(long nativeContentViewCoreImpl);
3175
3176    private native void nativeLoadUrl(
3177            long nativeContentViewCoreImpl,
3178            String url,
3179            int loadUrlType,
3180            int transitionType,
3181            String referrerUrl,
3182            int referrerPolicy,
3183            int uaOverrideOption,
3184            String extraHeaders,
3185            byte[] postData,
3186            String baseUrlForDataUrl,
3187            String virtualUrlForDataUrl,
3188            boolean canLoadLocalResources,
3189            boolean isRendererInitiated);
3190
3191    private native String nativeGetURL(long nativeContentViewCoreImpl);
3192
3193    private native boolean nativeIsIncognito(long nativeContentViewCoreImpl);
3194
3195    private native void nativeSetFocus(long nativeContentViewCoreImpl, boolean focused);
3196
3197    private native void nativeSendOrientationChangeEvent(
3198            long nativeContentViewCoreImpl, int orientation);
3199
3200    // All touch events (including flings, scrolls etc) accept coordinates in physical pixels.
3201    private native boolean nativeOnTouchEvent(
3202            long nativeContentViewCoreImpl, MotionEvent event,
3203            long timeMs, int action, int pointerCount, int historySize, int actionIndex,
3204            float x0, float y0, float x1, float y1,
3205            int pointerId0, int pointerId1,
3206            float touchMajor0, float touchMajor1,
3207            float rawX, float rawY,
3208            int androidToolType0, int androidToolType1, int androidButtonState,
3209            boolean isTouchHandleEvent);
3210
3211    private native int nativeSendMouseMoveEvent(
3212            long nativeContentViewCoreImpl, long timeMs, float x, float y);
3213
3214    private native int nativeSendMouseWheelEvent(
3215            long nativeContentViewCoreImpl, long timeMs, float x, float y, float verticalAxis);
3216
3217    private native void nativeScrollBegin(
3218            long nativeContentViewCoreImpl, long timeMs, float x, float y, float hintX,
3219            float hintY);
3220
3221    private native void nativeScrollEnd(long nativeContentViewCoreImpl, long timeMs);
3222
3223    private native void nativeScrollBy(
3224            long nativeContentViewCoreImpl, long timeMs, float x, float y,
3225            float deltaX, float deltaY);
3226
3227    private native void nativeFlingStart(
3228            long nativeContentViewCoreImpl, long timeMs, float x, float y, float vx, float vy);
3229
3230    private native void nativeFlingCancel(long nativeContentViewCoreImpl, long timeMs);
3231
3232    private native void nativeSingleTap(
3233            long nativeContentViewCoreImpl, long timeMs, float x, float y);
3234
3235    private native void nativeDoubleTap(
3236            long nativeContentViewCoreImpl, long timeMs, float x, float y);
3237
3238    private native void nativeLongPress(
3239            long nativeContentViewCoreImpl, long timeMs, float x, float y);
3240
3241    private native void nativePinchBegin(
3242            long nativeContentViewCoreImpl, long timeMs, float x, float y);
3243
3244    private native void nativePinchEnd(long nativeContentViewCoreImpl, long timeMs);
3245
3246    private native void nativePinchBy(long nativeContentViewCoreImpl, long timeMs,
3247            float anchorX, float anchorY, float deltaScale);
3248
3249    private native void nativeSelectBetweenCoordinates(
3250            long nativeContentViewCoreImpl, float x1, float y1, float x2, float y2);
3251
3252    private native void nativeMoveCaret(long nativeContentViewCoreImpl, float x, float y);
3253
3254    private native void nativeHideTextHandles(long nativeContentViewCoreImpl);
3255
3256    private native void nativeResetGestureDetection(long nativeContentViewCoreImpl);
3257    private native void nativeSetDoubleTapSupportEnabled(
3258            long nativeContentViewCoreImpl, boolean enabled);
3259    private native void nativeSetMultiTouchZoomSupportEnabled(
3260            long nativeContentViewCoreImpl, boolean enabled);
3261
3262    private native void nativeSelectPopupMenuItems(long nativeContentViewCoreImpl, int[] indices);
3263
3264    private native void nativeClearHistory(long nativeContentViewCoreImpl);
3265
3266    private native void nativeEvaluateJavaScript(long nativeContentViewCoreImpl,
3267            String script, JavaScriptCallback callback, boolean startRenderer);
3268
3269    private native void nativePostMessageToFrame(long nativeContentViewCoreImpl, String frameId,
3270            String message, String sourceOrigin, String targetOrigin);
3271
3272    private native long nativeGetNativeImeAdapter(long nativeContentViewCoreImpl);
3273
3274    private native int nativeGetCurrentRenderProcessId(long nativeContentViewCoreImpl);
3275
3276    private native void nativeSetUseDesktopUserAgent(long nativeContentViewCoreImpl,
3277            boolean enabled, boolean reloadOnChange);
3278    private native boolean nativeGetUseDesktopUserAgent(long nativeContentViewCoreImpl);
3279
3280    private native void nativeClearSslPreferences(long nativeContentViewCoreImpl);
3281
3282    private native void nativeSetAllowJavascriptInterfacesInspection(
3283            long nativeContentViewCoreImpl, boolean allow);
3284
3285    private native void nativeAddJavascriptInterface(long nativeContentViewCoreImpl, Object object,
3286            String name, Class requiredAnnotation);
3287
3288    private native void nativeRemoveJavascriptInterface(long nativeContentViewCoreImpl,
3289            String name);
3290
3291    private native int nativeGetNavigationHistory(long nativeContentViewCoreImpl, Object context);
3292    private native void nativeGetDirectedNavigationHistory(long nativeContentViewCoreImpl,
3293            Object context, boolean isForward, int maxEntries);
3294    private native String nativeGetOriginalUrlForActiveNavigationEntry(
3295            long nativeContentViewCoreImpl);
3296
3297    private native void nativeWasResized(long nativeContentViewCoreImpl);
3298
3299    private native void nativeSetAccessibilityEnabled(
3300            long nativeContentViewCoreImpl, boolean enabled);
3301
3302    private native void nativeExtractSmartClipData(long nativeContentViewCoreImpl,
3303            int x, int y, int w, int h);
3304    private native void nativeSetBackgroundOpaque(long nativeContentViewCoreImpl, boolean opaque);
3305
3306    private native void nativeResumeResponseDeferredAtStart(
3307            long nativeContentViewCoreImpl);
3308    private native void nativeSetHasPendingNavigationTransitionForTesting(
3309            long nativeContentViewCoreImpl);
3310}
3311