blob: 69cc4769317a3a6fb5712fda0f485c5eec708b33 [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.ui.base;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.graphics.Bitmap;
import android.os.Build;
import android.support.v4.view.MarginLayoutParamsCompat;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ObserverList;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.compat.ApiHelperForN;
import org.chromium.blink_public.web.WebCursorInfoType;
/**
* Class to acquire, position, and remove anchor views from the implementing View.
*/
@JNINamespace("ui")
public class ViewAndroidDelegate {
/**
* The current container view. This view can be updated with
* {@link #setContainerView()}.
*/
protected ViewGroup mContainerView;
// Temporary storage for use as a parameter of getLocationOnScreen().
private int[] mTemporaryContainerLocation = new int[2];
/**
* Notifies the observer when container view is updated.
*/
public interface ContainerViewObserver { void onUpdateContainerView(ViewGroup view); }
private ObserverList<ContainerViewObserver> mContainerViewObservers = new ObserverList<>();
/**
* Create and return a basic implementation of {@link ViewAndroidDelegate}.
* @param containerView {@link ViewGroup} to be used as a container view.
* @return a new instance of {@link ViewAndroidDelegate}.
*/
public static ViewAndroidDelegate createBasicDelegate(ViewGroup containerView) {
return new ViewAndroidDelegate(containerView);
}
protected ViewAndroidDelegate(ViewGroup containerView) {
mContainerView = containerView;
}
/**
* Adds observer that needs notification when container view is updated. Note that
* there is no {@code removObserver} since the added observers are all supposed to
* go away with this object together.
* @param observer {@link ContainerViewObserver} object. The object should have
* the lifetime same as this {@link ViewAndroidDelegate} to avoid gc issues.
*/
public final void addObserver(ContainerViewObserver observer) {
mContainerViewObservers.addObserver(observer);
}
/**
* Updates the current container view to which this class delegates.
*
* <p>WARNING: This method can also be used to replace the existing container view,
* but you should only do it if you have a very good reason to. Replacing the
* container view has been designed to support fullscreen in the Webview so it
* might not be appropriate for other use cases.
*
* <p>This method only performs a small part of replacing the container view and
* embedders are responsible for:
* <ul>
* <li>Disconnecting the old container view from all the references</li>
* <li>Updating the InternalAccessDelegate</li>
* <li>Reconciling the state with the new container view</li>
* <li>Tearing down and recreating the native GL rendering where appropriate</li>
* <li>etc.</li>
* </ul>
*/
public final void setContainerView(ViewGroup containerView) {
ViewGroup oldContainerView = mContainerView;
mContainerView = containerView;
updateAnchorViews(oldContainerView);
for (ContainerViewObserver observer : mContainerViewObservers) {
observer.onUpdateContainerView(containerView);
}
}
/**
* Transfer existing anchor views from the old to the new container view. Called by
* {@link setContainerView} only.
* @param oldContainerView Old container view just replaced by a new one.
*/
public void updateAnchorViews(ViewGroup oldContainerView) {}
/**
* @return An anchor view that can be used to anchor decoration views like Autofill popup.
*/
@CalledByNative
public View acquireView() {
ViewGroup containerView = getContainerView();
if (containerView == null || containerView.getParent() == null) return null;
View anchorView = new View(containerView.getContext());
containerView.addView(anchorView);
return anchorView;
}
/**
* Release given anchor view.
* @param anchorView The anchor view that needs to be released.
*/
@CalledByNative
public void removeView(View anchorView) {
ViewGroup containerView = getContainerView();
if (containerView == null) return;
containerView.removeView(anchorView);
}
/**
* Set the anchor view to specified position and size (all units in px).
* @param view The anchor view that needs to be positioned.
* @param x X coordinate of the top left corner of the anchor view.
* @param y Y coordinate of the top left corner of the anchor view.
* @param width The width of the anchor view.
* @param height The height of the anchor view.
*/
@CalledByNative
public void setViewPosition(
View view, float x, float y, float width, float height, int leftMargin, int topMargin) {
ViewGroup containerView = getContainerView();
if (containerView == null) return;
int widthInt = Math.round(width);
int heightInt = Math.round(height);
int startMargin;
if (ApiCompatibilityUtils.isLayoutRtl(containerView)) {
startMargin = containerView.getMeasuredWidth() - Math.round(width + x);
} else {
startMargin = leftMargin;
}
if (widthInt + startMargin > containerView.getWidth()) {
widthInt = containerView.getWidth() - startMargin;
}
LayoutParams lp = new LayoutParams(widthInt, heightInt);
MarginLayoutParamsCompat.setMarginStart(lp, startMargin);
lp.topMargin = topMargin;
view.setLayoutParams(lp);
}
/**
* Drag the text out of current view.
* @param text The dragged text.
* @param shadowImage The shadow image for the dragged text.
*/
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.N)
@CalledByNative
private boolean startDragAndDrop(String text, Bitmap shadowImage) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) return false;
ViewGroup containerView = getContainerView();
if (containerView == null) return false;
ImageView imageView = new ImageView(containerView.getContext());
imageView.setImageBitmap(shadowImage);
imageView.layout(0, 0, shadowImage.getWidth(), shadowImage.getHeight());
return ApiHelperForN.startDragAndDrop(containerView, ClipData.newPlainText(null, text),
new View.DragShadowBuilder(imageView), null, View.DRAG_FLAG_GLOBAL);
}
@VisibleForTesting
@CalledByNative
public void onCursorChangedToCustom(Bitmap customCursorBitmap, int hotspotX, int hotspotY) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PointerIcon icon =
ApiHelperForN.createPointerIcon(customCursorBitmap, hotspotX, hotspotY);
ApiHelperForN.setPointerIcon(getContainerView(), icon);
}
}
@VisibleForTesting
@CalledByNative
public void onCursorChanged(int cursorType) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return;
int pointerIconType = PointerIcon.TYPE_ARROW;
switch (cursorType) {
case WebCursorInfoType.NONE:
pointerIconType = PointerIcon.TYPE_NULL;
break;
case WebCursorInfoType.POINTER:
pointerIconType = PointerIcon.TYPE_ARROW;
break;
case WebCursorInfoType.CONTEXT_MENU:
pointerIconType = PointerIcon.TYPE_CONTEXT_MENU;
break;
case WebCursorInfoType.HAND:
pointerIconType = PointerIcon.TYPE_HAND;
break;
case WebCursorInfoType.HELP:
pointerIconType = PointerIcon.TYPE_HELP;
break;
case WebCursorInfoType.WAIT:
pointerIconType = PointerIcon.TYPE_WAIT;
break;
case WebCursorInfoType.CELL:
pointerIconType = PointerIcon.TYPE_CELL;
break;
case WebCursorInfoType.CROSS:
pointerIconType = PointerIcon.TYPE_CROSSHAIR;
break;
case WebCursorInfoType.I_BEAM:
pointerIconType = PointerIcon.TYPE_TEXT;
break;
case WebCursorInfoType.VERTICAL_TEXT:
pointerIconType = PointerIcon.TYPE_VERTICAL_TEXT;
break;
case WebCursorInfoType.ALIAS:
pointerIconType = PointerIcon.TYPE_ALIAS;
break;
case WebCursorInfoType.COPY:
pointerIconType = PointerIcon.TYPE_COPY;
break;
case WebCursorInfoType.NO_DROP:
pointerIconType = PointerIcon.TYPE_NO_DROP;
break;
case WebCursorInfoType.COLUMN_RESIZE:
pointerIconType = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.ROW_RESIZE:
pointerIconType = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.NORTH_EAST_SOUTH_WEST_RESIZE:
pointerIconType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.NORTH_WEST_SOUTH_EAST_RESIZE:
pointerIconType = PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.ZOOM_IN:
pointerIconType = PointerIcon.TYPE_ZOOM_IN;
break;
case WebCursorInfoType.ZOOM_OUT:
pointerIconType = PointerIcon.TYPE_ZOOM_OUT;
break;
case WebCursorInfoType.GRAB:
pointerIconType = PointerIcon.TYPE_GRAB;
break;
case WebCursorInfoType.GRABBING:
pointerIconType = PointerIcon.TYPE_GRABBING;
break;
// TODO(jaebaek): set types correctly
// after fixing http://crbug.com/584424.
case WebCursorInfoType.EAST_WEST_RESIZE:
pointerIconType = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.NORTH_SOUTH_RESIZE:
pointerIconType = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.EAST_RESIZE:
pointerIconType = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.NORTH_RESIZE:
pointerIconType = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.NORTH_EAST_RESIZE:
pointerIconType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.NORTH_WEST_RESIZE:
pointerIconType = PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.SOUTH_RESIZE:
pointerIconType = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.SOUTH_EAST_RESIZE:
pointerIconType = PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.SOUTH_WEST_RESIZE:
pointerIconType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.WEST_RESIZE:
pointerIconType = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
break;
case WebCursorInfoType.PROGRESS:
pointerIconType = PointerIcon.TYPE_WAIT;
break;
case WebCursorInfoType.NOT_ALLOWED:
pointerIconType = PointerIcon.TYPE_NO_DROP;
break;
case WebCursorInfoType.MOVE:
case WebCursorInfoType.MIDDLE_PANNING:
pointerIconType = PointerIcon.TYPE_ALL_SCROLL;
break;
case WebCursorInfoType.EAST_PANNING:
case WebCursorInfoType.NORTH_PANNING:
case WebCursorInfoType.NORTH_EAST_PANNING:
case WebCursorInfoType.NORTH_WEST_PANNING:
case WebCursorInfoType.SOUTH_PANNING:
case WebCursorInfoType.SOUTH_EAST_PANNING:
case WebCursorInfoType.SOUTH_WEST_PANNING:
case WebCursorInfoType.WEST_PANNING:
assert false : "These pointer icon types are not supported";
break;
case WebCursorInfoType.CUSTOM:
assert false : "onCursorChangedToCustom must be called instead";
break;
}
ViewGroup containerView = getContainerView();
PointerIcon icon = PointerIcon.getSystemIcon(containerView.getContext(), pointerIconType);
ApiHelperForN.setPointerIcon(containerView, icon);
}
/**
* Called whenever the background color of the page changes as notified by Blink.
* @param color The new ARGB color of the page background.
*/
@CalledByNative
public void onBackgroundColorChanged(int color) {}
/**
* Notify the client of the position of the top controls.
* @param topControlsOffsetY The Y offset of the top controls in physical pixels.
* @param topContentOffsetY The Y offset of the content in physical pixels.
*/
@CalledByNative
public void onTopControlsChanged(int topControlsOffsetY, int topContentOffsetY) {}
/**
* Notify the client of the position of the bottom controls.
* @param bottomControlsOffsetY The Y offset of the bottom controls in physical pixels.
* @param bottomContentOffsetY The Y offset of the content in physical pixels.
*/
@CalledByNative
public void onBottomControlsChanged(int bottomControlsOffsetY, int bottomContentOffsetY) {}
/**
* Returns the bottom system window inset in pixels. The system window inset represents the area
* of a full-screen window that is partially or fully obscured by the status bar, navigation
* bar, IME or other system windows.
* @return The bottom system window inset.
*/
@CalledByNative
public int getSystemWindowInsetBottom() {
return 0;
}
/**
* @return container view that the anchor views are added to. May be null.
*/
@CalledByNative
public final ViewGroup getContainerView() {
return mContainerView;
}
/**
* Return the X location of our container view.
*/
@CalledByNative
private int getXLocationOfContainerViewInWindow() {
ViewGroup container = getContainerView();
if (container == null) return 0;
container.getLocationInWindow(mTemporaryContainerLocation);
return mTemporaryContainerLocation[0];
}
/**
* Return the Y location of our container view.
*/
@CalledByNative
private int getYLocationOfContainerViewInWindow() {
ViewGroup container = getContainerView();
if (container == null) return 0;
container.getLocationInWindow(mTemporaryContainerLocation);
return mTemporaryContainerLocation[1];
}
/**
* Return the X location of our container view on screen.
*/
@CalledByNative
private int getXLocationOnScreen() {
ViewGroup container = getContainerView();
if (container == null) return 0;
container.getLocationOnScreen(mTemporaryContainerLocation);
return mTemporaryContainerLocation[0];
}
/**
* Return the Y location of our container view on screen.
*/
@CalledByNative
private int getYLocationOnScreen() {
ViewGroup container = getContainerView();
if (container == null) return 0;
container.getLocationOnScreen(mTemporaryContainerLocation);
return mTemporaryContainerLocation[1];
}
@CalledByNative
private void requestDisallowInterceptTouchEvent() {
ViewGroup container = getContainerView();
if (container != null) container.requestDisallowInterceptTouchEvent(true);
}
@CalledByNative
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void requestUnbufferedDispatch(MotionEvent event) {
ViewGroup container = getContainerView();
if (container != null) container.requestUnbufferedDispatch(event);
}
@CalledByNative
private boolean hasFocus() {
ViewGroup containerView = getContainerView();
return containerView == null ? false : ViewUtils.hasFocus(containerView);
}
@CalledByNative
private void requestFocus() {
ViewGroup containerView = getContainerView();
if (containerView != null) ViewUtils.requestFocus(containerView);
}
}