Commit bfde1c0b authored by Bo Liu's avatar Bo Liu Committed by Commit Bot

weblayer: Add callback for setSupportsEmbedding

Changing rendering mode is not synchronous, so provide a callback for
when an animation can start safely.

Add a callback aidl that that just passes an ObjectWrapper, which
provides no type safety.

Add Callback and ListenableResult generic types in the client lib.

Update the animation demo to use new API. Note now there is flickers
since mode switching is not visually seamless yet. Add a few tests.

Change-Id: I903f0b5805aee03bbf15e67bc6314b1e6f0fdc85
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1814758Reviewed-by: default avatarRichard Coles <torne@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Commit-Queue: Bo <boliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#699535}
parent 503c4b50
...@@ -3307,7 +3307,8 @@ def _CheckAndroidNewMdpiAssetLocation(input_api, output_api): ...@@ -3307,7 +3307,8 @@ def _CheckAndroidNewMdpiAssetLocation(input_api, output_api):
def _CheckAndroidWebkitImports(input_api, output_api): def _CheckAndroidWebkitImports(input_api, output_api):
"""Checks that code uses org.chromium.base.Callback instead of """Checks that code uses org.chromium.base.Callback instead of
android.widget.ValueCallback except in the WebView glue layer. android.webview.ValueCallback except in the WebView glue layer
and WebLayer.
""" """
valuecallback_import_pattern = input_api.re.compile( valuecallback_import_pattern = input_api.re.compile(
r'^import android\.webkit\.ValueCallback;$') r'^import android\.webkit\.ValueCallback;$')
...@@ -3319,7 +3320,8 @@ def _CheckAndroidWebkitImports(input_api, output_api): ...@@ -3319,7 +3320,8 @@ def _CheckAndroidWebkitImports(input_api, output_api):
black_list=(_EXCLUDED_PATHS + black_list=(_EXCLUDED_PATHS +
_TEST_CODE_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
input_api.DEFAULT_BLACK_LIST + input_api.DEFAULT_BLACK_LIST +
(r'^android_webview[\\/]glue[\\/].*',)), (r'^android_webview[\\/]glue[\\/].*',
r'^weblayer[\\/].*',)),
white_list=[r'.*\.java$']) white_list=[r'.*\.java$'])
for f in input_api.AffectedSourceFiles(sources): for f in input_api.AffectedSourceFiles(sources):
......
...@@ -10,6 +10,7 @@ import android.view.KeyEvent; ...@@ -10,6 +10,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import android.webkit.ValueCallback;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
...@@ -149,9 +150,10 @@ public final class BrowserControllerImpl extends IBrowserController.Stub { ...@@ -149,9 +150,10 @@ public final class BrowserControllerImpl extends IBrowserController.Stub {
} }
@Override @Override
public void setSupportsEmbedding(boolean enable) { public void setSupportsEmbedding(boolean enable, IObjectWrapper callback) {
mContentViewRenderView.requestMode(enable ? ContentViewRenderView.MODE_TEXTURE_VIEW mContentViewRenderView.requestMode(enable ? ContentViewRenderView.MODE_TEXTURE_VIEW
: ContentViewRenderView.MODE_SURFACE_VIEW); : ContentViewRenderView.MODE_SURFACE_VIEW,
(ValueCallback<Boolean>) ObjectWrapper.unwrap(callback, ValueCallback.class));
} }
@NativeMethods @NativeMethods
......
...@@ -18,5 +18,8 @@ interface IBrowserController { ...@@ -18,5 +18,8 @@ interface IBrowserController {
IObjectWrapper onCreateView() = 4; IObjectWrapper onCreateView() = 4;
void setSupportsEmbedding(in boolean enable) = 5; // |valueCallback| is a wrapped ValueCallback<Boolean> instead. The bool value in |valueCallback|
// indicates is whether the request was successful. Request might fail if it is subsumed by a
// following request, or if this object is destroyed.
void setSupportsEmbedding(in boolean enable, in IObjectWrapper valueCallback) = 5;
} }
...@@ -24,6 +24,8 @@ android_library("java") { ...@@ -24,6 +24,8 @@ android_library("java") {
"org/chromium/weblayer/BrowserController.java", "org/chromium/weblayer/BrowserController.java",
"org/chromium/weblayer/BrowserFragmentImpl.java", "org/chromium/weblayer/BrowserFragmentImpl.java",
"org/chromium/weblayer/BrowserObserver.java", "org/chromium/weblayer/BrowserObserver.java",
"org/chromium/weblayer/Callback.java",
"org/chromium/weblayer/ListenableResult.java",
"org/chromium/weblayer/Navigation.java", "org/chromium/weblayer/Navigation.java",
"org/chromium/weblayer/NavigationController.java", "org/chromium/weblayer/NavigationController.java",
"org/chromium/weblayer/NavigationObserver.java", "org/chromium/weblayer/NavigationObserver.java",
......
...@@ -6,6 +6,7 @@ package org.chromium.weblayer; ...@@ -6,6 +6,7 @@ package org.chromium.weblayer;
import android.os.RemoteException; import android.os.RemoteException;
import android.view.View; import android.view.View;
import android.webkit.ValueCallback;
import org.chromium.weblayer_private.aidl.APICallException; import org.chromium.weblayer_private.aidl.APICallException;
import org.chromium.weblayer_private.aidl.IBrowserController; import org.chromium.weblayer_private.aidl.IBrowserController;
...@@ -51,11 +52,21 @@ public final class BrowserFragmentImpl { ...@@ -51,11 +52,21 @@ public final class BrowserFragmentImpl {
* container view of the fragment is animated in any way, needs to be rotated or blended, or * container view of the fragment is animated in any way, needs to be rotated or blended, or
* need to control z-order with other views or other BrowserFragmentImpls. Note embedder should * need to control z-order with other views or other BrowserFragmentImpls. Note embedder should
* keep WebLayer in the default non-embedding mode when user is interacting with the web * keep WebLayer in the default non-embedding mode when user is interacting with the web
* content. * content. Embedding mode does not support encrypted video.
* @return a ListenableResult of whether the request succeeded. A request might fail if it is
* subsumed by a subsequent request, or if this object is destroyed.
*/ */
public void setSupportsEmbedding(boolean enable) { public ListenableResult<Boolean> setSupportsEmbedding(boolean enable) {
try { try {
getIBrowserController().setSupportsEmbedding(enable); final ListenableResult<Boolean> listenableResult = new ListenableResult<Boolean>();
getIBrowserController().setSupportsEmbedding(
enable, ObjectWrapper.wrap(new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean result) {
listenableResult.supplyResult(result);
}
}));
return listenableResult;
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
......
// Copyright 2019 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;
/**
* Simple callback that takes a generic value.
* @param <T> The type of value passed into the callback.
*/
public interface Callback<T> { void onResult(T result); }
// Copyright 2019 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;
import java.util.ArrayList;
/**
* Represents result of async computation which can be retrieved from a callback.
* @param <V> The type of the computation's result.
*/
public final class ListenableResult<V> {
private boolean mHasResult;
private V mResult;
private final ArrayList<Callback<V>> mCallbacks = new ArrayList<>();
/* package */ ListenableResult() {}
/**
* Call the callback with the result of computation.
* Note callback may be called immediately.
*/
public void addCallback(Callback<V> callback) {
if (mHasResult) {
callback.onResult(mResult);
return;
}
mCallbacks.add(callback);
}
/* package */ void supplyResult(V result) {
assert !mHasResult;
mResult = result;
mHasResult = true;
for (Callback<V> callback : mCallbacks) {
callback.onResult(mResult);
}
mCallbacks.clear();
}
}
...@@ -252,6 +252,7 @@ instrumentation_test_apk("weblayer_instrumentation_test_apk") { ...@@ -252,6 +252,7 @@ instrumentation_test_apk("weblayer_instrumentation_test_apk") {
java_files = [ java_files = [
"javatests/src/org/chromium/weblayer/test/NavigationTest.java", "javatests/src/org/chromium/weblayer/test/NavigationTest.java",
"javatests/src/org/chromium/weblayer/test/SmokeTest.java", "javatests/src/org/chromium/weblayer/test/SmokeTest.java",
"javatests/src/org/chromium/weblayer/test/RenderingTest.java",
"javatests/src/org/chromium/weblayer/test/WebLayerShellActivityTestRule.java", "javatests/src/org/chromium/weblayer/test/WebLayerShellActivityTestRule.java",
"shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java", "shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java",
] ]
......
...@@ -60,14 +60,20 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -60,14 +60,20 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
} }
public static class ContainerFrameLayout extends FrameLayout { public static class ContainerFrameLayout extends FrameLayout {
private final BrowserFragmentImpl mFragment;
private int mIndex; private int mIndex;
private boolean mInterceptTouchEvent; private boolean mInterceptTouchEvent;
public ContainerFrameLayout(Context context, int index) { public ContainerFrameLayout(Context context, BrowserFragmentImpl fragment, int index) {
super(context); super(context);
mFragment = fragment;
mIndex = index; mIndex = index;
} }
public BrowserFragmentImpl getFragment() {
return mFragment;
}
public void setInterceptTouchEvent(boolean intercept) { public void setInterceptTouchEvent(boolean intercept) {
mInterceptTouchEvent = intercept; mInterceptTouchEvent = intercept;
} }
...@@ -124,15 +130,16 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -124,15 +130,16 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
} }
private void createNewFragment(int index) { private void createNewFragment(int index) {
ContainerFrameLayout container = new ContainerFrameLayout(this, index); mBrowserFragments[index] = mProfile.createBrowserFragment(this);
final BrowserController controller = mBrowserFragments[index].getBrowserController();
ContainerFrameLayout container =
new ContainerFrameLayout(this, mBrowserFragments[index], index);
mContainerViews[index] = container; mContainerViews[index] = container;
int viewId = View.generateViewId(); int viewId = View.generateViewId();
container.setId(viewId); container.setId(viewId);
mMainView.addView(container); mMainView.addView(container);
mBrowserFragments[index] = mProfile.createBrowserFragment(this);
final BrowserController controller = mBrowserFragments[index].getBrowserController();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(viewId, new ShellFragment(mBrowserFragments[index])); transaction.add(viewId, new ShellFragment(mBrowserFragments[index]));
transaction.commit(); transaction.commit();
...@@ -253,12 +260,12 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -253,12 +260,12 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
mContainerViews[1].setOnClickListener(new OnClickImpl()); mContainerViews[1].setOnClickListener(new OnClickImpl());
mContainerViews[2].setOnClickListener(new OnClickImpl()); mContainerViews[2].setOnClickListener(new OnClickImpl());
mContainerViews[0].post(() -> { mContainerViews[0].post(() -> {
mBrowserFragments[0].setSupportsEmbedding(true); mBrowserFragments[0].setSupportsEmbedding(true).addCallback(
mBrowserFragments[1].setSupportsEmbedding(true); (Boolean result) -> animateDown(mContainerViews[0]));
mBrowserFragments[2].setSupportsEmbedding(true); mBrowserFragments[1].setSupportsEmbedding(true).addCallback(
animateDown(mContainerViews[0]); (Boolean result) -> animateDown(mContainerViews[1]));
animateDown(mContainerViews[1]); mBrowserFragments[2].setSupportsEmbedding(true).addCallback(
animateDown(mContainerViews[2]); (Boolean result) -> animateDown(mContainerViews[2]));
}); });
} }
} }
...@@ -284,8 +291,12 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -284,8 +291,12 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
ContainerFrameLayout container = (ContainerFrameLayout) v; ContainerFrameLayout container = (ContainerFrameLayout) v;
if (mFullscreenContainer == container) return; if (mFullscreenContainer == container) return;
mMainView.removeView(container); // Move to back by bringing others to the front.
mMainView.addView(container, 0); // This avoids detaching and reattaching any views.
for (int i = 0; i < 3; ++i) {
if (mContainerViews[i] == container) continue;
mMainView.bringChildToFront(mContainerViews[i]);
}
if (mFullscreenContainer != null) { if (mFullscreenContainer != null) {
animateDown(mFullscreenContainer); animateDown(mFullscreenContainer);
...@@ -297,15 +308,19 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -297,15 +308,19 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
} }
private static void animateDown(ContainerFrameLayout container) { private static void animateDown(ContainerFrameLayout container) {
// Start animation after fullying switched from SurfaceView to TextureView.
int index = container.getIndex(); int index = container.getIndex();
container.animate() container.getFragment().setSupportsEmbedding(true).addCallback((Boolean result) -> {
.scaleX(1.0f / 3) container.animate()
.scaleY(1.0f / 3) .scaleX(1.0f / 3)
.translationX(-container.getWidth() / 3.0f + (container.getWidth() / 3.0f * index)) .scaleY(1.0f / 3)
.translationY(container.getHeight() / 3.0f) .translationX(
.alpha(0.8f) -container.getWidth() / 3.0f + (container.getWidth() / 3.0f * index))
.setDuration(500); .translationY(container.getHeight() / 3.0f)
container.setInterceptTouchEvent(true); .alpha(0.8f)
.setDuration(500);
container.setInterceptTouchEvent(true);
});
} }
private static void animateUp(ContainerFrameLayout container) { private static void animateUp(ContainerFrameLayout container) {
...@@ -315,8 +330,11 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -315,8 +330,11 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
.translationX(0) .translationX(0)
.translationY(0) .translationY(0)
.alpha(1.0f) .alpha(1.0f)
.setDuration(500); .setDuration(500)
container.setInterceptTouchEvent(false); .withEndAction(() -> {
container.getFragment().setSupportsEmbedding(false);
container.setInterceptTouchEvent(false);
});
} }
private static void animateDown(MyWebView webview) { private static void animateDown(MyWebView webview) {
......
// Copyright 2019 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.test;
import android.support.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.weblayer.shell.WebLayerShellActivity;
import java.util.concurrent.CountDownLatch;
@RunWith(BaseJUnit4ClassRunner.class)
public class RenderingTest {
@Rule
public WebLayerShellActivityTestRule mActivityTestRule = new WebLayerShellActivityTestRule();
@Test
@SmallTest
public void testSetSupportEmbeddingFromCallback() {
WebLayerShellActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
Assert.assertNotNull(activity);
CountDownLatch latch = new CountDownLatch(1);
String url = "data:text,foo";
TestThreadUtils.runOnUiThreadBlocking(() -> {
activity.getBrowserFragmentImpl().setSupportsEmbedding(true).addCallback(
(Boolean result) -> {
Assert.assertTrue(result);
activity.getBrowserFragmentImpl().setSupportsEmbedding(false).addCallback(
(Boolean result2) -> {
Assert.assertTrue(result2);
mActivityTestRule.loadUrl(url);
latch.countDown();
});
});
});
try {
latch.await();
} catch (InterruptedException e) {
Assert.fail(e.toString());
}
mActivityTestRule.waitForNavigation(url);
}
@Test
@SmallTest
public void testRepeatSetSupportEmbeddingGeneratesCallback() {
WebLayerShellActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
Assert.assertNotNull(activity);
CountDownLatch latch = new CountDownLatch(2);
String url = "data:text,foo";
TestThreadUtils.runOnUiThreadBlocking(() -> {
activity.getBrowserFragmentImpl().setSupportsEmbedding(true).addCallback(
(Boolean result) -> {
Assert.assertTrue(result);
latch.countDown();
});
activity.getBrowserFragmentImpl().setSupportsEmbedding(true).addCallback(
(Boolean result) -> {
Assert.assertTrue(result);
latch.countDown();
});
});
try {
latch.await();
} catch (InterruptedException e) {
Assert.fail(e.toString());
}
}
}
...@@ -15,6 +15,8 @@ import org.chromium.base.test.BaseJUnit4ClassRunner; ...@@ -15,6 +15,8 @@ import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.weblayer.shell.WebLayerShellActivity; import org.chromium.weblayer.shell.WebLayerShellActivity;
import java.util.concurrent.CountDownLatch;
@RunWith(BaseJUnit4ClassRunner.class) @RunWith(BaseJUnit4ClassRunner.class)
public class SmokeTest { public class SmokeTest {
@Rule @Rule
...@@ -26,11 +28,23 @@ public class SmokeTest { ...@@ -26,11 +28,23 @@ public class SmokeTest {
WebLayerShellActivity activity = mActivityTestRule.launchShellWithUrl("about:blank"); WebLayerShellActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
Assert.assertNotNull(activity); Assert.assertNotNull(activity);
TestThreadUtils.runOnUiThreadBlocking( CountDownLatch latch = new CountDownLatch(1);
() -> { activity.getBrowserFragmentImpl().setSupportsEmbedding(true); });
String url = "data:text,foo"; String url = "data:text,foo";
mActivityTestRule.loadUrl(url);
TestThreadUtils.runOnUiThreadBlocking(() -> {
activity.getBrowserFragmentImpl().setSupportsEmbedding(true).addCallback(
(Boolean result) -> {
Assert.assertTrue(result);
mActivityTestRule.loadUrl(url);
latch.countDown();
});
});
try {
latch.await();
} catch (InterruptedException e) {
Assert.fail(e.toString());
}
mActivityTestRule.waitForNavigation(url); mActivityTestRule.waitForNavigation(url);
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment