Commit 11ce29f9 authored by Bo Liu's avatar Bo Liu Committed by Commit Bot

Weblayer animation demo

Allow WebLayer to switch between SurfaceView and TextureView at run
time.

Add a separate demo shell to host an apk with some fancy animations.
Also add a smoke test that does not actually verify anything.

Change-Id: I4544b675cec04d01fd60b3c27fac603a0b6a0f97
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1807577Reviewed-by: default avatarJohn Abd-El-Malek <jam@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Commit-Queue: Bo <boliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#697307}
parent 707fee6e
......@@ -321,6 +321,7 @@ group("gn_all") {
"//content/public/android:content_junit_tests",
"//content/shell/android:content_shell_apk",
"//device:device_junit_tests",
"//weblayer/shell/android:weblayer_demo_apk",
"//weblayer/shell/android:weblayer_shell_apk",
# TODO(https://crbug.com/879065): remove once tests have been migrated to
......
......@@ -150,6 +150,12 @@ public final class BrowserControllerImpl extends IBrowserController.Stub {
return ObjectWrapper.wrap(mContentViewRenderView);
}
@Override
public void setSupportsEmbedding(boolean enable) {
mContentViewRenderView.requestMode(enable ? ContentViewRenderView.MODE_TEXTURE_VIEW
: ContentViewRenderView.MODE_SURFACE_VIEW);
}
private static native long nativeCreateBrowserController(long profile);
private native void nativeSetTopControlsContainerView(
long nativeBrowserControllerImpl, long nativeTopControlsContainerView);
......
......@@ -127,7 +127,6 @@ public class ContentViewRenderView extends FrameLayout {
mSurfaceView = new SurfaceView(getContext());
mSurfaceView.setZOrderMediaOverlay(true);
mSurfaceView.setBackgroundColor(mBackgroundColor);
mWindowAndroid.setAnimationPlaceholderView(mSurfaceView);
mSurfaceCallback = new SurfaceHolder.Callback() {
@Override
......@@ -171,7 +170,6 @@ public class ContentViewRenderView extends FrameLayout {
private void uninitializeSurfaceView() {
if (mSurfaceView == null) return;
removeView(mSurfaceView);
mWindowAndroid.setAnimationPlaceholderView(null);
mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
mSurfaceCallback = null;
mSurfaceView = null;
......
......@@ -17,4 +17,6 @@ interface IBrowserController {
void destroy() = 3;
IObjectWrapper onCreateView() = 4;
void setSupportsEmbedding(in boolean enable) = 5;
}
......@@ -46,6 +46,21 @@ public final class BrowserFragmentImpl {
}
}
/**
* Control support for embedding use cases such as animations. This should be enabled when the
* 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
* keep WebLayer in the default non-embedding mode when user is interacting with the web
* content.
*/
public void setSupportsEmbedding(boolean enable) {
try {
getIBrowserController().setSupportsEmbedding(enable);
} catch (RemoteException e) {
throw new APICallException(e);
}
}
public BrowserController getBrowserController() {
return mBrowserController;
}
......
......@@ -59,6 +59,73 @@ android_apk("weblayer_shell_apk") {
native_lib_placeholders = [ "libdummy.so" ]
}
generate_wrapper("run_weblayer_shell") {
testonly = true
wrapper_script = "$root_out_dir/bin/run_weblayer_shell"
executable = "//weblayer/tools/run_weblayer_shell.py"
executable_args = [
"--shell-apk-path",
"@WrappedPath(apks/WebLayerShell.apk)",
"--support-apk-path",
"@WrappedPath(apks/WebLayerSupport.apk)",
]
deps = [
":weblayer_shell_apk",
":weblayer_support_apk",
]
}
demo_apk_manifest = "$target_gen_dir/demo_apk_manifest/AndroidManifest.xml"
jinja_template("demo_apk_manifest") {
input = "demo_apk/AndroidManifest.xml"
output = demo_apk_manifest
}
android_assets("demo_apk_assets") {
testonly = true
sources = [
"$root_out_dir/weblayer_shell.pak",
]
disable_compression = true
deps = [
"//weblayer/shell:shell_pak",
]
}
android_library("demo_apk_java") {
testonly = true
deps = [
":demo_apk_manifest",
"//third_party/android_deps:android_support_v4_java",
"//weblayer/public/java",
]
java_files = [ "demo_apk/src/org/chromium/weblayer/demo/WebLayerAnimationDemoActivity.java" ]
android_manifest_for_lint = demo_apk_manifest
}
android_apk("weblayer_demo_apk") {
testonly = true
deps = [
":demo_apk_assets",
":demo_apk_java",
":demo_apk_manifest",
]
apk_name = "WebLayerDemo"
android_manifest = demo_apk_manifest
min_sdk_version = 21
target_sdk_version = 28
android_manifest_dep = ":demo_apk_manifest"
# Add a native lib so the ABI is compatible with the implementation APK.
native_lib_placeholders = [ "libdummy.so" ]
}
weblayer_support_manifest =
"$target_gen_dir/weblayer_support_manifest/AndroidManifest.xml"
......@@ -117,23 +184,6 @@ android_apk("weblayer_support_apk") {
shared_libraries = [ "//weblayer:libweblayer" ]
}
generate_wrapper("run_weblayer_shell") {
testonly = true
wrapper_script = "$root_out_dir/bin/run_weblayer_shell"
executable = "//weblayer/tools/run_weblayer_shell.py"
executable_args = [
"--shell-apk-path",
"@WrappedPath(apks/WebLayerShell.apk)",
"--support-apk-path",
"@WrappedPath(apks/WebLayerSupport.apk)",
]
deps = [
":weblayer_shell_apk",
":weblayer_support_apk",
]
}
instrumentation_test_apk("weblayer_instrumentation_test_apk") {
apk_name = "WebLayerInstrumentationTest"
apk_under_test = "//weblayer/shell/android:weblayer_shell_apk"
......@@ -146,6 +196,7 @@ instrumentation_test_apk("weblayer_instrumentation_test_apk") {
]
java_files = [
"javatests/src/org/chromium/weblayer/test/NavigationTest.java",
"javatests/src/org/chromium/weblayer/test/SmokeTest.java",
"javatests/src/org/chromium/weblayer/test/WebLayerShellActivityTestRule.java",
"shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java",
]
......
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.chromium.weblayer.demo">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application android:label="Demo">
<activity android:name="WebLayerAnimationDemoActivity"
android:launchMode="singleTask"
android:theme="@android:style/Theme.Holo.Light.NoActionBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
android:windowSoftInputMode="adjustPan|stateUnspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
noparent = True
include_rules = [
"+weblayer/public/java",
]
# WebLayer Shell
This directory contains a demo app to demonstrate capabilities of WebLayer.
To build and run on Android:
```
autoninja -C out/Default weblayer_support_apk weblayer_demo_apk
out/Default/bin/weblayer_support_apk install
out/Default/bin/weblayer_demo_apk install
adb shell am start org.chromium.weblayer.demo/.WebLayerAnimationDemoActivity
```
// 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.demo;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.text.InputType;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import org.chromium.weblayer.BrowserController;
import org.chromium.weblayer.BrowserFragmentImpl;
import org.chromium.weblayer.BrowserObserver;
import org.chromium.weblayer.Profile;
import org.chromium.weblayer.WebLayer;
/**
* Activity for managing the Demo Shell.
*/
public class WebLayerAnimationDemoActivity extends FragmentActivity {
private static final String TAG = "AnimationDemo";
private Profile mProfile;
private final BrowserFragmentImpl mBrowserFragments[] = new BrowserFragmentImpl[4];
private final ContainerFrameLayout mContainerViews[] = new ContainerFrameLayout[4];
private FrameLayout mMainView;
public static class ShellFragment extends Fragment {
private BrowserFragmentImpl mBrowserFragment;
ShellFragment(BrowserFragmentImpl browserFragment) {
mBrowserFragment = browserFragment;
}
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return mBrowserFragment.onCreateView();
}
}
public static class ContainerFrameLayout extends FrameLayout {
private int mIndex;
private boolean mInterceptTouchEvent;
public ContainerFrameLayout(Context context, int index) {
super(context);
mIndex = index;
}
public void setInterceptTouchEvent(boolean intercept) {
mInterceptTouchEvent = intercept;
}
public int getIndex() {
return mIndex;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mInterceptTouchEvent;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mInterceptTouchEvent) return super.onTouchEvent(event);
if (event.getAction() == KeyEvent.ACTION_UP) {
performClick();
}
return true;
}
}
private void createNewFragment(int index) {
ContainerFrameLayout container = new ContainerFrameLayout(this, index);
mContainerViews[index] = container;
int viewId = View.generateViewId();
container.setId(viewId);
mMainView.addView(container);
mBrowserFragments[index] = mProfile.createBrowserFragment(this);
final BrowserController controller = mBrowserFragments[index].getBrowserController();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(viewId, new ShellFragment(mBrowserFragments[index]));
transaction.commit();
EditText urlView = new EditText(this);
urlView.setSelectAllOnFocus(true);
urlView.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
urlView.setImeOptions(EditorInfo.IME_ACTION_GO);
// The background of the top-view must be opaque, otherwise it bleeds through to the
// cc::Layer that mirrors the contents of the top-view.
urlView.setBackgroundColor(0xFFa9a9a9);
urlView.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((actionId != EditorInfo.IME_ACTION_GO)
&& (event == null || event.getKeyCode() != KeyEvent.KEYCODE_ENTER
|| event.getAction() != KeyEvent.ACTION_DOWN)) {
return false;
}
String url = urlView.getText().toString();
controller.getNavigationController().navigate(Uri.parse(sanitizeUrl(url)));
return true;
}
});
mBrowserFragments[index].setTopView(urlView);
controller.addObserver(new BrowserObserver() {
@Override
public void displayURLChanged(Uri uri) {
urlView.setText(uri.toString());
}
});
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
// Only call init for main process.
WebLayer.getInstance().init(getApplication());
super.onCreate(savedInstanceState);
FrameLayout mainView = new FrameLayout(this);
mMainView = mainView;
setContentView(mainView);
mProfile = WebLayer.getInstance().createProfile(null);
createNewFragment(0);
createNewFragment(1);
createNewFragment(2);
mBrowserFragments[0].getBrowserController().getNavigationController().navigate(
Uri.parse(sanitizeUrl("https://www.google.com")));
mBrowserFragments[1].getBrowserController().getNavigationController().navigate(
Uri.parse(sanitizeUrl("https://en.wikipedia.org")));
mBrowserFragments[2].getBrowserController().getNavigationController().navigate(
Uri.parse(sanitizeUrl("https://www.chromium.org")));
}
@Override
protected void onDestroy() {
for (int i = 0; i < mBrowserFragments.length; ++i) {
BrowserFragmentImpl fragment = mBrowserFragments[i];
if (fragment != null) fragment.destroy();
mBrowserFragments[i] = null;
}
if (mProfile != null) mProfile.destroy();
super.onDestroy();
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
mContainerViews[0].setOnClickListener(new OnClickImpl());
mContainerViews[1].setOnClickListener(new OnClickImpl());
mContainerViews[2].setOnClickListener(new OnClickImpl());
mContainerViews[0].post(() -> {
mBrowserFragments[0].setSupportsEmbedding(true);
mBrowserFragments[1].setSupportsEmbedding(true);
mBrowserFragments[2].setSupportsEmbedding(true);
animateDown(mContainerViews[0]);
animateDown(mContainerViews[1]);
animateDown(mContainerViews[2]);
});
}
private ContainerFrameLayout mFullscreenContainer;
public class OnClickImpl implements View.OnClickListener {
@Override
public void onClick(View v) {
ContainerFrameLayout container = (ContainerFrameLayout) v;
if (mFullscreenContainer == container) return;
mMainView.removeView(container);
mMainView.addView(container, 0);
if (mFullscreenContainer != null) {
animateDown(mFullscreenContainer);
}
animateUp(container);
mFullscreenContainer = container;
}
}
private static void animateDown(ContainerFrameLayout container) {
int index = container.getIndex();
container.animate()
.scaleX(1.0f / 3)
.scaleY(1.0f / 3)
.translationX(-container.getWidth() / 3.0f + (container.getWidth() / 3.0f * index))
.translationY(container.getHeight() / 3.0f)
.alpha(0.8f)
.setDuration(500);
container.setInterceptTouchEvent(true);
}
private static void animateUp(ContainerFrameLayout container) {
container.animate()
.scaleX(1.0f)
.scaleY(1.0f)
.translationX(0)
.translationY(0)
.alpha(1.0f)
.setDuration(500);
container.setInterceptTouchEvent(false);
}
/**
* Given an URL, this performs minimal sanitizing to ensure it will be valid.
* @param url The url to be sanitized.
* @return The sanitized URL.
*/
public static String sanitizeUrl(String url) {
if (url == null) return null;
if (url.startsWith("www.") || url.indexOf(":") == -1) url = "http://" + url;
return url;
}
}
// 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;
@RunWith(BaseJUnit4ClassRunner.class)
public class SmokeTest {
@Rule
public WebLayerShellActivityTestRule mActivityTestRule = new WebLayerShellActivityTestRule();
@Test
@SmallTest
public void testSetSupportEmbedding() {
WebLayerShellActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
Assert.assertNotNull(activity);
TestThreadUtils.runOnUiThreadBlocking(
() -> { activity.getBrowserFragmentImpl().setSupportsEmbedding(true); });
String url = "data:text,foo";
mActivityTestRule.loadUrl(url);
mActivityTestRule.waitForNavigation(url);
}
}
......@@ -58,6 +58,10 @@ public class WebLayerShellActivity extends FragmentActivity {
return mBrowserController;
}
public BrowserFragmentImpl getBrowserFragmentImpl() {
return mBrowserFragment;
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
// Only call init for main process.
......
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