blob: 8b5446be8938df901b17b16777aed1e10cf3f389 [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer_private;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnSystemUiVisibilityChangeListener;
import android.view.ViewGroup.OnHierarchyChangeListener;
import android.view.ViewStructure;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.FrameLayout;
import org.chromium.base.ObserverList;
import org.chromium.base.TraceEvent;
import org.chromium.base.compat.ApiHelperForO;
import org.chromium.content_public.browser.ImeAdapter;
import org.chromium.content_public.browser.RenderCoordinates;
import org.chromium.content_public.browser.SmartClipProvider;
import org.chromium.content_public.browser.ViewEventSink;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsAccessibility;
import org.chromium.ui.base.EventForwarder;
import org.chromium.ui.base.EventOffsetHandler;
/**
* The containing view for {@link WebContents} that exists in the Android UI hierarchy and exposes
* the various {@link View} functionality to it.
*/
public class ContentView extends FrameLayout
implements ViewEventSink.InternalAccessDelegate, SmartClipProvider,
OnHierarchyChangeListener, OnSystemUiVisibilityChangeListener {
private static final String TAG = "ContentView";
// Default value to signal that the ContentView's size need not be overridden.
public static final int DEFAULT_MEASURE_SPEC =
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
private final WebContents mWebContents;
private final ObserverList<OnHierarchyChangeListener> mHierarchyChangeListeners =
new ObserverList<>();
private final ObserverList<OnSystemUiVisibilityChangeListener> mSystemUiChangeListeners =
new ObserverList<>();
private ViewEventSink mViewEventSink;
private EventForwarder mEventForwarder;
/**
* The desired size of this view in {@link MeasureSpec}. Set by the host
* when it should be different from that of the parent.
*/
private int mDesiredWidthMeasureSpec = DEFAULT_MEASURE_SPEC;
private int mDesiredHeightMeasureSpec = DEFAULT_MEASURE_SPEC;
private EventOffsetHandler mEventOffsetHandler;
/**
* Constructs a new ContentView for the appropriate Android version.
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param webContents The WebContents managing this content view.
* @return an instance of a ContentView.
*/
public static ContentView createContentView(
Context context, WebContents webContents, EventOffsetHandler eventOffsetHandler) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return new ContentViewApi23(context, webContents, eventOffsetHandler);
}
return new ContentView(context, webContents, eventOffsetHandler);
}
/**
* Creates an instance of a ContentView.
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param webContents A pointer to the WebContents managing this content view.
*/
ContentView(Context context, WebContents webContents, EventOffsetHandler eventOffsetHandler) {
super(context, null, android.R.attr.webViewStyle);
if (getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) {
setHorizontalScrollBarEnabled(false);
setVerticalScrollBarEnabled(false);
}
mWebContents = webContents;
mEventOffsetHandler = eventOffsetHandler;
setFocusable(true);
setFocusableInTouchMode(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ApiHelperForO.setDefaultFocusHighlightEnabled(this, false);
}
setOnHierarchyChangeListener(this);
setOnSystemUiVisibilityChangeListener(this);
}
protected WebContentsAccessibility getWebContentsAccessibility() {
return !mWebContents.isDestroyed() ? WebContentsAccessibility.fromWebContents(mWebContents)
: null;
}
@Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
WebContentsAccessibility wcax = getWebContentsAccessibility();
return wcax != null && wcax.supportsAction(action)
? wcax.performAction(action, arguments)
: super.performAccessibilityAction(action, arguments);
}
/**
* Set the desired size of the view. The values are in {@link MeasureSpec}.
* @param width The width of the content view.
* @param height The height of the content view.
*/
public void setDesiredMeasureSpec(int width, int height) {
mDesiredWidthMeasureSpec = width;
mDesiredHeightMeasureSpec = height;
}
@Override
public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
assert listener == this : "Use add/removeOnHierarchyChangeListener instead.";
super.setOnHierarchyChangeListener(listener);
}
/**
* Registers the given listener to receive state changes for the content view hierarchy.
* @param listener Listener to receive view hierarchy state changes.
*/
public void addOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
mHierarchyChangeListeners.addObserver(listener);
}
/**
* Unregisters the given listener from receiving state changes for the content view hierarchy.
* @param listener Listener that doesn't want to receive view hierarchy state changes.
*/
public void removeOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
mHierarchyChangeListeners.removeObserver(listener);
}
@Override
public void setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener listener) {
assert listener == this : "Use add/removeOnSystemUiVisibilityChangeListener instead.";
super.setOnSystemUiVisibilityChangeListener(listener);
}
/**
* Registers the given listener to receive system UI visibility state changes.
* @param listener Listener to receive system UI visibility state changes.
*/
public void addOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener listener) {
mSystemUiChangeListeners.addObserver(listener);
}
/**
* Unregisters the given listener from receiving system UI visibility state changes.
* @param listener Listener that doesn't want to receive state changes.
*/
public void removeOnSystemUiVisibilityChangeListener(
OnSystemUiVisibilityChangeListener listener) {
mSystemUiChangeListeners.removeObserver(listener);
}
// View.OnHierarchyChangeListener implementation
@Override
public void onChildViewRemoved(View parent, View child) {
for (OnHierarchyChangeListener listener : mHierarchyChangeListeners) {
listener.onChildViewRemoved(parent, child);
}
}
@Override
public void onChildViewAdded(View parent, View child) {
for (OnHierarchyChangeListener listener : mHierarchyChangeListeners) {
listener.onChildViewAdded(parent, child);
}
}
// View.OnHierarchyChangeListener implementation
@Override
public void onSystemUiVisibilityChange(int visibility) {
for (OnSystemUiVisibilityChangeListener listener : mSystemUiChangeListeners) {
listener.onSystemUiVisibilityChange(visibility);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mDesiredWidthMeasureSpec != DEFAULT_MEASURE_SPEC) {
widthMeasureSpec = mDesiredWidthMeasureSpec;
}
if (mDesiredHeightMeasureSpec != DEFAULT_MEASURE_SPEC) {
heightMeasureSpec = mDesiredHeightMeasureSpec;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public AccessibilityNodeProvider getAccessibilityNodeProvider() {
WebContentsAccessibility wcax = getWebContentsAccessibility();
AccessibilityNodeProvider provider =
(wcax != null) ? wcax.getAccessibilityNodeProvider() : null;
return (provider != null) ? provider : super.getAccessibilityNodeProvider();
}
// Needed by ViewEventSink.InternalAccessDelegate
@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
// Calls may come while/after WebContents is destroyed. See https://crbug.com/821750#c8.
if (mWebContents.isDestroyed()) return null;
return ImeAdapter.fromWebContents(mWebContents).onCreateInputConnection(outAttrs);
}
@Override
public boolean onCheckIsTextEditor() {
if (mWebContents.isDestroyed()) return false;
return ImeAdapter.fromWebContents(mWebContents).onCheckIsTextEditor();
}
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
try {
TraceEvent.begin("ContentView.onFocusChanged");
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
getViewEventSink().setHideKeyboardOnBlur(true);
getViewEventSink().onViewFocusChanged(gainFocus);
} finally {
TraceEvent.end("ContentView.onFocusChanged");
}
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
getViewEventSink().onWindowFocusChanged(hasWindowFocus);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return getEventForwarder().onKeyUp(keyCode, event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return isFocused() ? getEventForwarder().dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
}
@Override
public boolean onDragEvent(DragEvent event) {
return getEventForwarder().onDragEvent(event, this);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
boolean ret = super.onInterceptTouchEvent(e);
mEventOffsetHandler.onInterceptTouchEvent(e);
return ret;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean ret = getEventForwarder().onTouchEvent(event);
mEventOffsetHandler.onTouchEvent(event);
return ret;
}
@Override
public boolean onInterceptHoverEvent(MotionEvent e) {
mEventOffsetHandler.onInterceptHoverEvent(e);
return super.onInterceptHoverEvent(e);
}
@Override
public boolean dispatchDragEvent(DragEvent e) {
mEventOffsetHandler.onPreDispatchDragEvent(e.getAction());
boolean ret = super.dispatchDragEvent(e);
mEventOffsetHandler.onPostDispatchDragEvent(e.getAction());
return ret;
}
/**
* Mouse move events are sent on hover enter, hover move and hover exit.
* They are sent on hover exit because sometimes it acts as both a hover
* move and hover exit.
*/
@Override
public boolean onHoverEvent(MotionEvent event) {
boolean consumed = getEventForwarder().onHoverEvent(event);
WebContentsAccessibility wcax = getWebContentsAccessibility();
if (wcax != null && !wcax.isTouchExplorationEnabled()) super.onHoverEvent(event);
return consumed;
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
return getEventForwarder().onGenericMotionEvent(event);
}
private EventForwarder getEventForwarder() {
if (mEventForwarder == null) {
mEventForwarder = mWebContents.getEventForwarder();
}
return mEventForwarder;
}
private ViewEventSink getViewEventSink() {
if (mViewEventSink == null) mViewEventSink = ViewEventSink.from(mWebContents);
return mViewEventSink;
}
@Override
public boolean performLongClick() {
return false;
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
getViewEventSink().onConfigurationChanged(newConfig);
super.onConfigurationChanged(newConfig);
}
/**
* Currently the ContentView scrolling happens in the native side. In
* the Java view system, it is always pinned at (0, 0). scrollBy() and scrollTo()
* are overridden, so that View's mScrollX and mScrollY will be unchanged at
* (0, 0). This is critical for drawing ContentView correctly.
*/
@Override
public void scrollBy(int x, int y) {
getEventForwarder().scrollBy(x, y);
}
@Override
public void scrollTo(int x, int y) {
getEventForwarder().scrollTo(x, y);
}
@Override
protected int computeHorizontalScrollExtent() {
RenderCoordinates rc = RenderCoordinates.fromWebContents(mWebContents);
return rc != null ? rc.getLastFrameViewportWidthPixInt() : 0;
}
@Override
protected int computeHorizontalScrollOffset() {
RenderCoordinates rc = RenderCoordinates.fromWebContents(mWebContents);
return rc != null ? rc.getScrollXPixInt() : 0;
}
@Override
protected int computeHorizontalScrollRange() {
RenderCoordinates rc = RenderCoordinates.fromWebContents(mWebContents);
return rc != null ? rc.getContentWidthPixInt() : 0;
}
@Override
protected int computeVerticalScrollExtent() {
RenderCoordinates rc = RenderCoordinates.fromWebContents(mWebContents);
return rc != null ? rc.getLastFrameViewportHeightPixInt() : 0;
}
@Override
protected int computeVerticalScrollOffset() {
RenderCoordinates rc = RenderCoordinates.fromWebContents(mWebContents);
return rc != null ? rc.getScrollYPixInt() : 0;
}
@Override
protected int computeVerticalScrollRange() {
RenderCoordinates rc = RenderCoordinates.fromWebContents(mWebContents);
return rc != null ? rc.getContentHeightPixInt() : 0;
}
// End FrameLayout overrides.
@Override
public boolean awakenScrollBars(int startDelay, boolean invalidate) {
// For the default implementation of ContentView which draws the scrollBars on the native
// side, calling this function may get us into a bad state where we keep drawing the
// scrollBars, so disable it by always returning false.
if (getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) return false;
return super.awakenScrollBars(startDelay, invalidate);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewEventSink().onAttachedToWindow();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewEventSink().onDetachedFromWindow();
}
// Implements SmartClipProvider
@Override
public void extractSmartClipData(int x, int y, int width, int height) {
mWebContents.requestSmartClipExtract(x, y, width, height);
}
// Implements SmartClipProvider
@Override
public void setSmartClipResultHandler(final Handler resultHandler) {
mWebContents.setSmartClipResultHandler(resultHandler);
}
///////////////////////////////////////////////////////////////////////////////////////////////
// Start Implementation of ViewEventSink.InternalAccessDelegate //
///////////////////////////////////////////////////////////////////////////////////////////////
@Override
public boolean super_onKeyUp(int keyCode, KeyEvent event) {
return super.onKeyUp(keyCode, event);
}
@Override
public boolean super_dispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event);
}
@Override
public boolean super_onGenericMotionEvent(MotionEvent event) {
return super.onGenericMotionEvent(event);
}
///////////////////////////////////////////////////////////////////////////////////////////////
// End Implementation of ViewEventSink.InternalAccessDelegate //
///////////////////////////////////////////////////////////////////////////////////////////////
private static class ContentViewApi23 extends ContentView {
public ContentViewApi23(
Context context, WebContents webContents, EventOffsetHandler eventOffsetHandler) {
super(context, webContents, eventOffsetHandler);
}
@Override
public void onProvideVirtualStructure(final ViewStructure structure) {
WebContentsAccessibility wcax = getWebContentsAccessibility();
if (wcax != null) wcax.onProvideVirtualStructure(structure, false);
}
}
}