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") { ...@@ -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/CastWebContentsIntentUtils.java",
"$java_src_dir/org/chromium/chromecast/shell/CastWebContentsService.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/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/LogcatElision.java",
"$java_src_dir/org/chromium/chromecast/shell/ElidedLogcatProvider.java", "$java_src_dir/org/chromium/chromecast/shell/ElidedLogcatProvider.java",
] ]
...@@ -160,6 +161,7 @@ junit_binary("cast_shell_junit_tests") { ...@@ -160,6 +161,7 @@ junit_binary("cast_shell_junit_tests") {
"junit/src/org/chromium/chromecast/shell/CastWebContentsActivityTest.java", "junit/src/org/chromium/chromecast/shell/CastWebContentsActivityTest.java",
"junit/src/org/chromium/chromecast/shell/CastWebContentsComponentTest.java", "junit/src/org/chromium/chromecast/shell/CastWebContentsComponentTest.java",
"junit/src/org/chromium/chromecast/shell/CastWebContentsIntentUtilsTest.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/LocalBroadcastReceiverScopeTest.java",
"junit/src/org/chromium/chromecast/shell/LogcatElisionUnitTest.java", "junit/src/org/chromium/chromecast/shell/LogcatElisionUnitTest.java",
"junit/src/org/chromium/chromecast/shell/ElidedLogcatProviderUnitTest.java", "junit/src/org/chromium/chromecast/shell/ElidedLogcatProviderUnitTest.java",
......
...@@ -7,7 +7,9 @@ package org.chromium.chromecast.shell; ...@@ -7,7 +7,9 @@ package org.chromium.chromecast.shell;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.graphics.Color;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
...@@ -19,6 +21,7 @@ import android.widget.Toast; ...@@ -19,6 +21,7 @@ import android.widget.Toast;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.annotations.RemovableInRelease; import org.chromium.base.annotations.RemovableInRelease;
import org.chromium.chromecast.base.Both; import org.chromium.chromecast.base.Both;
import org.chromium.chromecast.base.CastSwitches;
import org.chromium.chromecast.base.Controller; import org.chromium.chromecast.base.Controller;
import org.chromium.chromecast.base.Observable; import org.chromium.chromecast.base.Observable;
import org.chromium.chromecast.base.ScopeFactories; import org.chromium.chromecast.base.ScopeFactories;
...@@ -86,8 +89,12 @@ public class CastWebContentsActivity extends Activity { ...@@ -86,8 +89,12 @@ public class CastWebContentsActivity extends Activity {
setContentView(R.layout.cast_web_contents_activity); setContentView(R.layout.cast_web_contents_activity);
mSurfaceHelper = new CastWebContentsSurfaceHelper(this, /* hostActivity */ mSurfaceHelper = new CastWebContentsSurfaceHelper(this, /* hostActivity */
(FrameLayout) findViewById(R.id.web_contents_container), CastWebContentsView.onLayout(getApplicationContext(),
false /* showInFragment */); (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. // Initialize the audio manager in onCreate() if tests haven't already.
...@@ -157,9 +164,6 @@ public class CastWebContentsActivity extends Activity { ...@@ -157,9 +164,6 @@ public class CastWebContentsActivity extends Activity {
if (DEBUG) Log.d(TAG, "onPause"); if (DEBUG) Log.d(TAG, "onPause");
super.onPause(); super.onPause();
mResumedState.reset(); mResumedState.reset();
if (mSurfaceHelper != null) {
mSurfaceHelper.onPause();
}
} }
@Override @Override
...@@ -167,9 +171,6 @@ public class CastWebContentsActivity extends Activity { ...@@ -167,9 +171,6 @@ public class CastWebContentsActivity extends Activity {
if (DEBUG) Log.d(TAG, "onResume"); if (DEBUG) Log.d(TAG, "onResume");
super.onResume(); super.onResume();
mResumedState.set(Unit.unit()); mResumedState.set(Unit.unit());
if (mSurfaceHelper != null) {
mSurfaceHelper.onResume();
}
} }
@Override @Override
......
...@@ -7,6 +7,8 @@ package org.chromium.chromecast.shell; ...@@ -7,6 +7,8 @@ package org.chromium.chromecast.shell;
import android.app.Fragment; import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
...@@ -16,6 +18,9 @@ import android.widget.Toast; ...@@ -16,6 +18,9 @@ import android.widget.Toast;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.Log; 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. * Fragment for displaying a WebContents in CastShell.
...@@ -31,14 +36,12 @@ import org.chromium.base.Log; ...@@ -31,14 +36,12 @@ import org.chromium.base.Log;
public class CastWebContentsFragment extends Fragment { public class CastWebContentsFragment extends Fragment {
private static final String TAG = "cr_CastWebContentFrg"; private static final String TAG = "cr_CastWebContentFrg";
private Context mPackageContext; private final Controller<Unit> mResumedState = new Controller<>();
private Context mPackageContext;
private CastWebContentsSurfaceHelper mSurfaceHelper; private CastWebContentsSurfaceHelper mSurfaceHelper;
private View mFragmentRootView; private View mFragmentRootView;
private String mAppId; private String mAppId;
private int mInitialVisiblityPriority; private int mInitialVisiblityPriority;
@Override @Override
...@@ -61,12 +64,11 @@ public class CastWebContentsFragment extends Fragment { ...@@ -61,12 +64,11 @@ public class CastWebContentsFragment extends Fragment {
} }
if (mFragmentRootView == null) { if (mFragmentRootView == null) {
mFragmentRootView = inflater.cloneInContext(getContext()) mFragmentRootView = inflater.cloneInContext(getContext())
.inflate(R.layout.cast_web_contents_activity, null); .inflate(R.layout.cast_web_contents_activity, null);
} }
return mFragmentRootView; return mFragmentRootView;
} }
@Override @Override
public Context getContext() { public Context getContext() {
if (mPackageContext == null) { if (mPackageContext == null) {
...@@ -88,8 +90,13 @@ public class CastWebContentsFragment extends Fragment { ...@@ -88,8 +90,13 @@ public class CastWebContentsFragment extends Fragment {
} }
mSurfaceHelper = new CastWebContentsSurfaceHelper(getActivity(), /* hostActivity */ mSurfaceHelper = new CastWebContentsSurfaceHelper(getActivity(), /* hostActivity */
(FrameLayout) getView().findViewById(R.id.web_contents_container), CastWebContentsView.onLayout(getContext(),
true /* showInFragment */); (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(); Bundle bundle = getArguments();
CastWebContentsSurfaceHelper.StartParams params = CastWebContentsSurfaceHelper.StartParams params =
CastWebContentsSurfaceHelper.StartParams.fromBundle(bundle); CastWebContentsSurfaceHelper.StartParams.fromBundle(bundle);
...@@ -106,18 +113,14 @@ public class CastWebContentsFragment extends Fragment { ...@@ -106,18 +113,14 @@ public class CastWebContentsFragment extends Fragment {
public void onPause() { public void onPause() {
Log.d(TAG, "onPause"); Log.d(TAG, "onPause");
super.onPause(); super.onPause();
if (mSurfaceHelper != null) { mResumedState.reset();
mSurfaceHelper.onPause();
}
} }
@Override @Override
public void onResume() { public void onResume() {
Log.d(TAG, "onResume"); Log.d(TAG, "onResume");
super.onResume(); super.onResume();
if (mSurfaceHelper != null) { mResumedState.set(Unit.unit());
mSurfaceHelper.onResume();
}
} }
@Override @Override
......
...@@ -13,13 +13,11 @@ import android.widget.Toast; ...@@ -13,13 +13,11 @@ import android.widget.Toast;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
import org.chromium.chromecast.base.Both;
import org.chromium.chromecast.base.Controller; import org.chromium.chromecast.base.Controller;
import org.chromium.chromecast.base.Observable;
import org.chromium.chromecast.base.Unit; 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.content_public.browser.WebContents;
import org.chromium.ui.base.ViewAndroidDelegate;
import org.chromium.ui.base.WindowAndroid;
/** /**
* Service for "displaying" a WebContents in CastShell. * Service for "displaying" a WebContents in CastShell.
...@@ -34,11 +32,31 @@ public class CastWebContentsService extends Service { ...@@ -34,11 +32,31 @@ public class CastWebContentsService extends Service {
private static final int CAST_NOTIFICATION_ID = 100; private static final int CAST_NOTIFICATION_ID = 100;
private final Controller<Unit> mLifetimeController = new Controller<>(); private final Controller<Unit> mLifetimeController = new Controller<>();
private final Controller<WebContents> mWebContentsState = new Controller<>();
private String mInstanceId; private String mInstanceId;
private CastAudioManager mAudioManager; 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) { protected void handleIntent(Intent intent) {
intent.setExtrasClassLoader(WebContents.class.getClassLoader()); intent.setExtrasClassLoader(WebContents.class.getClassLoader());
...@@ -50,8 +68,7 @@ public class CastWebContentsService extends Service { ...@@ -50,8 +68,7 @@ public class CastWebContentsService extends Service {
return; return;
} }
detachWebContentsIfAny(); mWebContentsState.set(webContents);
showWebContents(webContents);
} }
@Override @Override
...@@ -65,14 +82,11 @@ public class CastWebContentsService extends Service { ...@@ -65,14 +82,11 @@ public class CastWebContentsService extends Service {
@Override @Override
public void onCreate() { public void onCreate() {
if (DEBUG) Log.d(TAG, "onCreate"); if (DEBUG) Log.d(TAG, "onCreate");
if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) { if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) {
Toast.makeText(this, R.string.browser_process_initialization_failed, Toast.LENGTH_SHORT) Toast.makeText(this, R.string.browser_process_initialization_failed, Toast.LENGTH_SHORT)
.show(); .show();
stopSelf(); stopSelf();
} }
mWindow = new WindowAndroid(this);
CastAudioManager.getAudioManager(this).requestAudioFocusWhen( CastAudioManager.getAudioManager(this).requestAudioFocusWhen(
mLifetimeController, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); mLifetimeController, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
mLifetimeController.set(Unit.unit()); mLifetimeController.set(Unit.unit());
...@@ -81,42 +95,7 @@ public class CastWebContentsService extends Service { ...@@ -81,42 +95,7 @@ public class CastWebContentsService extends Service {
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
if (DEBUG) Log.d(TAG, "onBind"); if (DEBUG) Log.d(TAG, "onBind");
handleIntent(intent); handleIntent(intent);
return null; 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