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