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