diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java index bf5b970311d0d..6e4535b7218a6 100644 --- a/core/java/android/service/dreams/DreamOverlayService.java +++ b/core/java/android/service/dreams/DreamOverlayService.java @@ -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(); } } diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index d3788862b6c06..d94e1c0493f4b 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -248,25 +248,39 @@ public class DreamService extends Service implements Window.Callback { private OverlayConnection mOverlayConnection; private static class OverlayConnection extends PersistentServiceConnection { - // 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> mConsumers = new ArrayList<>(); + private final ArrayList> mConsumers = new ArrayList<>(); + + private final IDreamOverlayClientCallback mClientCallback = + new IDreamOverlayClientCallback.Stub() { + @Override + public void onDreamOverlayClient(IDreamOverlayClient client) { + mClient = client; + + for (Consumer consumer : mConsumers) { + consumer.accept(mClient); + } + } + }; private final Callback mCallback = new Callback() { @Override public void onConnected(ObservableServiceConnection connection, IDreamOverlay service) { - mOverlay = service; - for (Consumer 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 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 consumer) { + public void addConsumer(Consumer consumer) { execute(() -> { mConsumers.add(consumer); - if (mOverlay != null) { - consumer.accept(mOverlay); + if (mClient != null) { + consumer.accept(mClient); } }); } - public void removeConsumer(Consumer consumer) { + public void removeConsumer(Consumer 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 mDreamStartOverlayConsumer; + private Consumer mDreamStartOverlayConsumer; @Override public void onViewAttachedToWindow(View v) { diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl index 0e4bd3bd547b6..7ec75a50ed00a 100644 --- a/core/java/android/service/dreams/IDreamOverlay.aidl +++ b/core/java/android/service/dreams/IDreamOverlay.aidl @@ -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); } diff --git a/core/java/android/service/dreams/IDreamOverlayClient.aidl b/core/java/android/service/dreams/IDreamOverlayClient.aidl new file mode 100644 index 0000000000000..78b7280ae652e --- /dev/null +++ b/core/java/android/service/dreams/IDreamOverlayClient.aidl @@ -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(); +} diff --git a/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl new file mode 100644 index 0000000000000..244d999c623bd --- /dev/null +++ b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl @@ -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); +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index 3b8bb8089910f..dfb4d5baeef5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -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 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(); diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java new file mode 100644 index 0000000000000..6c73f716493ce --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java @@ -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; + } +}