Commit 2db34c22 authored by Simeon Anfinrud's avatar Simeon Anfinrud Committed by Commit Bot

[Chromecast] Extract StartParams class for CWCSurfaceHelper.

This de-duplicates logic that appears in CastWebContentsActivity
and CastWebContentsFragment, and allows some of the complicated
procedural logic that appeared in CastWebContentsActivity's
handleIntent() method to be expressed in a purely reactive
paradigm.

Added tests for Intent handling in CastWebContentsActivity.

To make a clean, readable way to express that duplicate Intents
should be deleted, this patch adds a unique() method to
Observable, which creates an Observable that will not activate
if its source is activated with a value that is equivalent to
the previous activation value.

To make the implementation of this new method readable, first()
and changes() methods were also added to Observable. These are
likely to be useful elsewhere.

Bug: Internal b/36777136
Test: cast_base_junit_tests, cast_shell_junit_tests
Change-Id: I42871ef4bcd5b20cba813edf5ef92e0a255bb1d8
Reviewed-on: https://chromium-review.googlesource.com/957873
Commit-Queue: Simeon Anfinrud <sanfin@chromium.org>
Reviewed-by: default avatarLuke Halliwell <halliwell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#548126}
parent ae73061c
......@@ -281,6 +281,7 @@ if (is_android) {
java_files = [
"$java_src_dir/org/chromium/chromecast/base/BiConsumer.java",
"$java_src_dir/org/chromium/chromecast/base/BiFunction.java",
"$java_src_dir/org/chromium/chromecast/base/BiPredicate.java",
"$java_src_dir/org/chromium/chromecast/base/Both.java",
"$java_src_dir/org/chromium/chromecast/base/CircularBuffer.java",
"$java_src_dir/org/chromium/chromecast/base/Controller.java",
......
// 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.base;
/**
* A function that takes two arguments and returns a boolean.
*
* TODO(sanfin): replace with Java 8 library if we're ever able to use the Java 8 library.
*
* @param <A> The first argument type.
* @param <B> The second argument type.
*/
public interface BiPredicate<A, B> { public boolean test(A a, B b); }
......@@ -67,4 +67,11 @@ public class Both<A, B> {
public static <A, B> Consumer<Both<A, B>> adapt(BiConsumer<A, B> consumer) {
return (Both<A, B> data) -> consumer.accept(data.first, data.second);
}
/**
* Turns a predicate of two arguments into a predicate of a single Both argument.
*/
public static <A, B> Predicate<Both<A, B>> adapt(BiPredicate<A, B> predicate) {
return (Both<A, B> data) -> predicate.test(data.first, data.second);
}
}
......@@ -96,6 +96,61 @@ public abstract class Observable<T> {
return controller;
}
/**
* Returns an Observable that is activated only when `this` is first activated, and is not
* activated an subsequent activations of `this`.
*
* This is useful for ensuring that a callback registered with watch() is only run once.
*/
public final Observable<T> first() {
return new FirstActivationStateObserver<>(this).asObservable();
}
/**
* Returns an Observable that is activated when `this` is activated any time besides the first,
* and provides as activation data a `Both` object containing the previous and new activation
* data of `this`.
*
* This is useful if registered callbacks need to know the data of the previous activation.
*/
public final Observable<Both<T, T>> changes() {
return new ChangeStateObserver<>(this).asObservable();
}
/**
* Returns an Observable that does not activate if `this` is set with a value such that the
* given predicate returns true for the previous value and the current value.
*
* Can be used to ignore repeat activations that contain the same data. Beware that even though
* a repeat activation that passes the given predicate will not re-activate the new Observable,
* it will deactivate it.
*/
public final Observable<T> unique(BiPredicate<? super T, ? super T> predicate) {
Controller<T> controller = new Controller<>();
ScopeFactory<T> pipeToController = (T value) -> {
controller.set(value);
return controller::reset;
};
first().watch(pipeToController);
changes()
.filter(Both.adapt((T a, T b) -> !predicate.test(a, b)))
.map(Both::getSecond)
.watch(pipeToController);
return controller;
}
/**
* Returns an Observable that does not activate if `this` is activated with a value that is
* equal to the data of a previous activation, according to that data's `equals()` method.
*
* Can be used to ignore repeat activations that contain the same data. Beware that even though
* a repeat activation that passes the given predicate will not re-activate the new Observable,
* it will deactivate it.
*/
public final Observable<T> unique() {
return unique(Object::equals);
}
/**
* Returns an Observable that is activated only when the given Observable is not activated.
*/
......@@ -166,4 +221,45 @@ public abstract class Observable<T> {
return mController;
}
}
// Owns a Controller that is activated only on the Observable's first activation.
private static class FirstActivationStateObserver<T> {
private final Controller<T> mController = new Controller<>();
private boolean mIsActivated = false;
private FirstActivationStateObserver(Observable<T> state) {
state.watch((T value) -> {
if (!mIsActivated) {
mController.set(value);
mIsActivated = true;
}
return mController::reset;
});
}
private Observable<T> asObservable() {
return mController;
}
}
// Owns a Controller that is activated on non-first activations with the previous and new
// activation data.
private static class ChangeStateObserver<T> {
private final Controller<Both<T, T>> mController = new Controller<>();
private T mCurrent = null;
private ChangeStateObserver(Observable<T> state) {
state.watch((T value) -> {
if (mCurrent != null) {
mController.set(Both.both(mCurrent, value));
}
mCurrent = value;
return mController::reset;
});
}
private Observable<Both<T, T>> asObservable() {
return mController;
}
}
}
......@@ -6,7 +6,9 @@ package org.chromium.chromecast.base;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -67,7 +69,14 @@ public class BothTest {
@Test
public void testAdaptBiConsumer() {
List<String> result = new ArrayList<>();
Both.adapt((String a, String b) -> result.add(a + b)).apply(Both.both("A", "B"));
Both.adapt((String a, String b) -> { result.add(a + b); }).accept(Both.both("A", "B"));
assertThat(result, contains("AB"));
}
@Test
public void testAdaptBiPredicate() {
Predicate<Both<String, String>> p = Both.adapt(String::equals);
assertTrue(p.test(Both.both("a", "a")));
assertFalse(p.test(Both.both("a", "b")));
}
}
......@@ -261,6 +261,72 @@ public class ObservableAndControllerTest {
assertThat(result, contains("enter all four: A, B, C, D", "exit all four"));
}
@Test
public void testFirst() {
Controller<String> a = new Controller<>();
List<String> result = new ArrayList<>();
a.first().watch(report(result, "first"));
a.set("first");
a.set("second");
assertThat(result, contains("enter first: first", "exit first"));
}
@Test
public void testChanges() {
Controller<String> a = new Controller<>();
List<String> result = new ArrayList<>();
a.changes().watch(report(result, "changes"));
a.set("first");
a.set("second");
a.set("third");
assertThat(result,
contains("enter changes: first, second", "exit changes",
"enter changes: second, third"));
}
@Test
public void testChangesIsResetWhenSourceIsReset() {
Controller<String> a = new Controller<>();
List<String> result = new ArrayList<>();
a.changes().watch(report(result, "changes"));
a.set("first");
a.set("second");
a.reset();
assertThat(result, contains("enter changes: first, second", "exit changes"));
}
@Test
public void testUniqueDefault() {
Controller<String> a = new Controller<>();
List<String> result = new ArrayList<>();
a.unique().watch(report(result, "unique"));
a.set("hi");
a.set("ho");
a.set("hey");
a.set("hey");
a.set("hey");
a.set("hi");
assertThat(result,
contains("enter unique: hi", "exit unique", "enter unique: ho", "exit unique",
"enter unique: hey", "exit unique", "enter unique: hi"));
}
@Test
public void testUniqueWithCustomPredicate() {
Controller<String> a = new Controller<>();
List<String> result = new ArrayList<>();
a.unique((p, s) -> p.equalsIgnoreCase(s)).watch(report(result, "unique ignore case"));
a.set("kale");
a.set("KALE");
a.set("steamed kale");
a.set("STEAMED");
a.set("sTeAmEd");
assertThat(result,
contains("enter unique ignore case: kale", "exit unique ignore case",
"enter unique ignore case: steamed kale", "exit unique ignore case",
"enter unique ignore case: STEAMED", "exit unique ignore case"));
}
@Test
public void testMap() {
Controller<String> a = new Controller<>();
......
......@@ -383,7 +383,7 @@ Example:
}
```
### Composing Observables with and()
### Composing Observables with `and()`
In the motivating example, we wanted to invoke a callback once *two* independent
states have been activated.
......@@ -469,7 +469,7 @@ a cost to readability. The compiler can catch you if you mess up the
that this much work is required to read the compound data. Some methods for
alleviating this are described below.
### Imposing order dependency
### Imposing order dependency with `andThen()`
Every composition of states up to this point has been *time-independent*. For
example, `stateA.and(stateB)` doesn't care if `stateA` or `stateB` was activated
......@@ -502,6 +502,151 @@ Calling `stateA.andThen(stateB)` returns an `Observable` representing the
on the transition between `(just A)` and `(A and then B)`, and will not activate
on the transition between `(just B)` and `(B and then A)`.
### One-time callbacks with `first()`
Sometimes you want to ensure that an action is only performed once, even if it's
triggered by something that might happen more than once. With the `first()`
method, doing so is a breeze:
```java
Controller<Unit> windowFocusState = new Controller<>();
windowFocusState.first().watch(ScopeFactories.onEnter(this:: createWindow);
windowFocusState.set(Unit.unit()); // Will invoke createWindow()
windowFocusState.reset();
windowFocusState.set(Unit.unit()); // Does not invoke createWindow()
```
Note that the `Observable` created by `first()` will be deactivated the first
time that the source `Observable` is deactivated:
```java
Controller<String> messageState = new Controller<>();
messageState.first().watch(message -> {
Log.d(TAG, "first message: " + message);
return () -> Log.d(TAG, "first message discarded");
});
messageState.set("hello"); // Logs "first message: hello"
messageState.reset(); // Logs "first message discarded"
messageState.set("hello?"); // Nothing gets logged
```
### Compare old and new activation values with `changes()`
One drawback of the `watch()` method is that it provides only the activation
data with no other context. This is usually a good thing in that it helps with
encapsulation, but there are a number of applications where information about
the *previous activation* is useful. For example, say we want to log the delta
between volume levels:
```java
private final Controller<Integer> mVolumeLevel = new Controller<>();
private void logVolumeChanges(int oldLevel, int newLevel) {
if (newLevel > oldLevel) {
Log.d(TAG, "Volume increased by " + (newLevel - oldLevel));
} else if (newLevel < oldLevel) {
Log.d(TAG, "Volume decreased by " + (oldLevel - newLevel));
}
}
{
// How do we react to mVolumeLevel with logVolumeChanges()?
mVolumeLevel.set(0);
}
public void setVolume(int volume) {
mVolumeLevel.set(volume);
}
```
The problem here is that if you `watch()` `mVolumeLevel`, you will know the new
volume, but not the old volume, so a stateless lambda will not know enough to
call `logVolumeChanges()`.
If you tried, you'd probably have to end up creating a custom `ScopeFactory`
implementation that stores some internal state that is changed when activated.
But fortunately, an easier way is provided that doesn't force you to put state
into your `ScopeFactory`: the `changes()` method.
When you call `changes()` on an `Observable`, the resulting `Observable` will
be activated with a `Both` object containing the previous and new activation
data. Here's how you would use it in the above example:
```java
{
Observable<Both<Integer, Integer>> volumeChanges =
mVolumeLevel.changes()
volumeChanges.watch(ScopeFactories.onEnter((oldLevel, newLevel) -> {
logVolumeChanges(oldLevel, newLevel);
}));
}
```
Note: the above is using the `BiConsumer` version of `ScopeFactories.onEnter()`,
so it creates a `ScopeFactory<Both<Integer, Integer>>`, which is just the type
that we need. More info is given in the section on `ScopeFactories`.
### Ignore duplicates with `unique()`
Consider this:
```java
Controller<Intege> volumeLevel = new Controller<>();
volumeLevel.watch(ScopeFactories.onEnter(level -> {
Log.d(TAG, "New volume level: " + level);
}));
volumeLevel.set(3); // Logs "New volume level: 3"
volumeLevel.set(3); // Logs "New volume level: 3"... again
volumeLevel.set(3); // Logs "New volume level: 3"... yet again
volumeLevel.set(4); // Logs "New volume level: 4"... finally something new!
```
What if we only care about *changes* to the volume? Well, we could modify the
above section on `changes()` a bit, but it means we'd have to work with
two-argument functions when we really only care about one.
Fortunately, the `unique()` method is here to help:
```java
Controller<Intege> volumeLevel = new Controller<>();
Observable<Integer> uniqueVolumeLevel = volumeLevel.unique();
uniqueVolumeLevel.watch(ScopeFactories.onEnter(level -> {
Log.d(TAG, "New volume level: " + level);
}));
volumeLevel.set(3); // Logs "New volume level: 3"
volumeLevel.set(3); // Does not log.
volumeLevel.set(3); // Does not log.
volumeLevel.set(4); // Logs "New volume level: 4"
```
The `unique()` method returns an `Observable` that is only activated when the
source `Observable` gets *fresh* activation data, and ignores duplicate
activations.
By default `unique()` filters objects using the `equals()` method, but you can
optionally supply a custom `BiPredicate<T, T>`, a function that compares two
instances of `T` and returns `true` if, for your purposes, they should be
considered "equal".
For example, let's say we want to log *severe* volume changes, such as changes
by more than 10 steps at a time:
```java
Controller<Intege> volumeLevel = new Controller<>();
Observable<Integer> similarVolumeLevels = volumeLevel.unique(
(oldLevel, newLevel) -> {
return Math.abs(oldLevel - newLevel) < 10;
});
similarVolumeLevels.watch(ScopeFactories.onEnter(level -> {
Log.d(TAG, "Radically new volume level: " + level);
}));
volumeLevel.set(3); // Logs "Radically new volume level: 3"
volumeLevel.set(4); // Does not log.
volumeLevel.set(5); // Does not log.
volumeLevel.set(30); // Logs "Radically new volume level: 30"
```
### Increase readability for ScopeFactories with wrapper methods
The `ScopeFactories` class contains several helper methods to increase the
......
......@@ -8,7 +8,6 @@ import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.MotionEvent;
......@@ -24,7 +23,6 @@ import org.chromium.chromecast.base.Controller;
import org.chromium.chromecast.base.Observable;
import org.chromium.chromecast.base.ScopeFactories;
import org.chromium.chromecast.base.Unit;
import org.chromium.content_public.browser.WebContents;
/**
* Activity for displaying a WebContents in CastShell.
......@@ -110,7 +108,10 @@ public class CastWebContentsActivity extends Activity {
}));
// Handle each new Intent.
hasIntentState.watch(ScopeFactories.onEnter(this ::handleIntent));
hasIntentState.map(Intent::getExtras)
.map(CastWebContentsSurfaceHelper.StartParams::fromBundle)
.unique((previous, current) -> previous.uri.equals(current.uri))
.watch(ScopeFactories.onEnter(this ::notifyNewWebContents));
mIsFinishingState.watch(ScopeFactories.onEnter((String reason) -> {
if (DEBUG) Log.d(TAG, "Finishing activity: " + reason);
......@@ -135,32 +136,8 @@ public class CastWebContentsActivity extends Activity {
mGotIntentState.set(getIntent());
}
private void handleIntent(Intent intent) {
final Bundle bundle = intent.getExtras();
if (bundle == null) {
Log.i(TAG, "Intent without bundle received!");
return;
}
final String uriString = CastWebContentsIntentUtils.getUriString(intent);
if (uriString == null) {
Log.i(TAG, "Intent without uri received!");
return;
}
final Uri uri = Uri.parse(uriString);
// Do not load the WebContents if we are simply bringing the same
// activity to the foreground.
if (mSurfaceHelper.getInstanceId() != null
&& mSurfaceHelper.getInstanceId().equals(uri.getPath())) {
Log.i(TAG, "Duplicated intent received!");
return;
}
bundle.setClassLoader(WebContents.class.getClassLoader());
final WebContents webContents = CastWebContentsIntentUtils.getWebContents(intent);
final boolean touchInputEnabled = CastWebContentsIntentUtils.isTouchable(intent);
mSurfaceHelper.onNewWebContents(uri, webContents, touchInputEnabled);
private void notifyNewWebContents(CastWebContentsSurfaceHelper.StartParams params) {
if (mSurfaceHelper != null) mSurfaceHelper.onNewStartParams(params);
}
@Override
......@@ -294,4 +271,9 @@ public class CastWebContentsActivity extends Activity {
public void setAudioManagerForTesting(CastAudioManager audioManager) {
mAudioManagerState.set(audioManager);
}
@RemovableInRelease
public void setSurfaceHelperForTesting(CastWebContentsSurfaceHelper surfaceHelper) {
mSurfaceHelper = surfaceHelper;
}
}
......@@ -7,7 +7,6 @@ package org.chromium.chromecast.shell;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
......@@ -17,7 +16,6 @@ import android.widget.Toast;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.content_public.browser.WebContents;
/**
* Fragment for displaying a WebContents in CastShell.
......@@ -93,29 +91,17 @@ public class CastWebContentsFragment extends Fragment {
(FrameLayout) getView().findViewById(R.id.web_contents_container),
true /* showInFragment */);
Bundle bundle = getArguments();
CastWebContentsSurfaceHelper.StartParams params =
CastWebContentsSurfaceHelper.StartParams.fromBundle(bundle);
if (params == null) return;
String uriString = CastWebContentsIntentUtils.getUriString(bundle);
if (uriString == null) {
return;
}
Uri uri = Uri.parse(uriString);
WebContents webContents = CastWebContentsIntentUtils.getWebContents(bundle);
mAppId = CastWebContentsIntentUtils.getAppId(bundle);
mInitialVisiblityPriority = CastWebContentsIntentUtils.getVisibilityPriority(bundle);
boolean touchInputEnabled = CastWebContentsIntentUtils.isTouchable(bundle);
mSurfaceHelper.onNewWebContents(uri, webContents, touchInputEnabled);
mSurfaceHelper.onNewStartParams(params);
sendIntentSync(CastWebContentsIntentUtils.onVisibilityChange(mSurfaceHelper.getInstanceId(),
CastWebContentsIntentUtils.VISIBITY_TYPE_FULL_SCREEN));
}
@Override
public void setArguments(Bundle args) {
super.setArguments(args);
args.setClassLoader(WebContents.class.getClassLoader());
}
@Override
public void onPause() {
Log.d(TAG, "onPause");
......
......@@ -11,6 +11,7 @@ import android.content.IntentFilter;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.widget.FrameLayout;
......@@ -64,6 +65,50 @@ class CastWebContentsSurfaceHelper {
// TODO(vincentli) interrupt touch event from Fragment's root view when it's false.
private boolean mTouchInputEnabled = false;
public static class StartParams {
public final Uri uri;
public final WebContents webContents;
public final boolean touchInputEnabled;
public StartParams(Uri uri, WebContents webContents, boolean touchInputEnabled) {
this.uri = uri;
this.webContents = webContents;
this.touchInputEnabled = touchInputEnabled;
}
@Override
public boolean equals(Object other) {
if (other instanceof StartParams) {
StartParams that = (StartParams) other;
return this.uri.equals(that.uri) && this.webContents.equals(that.webContents)
&& this.touchInputEnabled == that.touchInputEnabled;
}
return false;
}
public static StartParams fromBundle(Bundle bundle) {
final String uriString = CastWebContentsIntentUtils.getUriString(bundle);
if (uriString == null) {
Log.i(TAG, "Intent without uri received!");
return null;
}
final Uri uri = Uri.parse(uriString);
if (uri == null) {
Log.i(TAG, "Invalid URI string: %s", uriString);
return null;
}
bundle.setClassLoader(WebContents.class.getClassLoader());
final WebContents webContents = CastWebContentsIntentUtils.getWebContents(bundle);
if (webContents == null) {
Log.e(TAG, "Received null WebContents in bundle.");
return null;
}
final boolean touchInputEnabled = CastWebContentsIntentUtils.isTouchable(bundle);
return new StartParams(uri, webContents, touchInputEnabled);
}
}
/**
* @param hostActivity Activity hosts the view showing WebContents
* @param castWebContentsLayout view group to add ContentViewRenderView and ContentView
......@@ -133,19 +178,11 @@ class CastWebContentsSurfaceHelper {
});
}
void onNewWebContents(
final Uri uri, final WebContents webContents, final boolean touchInputEnabled) {
if (webContents == null) {
Log.e(TAG, "Received null WebContents in bundle.");
maybeFinishLater();
return;
}
mTouchInputEnabled = touchInputEnabled;
Log.d(TAG, "content_uri=" + uri);
mUri = uri;
mInstanceId = uri.getPath();
void onNewStartParams(final StartParams params) {
mTouchInputEnabled = params.touchInputEnabled;
Log.d(TAG, "content_uri=" + params.uri);
mUri = params.uri;
mInstanceId = params.uri.getPath();
// Whenever our app is visible, volume controls should modify the music stream.
// For more information read:
......@@ -154,7 +191,7 @@ class CastWebContentsSurfaceHelper {
mHasUriState.set(mUri);
showWebContents(webContents);
showWebContents(params.webContents);
}
// Closes this activity if a new WebContents is not being displayed.
......
......@@ -7,7 +7,10 @@ package org.chromium.chromecast.shell;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.anyObject;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import android.content.Intent;
......@@ -16,6 +19,8 @@ import android.media.AudioManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
......@@ -24,6 +29,7 @@ import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowActivity;
import org.robolectric.shadows.ShadowAudioManager;
import org.chromium.content_public.browser.WebContents;
import org.chromium.testing.local.LocalRobolectricTestRunner;
/**
......@@ -37,10 +43,18 @@ public class CastWebContentsActivityTest {
private ActivityController<CastWebContentsActivity> mActivityLifecycle;
private CastWebContentsActivity mActivity;
private ShadowActivity mShadowActivity;
private @Mock WebContents mWebContents;
private static Intent defaultIntentForCastWebContentsActivity(WebContents webContents) {
return CastWebContentsIntentUtils.requestStartCastActivity(
RuntimeEnvironment.application, webContents, true, "0");
}
@Before
public void setUp() {
mActivityLifecycle = Robolectric.buildActivity(CastWebContentsActivity.class);
MockitoAnnotations.initMocks(this);
mActivityLifecycle = Robolectric.buildActivity(CastWebContentsActivity.class,
defaultIntentForCastWebContentsActivity(mWebContents));
mActivity = mActivityLifecycle.get();
mActivity.testingModeForTesting();
mShadowActivity = Shadows.shadowOf(mActivity);
......@@ -104,4 +118,57 @@ public class CastWebContentsActivityTest {
mActivityLifecycle.pause();
verify(mockAudioManager).releaseStreamMuteIfNecessary(AudioManager.STREAM_MUSIC);
}
@Test
public void testDropsIntentWithoutUri() {
CastWebContentsSurfaceHelper surfaceHelper = mock(CastWebContentsSurfaceHelper.class);
WebContents newWebContents = mock(WebContents.class);
Intent intent = CastWebContentsIntentUtils.requestStartCastActivity(
RuntimeEnvironment.application, newWebContents, true, null);
intent.removeExtra(CastWebContentsIntentUtils.INTENT_EXTRA_URI);
mActivity.setSurfaceHelperForTesting(surfaceHelper);
mActivityLifecycle.create();
reset(surfaceHelper);
mActivityLifecycle.newIntent(intent);
verify(surfaceHelper, never()).onNewStartParams(anyObject());
}
@Test
public void testDropsIntentWithoutWebContents() {
CastWebContentsSurfaceHelper surfaceHelper = mock(CastWebContentsSurfaceHelper.class);
Intent intent = CastWebContentsIntentUtils.requestStartCastActivity(
RuntimeEnvironment.application, null, true, "1");
mActivity.setSurfaceHelperForTesting(surfaceHelper);
mActivityLifecycle.create();
reset(surfaceHelper);
mActivityLifecycle.newIntent(intent);
verify(surfaceHelper, never()).onNewStartParams(anyObject());
}
@Test
public void testNotifiesSurfaceHelperWithValidIntent() {
CastWebContentsSurfaceHelper surfaceHelper = mock(CastWebContentsSurfaceHelper.class);
WebContents newWebContents = mock(WebContents.class);
Intent intent = CastWebContentsIntentUtils.requestStartCastActivity(
RuntimeEnvironment.application, newWebContents, true, "2");
mActivity.setSurfaceHelperForTesting(surfaceHelper);
mActivityLifecycle.create();
reset(surfaceHelper);
mActivityLifecycle.newIntent(intent);
verify(surfaceHelper)
.onNewStartParams(new CastWebContentsSurfaceHelper.StartParams(
CastWebContentsIntentUtils.getInstanceUri("2"), newWebContents, true));
}
@Test
public void testDropsIntentWithDuplicateUri() {
CastWebContentsSurfaceHelper surfaceHelper = mock(CastWebContentsSurfaceHelper.class);
mActivity.setSurfaceHelperForTesting(surfaceHelper);
mActivityLifecycle.create();
reset(surfaceHelper);
// Send duplicate Intent.
Intent intent = defaultIntentForCastWebContentsActivity(mWebContents);
mActivityLifecycle.newIntent(intent);
verify(surfaceHelper, never()).onNewStartParams(anyObject());
}
}
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