From c7b7735cb46cee0006495cfa6442c872b598a493 Mon Sep 17 00:00:00 2001 From: Bryce Lee Date: Fri, 27 Jan 2023 00:26:02 +0000 Subject: [PATCH] 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 --- .../service/dreams/DreamOverlayService.java | 121 +++++++++--- .../android/service/dreams/DreamService.java | 38 ++-- .../android/service/dreams/IDreamOverlay.aidl | 20 +- .../service/dreams/IDreamOverlayClient.aidl | 45 +++++ .../dreams/IDreamOverlayClientCallback.aidl | 30 +++ .../dreams/DreamOverlayServiceTest.java | 73 ++++---- .../dreams/DreamOverlayServiceTest.java | 175 ++++++++++++++++++ 7 files changed, 414 insertions(+), 88 deletions(-) create mode 100644 core/java/android/service/dreams/IDreamOverlayClient.aidl create mode 100644 core/java/android/service/dreams/IDreamOverlayClientCallback.aidl create mode 100644 services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java 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; + } +}