Interact with DreamOverlay through distinct clients.

DreamOverlay clients currently interact with the service endpoint
through a single binder interface. From the DreamOverlay perspective,
it is impossible to distinguish between clients. This makes it
difficult to understand if requests are coming from the currently
active client. This surfaces in the product as two dreams that
interfere with each other. For example, an exiting dream might send
the dream ending signal after the current dream has started.

This changelist introduces a DreamOverlay client binder interface,
which is minted by the DreamOverlayService on demand. The existing
actions on the overlay have been moved to this client. The
DreamOverlayService maintains a reference to the active client so
that it can verify which one can act upon the overlay.

Test: atest DreamOverlayServiceTest
Fixes: 266703170
Change-Id: I23e2e2adea626361bb9d2e6969341aade33b1a23
This commit is contained in:
Bryce Lee
2023-01-27 00:26:02 +00:00
parent 032f978c38
commit c7b7735cb4
7 changed files with 414 additions and 88 deletions

View File

@@ -36,39 +36,101 @@ import android.view.WindowManager;
public abstract class DreamOverlayService extends Service {
private static final String TAG = "DreamOverlayService";
private static final boolean DEBUG = false;
private boolean mShowComplications;
private ComponentName mDreamComponent;
private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
// The last client that started dreaming and hasn't ended
private OverlayClient mCurrentClient;
// An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
// requests to the {@link DreamOverlayService}
private static class OverlayClient extends IDreamOverlayClient.Stub {
private final DreamOverlayService mService;
private boolean mShowComplications;
private ComponentName mDreamComponent;
IDreamOverlayCallback mDreamOverlayCallback;
OverlayClient(DreamOverlayService service) {
mService = service;
}
@Override
public void startDream(WindowManager.LayoutParams layoutParams,
IDreamOverlayCallback callback, String dreamComponent,
boolean shouldShowComplications) {
mDreamOverlayCallback = callback;
public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
String dreamComponent, boolean shouldShowComplications) throws RemoteException {
mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
mShowComplications = shouldShowComplications;
onStartDream(layoutParams);
mDreamOverlayCallback = callback;
mService.startDream(this, params);
}
@Override
public void wakeUp() {
mService.wakeUp(this, () -> {
try {
mDreamOverlayCallback.onWakeUpComplete();
} catch (RemoteException e) {
Log.e(TAG, "Could not notify dream of wakeUp", e);
}
});
}
@Override
public void endDream() {
onEndDream();
mService.endDream(this);
}
private void onExitRequested() {
try {
mDreamOverlayCallback.onExitRequested();
} catch (RemoteException e) {
Log.e(TAG, "Could not request exit:" + e);
}
}
private boolean shouldShowComplications() {
return mShowComplications;
}
private ComponentName getComponent() {
return mDreamComponent;
}
}
private void startDream(OverlayClient client, WindowManager.LayoutParams params) {
endDream(mCurrentClient);
mCurrentClient = client;
onStartDream(params);
}
private void endDream(OverlayClient client) {
if (client == null || client != mCurrentClient) {
return;
}
onEndDream();
mCurrentClient = null;
}
private void wakeUp(OverlayClient client, Runnable callback) {
if (mCurrentClient != client) {
return;
}
onWakeUp(callback);
}
private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
@Override
public void wakeUp() {
onWakeUp(() -> {
try {
mDreamOverlayCallback.onWakeUpComplete();
} catch (RemoteException e) {
Log.e(TAG, "Could not notify dream of wakeUp:" + e);
}
});
public void getClient(IDreamOverlayClientCallback callback) {
try {
callback.onDreamOverlayClient(
new OverlayClient(DreamOverlayService.this));
} catch (RemoteException e) {
Log.e(TAG, "could not send client to callback", e);
}
}
};
IDreamOverlayCallback mDreamOverlayCallback;
public DreamOverlayService() {
}
@@ -110,18 +172,23 @@ public abstract class DreamOverlayService extends Service {
* This method is invoked to request the dream exit.
*/
public final void requestExit() {
try {
mDreamOverlayCallback.onExitRequested();
} catch (RemoteException e) {
Log.e(TAG, "Could not request exit:" + e);
if (mCurrentClient == null) {
throw new IllegalStateException("requested exit with no dream present");
}
mCurrentClient.onExitRequested();
}
/**
* Returns whether to show complications on the dream overlay.
*/
public final boolean shouldShowComplications() {
return mShowComplications;
if (mCurrentClient == null) {
throw new IllegalStateException(
"requested if should show complication when no dream active");
}
return mCurrentClient.shouldShowComplications();
}
/**
@@ -129,6 +196,10 @@ public abstract class DreamOverlayService extends Service {
* @hide
*/
public final ComponentName getDreamComponent() {
return mDreamComponent;
if (mCurrentClient == null) {
throw new IllegalStateException("requested dream component when no dream active");
}
return mCurrentClient.getComponent();
}
}

View File

@@ -248,25 +248,39 @@ public class DreamService extends Service implements Window.Callback {
private OverlayConnection mOverlayConnection;
private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> {
// Overlay set during onBind.
private IDreamOverlay mOverlay;
// Retrieved Client
private IDreamOverlayClient mClient;
// A list of pending requests to execute on the overlay.
private final ArrayList<Consumer<IDreamOverlay>> mConsumers = new ArrayList<>();
private final ArrayList<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>();
private final IDreamOverlayClientCallback mClientCallback =
new IDreamOverlayClientCallback.Stub() {
@Override
public void onDreamOverlayClient(IDreamOverlayClient client) {
mClient = client;
for (Consumer<IDreamOverlayClient> consumer : mConsumers) {
consumer.accept(mClient);
}
}
};
private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() {
@Override
public void onConnected(ObservableServiceConnection<IDreamOverlay> connection,
IDreamOverlay service) {
mOverlay = service;
for (Consumer<IDreamOverlay> consumer : mConsumers) {
consumer.accept(mOverlay);
try {
service.getClient(mClientCallback);
} catch (RemoteException e) {
Log.e(TAG, "could not get DreamOverlayClient", e);
}
}
@Override
public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
int reason) {
mOverlay = null;
mClient = null;
}
};
@@ -296,16 +310,16 @@ public class DreamService extends Service implements Window.Callback {
super.unbind();
}
public void addConsumer(Consumer<IDreamOverlay> consumer) {
public void addConsumer(Consumer<IDreamOverlayClient> consumer) {
execute(() -> {
mConsumers.add(consumer);
if (mOverlay != null) {
consumer.accept(mOverlay);
if (mClient != null) {
consumer.accept(mClient);
}
});
}
public void removeConsumer(Consumer<IDreamOverlay> consumer) {
public void removeConsumer(Consumer<IDreamOverlayClient> consumer) {
execute(() -> mConsumers.remove(consumer));
}
@@ -1365,7 +1379,7 @@ public class DreamService extends Service implements Window.Callback {
mWindow.getDecorView().addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
private Consumer<IDreamOverlay> mDreamStartOverlayConsumer;
private Consumer<IDreamOverlayClient> mDreamStartOverlayConsumer;
@Override
public void onViewAttachedToWindow(View v) {

View File

@@ -16,8 +16,7 @@
package android.service.dreams;
import android.service.dreams.IDreamOverlayCallback;
import android.view.WindowManager.LayoutParams;
import android.service.dreams.IDreamOverlayClientCallback;
/**
* {@link IDreamOverlay} provides a way for a component to annotate a dream with additional view
@@ -28,20 +27,7 @@ import android.view.WindowManager.LayoutParams;
*/
interface IDreamOverlay {
/**
* @param params The {@link LayoutParams} for the associated DreamWindow, including the window
token of the Dream Activity.
* @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
* dream.
* @param dreamComponent The component name of the dream service requesting overlay.
* @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
* and weather.
* Retrieves a client the caller can use to interact with the dream overlay.
*/
void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
in String dreamComponent, in boolean shouldShowComplications);
/** Called when the dream is waking, to do any exit animations */
void wakeUp();
/** Called when the dream has ended. */
void endDream();
void getClient(in IDreamOverlayClientCallback callback);
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2023, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.dreams;
import android.service.dreams.IDreamOverlayCallback;
import android.view.WindowManager.LayoutParams;
/**
* {@link IDreamOverlayClient} allows {@link DreamService} instances to act upon the dream overlay.
*
* @hide
*/
interface IDreamOverlayClient {
/**
* @param params The {@link LayoutParams} for the associated DreamWindow, including the window
token of the Dream Activity.
* @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
* dream.
* @param dreamComponent The component name of the dream service requesting overlay.
* @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
* and weather.
*/
void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
in String dreamComponent, in boolean shouldShowComplications);
/** Called when the dream is waking, to do any exit animations */
void wakeUp();
/** Called when the dream has ended. */
void endDream();
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2023, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.dreams;
import android.service.dreams.IDreamOverlayClient;
/**
* {@link IDreamOverlayClientCallback} allows receiving a requested {@link IDreamOverlayClient}.
* @hide
*/
interface IDreamOverlayClientCallback {
/**
* Called with a unique {@link IDreamOverlayClient}.
*/
void onDreamOverlayClient(in IDreamOverlayClient client);
}

View File

@@ -31,6 +31,8 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.service.dreams.IDreamOverlay;
import android.service.dreams.IDreamOverlayCallback;
import android.service.dreams.IDreamOverlayClient;
import android.service.dreams.IDreamOverlayClientCallback;
import android.testing.AndroidTestingRunner;
import android.view.View;
import android.view.ViewGroup;
@@ -58,6 +60,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SmallTest
@@ -148,13 +151,25 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
mDreamOverlayCallbackController);
}
@Test
public void testOnStartMetricsLogged() throws Exception {
public IDreamOverlayClient getClient() throws RemoteException {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
final IDreamOverlayClientCallback callback =
Mockito.mock(IDreamOverlayClientCallback.class);
overlay.getClient(callback);
final ArgumentCaptor<IDreamOverlayClient> clientCaptor =
ArgumentCaptor.forClass(IDreamOverlayClient.class);
verify(callback).onDreamOverlayClient(clientCaptor.capture());
return clientCaptor.getValue();
}
@Test
public void testOnStartMetricsLogged() throws Exception {
final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -165,11 +180,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testOverlayContainerViewAddedToWindow() throws Exception {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -178,11 +192,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testDreamOverlayContainerViewControllerInitialized() throws Exception {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -196,11 +209,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
.thenReturn(mDreamOverlayContainerViewParent)
.thenReturn(null);
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -209,11 +221,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testShouldShowComplicationsSetByStartDream() throws RemoteException {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
true /*shouldShowComplication*/);
assertThat(mService.shouldShowComplications()).isTrue();
@@ -221,11 +232,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testLowLightSetByStartDream() throws RemoteException {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback,
client.startDream(mWindowParams, mDreamOverlayCallback,
LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -235,11 +245,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testOnEndDream() throws RemoteException {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback,
client.startDream(mWindowParams, mDreamOverlayCallback,
LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -261,11 +270,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testDestroy() throws RemoteException {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback,
client.startDream(mWindowParams, mDreamOverlayCallback,
LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -305,15 +313,14 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
final IDreamOverlayClient client = getClient();
// Destroy the service.
mService.onDestroy();
mMainExecutor.runAllReady();
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -331,11 +338,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting. Do not show dream complications.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -352,7 +358,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
// New dream starting with dream complications showing. Note that when a new dream is
// binding to the dream overlay service, it receives the same instance of IBinder as the
// first one.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
true /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -371,11 +377,10 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testWakeUp() throws RemoteException {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
true /*shouldShowComplication*/);
mMainExecutor.runAllReady();

View File

@@ -0,0 +1,175 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.dreams;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.service.dreams.DreamOverlayService;
import android.service.dreams.IDreamOverlay;
import android.service.dreams.IDreamOverlayCallback;
import android.service.dreams.IDreamOverlayClient;
import android.service.dreams.IDreamOverlayClientCallback;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
/**
* A collection of tests to exercise {@link DreamOverlayService}.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DreamOverlayServiceTest {
private static final ComponentName FIRST_DREAM_COMPONENT =
ComponentName.unflattenFromString("com.foo.bar/.DreamService");
private static final ComponentName SECOND_DREAM_COMPONENT =
ComponentName.unflattenFromString("com.foo.baz/.DreamService");
@Mock
WindowManager.LayoutParams mLayoutParams;
@Mock
IDreamOverlayCallback mOverlayCallback;
/**
* {@link TestDreamOverlayService} is a simple {@link DreamOverlayService} implementation for
* tracking interactions across {@link IDreamOverlay} binder interface. The service reports
* interactions to a {@link Monitor} instance provided at construction.
*/
private static class TestDreamOverlayService extends DreamOverlayService {
/**
* An interface implemented to be informed when the corresponding methods in
* {@link TestDreamOverlayService} are invoked.
*/
interface Monitor {
void onStartDream();
void onEndDream();
void onWakeUp();
}
private final Monitor mMonitor;
TestDreamOverlayService(Monitor monitor) {
super();
mMonitor = monitor;
}
@Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
mMonitor.onStartDream();
}
@Override
public void onEndDream() {
mMonitor.onEndDream();
super.onEndDream();
}
@Override
public void onWakeUp(@NonNull Runnable onCompleteCallback) {
mMonitor.onWakeUp();
super.onWakeUp(onCompleteCallback);
}
}
/**
* A {@link IDreamOverlayClientCallback} implementation that captures the requested client.
*/
private static class OverlayClientCallback extends IDreamOverlayClientCallback.Stub {
public IDreamOverlayClient retrievedClient;
@Override
public void onDreamOverlayClient(IDreamOverlayClient client) throws RemoteException {
retrievedClient = client;
}
}
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
/**
* Verifies that only the currently started dream is able to affect the overlay.
*/
@Test
public void testOverlayClientInteraction() throws RemoteException {
final TestDreamOverlayService.Monitor monitor = Mockito.mock(
TestDreamOverlayService.Monitor.class);
final TestDreamOverlayService service = new TestDreamOverlayService(monitor);
final IBinder binder = service.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
// Create two overlay clients and ensure they are unique.
final IDreamOverlayClient firstClient = getClient(overlay);
assertThat(firstClient).isNotNull();
final IDreamOverlayClient secondClient = getClient(overlay);
assertThat(secondClient).isNotNull();
assertThat(firstClient).isNotEqualTo(secondClient);
// Start a dream with the first client and ensure the dream is now active from the
// overlay's perspective.
firstClient.startDream(mLayoutParams, mOverlayCallback,
FIRST_DREAM_COMPONENT.flattenToString(), false);
verify(monitor).onStartDream();
assertThat(service.getDreamComponent()).isEqualTo(FIRST_DREAM_COMPONENT);
Mockito.clearInvocations(monitor);
// Start a dream from the second client and verify that the overlay has both cycled to
// the new dream (ended/started).
secondClient.startDream(mLayoutParams, mOverlayCallback,
SECOND_DREAM_COMPONENT.flattenToString(), false);
verify(monitor).onEndDream();
verify(monitor).onStartDream();
assertThat(service.getDreamComponent()).isEqualTo(SECOND_DREAM_COMPONENT);
Mockito.clearInvocations(monitor);
// Verify that interactions with the first, now inactive client don't affect the overlay.
firstClient.endDream();
verify(monitor, never()).onEndDream();
firstClient.wakeUp();
verify(monitor, never()).onWakeUp();
}
private static IDreamOverlayClient getClient(IDreamOverlay overlay) throws RemoteException {
final OverlayClientCallback callback = new OverlayClientCallback();
overlay.getClient(callback);
return callback.retrievedClient;
}
}