ContentViewCore.java revision f66cc3b6f091903a97b577e413da0c2acd80d3b7
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
5package org.chromium.content.browser;
6
7import android.annotation.SuppressLint;
8import android.app.Activity;
9import android.app.SearchManager;
10import android.content.ClipboardManager;
11import android.content.ContentResolver;
12import android.content.Context;
13import android.content.Intent;
14import android.content.pm.PackageManager;
15import android.content.res.Configuration;
16import android.database.ContentObserver;
17import android.graphics.Bitmap;
18import android.graphics.Canvas;
19import android.graphics.Rect;
20import android.net.Uri;
21import android.os.Build;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.ResultReceiver;
25import android.os.SystemClock;
26import android.provider.Browser;
27import android.provider.Settings;
28import android.text.Editable;
29import android.text.Selection;
30import android.text.TextUtils;
31import android.util.Log;
32import android.util.Pair;
33import android.view.ActionMode;
34import android.view.HapticFeedbackConstants;
35import android.view.InputDevice;
36import android.view.KeyEvent;
37import android.view.MotionEvent;
38import android.view.View;
39import android.view.ViewGroup;
40import android.view.accessibility.AccessibilityEvent;
41import android.view.accessibility.AccessibilityManager;
42import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
43import android.view.accessibility.AccessibilityNodeInfo;
44import android.view.accessibility.AccessibilityNodeProvider;
45import android.view.inputmethod.EditorInfo;
46import android.view.inputmethod.InputConnection;
47import android.view.inputmethod.InputMethodManager;
48import android.widget.FrameLayout;
49
50import org.chromium.base.ApiCompatibilityUtils;
51import org.chromium.base.CalledByNative;
52import org.chromium.base.CommandLine;
53import org.chromium.base.JNINamespace;
54import org.chromium.base.ObserverList;
55import org.chromium.base.ObserverList.RewindableIterator;
56import org.chromium.base.TraceEvent;
57import org.chromium.base.VisibleForTesting;
58import org.chromium.content.R;
59import org.chromium.content.browser.ScreenOrientationListener.ScreenOrientationObserver;
60import org.chromium.content.browser.accessibility.AccessibilityInjector;
61import org.chromium.content.browser.accessibility.BrowserAccessibilityManager;
62import org.chromium.content.browser.input.AdapterInputConnection;
63import org.chromium.content.browser.input.GamepadList;
64import org.chromium.content.browser.input.ImeAdapter;
65import org.chromium.content.browser.input.ImeAdapter.AdapterInputConnectionFactory;
66import org.chromium.content.browser.input.InputMethodManagerWrapper;
67import org.chromium.content.browser.input.PastePopupMenu;
68import org.chromium.content.browser.input.PastePopupMenu.PastePopupMenuDelegate;
69import org.chromium.content.browser.input.PopupTouchHandleDrawable;
70import org.chromium.content.browser.input.PopupTouchHandleDrawable.PopupTouchHandleDrawableDelegate;
71import org.chromium.content.browser.input.SelectPopup;
72import org.chromium.content.browser.input.SelectPopupDialog;
73import org.chromium.content.browser.input.SelectPopupDropdown;
74import org.chromium.content.browser.input.SelectPopupItem;
75import org.chromium.content.browser.input.SelectionEventType;
76import org.chromium.content.common.ContentSwitches;
77import org.chromium.content_public.browser.GestureStateListener;
78import org.chromium.content_public.browser.JavaScriptCallback;
79import org.chromium.content_public.browser.WebContents;
80import org.chromium.ui.base.DeviceFormFactor;
81import org.chromium.ui.base.ViewAndroid;
82import org.chromium.ui.base.ViewAndroidDelegate;
83import org.chromium.ui.base.WindowAndroid;
84import org.chromium.ui.gfx.DeviceDisplayInfo;
85
86import java.lang.annotation.Annotation;
87import java.lang.reflect.Field;
88import java.util.ArrayList;
89import java.util.HashMap;
90import java.util.HashSet;
91import java.util.List;
92import 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")
100public 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                @Override
2258                public boolean isScrollInProgress() {
2259                    return ContentViewCore.this.isScrollInProgress();
2260                }
2261            };
2262        }
2263        return new PopupTouchHandleDrawable(mTouchHandleDelegate);
2264    }
2265
2266    @SuppressWarnings("unused")
2267    @CalledByNative
2268    private void onSelectionChanged(String text) {
2269        mLastSelectedText = text;
2270        getContentViewClient().onSelectionChanged(text);
2271    }
2272
2273    @SuppressWarnings("unused")
2274    @CalledByNative
2275    private void showPastePopupWithFeedback(int xDip, int yDip) {
2276        // TODO(jdduke): Remove this when there is a better signal that long press caused
2277        // showing of the paste popup. See http://crbug.com/150151.
2278        if (showPastePopup(xDip, yDip)) {
2279            mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2280        }
2281    }
2282
2283    private boolean showPastePopup(int xDip, int yDip) {
2284        if (!mHasInsertion || !canPaste()) return false;
2285        final float contentOffsetYPix = mRenderCoordinates.getContentOffsetYPix();
2286        getPastePopup().showAt(
2287            (int) mRenderCoordinates.fromDipToPix(xDip),
2288            (int) (mRenderCoordinates.fromDipToPix(yDip) + contentOffsetYPix));
2289        return true;
2290    }
2291
2292    private PastePopupMenu getPastePopup() {
2293        if (mPastePopupMenu == null) {
2294            mPastePopupMenu = new PastePopupMenu(getContainerView(),
2295                new PastePopupMenuDelegate() {
2296                    @Override
2297                    public void paste() {
2298                        mImeAdapter.paste();
2299                        hideTextHandles();
2300                    }
2301                });
2302        }
2303        return mPastePopupMenu;
2304    }
2305
2306    @VisibleForTesting
2307    public PastePopupMenu getPastePopupForTest() {
2308        return getPastePopup();
2309    }
2310
2311    private boolean canPaste() {
2312        if (!mFocusedNodeEditable) return false;
2313        return ((ClipboardManager) mContext.getSystemService(
2314                Context.CLIPBOARD_SERVICE)).hasPrimaryClip();
2315    }
2316
2317    @SuppressWarnings("unused")
2318    @CalledByNative
2319    private void onRenderProcessChange() {
2320        attachImeAdapter();
2321    }
2322
2323    /**
2324     * Attaches the native ImeAdapter object to the java ImeAdapter to allow communication via JNI.
2325     */
2326    public void attachImeAdapter() {
2327        if (mImeAdapter != null && mNativeContentViewCore != 0) {
2328            mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore));
2329        }
2330    }
2331
2332    /**
2333     * @see View#hasFocus()
2334     */
2335    @CalledByNative
2336    private boolean hasFocus() {
2337        // If the container view is not focusable, we consider it always focused from
2338        // Chromium's point of view.
2339        if (!mContainerView.isFocusable()) return true;
2340        return mContainerView.hasFocus();
2341    }
2342
2343    /**
2344     * Checks whether the ContentViewCore can be zoomed in.
2345     *
2346     * @return True if the ContentViewCore can be zoomed in.
2347     */
2348    // This method uses the term 'zoom' for legacy reasons, but relates
2349    // to what chrome calls the 'page scale factor'.
2350    public boolean canZoomIn() {
2351        final float zoomInExtent = mRenderCoordinates.getMaxPageScaleFactor()
2352                - mRenderCoordinates.getPageScaleFactor();
2353        return zoomInExtent > ZOOM_CONTROLS_EPSILON;
2354    }
2355
2356    /**
2357     * Checks whether the ContentViewCore can be zoomed out.
2358     *
2359     * @return True if the ContentViewCore can be zoomed out.
2360     */
2361    // This method uses the term 'zoom' for legacy reasons, but relates
2362    // to what chrome calls the 'page scale factor'.
2363    public boolean canZoomOut() {
2364        final float zoomOutExtent = mRenderCoordinates.getPageScaleFactor()
2365                - mRenderCoordinates.getMinPageScaleFactor();
2366        return zoomOutExtent > ZOOM_CONTROLS_EPSILON;
2367    }
2368
2369    /**
2370     * Zooms in the ContentViewCore by 25% (or less if that would result in
2371     * zooming in more than possible).
2372     *
2373     * @return True if there was a zoom change, false otherwise.
2374     */
2375    // This method uses the term 'zoom' for legacy reasons, but relates
2376    // to what chrome calls the 'page scale factor'.
2377    public boolean zoomIn() {
2378        if (!canZoomIn()) {
2379            return false;
2380        }
2381        return pinchByDelta(1.25f);
2382    }
2383
2384    /**
2385     * Zooms out the ContentViewCore by 20% (or less if that would result in
2386     * zooming out more than possible).
2387     *
2388     * @return True if there was a zoom change, false otherwise.
2389     */
2390    // This method uses the term 'zoom' for legacy reasons, but relates
2391    // to what chrome calls the 'page scale factor'.
2392    public boolean zoomOut() {
2393        if (!canZoomOut()) {
2394            return false;
2395        }
2396        return pinchByDelta(0.8f);
2397    }
2398
2399    /**
2400     * Resets the zoom factor of the ContentViewCore.
2401     *
2402     * @return True if there was a zoom change, false otherwise.
2403     */
2404    // This method uses the term 'zoom' for legacy reasons, but relates
2405    // to what chrome calls the 'page scale factor'.
2406    public boolean zoomReset() {
2407        // The page scale factor is initialized to mNativeMinimumScale when
2408        // the page finishes loading. Thus sets it back to mNativeMinimumScale.
2409        if (!canZoomOut()) return false;
2410        return pinchByDelta(
2411                mRenderCoordinates.getMinPageScaleFactor()
2412                        / mRenderCoordinates.getPageScaleFactor());
2413    }
2414
2415    /**
2416     * Simulate a pinch zoom gesture.
2417     *
2418     * @param delta the factor by which the current page scale should be multiplied by.
2419     * @return whether the gesture was sent.
2420     */
2421    public boolean pinchByDelta(float delta) {
2422        if (mNativeContentViewCore == 0) return false;
2423
2424        long timeMs = SystemClock.uptimeMillis();
2425        int xPix = getViewportWidthPix() / 2;
2426        int yPix = getViewportHeightPix() / 2;
2427
2428        nativePinchBegin(mNativeContentViewCore, timeMs, xPix, yPix);
2429        nativePinchBy(mNativeContentViewCore, timeMs, xPix, yPix, delta);
2430        nativePinchEnd(mNativeContentViewCore, timeMs);
2431
2432        return true;
2433    }
2434
2435    /**
2436     * Invokes the graphical zoom picker widget for this ContentView.
2437     */
2438    public void invokeZoomPicker() {
2439        mZoomControlsDelegate.invokeZoomPicker();
2440    }
2441
2442    /**
2443     * Enables or disables inspection of JavaScript objects added via
2444     * {@link #addJavascriptInterface(Object, String)} by means of Object.keys() method and
2445     * &quot;for .. in&quot; loop. Being able to inspect JavaScript objects is useful
2446     * when debugging hybrid Android apps, but can't be enabled for legacy applications due
2447     * to compatibility risks.
2448     *
2449     * @param allow Whether to allow JavaScript objects inspection.
2450     */
2451    public void setAllowJavascriptInterfacesInspection(boolean allow) {
2452        nativeSetAllowJavascriptInterfacesInspection(mNativeContentViewCore, allow);
2453    }
2454
2455    /**
2456     * Returns JavaScript interface objects previously injected via
2457     * {@link #addJavascriptInterface(Object, String)}.
2458     *
2459     * @return the mapping of names to interface objects and corresponding annotation classes
2460     */
2461    public Map<String, Pair<Object, Class>> getJavascriptInterfaces() {
2462        return mJavaScriptInterfaces;
2463    }
2464
2465    /**
2466     * This will mimic {@link #addPossiblyUnsafeJavascriptInterface(Object, String, Class)}
2467     * and automatically pass in {@link JavascriptInterface} as the required annotation.
2468     *
2469     * @param object The Java object to inject into the ContentViewCore's JavaScript context.  Null
2470     *               values are ignored.
2471     * @param name   The name used to expose the instance in JavaScript.
2472     */
2473    public void addJavascriptInterface(Object object, String name) {
2474        addPossiblyUnsafeJavascriptInterface(object, name, JavascriptInterface.class);
2475    }
2476
2477    /**
2478     * This method injects the supplied Java object into the ContentViewCore.
2479     * The object is injected into the JavaScript context of the main frame,
2480     * using the supplied name. This allows the Java object to be accessed from
2481     * JavaScript. Note that that injected objects will not appear in
2482     * JavaScript until the page is next (re)loaded. For example:
2483     * <pre> view.addJavascriptInterface(new Object(), "injectedObject");
2484     * view.loadData("<!DOCTYPE html><title></title>", "text/html", null);
2485     * view.loadUrl("javascript:alert(injectedObject.toString())");</pre>
2486     * <p><strong>IMPORTANT:</strong>
2487     * <ul>
2488     * <li> addJavascriptInterface() can be used to allow JavaScript to control
2489     * the host application. This is a powerful feature, but also presents a
2490     * security risk. Use of this method in a ContentViewCore containing
2491     * untrusted content could allow an attacker to manipulate the host
2492     * application in unintended ways, executing Java code with the permissions
2493     * of the host application. Use extreme care when using this method in a
2494     * ContentViewCore which could contain untrusted content. Particular care
2495     * should be taken to avoid unintentional access to inherited methods, such
2496     * as {@link Object#getClass()}. To prevent access to inherited methods,
2497     * pass an annotation for {@code requiredAnnotation}.  This will ensure
2498     * that only methods with {@code requiredAnnotation} are exposed to the
2499     * Javascript layer.  {@code requiredAnnotation} will be passed to all
2500     * subsequently injected Java objects if any methods return an object.  This
2501     * means the same restrictions (or lack thereof) will apply.  Alternatively,
2502     * {@link #addJavascriptInterface(Object, String)} can be called, which
2503     * automatically uses the {@link JavascriptInterface} annotation.
2504     * <li> JavaScript interacts with Java objects on a private, background
2505     * thread of the ContentViewCore. Care is therefore required to maintain
2506     * thread safety.</li>
2507     * </ul></p>
2508     *
2509     * @param object             The Java object to inject into the
2510     *                           ContentViewCore's JavaScript context. Null
2511     *                           values are ignored.
2512     * @param name               The name used to expose the instance in
2513     *                           JavaScript.
2514     * @param requiredAnnotation Restrict exposed methods to ones with this
2515     *                           annotation.  If {@code null} all methods are
2516     *                           exposed.
2517     *
2518     */
2519    public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
2520            Class<? extends Annotation> requiredAnnotation) {
2521        if (mNativeContentViewCore != 0 && object != null) {
2522            mJavaScriptInterfaces.put(name, new Pair<Object, Class>(object, requiredAnnotation));
2523            nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation);
2524        }
2525    }
2526
2527    /**
2528     * Removes a previously added JavaScript interface with the given name.
2529     *
2530     * @param name The name of the interface to remove.
2531     */
2532    public void removeJavascriptInterface(String name) {
2533        mJavaScriptInterfaces.remove(name);
2534        if (mNativeContentViewCore != 0) {
2535            nativeRemoveJavascriptInterface(mNativeContentViewCore, name);
2536        }
2537    }
2538
2539    /**
2540     * Return the current scale of the ContentView.
2541     * @return The current page scale factor.
2542     */
2543    @VisibleForTesting
2544    public float getScale() {
2545        return mRenderCoordinates.getPageScaleFactor();
2546    }
2547
2548    /**
2549     * If the view is ready to draw contents to the screen. In hardware mode,
2550     * the initialization of the surface texture may not occur until after the
2551     * view has been added to the layout. This method will return {@code true}
2552     * once the texture is actually ready.
2553     */
2554    public boolean isReady() {
2555        assert mWebContents != null;
2556        return mWebContents.isReady();
2557    }
2558
2559    @CalledByNative
2560    private void startContentIntent(String contentUrl) {
2561        getContentViewClient().onStartContentIntent(getContext(), contentUrl);
2562    }
2563
2564    @Override
2565    public void onAccessibilityStateChanged(boolean enabled) {
2566        setAccessibilityState(enabled);
2567    }
2568
2569    /**
2570     * Determines whether or not this ContentViewCore can handle this accessibility action.
2571     * @param action The action to perform.
2572     * @return Whether or not this action is supported.
2573     */
2574    public boolean supportsAccessibilityAction(int action) {
2575        return mAccessibilityInjector.supportsAccessibilityAction(action);
2576    }
2577
2578    /**
2579     * Attempts to perform an accessibility action on the web content.  If the accessibility action
2580     * cannot be processed, it returns {@code null}, allowing the caller to know to call the
2581     * super {@link View#performAccessibilityAction(int, Bundle)} method and use that return value.
2582     * Otherwise the return value from this method should be used.
2583     * @param action The action to perform.
2584     * @param arguments Optional action arguments.
2585     * @return Whether the action was performed or {@code null} if the call should be delegated to
2586     *         the super {@link View} class.
2587     */
2588    public boolean performAccessibilityAction(int action, Bundle arguments) {
2589        if (mAccessibilityInjector.supportsAccessibilityAction(action)) {
2590            return mAccessibilityInjector.performAccessibilityAction(action, arguments);
2591        }
2592
2593        return false;
2594    }
2595
2596    /**
2597     * Set the BrowserAccessibilityManager, used for native accessibility
2598     * (not script injection). This is only set when system accessibility
2599     * has been enabled.
2600     * @param manager The new BrowserAccessibilityManager.
2601     */
2602    public void setBrowserAccessibilityManager(BrowserAccessibilityManager manager) {
2603        mBrowserAccessibilityManager = manager;
2604    }
2605
2606    /**
2607     * Get the BrowserAccessibilityManager, used for native accessibility
2608     * (not script injection). This will return null when system accessibility
2609     * is not enabled.
2610     * @return This view's BrowserAccessibilityManager.
2611     */
2612    public BrowserAccessibilityManager getBrowserAccessibilityManager() {
2613        return mBrowserAccessibilityManager;
2614    }
2615
2616    /**
2617     * If native accessibility (not script injection) is enabled, and if this is
2618     * running on JellyBean or later, returns an AccessibilityNodeProvider that
2619     * implements native accessibility for this view. Returns null otherwise.
2620     * Lazily initializes native accessibility here if it's allowed.
2621     * @return The AccessibilityNodeProvider, if available, or null otherwise.
2622     */
2623    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
2624        if (mBrowserAccessibilityManager != null) {
2625            return mBrowserAccessibilityManager.getAccessibilityNodeProvider();
2626        }
2627
2628        if (mNativeAccessibilityAllowed &&
2629                !mNativeAccessibilityEnabled &&
2630                mNativeContentViewCore != 0 &&
2631                Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
2632            mNativeAccessibilityEnabled = true;
2633            nativeSetAccessibilityEnabled(mNativeContentViewCore, true);
2634        }
2635
2636        return null;
2637    }
2638
2639    /**
2640     * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
2641     */
2642    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2643        // Note: this is only used by the script-injecting accessibility code.
2644        mAccessibilityInjector.onInitializeAccessibilityNodeInfo(info);
2645    }
2646
2647    /**
2648     * @see View#onInitializeAccessibilityEvent(AccessibilityEvent)
2649     */
2650    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2651        // Note: this is only used by the script-injecting accessibility code.
2652        event.setClassName(this.getClass().getName());
2653
2654        // Identify where the top-left of the screen currently points to.
2655        event.setScrollX(mRenderCoordinates.getScrollXPixInt());
2656        event.setScrollY(mRenderCoordinates.getScrollYPixInt());
2657
2658        // The maximum scroll values are determined by taking the content dimensions and
2659        // subtracting off the actual dimensions of the ChromeView.
2660        int maxScrollXPix = Math.max(0, mRenderCoordinates.getMaxHorizontalScrollPixInt());
2661        int maxScrollYPix = Math.max(0, mRenderCoordinates.getMaxVerticalScrollPixInt());
2662        event.setScrollable(maxScrollXPix > 0 || maxScrollYPix > 0);
2663
2664        // Setting the maximum scroll values requires API level 15 or higher.
2665        final int sdkVersionRequiredToSetScroll = 15;
2666        if (Build.VERSION.SDK_INT >= sdkVersionRequiredToSetScroll) {
2667            event.setMaxScrollX(maxScrollXPix);
2668            event.setMaxScrollY(maxScrollYPix);
2669        }
2670    }
2671
2672    /**
2673     * Returns whether accessibility script injection is enabled on the device
2674     */
2675    public boolean isDeviceAccessibilityScriptInjectionEnabled() {
2676        try {
2677            // On JellyBean and higher, native accessibility is the default so script
2678            // injection is only allowed if enabled via a flag.
2679            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
2680                    !CommandLine.getInstance().hasSwitch(
2681                            ContentSwitches.ENABLE_ACCESSIBILITY_SCRIPT_INJECTION)) {
2682                return false;
2683            }
2684
2685            if (!mContentSettings.getJavaScriptEnabled()) {
2686                return false;
2687            }
2688
2689            int result = getContext().checkCallingOrSelfPermission(
2690                    android.Manifest.permission.INTERNET);
2691            if (result != PackageManager.PERMISSION_GRANTED) {
2692                return false;
2693            }
2694
2695            Field field = Settings.Secure.class.getField("ACCESSIBILITY_SCRIPT_INJECTION");
2696            field.setAccessible(true);
2697            String accessibilityScriptInjection = (String) field.get(null);
2698            ContentResolver contentResolver = getContext().getContentResolver();
2699
2700            if (mAccessibilityScriptInjectionObserver == null) {
2701                ContentObserver contentObserver = new ContentObserver(new Handler()) {
2702                    @Override
2703                    public void onChange(boolean selfChange, Uri uri) {
2704                        setAccessibilityState(mAccessibilityManager.isEnabled());
2705                    }
2706                };
2707                contentResolver.registerContentObserver(
2708                    Settings.Secure.getUriFor(accessibilityScriptInjection),
2709                    false,
2710                    contentObserver);
2711                mAccessibilityScriptInjectionObserver = contentObserver;
2712            }
2713
2714            return Settings.Secure.getInt(contentResolver, accessibilityScriptInjection, 0) == 1;
2715        } catch (NoSuchFieldException e) {
2716            // Do nothing, default to false.
2717        } catch (IllegalAccessException e) {
2718            // Do nothing, default to false.
2719        }
2720        return false;
2721    }
2722
2723    /**
2724     * Returns whether or not accessibility injection is being used.
2725     */
2726    public boolean isInjectingAccessibilityScript() {
2727        return mAccessibilityInjector.accessibilityIsAvailable();
2728    }
2729
2730    /**
2731     * Returns true if accessibility is on and touch exploration is enabled.
2732     */
2733    public boolean isTouchExplorationEnabled() {
2734        return mTouchExplorationEnabled;
2735    }
2736
2737    /**
2738     * Turns browser accessibility on or off.
2739     * If |state| is |false|, this turns off both native and injected accessibility.
2740     * Otherwise, if accessibility script injection is enabled, this will enable the injected
2741     * accessibility scripts. Native accessibility is enabled on demand.
2742     */
2743    public void setAccessibilityState(boolean state) {
2744        if (!state) {
2745            setInjectedAccessibility(false);
2746            mNativeAccessibilityAllowed = false;
2747            mTouchExplorationEnabled = false;
2748        } else {
2749            boolean useScriptInjection = isDeviceAccessibilityScriptInjectionEnabled();
2750            setInjectedAccessibility(useScriptInjection);
2751            mNativeAccessibilityAllowed = !useScriptInjection;
2752            mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
2753        }
2754    }
2755
2756    /**
2757     * Enable or disable injected accessibility features
2758     */
2759    public void setInjectedAccessibility(boolean enabled) {
2760        mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
2761        mAccessibilityInjector.setScriptEnabled(enabled);
2762    }
2763
2764    /**
2765     * Stop any TTS notifications that are currently going on.
2766     */
2767    public void stopCurrentAccessibilityNotifications() {
2768        mAccessibilityInjector.onPageLostFocus();
2769    }
2770
2771    /**
2772     * Return whether or not we should set accessibility focus on page load.
2773     */
2774    public boolean shouldSetAccessibilityFocusOnPageLoad() {
2775        return mShouldSetAccessibilityFocusOnPageLoad;
2776    }
2777
2778    /**
2779     * Sets whether or not we should set accessibility focus on page load.
2780     * This only applies if an accessibility service like TalkBack is running.
2781     * This is desirable behavior for a browser window, but not for an embedded
2782     * WebView.
2783     */
2784    public void setShouldSetAccessibilityFocusOnPageLoad(boolean on) {
2785        mShouldSetAccessibilityFocusOnPageLoad = on;
2786    }
2787
2788    /**
2789     * Inform WebKit that Fullscreen mode has been exited by the user.
2790     */
2791    public void exitFullscreen() {
2792        assert mWebContents != null;
2793        mWebContents.exitFullscreen();
2794    }
2795
2796    /**
2797     * Changes whether hiding the top controls is enabled.
2798     *
2799     * @param enableHiding Whether hiding the top controls should be enabled or not.
2800     * @param enableShowing Whether showing the top controls should be enabled or not.
2801     * @param animate Whether the transition should be animated or not.
2802     */
2803    public void updateTopControlsState(boolean enableHiding, boolean enableShowing,
2804            boolean animate) {
2805        assert mWebContents != null;
2806        mWebContents.updateTopControlsState(
2807                enableHiding, enableShowing, animate);
2808    }
2809
2810    /**
2811     * @return The cached copy of render positions and scales.
2812     */
2813    public RenderCoordinates getRenderCoordinates() {
2814        return mRenderCoordinates;
2815    }
2816
2817    @CalledByNative
2818    private static Rect createRect(int x, int y, int right, int bottom) {
2819        return new Rect(x, y, right, bottom);
2820    }
2821
2822    public void extractSmartClipData(int x, int y, int width, int height) {
2823        if (mNativeContentViewCore != 0) {
2824            x += mSmartClipOffsetX;
2825            y += mSmartClipOffsetY;
2826            nativeExtractSmartClipData(mNativeContentViewCore, x, y, width, height);
2827        }
2828    }
2829
2830    /**
2831     * Set offsets for smart clip.
2832     *
2833     * <p>This should be called if there is a viewport change introduced by,
2834     * e.g., show and hide of a location bar.
2835     *
2836     * @param offsetX Offset for X position.
2837     * @param offsetY Offset for Y position.
2838     */
2839    public void setSmartClipOffsets(int offsetX, int offsetY) {
2840        mSmartClipOffsetX = offsetX;
2841        mSmartClipOffsetY = offsetY;
2842    }
2843
2844    @CalledByNative
2845    private void onSmartClipDataExtracted(String text, String html, Rect clipRect) {
2846        // Translate the positions by the offsets introduced by location bar. Note that the
2847        // coordinates are in dp scale, and that this definitely has the potential to be
2848        // different from the offsets when extractSmartClipData() was called. However,
2849        // as long as OEM has a UI that consumes all the inputs and waits until the
2850        // callback is called, then there shouldn't be any difference.
2851        // TODO(changwan): once crbug.com/416432 is resolved, try to pass offsets as
2852        // separate params for extractSmartClipData(), and apply them not the new offset
2853        // values in the callback.
2854        final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
2855        final int offsetXInDp = (int) (mSmartClipOffsetX / deviceScale);
2856        final int offsetYInDp = (int) (mSmartClipOffsetY / deviceScale);
2857        clipRect.offset(-offsetXInDp, -offsetYInDp);
2858
2859        if (mSmartClipDataListener != null) {
2860            mSmartClipDataListener.onSmartClipDataExtracted(text, html, clipRect);
2861        }
2862    }
2863
2864    public void setSmartClipDataListener(SmartClipDataListener listener) {
2865        mSmartClipDataListener = listener;
2866    }
2867
2868    public void setBackgroundOpaque(boolean opaque) {
2869        if (mNativeContentViewCore != 0) {
2870            nativeSetBackgroundOpaque(mNativeContentViewCore, opaque);
2871        }
2872    }
2873
2874    /**
2875     * Offer a long press gesture to the embedding View, primarily for WebView compatibility.
2876     *
2877     * @return true if the embedder handled the event.
2878     */
2879    private boolean offerLongPressToEmbedder() {
2880        return mContainerView.performLongClick();
2881    }
2882
2883    /**
2884     * Reset scroll and fling accounting, notifying listeners as appropriate.
2885     * This is useful as a failsafe when the input stream may have been interruped.
2886     */
2887    private void resetScrollInProgress() {
2888        if (!isScrollInProgress()) return;
2889
2890        final boolean touchScrollInProgress = mTouchScrollInProgress;
2891        final int potentiallyActiveFlingCount = mPotentiallyActiveFlingCount;
2892
2893        mTouchScrollInProgress = false;
2894        mPotentiallyActiveFlingCount = 0;
2895
2896        if (touchScrollInProgress) updateGestureStateListener(GestureEventType.SCROLL_END);
2897        if (potentiallyActiveFlingCount > 0) updateGestureStateListener(GestureEventType.FLING_END);
2898    }
2899
2900    private native long nativeInit(long webContentsPtr,
2901            long viewAndroidPtr, long windowAndroidPtr, HashSet<Object> retainedObjectSet);
2902
2903    @CalledByNative
2904    private ContentVideoViewClient getContentVideoViewClient() {
2905        return getContentViewClient().getContentVideoViewClient();
2906    }
2907
2908    @CalledByNative
2909    private boolean shouldBlockMediaRequest(String url) {
2910        return getContentViewClient().shouldBlockMediaRequest(url);
2911    }
2912
2913    @CalledByNative
2914    private void onNativeFlingStopped() {
2915        // Note that mTouchScrollInProgress should normally be false at this
2916        // point, but we reset it anyway as another failsafe.
2917        mTouchScrollInProgress = false;
2918        if (mPotentiallyActiveFlingCount <= 0) return;
2919        mPotentiallyActiveFlingCount--;
2920        updateGestureStateListener(GestureEventType.FLING_END);
2921    }
2922
2923    @Override
2924    public void onScreenOrientationChanged(int orientation) {
2925        sendOrientationChangeEvent(orientation);
2926    }
2927
2928    public void resumeResponseDeferredAtStart() {
2929        assert mWebContents != null;
2930        mWebContents.resumeResponseDeferredAtStart();
2931    }
2932
2933    /**
2934     * Set whether the ContentViewCore requires the WebContents to be fullscreen in order to lock
2935     * the screen orientation.
2936     */
2937    public void setFullscreenRequiredForOrientationLock(boolean value) {
2938        mFullscreenRequiredForOrientationLock = value;
2939    }
2940
2941    @CalledByNative
2942    private boolean isFullscreenRequiredForOrientationLock() {
2943        return mFullscreenRequiredForOrientationLock;
2944    }
2945
2946    private native WebContents nativeGetWebContentsAndroid(long nativeContentViewCoreImpl);
2947
2948    private native void nativeOnJavaContentViewCoreDestroyed(long nativeContentViewCoreImpl);
2949
2950    private native void nativeSetFocus(long nativeContentViewCoreImpl, boolean focused);
2951
2952    private native void nativeSendOrientationChangeEvent(
2953            long nativeContentViewCoreImpl, int orientation);
2954
2955    // All touch events (including flings, scrolls etc) accept coordinates in physical pixels.
2956    private native boolean nativeOnTouchEvent(
2957            long nativeContentViewCoreImpl, MotionEvent event,
2958            long timeMs, int action, int pointerCount, int historySize, int actionIndex,
2959            float x0, float y0, float x1, float y1,
2960            int pointerId0, int pointerId1,
2961            float touchMajor0, float touchMajor1,
2962            float touchMinor0, float touchMinor1,
2963            float orientation0, float orientation1,
2964            float rawX, float rawY,
2965            int androidToolType0, int androidToolType1,
2966            int androidButtonState, int androidMetaState,
2967            boolean isTouchHandleEvent);
2968
2969    private native int nativeSendMouseMoveEvent(
2970            long nativeContentViewCoreImpl, long timeMs, float x, float y);
2971
2972    private native int nativeSendMouseWheelEvent(
2973            long nativeContentViewCoreImpl, long timeMs, float x, float y, float verticalAxis);
2974
2975    private native void nativeScrollBegin(
2976            long nativeContentViewCoreImpl, long timeMs, float x, float y, float hintX,
2977            float hintY);
2978
2979    private native void nativeScrollEnd(long nativeContentViewCoreImpl, long timeMs);
2980
2981    private native void nativeScrollBy(
2982            long nativeContentViewCoreImpl, long timeMs, float x, float y,
2983            float deltaX, float deltaY);
2984
2985    private native void nativeFlingStart(
2986            long nativeContentViewCoreImpl, long timeMs, float x, float y, float vx, float vy);
2987
2988    private native void nativeFlingCancel(long nativeContentViewCoreImpl, long timeMs);
2989
2990    private native void nativeSingleTap(
2991            long nativeContentViewCoreImpl, long timeMs, float x, float y);
2992
2993    private native void nativeDoubleTap(
2994            long nativeContentViewCoreImpl, long timeMs, float x, float y);
2995
2996    private native void nativeLongPress(
2997            long nativeContentViewCoreImpl, long timeMs, float x, float y);
2998
2999    private native void nativePinchBegin(
3000            long nativeContentViewCoreImpl, long timeMs, float x, float y);
3001
3002    private native void nativePinchEnd(long nativeContentViewCoreImpl, long timeMs);
3003
3004    private native void nativePinchBy(long nativeContentViewCoreImpl, long timeMs,
3005            float anchorX, float anchorY, float deltaScale);
3006
3007    private native void nativeSelectBetweenCoordinates(
3008            long nativeContentViewCoreImpl, float x1, float y1, float x2, float y2);
3009
3010    private native void nativeMoveCaret(long nativeContentViewCoreImpl, float x, float y);
3011
3012    private native void nativeHideTextHandles(long nativeContentViewCoreImpl);
3013
3014    private native void nativeResetGestureDetection(long nativeContentViewCoreImpl);
3015
3016    private native void nativeSetDoubleTapSupportEnabled(
3017            long nativeContentViewCoreImpl, boolean enabled);
3018
3019    private native void nativeSetMultiTouchZoomSupportEnabled(
3020            long nativeContentViewCoreImpl, boolean enabled);
3021
3022    private native void nativeSelectPopupMenuItems(long nativeContentViewCoreImpl,
3023            long nativeSelectPopupSourceFrame, int[] indices);
3024
3025
3026    private native void nativePostMessageToFrame(long nativeContentViewCoreImpl, String frameId,
3027            String message, String sourceOrigin, String targetOrigin);
3028
3029    private native long nativeGetNativeImeAdapter(long nativeContentViewCoreImpl);
3030
3031    private native int nativeGetCurrentRenderProcessId(long nativeContentViewCoreImpl);
3032
3033    private native void nativeSetAllowJavascriptInterfacesInspection(
3034            long nativeContentViewCoreImpl, boolean allow);
3035
3036    private native void nativeAddJavascriptInterface(long nativeContentViewCoreImpl, Object object,
3037            String name, Class requiredAnnotation);
3038
3039    private native void nativeRemoveJavascriptInterface(long nativeContentViewCoreImpl,
3040            String name);
3041
3042    private native void nativeWasResized(long nativeContentViewCoreImpl);
3043
3044    private native void nativeSetAccessibilityEnabled(
3045            long nativeContentViewCoreImpl, boolean enabled);
3046
3047    private native void nativeExtractSmartClipData(long nativeContentViewCoreImpl,
3048            int x, int y, int w, int h);
3049
3050    private native void nativeSetBackgroundOpaque(long nativeContentViewCoreImpl, boolean opaque);
3051}
3052