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 com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertTrue;
23 import static org.mockito.Mockito.clearInvocations;
24 import static org.mockito.Mockito.never;
25 import static org.mockito.Mockito.times;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.when;
28 
29 import android.testing.AndroidTestingRunner;
30 
31 import androidx.test.filters.SmallTest;
32 
33 import com.android.systemui.SysuiTestCase;
34 import com.android.systemui.complication.Complication;
35 import com.android.systemui.flags.FeatureFlags;
36 import com.android.systemui.flags.Flags;
37 import com.android.systemui.log.LogBuffer;
38 import com.android.systemui.log.core.FakeLogBuffer;
39 import com.android.systemui.util.concurrency.FakeExecutor;
40 import com.android.systemui.util.time.FakeSystemClock;
41 
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 import org.mockito.Mock;
46 import org.mockito.Mockito;
47 import org.mockito.MockitoAnnotations;
48 
49 import java.util.Collection;
50 
51 @SmallTest
52 @RunWith(AndroidTestingRunner.class)
53 public class DreamOverlayStateControllerTest extends SysuiTestCase {
54     @Mock
55     DreamOverlayStateController.Callback mCallback;
56 
57     @Mock
58     Complication mComplication;
59 
60     @Mock
61     private FeatureFlags mFeatureFlags;
62 
63     private final LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create();
64 
65     final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
66 
67     @Before
setup()68     public void setup() {
69         MockitoAnnotations.initMocks(this);
70 
71         when(mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)).thenReturn(false);
72     }
73 
74     @Test
testStateChange_overlayActive()75     public void testStateChange_overlayActive() {
76         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
77         stateController.addCallback(mCallback);
78         stateController.setOverlayActive(true);
79         mExecutor.runAllReady();
80 
81         verify(mCallback).onStateChanged();
82         assertThat(stateController.isOverlayActive()).isTrue();
83 
84         Mockito.clearInvocations(mCallback);
85         stateController.setOverlayActive(true);
86         mExecutor.runAllReady();
87         verify(mCallback, never()).onStateChanged();
88 
89         stateController.setOverlayActive(false);
90         mExecutor.runAllReady();
91         verify(mCallback).onStateChanged();
92         assertThat(stateController.isOverlayActive()).isFalse();
93     }
94 
95     @Test
testCallback()96     public void testCallback() {
97         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
98         stateController.addCallback(mCallback);
99 
100         // Add complication and verify callback is notified.
101         stateController.addComplication(mComplication);
102 
103         mExecutor.runAllReady();
104 
105         verify(mCallback, times(1)).onComplicationsChanged();
106 
107         final Collection<Complication> complications = stateController.getComplications();
108         assertEquals(complications.size(), 1);
109         assertTrue(complications.contains(mComplication));
110 
111         clearInvocations(mCallback);
112 
113         // Remove complication and verify callback is notified.
114         stateController.removeComplication(mComplication);
115         mExecutor.runAllReady();
116         verify(mCallback, times(1)).onComplicationsChanged();
117         assertTrue(stateController.getComplications().isEmpty());
118     }
119 
120     @Test
testNotifyOnCallbackAdd()121     public void testNotifyOnCallbackAdd() {
122         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
123 
124         stateController.addComplication(mComplication);
125         mExecutor.runAllReady();
126 
127         // Verify callback occurs on add when an overlay is already present.
128         stateController.addCallback(mCallback);
129         mExecutor.runAllReady();
130         verify(mCallback, times(1)).onComplicationsChanged();
131     }
132 
133     @Test
testNotifyOnCallbackAddOverlayDisabled()134     public void testNotifyOnCallbackAddOverlayDisabled() {
135         final DreamOverlayStateController stateController = getDreamOverlayStateController(false);
136 
137         stateController.addComplication(mComplication);
138         mExecutor.runAllReady();
139 
140         // Verify callback occurs on add when an overlay is already present.
141         stateController.addCallback(mCallback);
142         mExecutor.runAllReady();
143         verify(mCallback, never()).onComplicationsChanged();
144     }
145 
146 
147     @Test
testComplicationFilteringWhenShouldShowComplications()148     public void testComplicationFilteringWhenShouldShowComplications() {
149         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
150         stateController.setShouldShowComplications(true);
151 
152         final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
153         final Complication weatherComplication = Mockito.mock(Complication.class);
154         when(alwaysAvailableComplication.getRequiredTypeAvailability())
155                 .thenReturn(Complication.COMPLICATION_TYPE_NONE);
156         when(weatherComplication.getRequiredTypeAvailability())
157                 .thenReturn(Complication.COMPLICATION_TYPE_WEATHER);
158 
159         stateController.addComplication(alwaysAvailableComplication);
160         stateController.addComplication(weatherComplication);
161 
162         final DreamOverlayStateController.Callback callback =
163                 Mockito.mock(DreamOverlayStateController.Callback.class);
164 
165         stateController.addCallback(callback);
166         mExecutor.runAllReady();
167 
168         {
169             final Collection<Complication> complications = stateController.getComplications();
170             assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
171             assertThat(complications.contains(weatherComplication)).isFalse();
172         }
173 
174         stateController.setAvailableComplicationTypes(Complication.COMPLICATION_TYPE_WEATHER);
175         mExecutor.runAllReady();
176         verify(callback).onAvailableComplicationTypesChanged();
177 
178         {
179             final Collection<Complication> complications = stateController.getComplications();
180             assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
181             assertThat(complications.contains(weatherComplication)).isTrue();
182         }
183 
184     }
185 
186     @Test
testComplicationFilteringWhenShouldHideComplications()187     public void testComplicationFilteringWhenShouldHideComplications() {
188         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
189         stateController.setShouldShowComplications(true);
190 
191         final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
192         final Complication weatherComplication = Mockito.mock(Complication.class);
193         when(alwaysAvailableComplication.getRequiredTypeAvailability())
194                 .thenReturn(Complication.COMPLICATION_TYPE_NONE);
195         when(weatherComplication.getRequiredTypeAvailability())
196                 .thenReturn(Complication.COMPLICATION_TYPE_WEATHER);
197 
198         stateController.addComplication(alwaysAvailableComplication);
199         stateController.addComplication(weatherComplication);
200 
201         final DreamOverlayStateController.Callback callback =
202                 Mockito.mock(DreamOverlayStateController.Callback.class);
203 
204         stateController.setAvailableComplicationTypes(Complication.COMPLICATION_TYPE_WEATHER);
205         stateController.addCallback(callback);
206         mExecutor.runAllReady();
207 
208         {
209             clearInvocations(callback);
210             stateController.setShouldShowComplications(true);
211             mExecutor.runAllReady();
212 
213             verify(callback).onAvailableComplicationTypesChanged();
214             final Collection<Complication> complications = stateController.getComplications();
215             assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
216             assertThat(complications.contains(weatherComplication)).isTrue();
217         }
218 
219         {
220             clearInvocations(callback);
221             stateController.setShouldShowComplications(false);
222             mExecutor.runAllReady();
223 
224             verify(callback).onAvailableComplicationTypesChanged();
225             final Collection<Complication> complications = stateController.getComplications();
226             assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
227             assertThat(complications.contains(weatherComplication)).isFalse();
228         }
229     }
230 
231     @Test
testComplicationWithNoTypeNotFiltered()232     public void testComplicationWithNoTypeNotFiltered() {
233         final Complication complication = Mockito.mock(Complication.class);
234         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
235         stateController.addComplication(complication);
236         mExecutor.runAllReady();
237         assertThat(stateController.getComplications(true).contains(complication))
238                 .isTrue();
239     }
240 
241     @Test
testComplicationsNotShownForLowLight()242     public void testComplicationsNotShownForLowLight() {
243         final Complication complication = Mockito.mock(Complication.class);
244         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
245 
246         // Add a complication and verify it's returned in getComplications.
247         stateController.addComplication(complication);
248         mExecutor.runAllReady();
249         assertThat(stateController.getComplications().contains(complication))
250                 .isTrue();
251 
252         stateController.setLowLightActive(true);
253         mExecutor.runAllReady();
254 
255         assertThat(stateController.getComplications()).isEmpty();
256     }
257 
258     @Test
testNotifyLowLightChanged()259     public void testNotifyLowLightChanged() {
260         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
261 
262         stateController.addCallback(mCallback);
263         mExecutor.runAllReady();
264         assertThat(stateController.isLowLightActive()).isFalse();
265 
266         stateController.setLowLightActive(true);
267 
268         mExecutor.runAllReady();
269         verify(mCallback, times(1)).onStateChanged();
270         assertThat(stateController.isLowLightActive()).isTrue();
271     }
272 
273     @Test
testNotifyLowLightExit()274     public void testNotifyLowLightExit() {
275         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
276 
277         stateController.addCallback(mCallback);
278         mExecutor.runAllReady();
279         assertThat(stateController.isLowLightActive()).isFalse();
280 
281         // Turn low light on then off to trigger the exiting callback.
282         stateController.setLowLightActive(true);
283         stateController.setLowLightActive(false);
284 
285         // Callback was only called once, when
286         mExecutor.runAllReady();
287         verify(mCallback, times(1)).onExitLowLight();
288         assertThat(stateController.isLowLightActive()).isFalse();
289 
290         // Set with false again, which should not cause the callback to trigger again.
291         stateController.setLowLightActive(false);
292         mExecutor.runAllReady();
293         verify(mCallback, times(1)).onExitLowLight();
294     }
295 
296     @Test
testNotifyEntryAnimationsFinishedChanged()297     public void testNotifyEntryAnimationsFinishedChanged() {
298         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
299 
300         stateController.addCallback(mCallback);
301         mExecutor.runAllReady();
302         assertThat(stateController.areEntryAnimationsFinished()).isFalse();
303 
304         stateController.setEntryAnimationsFinished(true);
305         mExecutor.runAllReady();
306 
307         verify(mCallback, times(1)).onStateChanged();
308         assertThat(stateController.areEntryAnimationsFinished()).isTrue();
309     }
310 
311     @Test
testNotifyDreamOverlayStatusBarVisibleChanged()312     public void testNotifyDreamOverlayStatusBarVisibleChanged() {
313         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
314 
315         stateController.addCallback(mCallback);
316         mExecutor.runAllReady();
317         assertThat(stateController.isDreamOverlayStatusBarVisible()).isFalse();
318 
319         stateController.setDreamOverlayStatusBarVisible(true);
320         mExecutor.runAllReady();
321 
322         verify(mCallback, times(1)).onStateChanged();
323         assertThat(stateController.isDreamOverlayStatusBarVisible()).isTrue();
324     }
325 
326     @Test
testNotifyHasAssistantAttentionChanged()327     public void testNotifyHasAssistantAttentionChanged() {
328         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
329 
330         stateController.addCallback(mCallback);
331         mExecutor.runAllReady();
332         assertThat(stateController.hasAssistantAttention()).isFalse();
333 
334         stateController.setHasAssistantAttention(true);
335         mExecutor.runAllReady();
336 
337         verify(mCallback, times(1)).onStateChanged();
338         assertThat(stateController.hasAssistantAttention()).isTrue();
339     }
340 
341     @Test
testShouldShowComplicationsSetToFalse_stillShowsHomeControls_featureEnabled()342     public void testShouldShowComplicationsSetToFalse_stillShowsHomeControls_featureEnabled() {
343         when(mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)).thenReturn(true);
344 
345         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
346         stateController.setShouldShowComplications(true);
347 
348         final Complication homeControlsComplication = Mockito.mock(Complication.class);
349         when(homeControlsComplication.getRequiredTypeAvailability())
350                 .thenReturn(Complication.COMPLICATION_TYPE_HOME_CONTROLS);
351 
352         stateController.addComplication(homeControlsComplication);
353 
354         final DreamOverlayStateController.Callback callback =
355                 Mockito.mock(DreamOverlayStateController.Callback.class);
356 
357         stateController.setAvailableComplicationTypes(
358                 Complication.COMPLICATION_TYPE_HOME_CONTROLS);
359         stateController.addCallback(callback);
360         mExecutor.runAllReady();
361 
362         {
363             clearInvocations(callback);
364             stateController.setShouldShowComplications(true);
365             mExecutor.runAllReady();
366 
367             verify(callback).onAvailableComplicationTypesChanged();
368             final Collection<Complication> complications = stateController.getComplications();
369             assertThat(complications.contains(homeControlsComplication)).isTrue();
370         }
371 
372         {
373             clearInvocations(callback);
374             stateController.setShouldShowComplications(false);
375             mExecutor.runAllReady();
376 
377             verify(callback).onAvailableComplicationTypesChanged();
378             final Collection<Complication> complications = stateController.getComplications();
379             assertThat(complications.contains(homeControlsComplication)).isTrue();
380         }
381     }
382 
383     @Test
testHomeControlsDoNotShowIfNotAvailable_featureEnabled()384     public void testHomeControlsDoNotShowIfNotAvailable_featureEnabled() {
385         when(mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)).thenReturn(true);
386 
387         final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
388         stateController.setShouldShowComplications(true);
389 
390         final Complication homeControlsComplication = Mockito.mock(Complication.class);
391         when(homeControlsComplication.getRequiredTypeAvailability())
392                 .thenReturn(Complication.COMPLICATION_TYPE_HOME_CONTROLS);
393 
394         stateController.addComplication(homeControlsComplication);
395 
396         final DreamOverlayStateController.Callback callback =
397                 Mockito.mock(DreamOverlayStateController.Callback.class);
398 
399         stateController.addCallback(callback);
400         mExecutor.runAllReady();
401 
402         // No home controls since it is not available.
403         assertThat(stateController.getComplications()).doesNotContain(homeControlsComplication);
404 
405         stateController.setAvailableComplicationTypes(Complication.COMPLICATION_TYPE_HOME_CONTROLS
406                 | Complication.COMPLICATION_TYPE_WEATHER);
407         mExecutor.runAllReady();
408         assertThat(stateController.getComplications()).contains(homeControlsComplication);
409     }
410 
getDreamOverlayStateController(boolean overlayEnabled)411     private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
412         return new DreamOverlayStateController(
413                 mExecutor,
414                 overlayEnabled,
415                 mFeatureFlags,
416                 mLogBuffer
417         );
418     }
419 }
420