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