diff options
19 files changed, 754 insertions, 57 deletions
diff --git a/android_webview/browser/browser_view_renderer.cc b/android_webview/browser/browser_view_renderer.cc index f27a90a..b0b34da 100644 --- a/android_webview/browser/browser_view_renderer.cc +++ b/android_webview/browser/browser_view_renderer.cc @@ -104,6 +104,7 @@ BrowserViewRenderer::BrowserViewRenderer( attached_to_window_(false), hardware_enabled_(false), dip_scale_(0.0), + has_transparent_background_(false), page_scale_factor_(1.0), on_new_picture_enable_(false), clear_view_(false), @@ -254,6 +255,7 @@ bool BrowserViewRenderer::OnDrawHardware(jobject java_canvas) { draw_gl_input->scroll_offset = last_on_draw_scroll_offset_; draw_gl_input->width = width_; draw_gl_input->height = height_; + draw_gl_input->has_transparent_background = has_transparent_background_; gfx::Transform transform; gfx::Size surface_size(width_, height_); @@ -475,6 +477,10 @@ void BrowserViewRenderer::SetDipScale(float dip_scale) { CHECK(dip_scale_ > 0); } +void BrowserViewRenderer::SetHasTransparentBackground(bool transparent) { + has_transparent_background_ = transparent; +} + gfx::Vector2d BrowserViewRenderer::max_scroll_offset() const { DCHECK_GT(dip_scale_, 0); return gfx::ToCeiledVector2d(gfx::ScaleVector2d( @@ -725,6 +731,8 @@ std::string BrowserViewRenderer::ToString(AwDrawGLInfo* draw_info) const { base::StringAppendF(&str, "view_visible: %d ", view_visible_); base::StringAppendF(&str, "window_visible: %d ", window_visible_); base::StringAppendF(&str, "dip_scale: %f ", dip_scale_); + base::StringAppendF(&str, "has_transparent_background_: %d", + has_transparent_background_); base::StringAppendF(&str, "page_scale_factor: %f ", page_scale_factor_); base::StringAppendF(&str, "compositor_needs_continuous_invalidate: %d ", diff --git a/android_webview/browser/browser_view_renderer.h b/android_webview/browser/browser_view_renderer.h index 378eeb6..876f563 100644 --- a/android_webview/browser/browser_view_renderer.h +++ b/android_webview/browser/browser_view_renderer.h @@ -99,6 +99,9 @@ class BrowserViewRenderer : public content::SynchronousCompositorClient, // Sets the scale for logical<->physical pixel conversions. void SetDipScale(float dip_scale); + // Sets the background of the layer tree to transparent. + void SetHasTransparentBackground(bool transparent); + // Set the root layer scroll offset to |new_value|. void ScrollTo(gfx::Vector2d new_value); @@ -192,6 +195,7 @@ class BrowserViewRenderer : public content::SynchronousCompositorClient, bool attached_to_window_; bool hardware_enabled_; float dip_scale_; + bool has_transparent_background_; float page_scale_factor_; bool on_new_picture_enable_; bool clear_view_; diff --git a/android_webview/browser/hardware_renderer.cc b/android_webview/browser/hardware_renderer.cc index 3f37396..7d0f5c0 100644 --- a/android_webview/browser/hardware_renderer.cc +++ b/android_webview/browser/hardware_renderer.cc @@ -166,6 +166,8 @@ void HardwareRenderer::DrawGL(bool stencil_enabled, bool size_changed = frame_size != frame_size_; frame_size_ = frame_size; scroll_offset_ = input->scroll_offset; + layer_tree_host_->set_has_transparent_background( + input->has_transparent_background); if (!frame_provider_ || size_changed) { if (delegated_layer_) { diff --git a/android_webview/browser/shared_renderer_state.cc b/android_webview/browser/shared_renderer_state.cc index 8845b76..36fece5 100644 --- a/android_webview/browser/shared_renderer_state.cc +++ b/android_webview/browser/shared_renderer_state.cc @@ -10,7 +10,8 @@ namespace android_webview { -DrawGLInput::DrawGLInput() : width(0), height(0) { +DrawGLInput::DrawGLInput() : width(0), height(0), + has_transparent_background(false) { } DrawGLInput::~DrawGLInput() { diff --git a/android_webview/browser/shared_renderer_state.h b/android_webview/browser/shared_renderer_state.h index 89554ac..9a458e4 100644 --- a/android_webview/browser/shared_renderer_state.h +++ b/android_webview/browser/shared_renderer_state.h @@ -31,6 +31,7 @@ struct DrawGLInput { int width; int height; cc::CompositorFrame frame; + bool has_transparent_background; DrawGLInput(); ~DrawGLInput(); diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentViewClient.java b/android_webview/java/src/org/chromium/android_webview/AwContentViewClient.java index 55afba5..91c583f 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContentViewClient.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContentViewClient.java @@ -9,10 +9,13 @@ import android.view.KeyEvent; import android.view.View; import android.webkit.URLUtil; import android.webkit.WebChromeClient; +import android.widget.FrameLayout; +import org.chromium.base.CommandLine; import org.chromium.content.browser.ContentVideoView; import org.chromium.content.browser.ContentVideoViewClient; import org.chromium.content.browser.ContentViewClient; +import org.chromium.content.common.ContentSwitches; /** * ContentViewClient implementation for WebView @@ -30,12 +33,44 @@ public class AwContentViewClient extends ContentViewClient { contentVideoView.exitFullscreen(false); } }; - mAwContentsClient.onShowCustomView(view, cb); + // TODO(igsolla): remove the legacy path (kept as a fallback if things go awry). + if (!areHtmlControlsEnabled()) { + onShowCustomViewLegacy(view, cb); + } else { + onShowCustomView(view, cb); + } return true; } + private void onShowCustomViewLegacy(View view, WebChromeClient.CustomViewCallback cb) { + mAwContentsClient.onShowCustomView(view, cb); + } + + private void onShowCustomView(View view, WebChromeClient.CustomViewCallback cb) { + final FrameLayout viewGroup = new FrameLayout(mContext); + viewGroup.addView(view); + viewGroup.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewDetachedFromWindow(View v) { + // Intentional no-op (see onDestroyContentVideoView). + } + + @Override + public void onViewAttachedToWindow(View v) { + if (mAwContents.isFullScreen()) { + return; + } + viewGroup.addView(mAwContents.enterFullScreen()); + } + }); + mAwContentsClient.onShowCustomView(viewGroup, cb); + } + @Override public void onDestroyContentVideoView() { + if (areHtmlControlsEnabled()) { + mAwContents.exitFullScreen(); + } mAwContentsClient.onHideCustomView(); } @@ -47,10 +82,16 @@ public class AwContentViewClient extends ContentViewClient { private AwContentsClient mAwContentsClient; private AwSettings mAwSettings; + private AwContents mAwContents; + private Context mContext; - public AwContentViewClient(AwContentsClient awContentsClient, AwSettings awSettings) { + public AwContentViewClient( + AwContentsClient awContentsClient, AwSettings awSettings, AwContents awContents, + Context context) { mAwContentsClient = awContentsClient; mAwSettings = awSettings; + mAwContents = awContents; + mContext = context; } @Override @@ -84,4 +125,9 @@ public class AwContentViewClient extends ContentViewClient { return mAwSettings != null ? mAwSettings.getBlockNetworkLoads() && URLUtil.isNetworkUrl(url) : true; } + + private static boolean areHtmlControlsEnabled() { + return !CommandLine.getInstance().hasSwitch( + ContentSwitches.DISABLE_OVERLAY_FULLSCREEN_VIDEO_SUBTITLE); + } } diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java index 380cc6e..e66afec 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContents.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java @@ -175,16 +175,17 @@ public class AwContents { private long mNativeAwContents; private final AwBrowserContext mBrowserContext; - private final ViewGroup mContainerView; + private ViewGroup mContainerView; + private final AwLayoutChangeListener mLayoutChangeListener; private final Context mContext; private ContentViewCore mContentViewCore; private final AwContentsClient mContentsClient; private final AwContentViewClient mContentViewClient; private final AwContentsClientBridge mContentsClientBridge; - private final AwWebContentsDelegate mWebContentsDelegate; + private final AwWebContentsDelegateAdapter mWebContentsDelegate; private final AwContentsIoThreadClient mIoThreadClient; private final InterceptNavigationDelegateImpl mInterceptNavigationDelegate; - private final InternalAccessDelegate mInternalAccessAdapter; + private InternalAccessDelegate mInternalAccessAdapter; private final NativeGLDelegate mNativeGLDelegate; private final AwLayoutSizer mLayoutSizer; private final AwZoomControls mZoomControls; @@ -230,7 +231,8 @@ public class AwContents { private AwPdfExporter mAwPdfExporter; - private final AwViewMethods mAwViewMethods; + private AwViewMethods mAwViewMethods; + private final FullScreenTransitionsState mFullScreenTransitionsState; // This flag indicates that ShouldOverrideUrlNavigation should be posted // through the resourcethrottle. This is only used for popup windows. @@ -247,6 +249,52 @@ public class AwContents { } } + /** + * A class that stores the state needed to enter and exit fullscreen. + */ + private static class FullScreenTransitionsState { + private final ViewGroup mInitialContainerView; + private final InternalAccessDelegate mInitialInternalAccessAdapter; + private final AwViewMethods mInitialAwViewMethods; + private FullScreenView mFullScreenView; + + private FullScreenTransitionsState(ViewGroup initialContainerView, + InternalAccessDelegate initialInternalAccessAdapter, + AwViewMethods initialAwViewMethods) { + mInitialContainerView = initialContainerView; + mInitialInternalAccessAdapter = initialInternalAccessAdapter; + mInitialAwViewMethods = initialAwViewMethods; + } + + private void enterFullScreen(FullScreenView fullScreenView) { + mFullScreenView = fullScreenView; + } + + private void exitFullScreen() { + mFullScreenView = null; + } + + private boolean isFullScreen() { + return mFullScreenView != null; + } + + private ViewGroup getInitialContainerView() { + return mInitialContainerView; + } + + private InternalAccessDelegate getInitialInternalAccessDelegate() { + return mInitialInternalAccessAdapter; + } + + private AwViewMethods getInitialAwViewMethods() { + return mInitialAwViewMethods; + } + + private FullScreenView getFullScreenView() { + return mFullScreenView; + } + } + // Reference to the active mNativeAwContents pointer while it is active use // (ie before it is destroyed). private CleanupReference mCleanupReference; @@ -510,7 +558,9 @@ public class AwContents { mNativeGLDelegate = nativeGLDelegate; mContentsClient = contentsClient; mAwViewMethods = new AwViewMethodsImpl(); - mContentViewClient = new AwContentViewClient(contentsClient, settings); + mFullScreenTransitionsState = new FullScreenTransitionsState( + mContainerView, mInternalAccessAdapter, mAwViewMethods); + mContentViewClient = new AwContentViewClient(contentsClient, settings, this, mContext); mLayoutSizer = dependencyFactory.createLayoutSizer(); mSettings = settings; mDIPScale = DeviceDisplayInfo.create(mContext).getDIPScale(); @@ -544,12 +594,12 @@ public class AwContents { setOverScrollMode(mContainerView.getOverScrollMode()); setScrollBarStyle(mInternalAccessAdapter.super_getScrollBarStyle()); - mContainerView.addOnLayoutChangeListener(new AwLayoutChangeListener()); + mLayoutChangeListener = new AwLayoutChangeListener(); + mContainerView.addOnLayoutChangeListener(mLayoutChangeListener); setNewAwContents(nativeInit(mBrowserContext)); - onVisibilityChanged(mContainerView, mContainerView.getVisibility()); - onWindowVisibilityChanged(mContainerView.getWindowVisibility()); + onContainerViewChanged(); } private static ContentViewCore createAndInitializeContentViewCore(ViewGroup containerView, @@ -568,8 +618,112 @@ public class AwContents { return contentViewCore; } + boolean isFullScreen() { + return mFullScreenTransitionsState.isFullScreen(); + } + + /** + * Transitions this {@link AwContents} to fullscreen mode and returns the + * {@link View} where the contents will be drawn while in fullscreen. + */ + View enterFullScreen() { + assert !isFullScreen(); + + // Detach to tear down the GL functor if this is still associated with the old + // container view. It will be recreated during the next call to onDraw attached to + // the new container view. + onDetachedFromWindow(); + + // In fullscreen mode FullScreenView owns the AwViewMethodsImpl and AwContents + // a NullAwViewMethods. + FullScreenView fullScreenView = new FullScreenView(mContext, mAwViewMethods); + mFullScreenTransitionsState.enterFullScreen(fullScreenView); + mAwViewMethods = new NullAwViewMethods(this, mInternalAccessAdapter, mContainerView); + mContainerView.removeOnLayoutChangeListener(mLayoutChangeListener); + fullScreenView.addOnLayoutChangeListener(mLayoutChangeListener); + + // Associate this AwContents with the FullScreenView. + setInternalAccessAdapter(fullScreenView.getInternalAccessAdapter()); + setContainerView(fullScreenView); + + // Make the background transparent so that the ContentVideoView is visible + // behind the FullScreenView. + nativeSetHasTransparentBackground(mNativeAwContents, true); + return fullScreenView; + } + /** - * Common initialization routine for adopting a native AwContents instance into this + * Returns this {@link AwContents} to embedded mode, where the {@link AwContents} are drawn + * in the WebView. + */ + void exitFullScreen() { + assert isFullScreen(); + + // Detach to tear down the GL functor if this is still associated with the old + // container view. It will be recreated during the next call to onDraw attached to + // the new container view. + // NOTE: we cannot use mAwViewMethods here because its type is NullAwViewMethods. + AwViewMethods awViewMethodsImpl = mFullScreenTransitionsState.getInitialAwViewMethods(); + awViewMethodsImpl.onDetachedFromWindow(); + + // Swap the view delegates. In embedded mode the FullScreenView owns a + // NullAwViewMethods and AwContents the AwViewMethodsImpl. + FullScreenView fullscreenView = mFullScreenTransitionsState.getFullScreenView(); + fullscreenView.setAwViewMethods(new NullAwViewMethods( + this, fullscreenView.getInternalAccessAdapter(), fullscreenView)); + mAwViewMethods = awViewMethodsImpl; + ViewGroup initialContainerView = mFullScreenTransitionsState.getInitialContainerView(); + initialContainerView.addOnLayoutChangeListener(mLayoutChangeListener); + fullscreenView.removeOnLayoutChangeListener(mLayoutChangeListener); + + // Re-associate this AwContents with the WebView. + setInternalAccessAdapter(mFullScreenTransitionsState.getInitialInternalAccessDelegate()); + setContainerView(initialContainerView); + + nativeSetHasTransparentBackground(mNativeAwContents, false); + mFullScreenTransitionsState.exitFullScreen(); + } + + private void setInternalAccessAdapter(InternalAccessDelegate internalAccessAdapter) { + mInternalAccessAdapter = internalAccessAdapter; + mContentViewCore.setContainerViewInternals(mInternalAccessAdapter); + } + + private void setContainerView(ViewGroup newContainerView) { + mContainerView = newContainerView; + mContentViewCore.setContainerView(mContainerView); + if (mAwPdfExporter != null) { + mAwPdfExporter.setContainerView(mContainerView); + } + mWebContentsDelegate.setContainerView(mContainerView); + + onContainerViewChanged(); + } + + /** + * Reconciles the state of this AwContents object with the state of the new container view. + */ + private void onContainerViewChanged() { + // NOTE: mAwViewMethods is used by the old container view, the WebView, so it might refer + // to a NullAwViewMethods when in fullscreen. To ensure that the state is reconciled with + // the new container view correctly, we bypass mAwViewMethods and use the real + // implementation directly. + AwViewMethods awViewMethodsImpl = mFullScreenTransitionsState.getInitialAwViewMethods(); + awViewMethodsImpl.onVisibilityChanged(mContainerView, mContainerView.getVisibility()); + awViewMethodsImpl.onWindowVisibilityChanged(mContainerView.getWindowVisibility()); + if (mContainerView.isAttachedToWindow()) { + awViewMethodsImpl.onAttachedToWindow(); + } else { + awViewMethodsImpl.onDetachedFromWindow(); + } + awViewMethodsImpl.onSizeChanged( + mContainerView.getWidth(), mContainerView.getHeight(), 0, 0); + awViewMethodsImpl.onWindowFocusChanged(mContainerView.hasWindowFocus()); + awViewMethodsImpl.onFocusChanged(mContainerView.hasFocus(), 0, null); + mContainerView.requestLayout(); + } + + /* Common initialization routine for adopting a native AwContents instance into this * java instance. * * TAKE CARE! This method can get called multiple times per java instance. Code accordingly. @@ -1969,7 +2123,7 @@ public class AwContents { // -------------------------------------------------------------------------------------------- // This is the AwViewMethods implementation that does real work. The AwViewMethodsImpl is - // hooked up to the WebView in embedded mode and to the FullscreenView in fullscreen mode, + // hooked up to the WebView in embedded mode and to the FullScreenView in fullscreen mode, // but not to both at the same time. private class AwViewMethodsImpl implements AwViewMethods { private int mLayerType = View.LAYER_TYPE_NONE; @@ -2260,6 +2414,8 @@ public class AwContents { private native long nativeReleasePopupAwContents(long nativeAwContents); private native void nativeFocusFirstNode(long nativeAwContents); private native void nativeSetBackgroundColor(long nativeAwContents, int color); + private native void nativeSetHasTransparentBackground( + long nativeAwContents, boolean transparent); private native long nativeGetAwDrawGLViewContext(long nativeAwContents); private native long nativeCapturePicture(long nativeAwContents, int width, int height); diff --git a/android_webview/java/src/org/chromium/android_webview/AwPdfExporter.java b/android_webview/java/src/org/chromium/android_webview/AwPdfExporter.java index d8b8e37..f9a02ce 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwPdfExporter.java +++ b/android_webview/java/src/org/chromium/android_webview/AwPdfExporter.java @@ -31,9 +31,14 @@ public class AwPdfExporter { // Maintain a reference to the top level object (i.e. WebView) since in a common // use case (offscreen webview) application may expect the framework's print manager // to own the Webview (via PrintDocumentAdapter). - private final ViewGroup mContainerView; + // NOTE: it looks unused, but please do not remove this reference. + private ViewGroup mContainerView; AwPdfExporter(ViewGroup containerView) { + setContainerView(containerView); + } + + public void setContainerView(ViewGroup containerView) { mContainerView = containerView; } diff --git a/android_webview/java/src/org/chromium/android_webview/AwViewMethods.java b/android_webview/java/src/org/chromium/android_webview/AwViewMethods.java index 070e2e3..e5031ad 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwViewMethods.java +++ b/android_webview/java/src/org/chromium/android_webview/AwViewMethods.java @@ -18,7 +18,7 @@ import android.view.inputmethod.InputConnection; * An interface that defines a subset of the {@link View} functionality. * * <p>This interface allows us to hook up drawing and input related methods to the - * {@link AwContents}'s consumer in embedded mode, and to the {@link FullscreenView} + * {@link AwContents}'s consumer in embedded mode, and to the {@link FullScreenView} * in fullscreen mode. */ interface AwViewMethods { diff --git a/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java b/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java index 7e085662..2edef84 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java +++ b/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java @@ -24,11 +24,15 @@ class AwWebContentsDelegateAdapter extends AwWebContentsDelegate { private static final String TAG = "AwWebContentsDelegateAdapter"; final AwContentsClient mContentsClient; - final View mContainerView; + View mContainerView; public AwWebContentsDelegateAdapter(AwContentsClient contentsClient, View containerView) { mContentsClient = contentsClient; + setContainerView(containerView); + } + + public void setContainerView(View containerView) { mContainerView = containerView; } diff --git a/android_webview/java/src/org/chromium/android_webview/FullScreenView.java b/android_webview/java/src/org/chromium/android_webview/FullScreenView.java new file mode 100644 index 0000000..fc47d26 --- /dev/null +++ b/android_webview/java/src/org/chromium/android_webview/FullScreenView.java @@ -0,0 +1,214 @@ +// Copyright 2014 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.android_webview; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.widget.AbsoluteLayout; + +/** + * A view that is used to render the web contents in fullscreen mode, ie. + * html controls and subtitles, over the {@link ContentVideoView}. + */ +public class FullScreenView extends AbsoluteLayout { + + private AwViewMethods mAwViewMethods; + private InternalAccessAdapter mInternalAccessAdapter; + + public FullScreenView(Context context, AwViewMethods awViewMethods) { + super(context); + setAwViewMethods(awViewMethods); + mInternalAccessAdapter = new InternalAccessAdapter(); + } + + public InternalAccessAdapter getInternalAccessAdapter() { + return mInternalAccessAdapter; + } + + public void setAwViewMethods(AwViewMethods awViewMethods) { + mAwViewMethods = awViewMethods; + } + + @Override + public void onDraw(final Canvas canvas) { + mAwViewMethods.onDraw(canvas); + } + + @Override + public void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { + mAwViewMethods.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public boolean requestFocus(final int direction, final Rect previouslyFocusedRect) { + mAwViewMethods.requestFocus(); + return super.requestFocus(direction, previouslyFocusedRect); + } + + @Override + public void setLayerType(int layerType, Paint paint) { + super.setLayerType(layerType, paint); + mAwViewMethods.setLayerType(layerType, paint); + } + + @Override + public InputConnection onCreateInputConnection(final EditorInfo outAttrs) { + return mAwViewMethods.onCreateInputConnection(outAttrs); + } + + @Override + public boolean onKeyUp(final int keyCode, final KeyEvent event) { + return mAwViewMethods.onKeyUp(keyCode, event); + } + + @Override + public boolean dispatchKeyEvent(final KeyEvent event) { + return mAwViewMethods.dispatchKeyEvent(event); + } + + @Override + public boolean onTouchEvent(final MotionEvent event) { + return mAwViewMethods.onTouchEvent(event); + } + + @Override + public boolean onHoverEvent(final MotionEvent event) { + return mAwViewMethods.onHoverEvent(event); + } + + @Override + public boolean onGenericMotionEvent(final MotionEvent event) { + return mAwViewMethods.onGenericMotionEvent(event); + } + + @Override + public void onConfigurationChanged(final Configuration newConfig) { + mAwViewMethods.onConfigurationChanged(newConfig); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mAwViewMethods.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mAwViewMethods.onDetachedFromWindow(); + } + + @Override + public void onWindowFocusChanged(final boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + mAwViewMethods.onWindowFocusChanged(hasWindowFocus); + } + + @Override + public void onFocusChanged(final boolean focused, final int direction, + final Rect previouslyFocusedRect) { + super.onFocusChanged(focused, direction, previouslyFocusedRect); + mAwViewMethods.onFocusChanged( + focused, direction, previouslyFocusedRect); + } + + @Override + public void onSizeChanged(final int w, final int h, final int ow, final int oh) { + super.onSizeChanged(w, h, ow, oh); + mAwViewMethods.onSizeChanged(w, h, ow, oh); + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + mAwViewMethods.onVisibilityChanged(changedView, visibility); + } + + @Override + public void onWindowVisibilityChanged(final int visibility) { + super.onWindowVisibilityChanged(visibility); + mAwViewMethods.onWindowVisibilityChanged(visibility); + } + + // AwContents.InternalAccessDelegate implementation -------------------------------------- + private class InternalAccessAdapter implements AwContents.InternalAccessDelegate { + + @Override + public boolean drawChild(Canvas canvas, View child, long drawingTime) { + // Intentional no-op + return false; + } + + @Override + public boolean super_onKeyUp(int keyCode, KeyEvent event) { + return FullScreenView.super.onKeyUp(keyCode, event); + } + + @Override + public boolean super_dispatchKeyEventPreIme(KeyEvent event) { + return FullScreenView.super.dispatchKeyEventPreIme(event); + } + + @Override + public boolean super_dispatchKeyEvent(KeyEvent event) { + return FullScreenView.super.dispatchKeyEvent(event); + } + + @Override + public boolean super_onGenericMotionEvent(MotionEvent event) { + return FullScreenView.super.onGenericMotionEvent(event); + } + + @Override + public void super_onConfigurationChanged(Configuration newConfig) { + // Intentional no-op + } + + @Override + public int super_getScrollBarStyle() { + return FullScreenView.super.getScrollBarStyle(); + } + + @Override + public boolean awakenScrollBars() { + return false; + } + + @Override + public boolean super_awakenScrollBars(int startDelay, boolean invalidate) { + return false; + } + + @Override + public void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix) { + // Intentional no-op. + } + + @Override + public void overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY, int maxOverScrollX, + int maxOverScrollY, boolean isTouchEvent) { + // Intentional no-op. + } + + @Override + public void super_scrollTo(int scrollX, int scrollY) { + // Intentional no-op. + } + + @Override + public void setMeasuredDimension(int measuredWidth, int measuredHeight) { + FullScreenView.this.setMeasuredDimension(measuredWidth, measuredHeight); + } + } +} diff --git a/android_webview/java/src/org/chromium/android_webview/NullAwViewMethods.java b/android_webview/java/src/org/chromium/android_webview/NullAwViewMethods.java new file mode 100644 index 0000000..2a94e60 --- /dev/null +++ b/android_webview/java/src/org/chromium/android_webview/NullAwViewMethods.java @@ -0,0 +1,130 @@ +// Copyright 2014 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.android_webview; + +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +import org.chromium.android_webview.AwContents.InternalAccessDelegate; + +/** + * No-op implementation of {@link AwViewMethods} that follows the null object pattern. + * This {@link NullAwViewMethods} is hooked up to the WebView in fullscreen mode, and + * to the {@link FullScreenView} in embedded mode, but not to both at the same time. + */ +class NullAwViewMethods implements AwViewMethods { + private AwContents mAwContents; + private InternalAccessDelegate mInternalAccessAdapter; + private View mContainerView; + + public NullAwViewMethods( + AwContents awContents, InternalAccessDelegate internalAccessAdapter, + View containerView) { + mAwContents = awContents; + mInternalAccessAdapter = internalAccessAdapter; + mContainerView = containerView; + } + + @Override + public void onDraw(Canvas canvas) { + canvas.drawColor(mAwContents.getEffectiveBackgroundColor()); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // When the containerView is using the NullAwViewMethods then it is not + // attached to the AwContents. As such, we don't have any contents to measure + // and using the last measured dimension is the best we can do. + mInternalAccessAdapter.setMeasuredDimension( + mContainerView.getMeasuredWidth(), mContainerView.getMeasuredHeight()); + } + + @Override + public void requestFocus() { + // Intentional no-op. + } + + @Override + public void setLayerType(int layerType, Paint paint) { + // Intentional no-op. + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + return null; // Intentional no-op. + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return false; // Intentional no-op. + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return false; // Intentional no-op. + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return false; // Intentional no-op. + } + + @Override + public boolean onHoverEvent(MotionEvent event) { + return false; // Intentional no-op. + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + return false; // Intentional no-op. + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + // Intentional no-op. + } + + @Override + public void onAttachedToWindow() { + // Intentional no-op. + } + + @Override + public void onDetachedFromWindow() { + // Intentional no-op. + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + // Intentional no-op. + } + + @Override + public void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + // Intentional no-op. + } + + @Override + public void onSizeChanged(int w, int h, int ow, int oh) { + // Intentional no-op. + } + + @Override + public void onVisibilityChanged(View changedView, int visibility) { + // Intentional no-op. + } + + @Override + public void onWindowVisibilityChanged(int visibility) { + // Intentional no-op. + } +} diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientFullScreenVideoTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientFullScreenVideoTest.java index ee50901..fb29ea5 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientFullScreenVideoTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientFullScreenVideoTest.java @@ -6,17 +6,44 @@ package org.chromium.android_webview.test; import android.test.suitebuilder.annotation.MediumTest; import android.view.KeyEvent; +import android.view.View; + +import junit.framework.Assert; import org.chromium.android_webview.test.util.VideoTestWebServer; +import org.chromium.base.CommandLine; import org.chromium.base.test.util.Feature; -import org.chromium.content.browser.ContentVideoView; +import org.chromium.content.browser.ContentViewCore; +import org.chromium.content.browser.test.util.DOMUtils; import org.chromium.content.browser.test.util.TouchCommon; +import org.chromium.content.common.ContentSwitches; /** * Test WebChromeClient::onShow/HideCustomView. */ public class AwContentsClientFullScreenVideoTest extends AwTestBase { private FullScreenVideoTestAwContentsClient mContentsClient; + private ContentViewCore mContentViewCore; + private VideoTestWebServer webServer; + private AwTestContainerView testContainerView; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mContentsClient = new FullScreenVideoTestAwContentsClient(getActivity()); + testContainerView = + createAwTestContainerViewOnMainSync(mContentsClient); + mContentViewCore = testContainerView.getContentViewCore(); + enableJavaScriptOnUiThread(testContainerView.getAwContents()); + webServer = new VideoTestWebServer( + getInstrumentation().getTargetContext()); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + if (webServer != null) webServer.getTestWebServer().shutdown(); + } @MediumTest @Feature({"AndroidWebView"}) @@ -31,38 +58,77 @@ public class AwContentsClientFullScreenVideoTest extends AwTestBase { @MediumTest @Feature({"AndroidWebView"}) - public void testOnShowAndHideCustomViewWithBackKey() throws Throwable { + public void testOnShowAndHideCustomViewWithBackKeyLegacy() throws Throwable { + // When html controls are enabled we skip this test because pressing the back key + // moves away from the current activity instead of exiting fullscreen mode. + if (areHtmlControlsEnabled()) + return; + doOnShowAndHideCustomViewTest(new Runnable() { @Override public void run() { - ContentVideoView view = mContentsClient.getVideoView(); - view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)); - view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)); + View customView = mContentsClient.getCustomView(); + customView.dispatchKeyEvent( + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)); + customView.dispatchKeyEvent( + new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)); } }); } - private void doOnShowAndHideCustomViewTest(Runnable existFullscreen) throws Throwable { - mContentsClient = new FullScreenVideoTestAwContentsClient(getActivity()); - AwTestContainerView testContainerView = - createAwTestContainerViewOnMainSync(mContentsClient); - enableJavaScriptOnUiThread(testContainerView.getAwContents()); - VideoTestWebServer webServer = new VideoTestWebServer( - getInstrumentation().getTargetContext()); - try { - loadUrlSync(testContainerView.getAwContents(), - mContentsClient.getOnPageFinishedHelper(), - webServer.getFullScreenVideoTestURL()); - Thread.sleep(5 * 1000); - - TouchCommon touchCommon = new TouchCommon(this); - touchCommon.singleClickView(testContainerView); - mContentsClient.waitForCustomViewShown(); - - getInstrumentation().runOnMainSync(existFullscreen); - mContentsClient.waitForCustomViewHidden(); - } finally { - if (webServer != null) webServer.getTestWebServer().shutdown(); - } + @MediumTest + @Feature({"AndroidWebView"}) + public void testOnShowAndHideCustomViewWithJavascript() throws Throwable { + doOnShowAndHideCustomViewTest(new Runnable() { + @Override + public void run() { + DOMUtils.exitFullscreen(mContentViewCore); + } + }); + } + + @MediumTest + @Feature({"AndroidWebView"}) + public void testOnShowCustomViewAndPlayWithHtmlControl() throws Throwable { + if (!areHtmlControlsEnabled()) + return; + + doOnShowCustomViewTest(); + Assert.assertFalse(DOMUtils.hasVideoEnded( + mContentViewCore, VideoTestWebServer.VIDEO_ID)); + + // Click the html play button that is rendered above the video right in the middle + // of the custom view. Note that we're not able to get the precise location of the + // control since it is a shadow element, so this test might break if the location + // ever moves. + TouchCommon touchCommon = new TouchCommon( + AwContentsClientFullScreenVideoTest.this); + touchCommon.singleClickView(mContentsClient.getCustomView()); + + Assert.assertTrue(DOMUtils.waitForEndOfVideo( + mContentViewCore, VideoTestWebServer.VIDEO_ID)); + } + + private static boolean areHtmlControlsEnabled() { + return !CommandLine.getInstance().hasSwitch( + ContentSwitches.DISABLE_OVERLAY_FULLSCREEN_VIDEO_SUBTITLE); + } + + private void doOnShowAndHideCustomViewTest(final Runnable existFullscreen) + throws Throwable { + doOnShowCustomViewTest(); + getInstrumentation().runOnMainSync(existFullscreen); + mContentsClient.waitForCustomViewHidden(); + } + + private void doOnShowCustomViewTest() throws Exception { + loadUrlSync(testContainerView.getAwContents(), + mContentsClient.getOnPageFinishedHelper(), + webServer.getFullScreenVideoTestURL()); + + // Click the button in full_screen_video_test.html to enter fullscreen. + TouchCommon touchCommon = new TouchCommon(this); + touchCommon.singleClickView(testContainerView); + mContentsClient.waitForCustomViewShown(); } } diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/FullScreenVideoTestAwContentsClient.java b/android_webview/javatests/src/org/chromium/android_webview/test/FullScreenVideoTestAwContentsClient.java index 2307109..37edab5 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/FullScreenVideoTestAwContentsClient.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/FullScreenVideoTestAwContentsClient.java @@ -14,7 +14,6 @@ import android.widget.FrameLayout; import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout; -import org.chromium.content.browser.ContentVideoView; import org.chromium.content.browser.test.util.CallbackHelper; import java.util.concurrent.TimeUnit; @@ -29,7 +28,7 @@ public class FullScreenVideoTestAwContentsClient extends TestAwContentsClient { private CallbackHelper mOnHideCustomViewCallbackHelper = new CallbackHelper(); private Activity mActivity; - private ContentVideoView mVideoView; + private View mCustomView; private WebChromeClient.CustomViewCallback mExitCallback; public FullScreenVideoTestAwContentsClient(Activity activity) { @@ -38,9 +37,7 @@ public class FullScreenVideoTestAwContentsClient extends TestAwContentsClient { @Override public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { - if (view instanceof ContentVideoView) { - mVideoView = (ContentVideoView)view; - } + mCustomView = view; mExitCallback = callback; mActivity.getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, @@ -64,8 +61,8 @@ public class FullScreenVideoTestAwContentsClient extends TestAwContentsClient { return mExitCallback; } - public ContentVideoView getVideoView() { - return mVideoView; + public View getCustomView() { + return mCustomView; } public void waitForCustomViewShown() throws TimeoutException, InterruptedException { diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/util/VideoTestWebServer.java b/android_webview/javatests/src/org/chromium/android_webview/test/util/VideoTestWebServer.java index 6acb9ee..cbd5cf5 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/util/VideoTestWebServer.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/util/VideoTestWebServer.java @@ -19,6 +19,8 @@ import java.util.List; */ public class VideoTestWebServer { + // VIDEO_ID must be kept in sync with the id in full_screen_video_test.html. + public static final String VIDEO_ID = "video"; public static final String ONE_PIXEL_ONE_FRAME_WEBM_FILENAME = "one_pixel_one_frame.webm"; public static final String ONE_PIXEL_ONE_FRAME_WEBM_BASE64 = "GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQN8VSalmQCgq17FAAw9C" + diff --git a/android_webview/native/aw_contents.cc b/android_webview/native/aw_contents.cc index 10bbb18..f5336e0 100644 --- a/android_webview/native/aw_contents.cc +++ b/android_webview/native/aw_contents.cc @@ -976,6 +976,12 @@ void AwContents::SetBackgroundColor(JNIEnv* env, jobject obj, jint color) { render_view_host_ext_->SetBackgroundColor(color); } +void AwContents::SetHasTransparentBackground( + JNIEnv* env, jobject obj, bool transparent) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + browser_view_renderer_.SetHasTransparentBackground(transparent); +} + jlong AwContents::ReleasePopupAwContents(JNIEnv* env, jobject obj) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); return reinterpret_cast<intptr_t>(pending_contents_.release()); diff --git a/android_webview/native/aw_contents.h b/android_webview/native/aw_contents.h index 8f69b619..e334829 100644 --- a/android_webview/native/aw_contents.h +++ b/android_webview/native/aw_contents.h @@ -111,6 +111,7 @@ class AwContents : public FindHelper::Listener, jboolean RestoreFromOpaqueState(JNIEnv* env, jobject obj, jbyteArray state); void FocusFirstNode(JNIEnv* env, jobject obj); void SetBackgroundColor(JNIEnv* env, jobject obj, jint color); + void SetHasTransparentBackground(JNIEnv* env, jobject obj, bool transparent); bool OnDraw(JNIEnv* env, jobject obj, jobject canvas, diff --git a/android_webview/test/shell/assets/full_screen_video_test.html b/android_webview/test/shell/assets/full_screen_video_test.html index 466a8d4..618b342 100644 --- a/android_webview/test/shell/assets/full_screen_video_test.html +++ b/android_webview/test/shell/assets/full_screen_video_test.html @@ -19,6 +19,7 @@ addEventListener('Loaded', function() { </script></head><body> <button autofocus style ='padding:1024px 1024px;' onclick="goFullscreen('video'); return false">Big enough you can't miss it</button> <p></p> +<!-- The video id must be kept in sync with VideoTestWebServer.VIDEO_ID --> <video style = 'width: 10px; height: 10px;' id='video' controls> <source id="webm" src="VIDEO_FILE_URL" type="video/webm"> </video> diff --git a/content/public/test/android/javatests/src/org/chromium/content/browser/test/util/DOMUtils.java b/content/public/test/android/javatests/src/org/chromium/content/browser/test/util/DOMUtils.java index 7851318..fc2c228 100644 --- a/content/public/test/android/javatests/src/org/chromium/content/browser/test/util/DOMUtils.java +++ b/content/public/test/android/javatests/src/org/chromium/content/browser/test/util/DOMUtils.java @@ -22,6 +22,48 @@ import java.util.concurrent.TimeoutException; public class DOMUtils { /** + * Returns whether the video with given {@code nodeId} has ended. + */ + public static boolean hasVideoEnded(final ContentViewCore viewCore, final String nodeId) + throws InterruptedException, TimeoutException { + return getNodeField("ended", viewCore, nodeId, Boolean.class); + } + + /** + * Wait until the end of the video with given {@code nodeId}. + * @return Whether the video has ended. + */ + public static boolean waitForEndOfVideo(final ContentViewCore viewCore, final String nodeId) + throws InterruptedException { + return CriteriaHelper.pollForCriteria(new Criteria() { + @Override + public boolean isSatisfied() { + try { + return DOMUtils.hasVideoEnded(viewCore, nodeId); + } catch (InterruptedException e) { + // Intentionally do nothing + return false; + } catch (TimeoutException e) { + // Intentionally do nothing + return false; + } + } + }); + } + + /** + * Makes the document exit fullscreen. + */ + public static void exitFullscreen(final ContentViewCore viewCore) { + StringBuilder sb = new StringBuilder(); + sb.append("(function() {"); + sb.append(" if (document.webkitExitFullscreen) document.webkitExitFullscreen();"); + sb.append("})();"); + + JavaScriptUtils.executeJavaScript(viewCore, sb.toString()); + } + + /** * Returns the rect boundaries for a node by its id. */ public static Rect getNodeBounds(final ContentViewCore viewCore, String nodeId) @@ -116,7 +158,7 @@ public class DOMUtils { */ public static String getNodeContents(ContentViewCore viewCore, String nodeId) throws InterruptedException, TimeoutException { - return getNodeField("textContent", viewCore, nodeId); + return getNodeField("textContent", viewCore, nodeId, String.class); } /** @@ -124,17 +166,16 @@ public class DOMUtils { */ public static String getNodeValue(final ContentViewCore viewCore, String nodeId) throws InterruptedException, TimeoutException { - return getNodeField("value", viewCore, nodeId); + return getNodeField("value", viewCore, nodeId, String.class); } - public static String getNodeField(String fieldName, final ContentViewCore viewCore, - String nodeId) + private static <T> T getNodeField(String fieldName, final ContentViewCore viewCore, + String nodeId, Class<T> valueType) throws InterruptedException, TimeoutException { StringBuilder sb = new StringBuilder(); sb.append("(function() {"); sb.append(" var node = document.getElementById('" + nodeId + "');"); sb.append(" if (!node) return null;"); - sb.append(" if (!node." + fieldName + ") return null;"); sb.append(" return [ node." + fieldName + " ];"); sb.append("})();"); @@ -144,10 +185,10 @@ public class DOMUtils { jsonText.trim().equalsIgnoreCase("null")); JsonReader jsonReader = new JsonReader(new StringReader(jsonText)); - String value = null; + T value = null; try { jsonReader.beginArray(); - if (jsonReader.hasNext()) value = jsonReader.nextString(); + if (jsonReader.hasNext()) value = readValue(jsonReader, valueType); jsonReader.endArray(); Assert.assertNotNull("Invalid contents returned.", value); @@ -158,6 +199,18 @@ public class DOMUtils { return value; } + @SuppressWarnings("unchecked") + private static <T> T readValue(JsonReader jsonReader, Class<T> valueType) + throws IOException { + if (valueType.equals(String.class)) return ((T) jsonReader.nextString()); + if (valueType.equals(Boolean.class)) return ((T) ((Boolean) jsonReader.nextBoolean())); + if (valueType.equals(Integer.class)) return ((T) ((Integer) jsonReader.nextInt())); + if (valueType.equals(Long.class)) return ((T) ((Long) jsonReader.nextLong())); + if (valueType.equals(Double.class)) return ((T) ((Double) jsonReader.nextDouble())); + + throw new IllegalArgumentException("Cannot read values of type " + valueType); + } + /** * Wait until a given node has non-zero bounds. * @return Whether the node started having non-zero bounds. |