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