blob: 7b35641b34f399f26cd2d9e9633a17b047d363f1 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.android_webview.test;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.FrameLayout;
import org.chromium.android_webview.AwContents;
import org.chromium.android_webview.gfx.AwDrawFnImpl;
import org.chromium.android_webview.shell.ContextManager;
import org.chromium.base.Callback;
import org.chromium.content_public.browser.WebContents;
/**
* A View used for testing the AwContents internals.
*
* This class takes the place android.webkit.WebView would have in the production configuration.
*/
public class AwTestContainerView extends FrameLayout {
private static HandlerThread sRenderThread;
private static Handler sRenderThreadHandler;
private AwContents mAwContents;
private AwContents.InternalAccessDelegate mInternalAccessDelegate;
private HardwareView mHardwareView;
private boolean mAttachedContents;
private Rect mWindowVisibleDisplayFrameOverride;
private static final class WaitableEvent {
private final Object mLock = new Object();
private boolean mSignaled;
public void waitForEvent() {
synchronized (mLock) {
while (!mSignaled) {
try {
mLock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public void signal() {
synchronized (mLock) {
assert !mSignaled;
mSignaled = true;
mLock.notifyAll();
}
}
}
public static void installDrawFnFunctionTable(boolean useVulkan) {
AwDrawFnImpl.setDrawFnFunctionTable(ContextManager.getDrawFnFunctionTable(useVulkan));
}
private class HardwareView extends SurfaceView implements SurfaceHolder.Callback {
// Only accessed on UI thread.
private int mWidth;
private int mHeight;
private int mLastScrollX;
private int mLastScrollY;
private boolean mHaveSurface;
private Runnable mReadyToRenderCallback;
private SurfaceView mOverlaysSurfaceView;
// Only accessed on render thread.
private final ContextManager mContextManager;
public HardwareView(Context context) {
super(context);
if (sRenderThread == null) {
sRenderThread = new HandlerThread("RenderThreadInstr");
sRenderThread.start();
sRenderThreadHandler = new Handler(sRenderThread.getLooper());
}
mContextManager = new ContextManager();
getHolder().setFormat(PixelFormat.TRANSPARENT);
getHolder().addCallback(this);
// Main SurfaceView needs to be positioned above the media content.
setZOrderMediaOverlay(true);
mOverlaysSurfaceView = new SurfaceView(context);
mOverlaysSurfaceView.getHolder().addCallback(this);
// This SurfaceView is used to present media and must be positioned below main surface.
mOverlaysSurfaceView.setZOrderMediaOverlay(false);
}
public void readbackQuadrantColors(Callback<int[]> callback) {
sRenderThreadHandler.post(
() -> {
callback.onResult(
mContextManager.draw(
mWidth,
mHeight,
mLastScrollX,
mLastScrollY,
/* readbackQuadrants= */ true));
});
}
public boolean isReadyToRender() {
return mHaveSurface;
}
public void setReadyToRenderCallback(Runnable runner) {
assert !isReadyToRender() || runner == null;
mReadyToRenderCallback = runner;
}
public SurfaceView getOverlaysView() {
return mOverlaysSurfaceView;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (holder == mOverlaysSurfaceView.getHolder()) {
Surface surface = holder.getSurface();
sRenderThreadHandler.post(
() -> {
mContextManager.setOverlaysSurface(surface);
});
return;
}
mWidth = width;
mHeight = height;
mHaveSurface = true;
Surface surface = holder.getSurface();
sRenderThreadHandler.post(
() -> {
mContextManager.setSurface(surface, width, height);
});
if (mReadyToRenderCallback != null) {
mReadyToRenderCallback.run();
mReadyToRenderCallback = null;
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (holder == mOverlaysSurfaceView.getHolder()) {
WaitableEvent event = new WaitableEvent();
sRenderThreadHandler.post(
() -> {
mContextManager.setOverlaysSurface(null);
event.signal();
});
event.waitForEvent();
return;
}
mHaveSurface = false;
WaitableEvent event = new WaitableEvent();
sRenderThreadHandler.post(
() -> {
mContextManager.setSurface(null, 0, 0);
event.signal();
});
event.waitForEvent();
}
public void updateScroll(int x, int y) {
mLastScrollX = x;
mLastScrollY = y;
}
public void drawWebViewFunctor(int functor) {
if (!mHaveSurface) {
return;
}
WaitableEvent syncEvent = new WaitableEvent();
sRenderThreadHandler.post(
() -> {
drawOnRt(syncEvent, functor, mWidth, mHeight, mLastScrollX, mLastScrollY);
});
syncEvent.waitForEvent();
}
private void drawOnRt(
WaitableEvent syncEvent,
int functor,
int width,
int height,
int scrollX,
int scrollY) {
mContextManager.sync(functor, false);
syncEvent.signal();
mContextManager.draw(width, height, scrollX, scrollY, /* readbackQuadrants= */ false);
}
}
private static boolean sCreatedOnce;
private HardwareView createHardwareViewOnlyOnce(Context context) {
if (sCreatedOnce) return null;
sCreatedOnce = true;
return new HardwareView(context);
}
public AwTestContainerView(Context context, boolean allowHardwareAcceleration) {
super(context);
if (allowHardwareAcceleration) {
mHardwareView = createHardwareViewOnlyOnce(context);
}
if (isBackedByHardwareView()) {
addView(
mHardwareView.getOverlaysView(),
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
addView(
mHardwareView,
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
} else {
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
mInternalAccessDelegate = new InternalAccessAdapter();
setOverScrollMode(View.OVER_SCROLL_ALWAYS);
setFocusable(true);
setFocusableInTouchMode(true);
}
public void initialize(AwContents awContents) {
mAwContents = awContents;
}
public void setWindowVisibleDisplayFrameOverride(Rect rect) {
mWindowVisibleDisplayFrameOverride = rect;
}
@Override
public void getWindowVisibleDisplayFrame(Rect outRect) {
if (mWindowVisibleDisplayFrameOverride != null) {
outRect.set(mWindowVisibleDisplayFrameOverride);
} else {
super.getWindowVisibleDisplayFrame(outRect);
}
}
public boolean isBackedByHardwareView() {
return mHardwareView != null;
}
/** Use glReadPixels to get 4 pixels from center of 4 quadrants. Result is in row-major order. */
public void readbackQuadrantColors(Callback<int[]> callback) {
assert isBackedByHardwareView();
mHardwareView.readbackQuadrantColors(callback);
}
public WebContents getWebContents() {
return mAwContents.getWebContents();
}
public AwContents getAwContents() {
return mAwContents;
}
public AwContents.NativeDrawFunctorFactory getNativeDrawFunctorFactory() {
return new NativeDrawFunctorFactory();
}
public AwContents.InternalAccessDelegate getInternalAccessDelegate() {
return mInternalAccessDelegate;
}
public void destroy() {
mAwContents.destroy();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mAwContents.onConfigurationChanged(newConfig);
}
private void attachedContentsInternal() {
assert !mAttachedContents;
mAwContents.onAttachedToWindow();
mAttachedContents = true;
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
attachedContentsInternal();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mAttachedContents) {
mAwContents.onDetachedFromWindow();
mAttachedContents = false;
}
}
@Override
public void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
mAwContents.onFocusChanged(focused, direction, previouslyFocusedRect);
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return mAwContents.onCreateInputConnection(outAttrs);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mAwContents.onKeyUp(keyCode, event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return mAwContents.dispatchKeyEvent(event);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mAwContents.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
mAwContents.onSizeChanged(w, h, ow, oh);
}
@Override
public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
mAwContents.onContainerViewOverScrolled(scrollX, scrollY, clampedX, clampedY);
}
@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mAwContents != null) {
mAwContents.onContainerViewScrollChanged(l, t, oldl, oldt);
}
}
@Override
public void computeScroll() {
mAwContents.computeScroll();
}
@Override
public void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mAwContents.onVisibilityChanged(changedView, visibility);
}
@Override
public void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
mAwContents.onWindowVisibilityChanged(visibility);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
return mAwContents.onTouchEvent(ev);
}
@Override
public boolean onGenericMotionEvent(MotionEvent ev) {
super.onGenericMotionEvent(ev);
return mAwContents.onGenericMotionEvent(ev);
}
@Override
public boolean onHoverEvent(MotionEvent ev) {
super.onHoverEvent(ev);
return mAwContents.onHoverEvent(ev);
}
@Override
public void onDraw(Canvas canvas) {
if (isBackedByHardwareView()) {
mHardwareView.updateScroll(getScrollX(), getScrollY());
}
mAwContents.onDraw(canvas);
super.onDraw(canvas);
}
@Override
public AccessibilityNodeProvider getAccessibilityNodeProvider() {
AccessibilityNodeProvider provider = mAwContents.getAccessibilityNodeProvider();
return provider == null ? super.getAccessibilityNodeProvider() : provider;
}
@Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
return mAwContents.performAccessibilityAction(action, arguments);
}
@Override
public boolean onDragEvent(DragEvent event) {
return mAwContents.onDragEvent(event);
}
private class NativeDrawFunctorFactory implements AwContents.NativeDrawFunctorFactory {
@Override
public AwContents.NativeDrawGLFunctor createGLFunctor(long context) {
return null;
}
@Override
public AwDrawFnImpl.DrawFnAccess getDrawFnAccess() {
return new DrawFnAccess();
}
}
private class DrawFnAccess implements AwDrawFnImpl.DrawFnAccess {
@Override
public void drawWebViewFunctor(Canvas canvas, int functor) {
assert isBackedByHardwareView();
mHardwareView.drawWebViewFunctor(functor);
}
}
// TODO: AwContents could define a generic class that holds an implementation similar to
// the one below.
private class InternalAccessAdapter implements AwContents.InternalAccessDelegate {
@Override
public boolean super_onKeyUp(int keyCode, KeyEvent event) {
return AwTestContainerView.super.onKeyUp(keyCode, event);
}
@Override
public boolean super_dispatchKeyEvent(KeyEvent event) {
return AwTestContainerView.super.dispatchKeyEvent(event);
}
@Override
public boolean super_onGenericMotionEvent(MotionEvent event) {
return AwTestContainerView.super.onGenericMotionEvent(event);
}
@Override
public void super_onConfigurationChanged(Configuration newConfig) {
AwTestContainerView.super.onConfigurationChanged(newConfig);
}
@Override
public void super_scrollTo(int scrollX, int scrollY) {
// We're intentionally not calling super.scrollTo here to make testing easier.
AwTestContainerView.this.scrollTo(scrollX, scrollY);
if (isBackedByHardwareView()) {
// Undo the scroll that will be applied because of mHardwareView
// being a child of |this|.
mHardwareView.setTranslationX(scrollX);
mHardwareView.setTranslationY(scrollY);
}
}
@Override
public void overScrollBy(
int deltaX,
int deltaY,
int scrollX,
int scrollY,
int scrollRangeX,
int scrollRangeY,
int maxOverScrollX,
int maxOverScrollY,
boolean isTouchEvent) {
// We're intentionally not calling super.scrollTo here to make testing easier.
AwTestContainerView.this.overScrollBy(
deltaX,
deltaY,
scrollX,
scrollY,
scrollRangeX,
scrollRangeY,
maxOverScrollX,
maxOverScrollY,
isTouchEvent);
}
@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
AwTestContainerView.super.onScrollChanged(l, t, oldl, oldt);
}
@Override
public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
AwTestContainerView.super.setMeasuredDimension(measuredWidth, measuredHeight);
}
@Override
public int super_getScrollBarStyle() {
return AwTestContainerView.super.getScrollBarStyle();
}
@Override
public void super_startActivityForResult(Intent intent, int requestCode) {}
}
}