Commit 3a2e44cc authored by Simeon Anfinrud's avatar Simeon Anfinrud Committed by Commit Bot

[Chromecast] Reactive WebContents presentation.

No more detatchWebContentsIfAny() and similar helpers.
This boils the presentation of WebContents into a simple state
machine compatible with Observables.

This refactor includes covering the business logic of
CastWebContentsSurfaceHelper in unittests. The implementation
details of CastWebContentsView are still not unit-tested, but
are now better isolated from other logic.

Bug: Internal b/36777136
Test: cast_shell_junit_tests
Change-Id: Ia555b4d3ea9ee07607bf54cc339b950fe068d220
Reviewed-on: https://chromium-review.googlesource.com/1003412Reviewed-by: default avatarLuke Halliwell <halliwell@chromium.org>
Commit-Queue: Simeon Anfinrud <sanfin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#551464}
parent fce48036
......@@ -112,6 +112,7 @@ android_library("cast_shell_java") {
"$java_src_dir/org/chromium/chromecast/shell/CastWebContentsIntentUtils.java",
"$java_src_dir/org/chromium/chromecast/shell/CastWebContentsService.java",
"$java_src_dir/org/chromium/chromecast/shell/CastWebContentsSurfaceHelper.java",
"$java_src_dir/org/chromium/chromecast/shell/CastWebContentsView.java",
"$java_src_dir/org/chromium/chromecast/shell/LogcatElision.java",
"$java_src_dir/org/chromium/chromecast/shell/ElidedLogcatProvider.java",
]
......@@ -160,6 +161,7 @@ junit_binary("cast_shell_junit_tests") {
"junit/src/org/chromium/chromecast/shell/CastWebContentsActivityTest.java",
"junit/src/org/chromium/chromecast/shell/CastWebContentsComponentTest.java",
"junit/src/org/chromium/chromecast/shell/CastWebContentsIntentUtilsTest.java",
"junit/src/org/chromium/chromecast/shell/CastWebContentsSurfaceHelperTest.java",
"junit/src/org/chromium/chromecast/shell/LocalBroadcastReceiverScopeTest.java",
"junit/src/org/chromium/chromecast/shell/LogcatElisionUnitTest.java",
"junit/src/org/chromium/chromecast/shell/ElidedLogcatProviderUnitTest.java",
......
......@@ -7,7 +7,9 @@ package org.chromium.chromecast.shell;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.MotionEvent;
......@@ -19,6 +21,7 @@ import android.widget.Toast;
import org.chromium.base.Log;
import org.chromium.base.annotations.RemovableInRelease;
import org.chromium.chromecast.base.Both;
import org.chromium.chromecast.base.CastSwitches;
import org.chromium.chromecast.base.Controller;
import org.chromium.chromecast.base.Observable;
import org.chromium.chromecast.base.ScopeFactories;
......@@ -86,8 +89,12 @@ public class CastWebContentsActivity extends Activity {
setContentView(R.layout.cast_web_contents_activity);
mSurfaceHelper = new CastWebContentsSurfaceHelper(this, /* hostActivity */
(FrameLayout) findViewById(R.id.web_contents_container),
false /* showInFragment */);
CastWebContentsView.onLayout(getApplicationContext(),
(FrameLayout) findViewById(R.id.web_contents_container),
CastSwitches.getSwitchValueColor(
CastSwitches.CAST_APP_BACKGROUND_COLOR, Color.BLACK),
mResumedState),
(Uri uri) -> mIsFinishingState.set("Delayed teardown for URI: " + uri));
}));
// Initialize the audio manager in onCreate() if tests haven't already.
......@@ -157,9 +164,6 @@ public class CastWebContentsActivity extends Activity {
if (DEBUG) Log.d(TAG, "onPause");
super.onPause();
mResumedState.reset();
if (mSurfaceHelper != null) {
mSurfaceHelper.onPause();
}
}
@Override
......@@ -167,9 +171,6 @@ public class CastWebContentsActivity extends Activity {
if (DEBUG) Log.d(TAG, "onResume");
super.onResume();
mResumedState.set(Unit.unit());
if (mSurfaceHelper != null) {
mSurfaceHelper.onResume();
}
}
@Override
......
......@@ -7,6 +7,8 @@ package org.chromium.chromecast.shell;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
......@@ -16,6 +18,9 @@ import android.widget.Toast;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.chromecast.base.CastSwitches;
import org.chromium.chromecast.base.Controller;
import org.chromium.chromecast.base.Unit;
/**
* Fragment for displaying a WebContents in CastShell.
......@@ -31,14 +36,12 @@ import org.chromium.base.Log;
public class CastWebContentsFragment extends Fragment {
private static final String TAG = "cr_CastWebContentFrg";
private Context mPackageContext;
private final Controller<Unit> mResumedState = new Controller<>();
private Context mPackageContext;
private CastWebContentsSurfaceHelper mSurfaceHelper;
private View mFragmentRootView;
private String mAppId;
private int mInitialVisiblityPriority;
@Override
......@@ -61,12 +64,11 @@ public class CastWebContentsFragment extends Fragment {
}
if (mFragmentRootView == null) {
mFragmentRootView = inflater.cloneInContext(getContext())
.inflate(R.layout.cast_web_contents_activity, null);
.inflate(R.layout.cast_web_contents_activity, null);
}
return mFragmentRootView;
}
@Override
public Context getContext() {
if (mPackageContext == null) {
......@@ -88,8 +90,13 @@ public class CastWebContentsFragment extends Fragment {
}
mSurfaceHelper = new CastWebContentsSurfaceHelper(getActivity(), /* hostActivity */
(FrameLayout) getView().findViewById(R.id.web_contents_container),
true /* showInFragment */);
CastWebContentsView.onLayout(getContext(),
(FrameLayout) getView().findViewById(R.id.web_contents_container),
CastSwitches.getSwitchValueColor(
CastSwitches.CAST_APP_BACKGROUND_COLOR, Color.BLACK),
mResumedState),
(Uri uri) -> sendIntentSync(CastWebContentsIntentUtils.onWebContentStopped(uri)));
Bundle bundle = getArguments();
CastWebContentsSurfaceHelper.StartParams params =
CastWebContentsSurfaceHelper.StartParams.fromBundle(bundle);
......@@ -106,18 +113,14 @@ public class CastWebContentsFragment extends Fragment {
public void onPause() {
Log.d(TAG, "onPause");
super.onPause();
if (mSurfaceHelper != null) {
mSurfaceHelper.onPause();
}
mResumedState.reset();
}
@Override
public void onResume() {
Log.d(TAG, "onResume");
super.onResume();
if (mSurfaceHelper != null) {
mSurfaceHelper.onResume();
}
mResumedState.set(Unit.unit());
}
@Override
......
......@@ -13,13 +13,11 @@ import android.widget.Toast;
import org.chromium.base.Log;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chromecast.base.Both;
import org.chromium.chromecast.base.Controller;
import org.chromium.chromecast.base.Observable;
import org.chromium.chromecast.base.Unit;
import org.chromium.components.content_view.ContentView;
import org.chromium.content_public.browser.ContentViewCore;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.ViewAndroidDelegate;
import org.chromium.ui.base.WindowAndroid;
/**
* Service for "displaying" a WebContents in CastShell.
......@@ -34,11 +32,31 @@ public class CastWebContentsService extends Service {
private static final int CAST_NOTIFICATION_ID = 100;
private final Controller<Unit> mLifetimeController = new Controller<>();
private final Controller<WebContents> mWebContentsState = new Controller<>();
private String mInstanceId;
private CastAudioManager mAudioManager;
private WindowAndroid mWindow;
private ContentViewCore mContentViewCore;
private ContentView mContentView;
{
// Construct an Observable that is deactivated when either mWebContentsState or
// mLifetimeController is reset, and has the activation data of mWebContentsState.
Observable<WebContents> hasWebContentsState =
mWebContentsState.and(mLifetimeController).map(Both::getFirst);
// React to web contents by presenting them in a headless view.
hasWebContentsState.watch(CastWebContentsView.withoutLayout(this));
hasWebContentsState.watch(() -> {
if (DEBUG) Log.d(TAG, "show web contents");
// TODO(thoren): Notification.Builder(Context) is deprecated in O. Use the
// (Context, String) constructor when CastWebContentsService starts supporting O.
Notification notification = new Notification.Builder(this).build();
startForeground(CAST_NOTIFICATION_ID, notification);
return () -> {
if (DEBUG) Log.d(TAG, "detach web contents");
stopForeground(true /*removeNotification*/);
// Inform CastContentWindowAndroid we're detaching.
CastWebContentsComponent.onComponentClosed(mInstanceId);
};
});
}
protected void handleIntent(Intent intent) {
intent.setExtrasClassLoader(WebContents.class.getClassLoader());
......@@ -50,8 +68,7 @@ public class CastWebContentsService extends Service {
return;
}
detachWebContentsIfAny();
showWebContents(webContents);
mWebContentsState.set(webContents);
}
@Override
......@@ -65,14 +82,11 @@ public class CastWebContentsService extends Service {
@Override
public void onCreate() {
if (DEBUG) Log.d(TAG, "onCreate");
if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) {
Toast.makeText(this, R.string.browser_process_initialization_failed, Toast.LENGTH_SHORT)
.show();
stopSelf();
}
mWindow = new WindowAndroid(this);
CastAudioManager.getAudioManager(this).requestAudioFocusWhen(
mLifetimeController, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
mLifetimeController.set(Unit.unit());
......@@ -81,42 +95,7 @@ public class CastWebContentsService extends Service {
@Override
public IBinder onBind(Intent intent) {
if (DEBUG) Log.d(TAG, "onBind");
handleIntent(intent);
return null;
}
// Sets webContents to be the currently displayed webContents.
// TODO(thoren): Notification.Builder(Context) is deprecated in O. Use the (Context, String)
// constructor when CastWebContentsService starts supporting O.
@SuppressWarnings("deprecation")
private void showWebContents(WebContents webContents) {
if (DEBUG) Log.d(TAG, "showWebContents");
Notification notification = new Notification.Builder(this).build();
startForeground(CAST_NOTIFICATION_ID, notification);
mContentView = ContentView.createContentView(this, webContents);
// TODO(derekjchow): productVersion
mContentViewCore = ContentViewCore.create(this, "", webContents,
ViewAndroidDelegate.createBasicDelegate(mContentView), mContentView, mWindow);
// Enable display of current webContents.
webContents.onShow();
}
// Remove the currently displayed webContents. no-op if nothing is being displayed.
private void detachWebContentsIfAny() {
if (DEBUG) Log.d(TAG, "detachWebContentsIfAny");
stopForeground(true /*removeNotification*/);
if (mContentView != null) {
mContentView = null;
mContentViewCore = null;
// Inform CastContentWindowAndroid we're detaching.
CastWebContentsComponent.onComponentClosed(mInstanceId);
}
}
}
// Copyright 2018 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.chromecast.shell;
import android.content.Context;
import android.widget.FrameLayout;
import org.chromium.chromecast.base.Observable;
import org.chromium.chromecast.base.ScopeFactory;
import org.chromium.components.content_view.ContentView;
import org.chromium.content.browser.ContentViewRenderView;
import org.chromium.content_public.browser.ContentViewCore;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.ViewAndroidDelegate;
import org.chromium.ui.base.WindowAndroid;
class CastWebContentsView {
public static ScopeFactory<WebContents> onLayout(
Context context, FrameLayout layout, int backgroundColor, Observable<?> resumedState) {
layout.setBackgroundColor(backgroundColor);
return (WebContents webContents) -> {
WindowAndroid window = new WindowAndroid(context);
ContentViewRenderView contentViewRenderView = new ContentViewRenderView(context) {
@Override
protected void onReadyToRender() {
setOverlayVideoMode(true);
}
};
contentViewRenderView.onNativeLibraryLoaded(window);
contentViewRenderView.setSurfaceViewBackgroundColor(backgroundColor);
FrameLayout.LayoutParams matchParent = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
layout.addView(contentViewRenderView, matchParent);
ContentView contentView = ContentView.createContentView(context, webContents);
// TODO(derekjchow): productVersion
ContentViewCore contentViewCore = ContentViewCore.create(context, "", webContents,
ViewAndroidDelegate.createBasicDelegate(contentView), contentView, window);
// Enable display of current webContents.
webContents.onShow();
layout.addView(contentView, matchParent);
contentView.setFocusable(true);
contentView.requestFocus();
contentViewRenderView.setCurrentWebContents(webContents);
resumedState.watch(() -> {
contentViewCore.onResume();
return contentViewCore::onPause;
});
return () -> {
layout.removeView(contentView);
layout.removeView(contentViewRenderView);
contentViewCore.destroy();
contentViewRenderView.destroy();
window.destroy();
};
};
}
public static ScopeFactory<WebContents> withoutLayout(Context context) {
return (WebContents webContents) -> {
WindowAndroid window = new WindowAndroid(context);
ContentView contentView = ContentView.createContentView(context, webContents);
// TODO(derekjchow): productVersion
ContentViewCore contentViewCore = ContentViewCore.create(context, "", webContents,
ViewAndroidDelegate.createBasicDelegate(contentView), contentView, window);
// Enable display of current webContents.
webContents.onShow();
return webContents::onHide;
};
}
}
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