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