1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.dreams;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.Mockito.any;
22 import static org.mockito.Mockito.doAnswer;
23 import static org.mockito.Mockito.never;
24 import static org.mockito.Mockito.verify;
25 
26 import android.content.ComponentName;
27 import android.content.Intent;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.service.dreams.DreamOverlayService;
31 import android.service.dreams.IDreamOverlay;
32 import android.service.dreams.IDreamOverlayCallback;
33 import android.service.dreams.IDreamOverlayClient;
34 import android.service.dreams.IDreamOverlayClientCallback;
35 import android.view.WindowManager;
36 
37 import androidx.annotation.NonNull;
38 import androidx.test.filters.SmallTest;
39 import androidx.test.runner.AndroidJUnit4;
40 
41 import org.junit.Before;
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 import org.mockito.ArgumentCaptor;
45 import org.mockito.Mock;
46 import org.mockito.Mockito;
47 import org.mockito.MockitoAnnotations;
48 
49 import java.util.concurrent.Executor;
50 
51 /**
52  * A collection of tests to exercise {@link DreamOverlayService}.
53  */
54 @SmallTest
55 @RunWith(AndroidJUnit4.class)
56 public class DreamOverlayServiceTest {
57     private static final ComponentName FIRST_DREAM_COMPONENT =
58             ComponentName.unflattenFromString("com.foo.bar/.DreamService");
59     private static final ComponentName SECOND_DREAM_COMPONENT =
60             ComponentName.unflattenFromString("com.foo.baz/.DreamService");
61 
62     @Mock
63     WindowManager.LayoutParams mLayoutParams;
64 
65     @Mock
66     IDreamOverlayCallback mOverlayCallback;
67 
68     @Mock
69     Executor mExecutor;
70 
71     /**
72      * {@link TestDreamOverlayService} is a simple {@link DreamOverlayService} implementation for
73      * tracking interactions across {@link IDreamOverlay} binder interface. The service reports
74      * interactions to a {@link Monitor} instance provided at construction.
75      */
76     private static class TestDreamOverlayService extends DreamOverlayService {
77         /**
78          * An interface implemented to be informed when the corresponding methods in
79          * {@link TestDreamOverlayService} are invoked.
80          */
81         interface Monitor {
onStartDream()82             void onStartDream();
onEndDream()83             void onEndDream();
onWakeUp()84             void onWakeUp();
85         }
86 
87         private final Monitor mMonitor;
88 
TestDreamOverlayService(Monitor monitor, Executor executor)89         TestDreamOverlayService(Monitor monitor, Executor executor) {
90             super(executor);
91             mMonitor = monitor;
92         }
93 
94         @Override
onStartDream(@onNull WindowManager.LayoutParams layoutParams)95         public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
96             mMonitor.onStartDream();
97         }
98 
99         @Override
onEndDream()100         public void onEndDream() {
101             mMonitor.onEndDream();
102             super.onEndDream();
103         }
104     }
105 
106     /**
107      * A {@link IDreamOverlayClientCallback} implementation that captures the requested client.
108      */
109     private static class OverlayClientCallback extends IDreamOverlayClientCallback.Stub {
110         public IDreamOverlayClient retrievedClient;
111         @Override
onDreamOverlayClient(IDreamOverlayClient client)112         public void onDreamOverlayClient(IDreamOverlayClient client) throws RemoteException {
113             retrievedClient = client;
114         }
115     }
116 
117     @Before
setup()118     public void setup() {
119         MockitoAnnotations.initMocks(this);
120     }
121 
122     /**
123      * Verifies that callbacks for subclasses are run on the provided executor.
124      */
125     @Test
testCallbacksRunOnExecutor()126     public void testCallbacksRunOnExecutor() throws RemoteException {
127         final TestDreamOverlayService.Monitor monitor = Mockito.mock(
128                 TestDreamOverlayService.Monitor.class);
129         final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor);
130         final IBinder binder = service.onBind(new Intent());
131         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
132 
133         final IDreamOverlayClient client = getClient(overlay);
134 
135         // Start the dream.
136         client.startDream(mLayoutParams, mOverlayCallback,
137                 FIRST_DREAM_COMPONENT.flattenToString(), false);
138 
139         // The callback should not have run yet.
140         verify(monitor, never()).onStartDream();
141 
142         // Run the Runnable sent to the executor.
143         ArgumentCaptor<Runnable> mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
144         verify(mExecutor).execute(mRunnableCaptor.capture());
145         mRunnableCaptor.getValue().run();
146 
147         // Callback is run.
148         verify(monitor).onStartDream();
149 
150         // Verify onWakeUp is run on the executor.
151         client.wakeUp();
152         verify(monitor, never()).onWakeUp();
153         mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
154         verify(mExecutor).execute(mRunnableCaptor.capture());
155         mRunnableCaptor.getValue().run();
156         verify(monitor).onWakeUp();
157 
158         // Verify onEndDream is run on the executor.
159         client.endDream();
160         verify(monitor, never()).onEndDream();
161         mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
162         verify(mExecutor).execute(mRunnableCaptor.capture());
163         mRunnableCaptor.getValue().run();
164         verify(monitor).onEndDream();
165     }
166 
167     /**
168      * Verifies that only the currently started dream is able to affect the overlay.
169      */
170     @Test
testOverlayClientInteraction()171     public void testOverlayClientInteraction() throws RemoteException {
172         doAnswer(invocation -> {
173             ((Runnable) invocation.getArgument(0)).run();
174             return null;
175         }).when(mExecutor).execute(any());
176 
177         final TestDreamOverlayService.Monitor monitor = Mockito.mock(
178                 TestDreamOverlayService.Monitor.class);
179         final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor);
180         final IBinder binder = service.onBind(new Intent());
181         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
182 
183         // Create two overlay clients and ensure they are unique.
184         final IDreamOverlayClient firstClient = getClient(overlay);
185         assertThat(firstClient).isNotNull();
186 
187         final IDreamOverlayClient secondClient = getClient(overlay);
188         assertThat(secondClient).isNotNull();
189 
190         assertThat(firstClient).isNotEqualTo(secondClient);
191 
192         // Start a dream with the first client and ensure the dream is now active from the
193         // overlay's perspective.
194         firstClient.startDream(mLayoutParams, mOverlayCallback,
195                 FIRST_DREAM_COMPONENT.flattenToString(), false);
196 
197 
198         verify(monitor).onStartDream();
199         assertThat(service.getDreamComponent()).isEqualTo(FIRST_DREAM_COMPONENT);
200 
201         Mockito.clearInvocations(monitor);
202 
203         // Start a dream from the second client and verify that the overlay has both cycled to
204         // the new dream (ended/started).
205         secondClient.startDream(mLayoutParams, mOverlayCallback,
206                 SECOND_DREAM_COMPONENT.flattenToString(), false);
207 
208         verify(monitor).onEndDream();
209         verify(monitor).onStartDream();
210         assertThat(service.getDreamComponent()).isEqualTo(SECOND_DREAM_COMPONENT);
211 
212         Mockito.clearInvocations(monitor);
213 
214         // Verify that interactions with the first, now inactive client don't affect the overlay.
215         firstClient.endDream();
216         verify(monitor, never()).onEndDream();
217 
218         firstClient.wakeUp();
219         verify(monitor, never()).onWakeUp();
220     }
221 
getClient(IDreamOverlay overlay)222     private static IDreamOverlayClient getClient(IDreamOverlay overlay) throws RemoteException {
223         final OverlayClientCallback callback = new OverlayClientCallback();
224         overlay.getClient(callback);
225         return callback.retrievedClient;
226     }
227 }
228