Merge "Interact with DreamOverlay through distinct clients." into tm-qpr-dev

This commit is contained in:
Bryce Lee
2023-01-30 17:00:37 +00:00
committed by Android (Google) Code Review
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;
}
}