1 /*
2  * Copyright (C) 2018 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.shade;
18 
19 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
20 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
21 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
22 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.mockito.ArgumentMatchers.any;
27 import static org.mockito.ArgumentMatchers.anyInt;
28 import static org.mockito.ArgumentMatchers.eq;
29 import static org.mockito.Mockito.atLeastOnce;
30 import static org.mockito.Mockito.clearInvocations;
31 import static org.mockito.Mockito.never;
32 import static org.mockito.Mockito.reset;
33 import static org.mockito.Mockito.spy;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.verifyNoMoreInteractions;
36 import static org.mockito.Mockito.when;
37 
38 import android.app.IActivityManager;
39 import android.content.pm.ActivityInfo;
40 import android.content.res.Configuration;
41 import android.testing.AndroidTestingRunner;
42 import android.testing.TestableLooper.RunWithLooper;
43 import android.view.View;
44 import android.view.WindowManager;
45 
46 import androidx.test.filters.SmallTest;
47 
48 import com.android.internal.colorextraction.ColorExtractor;
49 import com.android.systemui.R;
50 import com.android.systemui.SysuiTestCase;
51 import com.android.systemui.biometrics.AuthController;
52 import com.android.systemui.colorextraction.SysuiColorExtractor;
53 import com.android.systemui.dump.DumpManager;
54 import com.android.systemui.keyguard.KeyguardViewMediator;
55 import com.android.systemui.plugins.statusbar.StatusBarStateController;
56 import com.android.systemui.statusbar.StatusBarState;
57 import com.android.systemui.statusbar.SysuiStatusBarStateController;
58 import com.android.systemui.statusbar.phone.DozeParameters;
59 import com.android.systemui.statusbar.phone.KeyguardBypassController;
60 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
61 import com.android.systemui.statusbar.phone.ScrimController;
62 import com.android.systemui.statusbar.policy.ConfigurationController;
63 import com.android.systemui.statusbar.policy.KeyguardStateController;
64 
65 import com.google.common.util.concurrent.MoreExecutors;
66 
67 import org.junit.Before;
68 import org.junit.Test;
69 import org.junit.runner.RunWith;
70 import org.mockito.ArgumentCaptor;
71 import org.mockito.Captor;
72 import org.mockito.Mock;
73 import org.mockito.MockitoAnnotations;
74 import org.mockito.Spy;
75 
76 import java.util.List;
77 import java.util.concurrent.Executor;
78 
79 @RunWith(AndroidTestingRunner.class)
80 @RunWithLooper
81 @SmallTest
82 public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
83 
84     @Mock private WindowManager mWindowManager;
85     @Mock private DozeParameters mDozeParameters;
86     @Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy(
87             new NotificationShadeWindowView(mContext, null));
88     @Mock private IActivityManager mActivityManager;
89     @Mock private SysuiStatusBarStateController mStatusBarStateController;
90     @Mock private ConfigurationController mConfigurationController;
91     @Mock private KeyguardViewMediator mKeyguardViewMediator;
92     @Mock private KeyguardBypassController mKeyguardBypassController;
93     @Mock private SysuiColorExtractor mColorExtractor;
94     @Mock ColorExtractor.GradientColors mGradientColors;
95     @Mock private DumpManager mDumpManager;
96     @Mock private KeyguardStateController mKeyguardStateController;
97     @Mock private ScreenOffAnimationController mScreenOffAnimationController;
98     @Mock private AuthController mAuthController;
99     @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
100     @Mock private ShadeWindowLogger mShadeWindowLogger;
101     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
102     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
103     private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
104 
105     private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
106     private float mPreferredRefreshRate = -1;
107 
108     @Before
setUp()109     public void setUp() {
110         MockitoAnnotations.initMocks(this);
111         // Preferred refresh rate is equal to the first displayMode's refresh rate
112         mPreferredRefreshRate = mContext.getDisplay().getSupportedModes()[0].getRefreshRate();
113         overrideResource(
114                 R.integer.config_keyguardRefreshRate,
115                 (int) mPreferredRefreshRate
116         );
117 
118         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
119         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
120 
121         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
122                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
123                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
124                 mBackgroundExecutor, mColorExtractor, mDumpManager, mKeyguardStateController,
125                 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
126                 mShadeWindowLogger) {
127                     @Override
128                     protected boolean isDebuggable() {
129                         return false;
130                     }
131             };
132         mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {});
133         mNotificationShadeWindowController.setWindowRootView(mNotificationShadeWindowView);
134 
135         mNotificationShadeWindowController.attach();
136         verify(mWindowManager).addView(eq(mNotificationShadeWindowView), any());
137         verify(mStatusBarStateController).addCallback(mStateListener.capture(), anyInt());
138     }
139 
140     @Test
testSetDozing_hidesSystemOverlays()141     public void testSetDozing_hidesSystemOverlays() {
142         mNotificationShadeWindowController.setDozing(true);
143         ArgumentCaptor<WindowManager.LayoutParams> captor =
144                 ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
145         verify(mWindowManager).updateViewLayout(any(), captor.capture());
146         int flag = captor.getValue().privateFlags
147                 & WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
148         assertThat(flag).isNotEqualTo(0);
149 
150         reset(mWindowManager);
151         mNotificationShadeWindowController.setDozing(false);
152         verify(mWindowManager).updateViewLayout(any(), captor.capture());
153         flag = captor.getValue().privateFlags
154                 & WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
155         assertThat(flag).isEqualTo(0);
156     }
157 
158     @Test
testOnThemeChanged_doesntCrash()159     public void testOnThemeChanged_doesntCrash() {
160         mNotificationShadeWindowController.onThemeChanged();
161     }
162 
163     @Test
testAdd_updatesVisibilityFlags()164     public void testAdd_updatesVisibilityFlags() {
165         verify(mNotificationShadeWindowView).setSystemUiVisibility(anyInt());
166     }
167 
168     @Test
testSetForcePluginOpen_beforeStatusBarInitialization()169     public void testSetForcePluginOpen_beforeStatusBarInitialization() {
170         mNotificationShadeWindowController.setForcePluginOpen(true, this);
171     }
172 
173     @Test
attach_visibleWithWallpaper()174     public void attach_visibleWithWallpaper() {
175         clearInvocations(mWindowManager);
176         when(mKeyguardViewMediator.isShowingAndNotOccluded()).thenReturn(true);
177         mNotificationShadeWindowController.attach();
178 
179         verify(mNotificationShadeWindowView).setVisibility(eq(View.VISIBLE));
180         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
181         assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) != 0).isTrue();
182     }
183 
184     @Test
attach_lightScrimHidesWallpaper()185     public void attach_lightScrimHidesWallpaper() {
186         when(mKeyguardViewMediator.isShowingAndNotOccluded()).thenReturn(true);
187         mNotificationShadeWindowController.attach();
188 
189         clearInvocations(mWindowManager);
190         mNotificationShadeWindowController.setLightRevealScrimOpaque(true);
191         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
192         assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) == 0).isTrue();
193     }
194 
195     @Test
attach_scrimDoesntHideWallpaper()196     public void attach_scrimDoesntHideWallpaper() {
197         when(mKeyguardViewMediator.isShowingAndNotOccluded()).thenReturn(true);
198         mNotificationShadeWindowController.attach();
199 
200         clearInvocations(mWindowManager);
201         mNotificationShadeWindowController.setScrimsVisibility(ScrimController.OPAQUE);
202         // The scrim used to remove the wallpaper flag, but this causes a relayout.
203         // Instead, we're not relying on SurfaceControl#setOpaque on
204         // NotificationShadeDepthController.
205         verify(mWindowManager, never()).updateViewLayout(any(), mLayoutParameters.capture());
206     }
207 
208     @Test
setScrimsVisibility_earlyReturn()209     public void setScrimsVisibility_earlyReturn() {
210         clearInvocations(mWindowManager);
211         mNotificationShadeWindowController.setScrimsVisibility(ScrimController.TRANSPARENT);
212         // Abort early if value didn't change
213         verify(mWindowManager, never()).updateViewLayout(any(), mLayoutParameters.capture());
214     }
215 
216     @Test
attach_animatingKeyguardAndSurface_wallpaperVisible()217     public void attach_animatingKeyguardAndSurface_wallpaperVisible() {
218         clearInvocations(mWindowManager);
219         when(mKeyguardViewMediator.isShowingAndNotOccluded()).thenReturn(true);
220         when(mKeyguardViewMediator
221                 .isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe())
222                 .thenReturn(true);
223         mNotificationShadeWindowController.attach();
224 
225         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
226         assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) != 0).isTrue();
227     }
228 
229     @Test
setBackgroundBlurRadius_expandedWithBlurs()230     public void setBackgroundBlurRadius_expandedWithBlurs() {
231         mNotificationShadeWindowController.setBackgroundBlurRadius(10);
232         verify(mNotificationShadeWindowView).setVisibility(eq(View.VISIBLE));
233 
234         mNotificationShadeWindowController.setBackgroundBlurRadius(0);
235         verify(mNotificationShadeWindowView).setVisibility(eq(View.INVISIBLE));
236     }
237 
238     @Test
setBouncerShowing_isFocusable_whenNeedsInput()239     public void setBouncerShowing_isFocusable_whenNeedsInput() {
240         mNotificationShadeWindowController.setKeyguardNeedsInput(true);
241         clearInvocations(mWindowManager);
242         mNotificationShadeWindowController.setBouncerShowing(true);
243 
244         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
245         assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) == 0).isTrue();
246         assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue();
247     }
248 
249     @Test
setKeyguardShowing_focusable_notAltFocusable_whenNeedsInput()250     public void setKeyguardShowing_focusable_notAltFocusable_whenNeedsInput() {
251         mNotificationShadeWindowController.setKeyguardShowing(true);
252         clearInvocations(mWindowManager);
253         mNotificationShadeWindowController.setKeyguardNeedsInput(true);
254 
255         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
256         assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) == 0).isTrue();
257         assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue();
258     }
259 
260     @Test
setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen()261     public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() {
262         mNotificationShadeWindowController.onShadeExpansionFullyChanged(true);
263         clearInvocations(mWindowManager);
264         mNotificationShadeWindowController.onShadeExpansionFullyChanged(true);
265         verifyNoMoreInteractions(mWindowManager);
266         mNotificationShadeWindowController.setNotificationShadeFocusable(true);
267 
268         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
269         assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) == 0).isTrue();
270         assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) != 0).isTrue();
271     }
272 
273     @Test
setKeyguardShowing_notFocusable_byDefault()274     public void setKeyguardShowing_notFocusable_byDefault() {
275         mNotificationShadeWindowController.setKeyguardShowing(false);
276 
277         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
278         assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) != 0).isTrue();
279         assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue();
280     }
281 
282     @Test
setKeyguardShowing_enablesSecureFlag()283     public void setKeyguardShowing_enablesSecureFlag() {
284         mNotificationShadeWindowController.setBouncerShowing(true);
285 
286         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
287         assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) != 0).isTrue();
288     }
289 
290     @Test
setKeyguardNotShowing_disablesSecureFlag()291     public void setKeyguardNotShowing_disablesSecureFlag() {
292         mNotificationShadeWindowController.setBouncerShowing(false);
293 
294         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
295         assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue();
296     }
297 
298     @Test
rotationBecameAllowed_layoutParamsUpdated()299     public void rotationBecameAllowed_layoutParamsUpdated() {
300         mNotificationShadeWindowController.setKeyguardShowing(true);
301         when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
302         mNotificationShadeWindowController.onConfigChanged(new Configuration());
303         clearInvocations(mWindowManager);
304 
305         when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true);
306         mNotificationShadeWindowController.onConfigChanged(new Configuration());
307 
308         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
309         assertThat(mLayoutParameters.getValue().screenOrientation)
310                 .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_USER);
311     }
312 
313     @Test
rotationBecameNotAllowed_layoutParamsUpdated()314     public void rotationBecameNotAllowed_layoutParamsUpdated() {
315         mNotificationShadeWindowController.setKeyguardShowing(true);
316         when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true);
317         mNotificationShadeWindowController.onConfigChanged(new Configuration());
318         clearInvocations(mWindowManager);
319 
320         when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
321         mNotificationShadeWindowController.onConfigChanged(new Configuration());
322 
323         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
324         assertThat(mLayoutParameters.getValue().screenOrientation)
325                 .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
326     }
327 
328     @Test
batchApplyWindowLayoutParams_doesNotDispatchEvents()329     public void batchApplyWindowLayoutParams_doesNotDispatchEvents() {
330         mNotificationShadeWindowController.setForceDozeBrightness(true);
331         verify(mWindowManager).updateViewLayout(any(), any());
332         mNotificationShadeWindowController.setForceDozeBrightness(true);
333         verifyNoMoreInteractions(mWindowManager);
334 
335         clearInvocations(mWindowManager);
336         mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
337             mNotificationShadeWindowController.setForceDozeBrightness(false);
338             verify(mWindowManager, never()).updateViewLayout(any(), any());
339         });
340         verify(mWindowManager).updateViewLayout(any(), any());
341     }
342 
343     @Test
bouncerShowing_OrientationNoSensor()344     public void bouncerShowing_OrientationNoSensor() {
345         mNotificationShadeWindowController.setKeyguardShowing(true);
346         mNotificationShadeWindowController.setKeyguardOccluded(true);
347         mNotificationShadeWindowController.setBouncerShowing(true);
348         when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
349         mNotificationShadeWindowController.onConfigChanged(new Configuration());
350 
351         verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
352         assertThat(mLayoutParameters.getValue().screenOrientation)
353                 .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
354     }
355 
356     @Test
udfpsEnrolled_minAndMaxRefreshRateSetToPreferredRefreshRate()357     public void udfpsEnrolled_minAndMaxRefreshRateSetToPreferredRefreshRate() {
358         // GIVEN udfps is enrolled
359         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
360 
361         // WHEN keyguard is showing
362         setKeyguardShowing();
363 
364         // THEN min and max refresh rate is set to the preferredRefreshRate
365         verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
366         final List<WindowManager.LayoutParams> lpList = mLayoutParameters.getAllValues();
367         final WindowManager.LayoutParams lp = lpList.get(lpList.size() - 1);
368         assertThat(lp.preferredMaxDisplayRefreshRate).isEqualTo(mPreferredRefreshRate);
369         assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(mPreferredRefreshRate);
370     }
371 
372     @Test
udfpsNotEnrolled_refreshRateUnset()373     public void udfpsNotEnrolled_refreshRateUnset() {
374         // GIVEN udfps is NOT enrolled
375         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
376 
377         // WHEN keyguard is showing
378         setKeyguardShowing();
379 
380         // THEN min and max refresh rate aren't set (set to 0)
381         verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
382         final List<WindowManager.LayoutParams> lpList = mLayoutParameters.getAllValues();
383         final WindowManager.LayoutParams lp = lpList.get(lpList.size() - 1);
384         assertThat(lp.preferredMaxDisplayRefreshRate).isEqualTo(0);
385         assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(0);
386     }
387 
388     @Test
keyguardNotShowing_refreshRateUnset()389     public void keyguardNotShowing_refreshRateUnset() {
390         // GIVEN UDFPS is enrolled
391         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
392 
393         // WHEN keyguard is NOT showing
394         mNotificationShadeWindowController.setKeyguardShowing(false);
395 
396         // THEN min and max refresh rate aren't set (set to 0)
397         verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
398         final List<WindowManager.LayoutParams> lpList = mLayoutParameters.getAllValues();
399         final WindowManager.LayoutParams lp = lpList.get(lpList.size() - 1);
400         assertThat(lp.preferredMaxDisplayRefreshRate).isEqualTo(0);
401         assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(0);
402     }
403 
setKeyguardShowing()404     private void setKeyguardShowing() {
405         mNotificationShadeWindowController.setKeyguardShowing(true);
406         mNotificationShadeWindowController.setKeyguardGoingAway(false);
407         mNotificationShadeWindowController.setKeyguardFadingAway(false);
408         mStateListener.getValue().onStateChanged(StatusBarState.KEYGUARD);
409     }
410 }
411