1 /*
2  * Copyright (C) 2021 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.systemui.dreams;
18 
19 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.Mockito.clearInvocations;
25 import static org.mockito.Mockito.doAnswer;
26 import static org.mockito.Mockito.doThrow;
27 import static org.mockito.Mockito.inOrder;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 
32 import android.content.ComponentName;
33 import android.content.Intent;
34 import android.os.IBinder;
35 import android.os.RemoteException;
36 import android.service.dreams.IDreamOverlay;
37 import android.service.dreams.IDreamOverlayCallback;
38 import android.service.dreams.IDreamOverlayClient;
39 import android.service.dreams.IDreamOverlayClientCallback;
40 import android.testing.AndroidTestingRunner;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.view.WindowManager;
44 import android.view.WindowManagerImpl;
45 
46 import androidx.lifecycle.Lifecycle;
47 import androidx.lifecycle.LifecycleRegistry;
48 import androidx.test.filters.SmallTest;
49 
50 import com.android.internal.logging.UiEventLogger;
51 import com.android.keyguard.KeyguardUpdateMonitor;
52 import com.android.systemui.SysuiTestCase;
53 import com.android.systemui.complication.ComplicationLayoutEngine;
54 import com.android.systemui.dreams.complication.HideComplicationTouchHandler;
55 import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
56 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
57 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
58 import com.android.systemui.touch.TouchInsetManager;
59 import com.android.systemui.util.concurrency.FakeExecutor;
60 import com.android.systemui.util.time.FakeSystemClock;
61 import com.android.systemui.utils.leaks.LeakCheckedTest;
62 
63 import org.junit.Before;
64 import org.junit.Rule;
65 import org.junit.Test;
66 import org.junit.runner.RunWith;
67 import org.mockito.ArgumentCaptor;
68 import org.mockito.Captor;
69 import org.mockito.InOrder;
70 import org.mockito.Mock;
71 import org.mockito.Mockito;
72 import org.mockito.MockitoAnnotations;
73 
74 @SmallTest
75 @RunWith(AndroidTestingRunner.class)
76 public class DreamOverlayServiceTest extends SysuiTestCase {
77     private static final ComponentName LOW_LIGHT_COMPONENT = new ComponentName("package",
78             "lowlight");
79     private static final String DREAM_COMPONENT = "package/dream";
80     private static final String WINDOW_NAME = "test";
81     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
82     private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
83 
84     @Mock
85     DreamOverlayLifecycleOwner mLifecycleOwner;
86 
87     @Mock
88     LifecycleRegistry mLifecycleRegistry;
89 
90     @Rule
91     public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
92 
93     WindowManager.LayoutParams mWindowParams = new WindowManager.LayoutParams();
94 
95     @Mock
96     IDreamOverlayCallback mDreamOverlayCallback;
97 
98     @Mock
99     WindowManagerImpl mWindowManager;
100 
101     @Mock
102     com.android.systemui.complication.dagger.ComplicationComponent.Factory
103             mComplicationComponentFactory;
104 
105     @Mock
106     com.android.systemui.complication.dagger.ComplicationComponent mComplicationComponent;
107 
108     @Mock
109     ComplicationLayoutEngine mComplicationVisibilityController;
110 
111     @Mock
112     ComplicationComponent.Factory mDreamComplicationComponentFactory;
113 
114     @Mock
115     ComplicationComponent mDreamComplicationComponent;
116 
117     @Mock
118     HideComplicationTouchHandler mHideComplicationTouchHandler;
119 
120     @Mock
121     DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
122 
123     @Mock
124     DreamOverlayComponent mDreamOverlayComponent;
125 
126     @Mock
127     DreamOverlayContainerView mDreamOverlayContainerView;
128 
129     @Mock
130     DreamOverlayContainerViewController mDreamOverlayContainerViewController;
131 
132     @Mock
133     KeyguardUpdateMonitor mKeyguardUpdateMonitor;
134 
135     @Mock
136     DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
137 
138     @Mock
139     DreamOverlayStateController mStateController;
140 
141     @Mock
142     ViewGroup mDreamOverlayContainerViewParent;
143 
144     @Mock
145     TouchInsetManager mTouchInsetManager;
146 
147     @Mock
148     UiEventLogger mUiEventLogger;
149 
150     @Mock
151     DreamOverlayCallbackController mDreamOverlayCallbackController;
152 
153     @Captor
154     ArgumentCaptor<View> mViewCaptor;
155 
156     DreamOverlayService mService;
157 
158     @Before
setup()159     public void setup() {
160         MockitoAnnotations.initMocks(this);
161 
162         when(mDreamOverlayComponent.getDreamOverlayContainerViewController())
163                 .thenReturn(mDreamOverlayContainerViewController);
164         when(mLifecycleOwner.getRegistry())
165                 .thenReturn(mLifecycleRegistry);
166         when(mDreamOverlayComponent.getDreamOverlayTouchMonitor())
167                 .thenReturn(mDreamOverlayTouchMonitor);
168         when(mComplicationComponentFactory
169                 .create(any(), any(), any(), any()))
170                 .thenReturn(mComplicationComponent);
171         when(mComplicationComponent.getVisibilityController())
172                 .thenReturn(mComplicationVisibilityController);
173         when(mDreamComplicationComponent.getHideComplicationTouchHandler())
174                 .thenReturn(mHideComplicationTouchHandler);
175         when(mDreamComplicationComponentFactory
176                 .create(any(), any()))
177                 .thenReturn(mDreamComplicationComponent);
178         when(mDreamOverlayComponentFactory
179                 .create(any(), any(), any(), any()))
180                 .thenReturn(mDreamOverlayComponent);
181         when(mDreamOverlayContainerViewController.getContainerView())
182                 .thenReturn(mDreamOverlayContainerView);
183 
184         mService = new DreamOverlayService(
185                 mContext,
186                 mLifecycleOwner,
187                 mMainExecutor,
188                 mWindowManager,
189                 mComplicationComponentFactory,
190                 mDreamComplicationComponentFactory,
191                 mDreamOverlayComponentFactory,
192                 mStateController,
193                 mKeyguardUpdateMonitor,
194                 mUiEventLogger,
195                 mTouchInsetManager,
196                 LOW_LIGHT_COMPONENT,
197                 mDreamOverlayCallbackController,
198                 WINDOW_NAME);
199     }
200 
getClient()201     public IDreamOverlayClient getClient() throws RemoteException {
202         final IBinder proxy = mService.onBind(new Intent());
203         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
204         final IDreamOverlayClientCallback callback =
205                 Mockito.mock(IDreamOverlayClientCallback.class);
206         overlay.getClient(callback);
207         final ArgumentCaptor<IDreamOverlayClient> clientCaptor =
208                 ArgumentCaptor.forClass(IDreamOverlayClient.class);
209         verify(callback).onDreamOverlayClient(clientCaptor.capture());
210 
211         return clientCaptor.getValue();
212     }
213 
214     @Test
testOnStartMetricsLogged()215     public void testOnStartMetricsLogged() throws Exception {
216         final IDreamOverlayClient client = getClient();
217 
218         // Inform the overlay service of dream starting.
219         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
220                 false /*shouldShowComplication*/);
221         mMainExecutor.runAllReady();
222 
223         verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
224         verify(mUiEventLogger).log(
225                 DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
226     }
227 
228     @Test
testOverlayContainerViewAddedToWindow()229     public void testOverlayContainerViewAddedToWindow() throws Exception {
230         final IDreamOverlayClient client = getClient();
231 
232         // Inform the overlay service of dream starting.
233         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
234                 false /*shouldShowComplication*/);
235         mMainExecutor.runAllReady();
236 
237         verify(mWindowManager).addView(any(), any());
238     }
239 
240     // Validates that {@link DreamOverlayService} properly handles the case where the dream's
241     // window is no longer valid by the time start is called.
242     @Test
testInvalidWindowAddStart()243     public void testInvalidWindowAddStart() throws Exception {
244         final IDreamOverlayClient client = getClient();
245 
246         doThrow(new WindowManager.BadTokenException()).when(mWindowManager).addView(any(), any());
247         // Inform the overlay service of dream starting.
248         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
249                 false /*shouldShowComplication*/);
250         mMainExecutor.runAllReady();
251 
252         verify(mWindowManager).addView(any(), any());
253 
254         verify(mStateController).setOverlayActive(false);
255         verify(mStateController).setLowLightActive(false);
256         verify(mStateController).setEntryAnimationsFinished(false);
257 
258         verify(mStateController, never()).setOverlayActive(true);
259         verify(mUiEventLogger, never()).log(
260                 DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
261 
262         verify(mDreamOverlayCallbackController, never()).onStartDream();
263     }
264 
265     @Test
testDreamOverlayContainerViewControllerInitialized()266     public void testDreamOverlayContainerViewControllerInitialized() throws Exception {
267         final IDreamOverlayClient client = getClient();
268 
269         // Inform the overlay service of dream starting.
270         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
271                 false /*shouldShowComplication*/);
272         mMainExecutor.runAllReady();
273 
274         verify(mDreamOverlayContainerViewController).init();
275     }
276 
277     @Test
testDreamOverlayContainerViewRemovedFromOldParentWhenInitialized()278     public void testDreamOverlayContainerViewRemovedFromOldParentWhenInitialized()
279             throws Exception {
280         when(mDreamOverlayContainerView.getParent())
281                 .thenReturn(mDreamOverlayContainerViewParent)
282                 .thenReturn(null);
283 
284         final IDreamOverlayClient client = getClient();
285 
286         // Inform the overlay service of dream starting.
287         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
288                 false /*shouldShowComplication*/);
289         mMainExecutor.runAllReady();
290 
291         verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView);
292     }
293 
294     @Test
testShouldShowComplicationsSetByStartDream()295     public void testShouldShowComplicationsSetByStartDream() throws RemoteException {
296         final IDreamOverlayClient client = getClient();
297 
298         // Inform the overlay service of dream starting.
299         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
300                 true /*shouldShowComplication*/);
301         mMainExecutor.runAllReady();
302 
303         assertThat(mService.shouldShowComplications()).isTrue();
304     }
305 
306     @Test
testLowLightSetByStartDream()307     public void testLowLightSetByStartDream() throws RemoteException {
308         final IDreamOverlayClient client = getClient();
309 
310         // Inform the overlay service of dream starting.
311         client.startDream(mWindowParams, mDreamOverlayCallback,
312                 LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
313         mMainExecutor.runAllReady();
314 
315         assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT);
316         verify(mStateController).setLowLightActive(true);
317     }
318 
319     @Test
testOnEndDream()320     public void testOnEndDream() throws RemoteException {
321         final IDreamOverlayClient client = getClient();
322 
323         // Inform the overlay service of dream starting.
324         client.startDream(mWindowParams, mDreamOverlayCallback,
325                 LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
326         mMainExecutor.runAllReady();
327 
328         // Verify view added.
329         verify(mWindowManager).addView(mViewCaptor.capture(), any());
330 
331         // Service destroyed.
332         mService.onEndDream();
333         mMainExecutor.runAllReady();
334 
335         // Verify view removed.
336         verify(mWindowManager).removeView(mViewCaptor.getValue());
337 
338         // Verify state correctly set.
339         verify(mStateController).setOverlayActive(false);
340         verify(mStateController).setLowLightActive(false);
341         verify(mStateController).setEntryAnimationsFinished(false);
342     }
343 
344     @Test
testImmediateEndDream()345     public void testImmediateEndDream() throws Exception {
346         final IDreamOverlayClient client = getClient();
347 
348         // Start the dream, but don't execute any Runnables put on the executor yet. We delay
349         // executing Runnables as the timing isn't guaranteed and we want to verify that the overlay
350         // starts and finishes in the proper order even if Runnables are delayed.
351         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
352                 false /*shouldShowComplication*/);
353         // Immediately end the dream.
354         client.endDream();
355         // Run any scheduled Runnables.
356         mMainExecutor.runAllReady();
357 
358         // The overlay starts then finishes.
359         InOrder inOrder = inOrder(mWindowManager);
360         inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any());
361         inOrder.verify(mWindowManager).removeView(mViewCaptor.getValue());
362     }
363 
364     @Test
testEndDreamDuringStartDream()365     public void testEndDreamDuringStartDream() throws Exception {
366         final IDreamOverlayClient client = getClient();
367 
368         // Schedule the endDream call in the middle of the startDream implementation, as any
369         // ordering is possible.
370         doAnswer(invocation -> {
371             client.endDream();
372             return null;
373         }).when(mStateController).setOverlayActive(true);
374 
375         // Start the dream.
376         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
377                 false /*shouldShowComplication*/);
378         mMainExecutor.runAllReady();
379 
380         // The overlay starts then finishes.
381         InOrder inOrder = inOrder(mWindowManager);
382         inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any());
383         inOrder.verify(mWindowManager).removeView(mViewCaptor.getValue());
384     }
385 
386     @Test
testDestroy()387     public void testDestroy() throws RemoteException {
388         final IDreamOverlayClient client = getClient();
389 
390         // Inform the overlay service of dream starting.
391         client.startDream(mWindowParams, mDreamOverlayCallback,
392                 LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
393         mMainExecutor.runAllReady();
394 
395         // Verify view added.
396         verify(mWindowManager).addView(mViewCaptor.capture(), any());
397 
398         // Service destroyed.
399         mService.onDestroy();
400         mMainExecutor.runAllReady();
401 
402         // Verify view removed.
403         verify(mWindowManager).removeView(mViewCaptor.getValue());
404 
405         // Verify state correctly set.
406         verify(mKeyguardUpdateMonitor).removeCallback(any());
407         verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
408         verify(mStateController).setOverlayActive(false);
409         verify(mStateController).setLowLightActive(false);
410         verify(mStateController).setEntryAnimationsFinished(false);
411     }
412 
413     @Test
testDoNotRemoveViewOnDestroyIfOverlayNotStarted()414     public void testDoNotRemoveViewOnDestroyIfOverlayNotStarted() {
415         // Service destroyed without ever starting dream.
416         mService.onDestroy();
417         mMainExecutor.runAllReady();
418 
419         // Verify no view is removed.
420         verify(mWindowManager, never()).removeView(any());
421 
422         // Verify state still correctly set.
423         verify(mKeyguardUpdateMonitor).removeCallback(any());
424         verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
425         verify(mStateController).setOverlayActive(false);
426         verify(mStateController).setLowLightActive(false);
427     }
428 
429     @Test
testDecorViewNotAddedToWindowAfterDestroy()430     public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception {
431         final IDreamOverlayClient client = getClient();
432 
433         // Destroy the service.
434         mService.onDestroy();
435         mMainExecutor.runAllReady();
436 
437         // Inform the overlay service of dream starting.
438         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
439                 false /*shouldShowComplication*/);
440         mMainExecutor.runAllReady();
441 
442         verify(mWindowManager, never()).addView(any(), any());
443     }
444 
445     @Test
testNeverRemoveDecorViewIfNotAdded()446     public void testNeverRemoveDecorViewIfNotAdded() {
447         // Service destroyed before dream started.
448         mService.onDestroy();
449         mMainExecutor.runAllReady();
450 
451         verify(mWindowManager, never()).removeView(any());
452     }
453 
454     @Test
testResetCurrentOverlayWhenConnectedToNewDream()455     public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
456         final IDreamOverlayClient client = getClient();
457 
458         // Inform the overlay service of dream starting. Do not show dream complications.
459         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
460                 false /*shouldShowComplication*/);
461         mMainExecutor.runAllReady();
462 
463         // Verify that a new window is added.
464         verify(mWindowManager).addView(mViewCaptor.capture(), any());
465         final View windowDecorView = mViewCaptor.getValue();
466 
467         // Assert that the overlay is not showing complications.
468         assertThat(mService.shouldShowComplications()).isFalse();
469 
470         clearInvocations(mDreamOverlayComponent);
471         clearInvocations(mWindowManager);
472 
473         // New dream starting with dream complications showing. Note that when a new dream is
474         // binding to the dream overlay service, it receives the same instance of IBinder as the
475         // first one.
476         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
477                 true /*shouldShowComplication*/);
478         mMainExecutor.runAllReady();
479 
480         // Assert that the overlay is showing complications.
481         assertThat(mService.shouldShowComplications()).isTrue();
482 
483         // Verify that the old overlay window has been removed, and a new one created.
484         verify(mWindowManager).removeView(windowDecorView);
485         verify(mWindowManager).addView(any(), any());
486 
487         // Verify that new instances of overlay container view controller and overlay touch monitor
488         // are created.
489         verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
490         verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
491     }
492 
493     @Test
testWakeUp()494     public void testWakeUp() throws RemoteException {
495         final IDreamOverlayClient client = getClient();
496 
497         // Inform the overlay service of dream starting.
498         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
499                 true /*shouldShowComplication*/);
500         mMainExecutor.runAllReady();
501 
502         mService.onWakeUp();
503         verify(mDreamOverlayContainerViewController).wakeUp();
504         verify(mDreamOverlayCallbackController).onWakeUp();
505     }
506 
507     @Test
testWakeUpBeforeStartDoesNothing()508     public void testWakeUpBeforeStartDoesNothing() {
509         mService.onWakeUp();
510         verify(mDreamOverlayContainerViewController, never()).wakeUp();
511     }
512 
513     @Test
testSystemFlagShowForAllUsersSetOnWindow()514     public void testSystemFlagShowForAllUsersSetOnWindow() throws RemoteException {
515         final IDreamOverlayClient client = getClient();
516 
517         // Inform the overlay service of dream starting. Do not show dream complications.
518         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
519                 false /*shouldShowComplication*/);
520         mMainExecutor.runAllReady();
521 
522         final ArgumentCaptor<WindowManager.LayoutParams> paramsCaptor =
523                 ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
524 
525         // Verify that a new window is added.
526         verify(mWindowManager).addView(any(), paramsCaptor.capture());
527 
528         assertThat((paramsCaptor.getValue().privateFlags & SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
529                 == SYSTEM_FLAG_SHOW_FOR_ALL_USERS).isTrue();
530     }
531 }
532