1 /*
<lambda>null2  * Copyright (C) 2020 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.statusbar
18 
19 import android.app.WallpaperManager
20 import android.testing.AndroidTestingRunner
21 import android.testing.TestableLooper.RunWithLooper
22 import android.view.Choreographer
23 import android.view.View
24 import android.view.ViewRootImpl
25 import androidx.test.filters.SmallTest
26 import com.android.systemui.SysuiTestCase
27 import com.android.systemui.dump.DumpManager
28 import com.android.systemui.plugins.statusbar.StatusBarStateController
29 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
30 import com.android.systemui.statusbar.phone.BiometricUnlockController
31 import com.android.systemui.statusbar.phone.DozeParameters
32 import com.android.systemui.statusbar.phone.NotificationShadeWindowController
33 import com.android.systemui.statusbar.policy.KeyguardStateController
34 import com.android.systemui.util.mockito.eq
35 import org.junit.Before
36 import org.junit.Rule
37 import org.junit.Test
38 import org.junit.runner.RunWith
39 import org.mockito.ArgumentCaptor
40 import org.mockito.ArgumentMatchers.anyInt
41 import org.mockito.Mock
42 import org.mockito.Mockito.`when`
43 import org.mockito.Mockito.any
44 import org.mockito.Mockito.anyFloat
45 import org.mockito.Mockito.anyString
46 import org.mockito.Mockito.clearInvocations
47 import org.mockito.Mockito.doThrow
48 import org.mockito.Mockito.never
49 import org.mockito.Mockito.verify
50 import org.mockito.junit.MockitoJUnit
51 
52 @RunWith(AndroidTestingRunner::class)
53 @RunWithLooper
54 @SmallTest
55 class NotificationShadeDepthControllerTest : SysuiTestCase() {
56 
57     @Mock private lateinit var statusBarStateController: StatusBarStateController
58     @Mock private lateinit var blurUtils: BlurUtils
59     @Mock private lateinit var biometricUnlockController: BiometricUnlockController
60     @Mock private lateinit var keyguardStateController: KeyguardStateController
61     @Mock private lateinit var choreographer: Choreographer
62     @Mock private lateinit var wallpaperManager: WallpaperManager
63     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
64     @Mock private lateinit var dumpManager: DumpManager
65     @Mock private lateinit var root: View
66     @Mock private lateinit var viewRootImpl: ViewRootImpl
67     @Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation
68     @Mock private lateinit var shadeAnimation: NotificationShadeDepthController.DepthAnimation
69     @Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation
70     @Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation
71     @Mock private lateinit var listener: NotificationShadeDepthController.DepthListener
72     @Mock private lateinit var dozeParameters: DozeParameters
73     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
74 
75     private lateinit var statusBarStateListener: StatusBarStateController.StateListener
76     private var statusBarState = StatusBarState.SHADE
77     private val maxBlur = 150
78     private lateinit var notificationShadeDepthController: NotificationShadeDepthController
79 
80     @Before
81     fun setup() {
82         `when`(root.viewRootImpl).thenReturn(viewRootImpl)
83         `when`(statusBarStateController.state).then { statusBarState }
84         `when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
85             (answer.arguments[0] as Float * maxBlur).toInt()
86         }
87         `when`(blurUtils.minBlurRadius).thenReturn(0)
88         `when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
89 
90         notificationShadeDepthController = NotificationShadeDepthController(
91                 statusBarStateController, blurUtils, biometricUnlockController,
92                 keyguardStateController, choreographer, wallpaperManager,
93                 notificationShadeWindowController, dozeParameters, dumpManager)
94         notificationShadeDepthController.shadeSpring = shadeSpring
95         notificationShadeDepthController.shadeAnimation = shadeAnimation
96         notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
97         notificationShadeDepthController.globalActionsSpring = globalActionsSpring
98         notificationShadeDepthController.root = root
99 
100         val captor = ArgumentCaptor.forClass(StatusBarStateController.StateListener::class.java)
101         verify(statusBarStateController).addCallback(captor.capture())
102         statusBarStateListener = captor.value
103     }
104 
105     @Test
106     fun setupListeners() {
107         verify(dumpManager).registerDumpable(anyString(), eq(notificationShadeDepthController))
108     }
109 
110     @Test
111     fun onPanelExpansionChanged_apliesBlur_ifShade() {
112         notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
113                 false /* tracking */)
114         verify(shadeSpring).animateTo(eq(maxBlur), any())
115         verify(shadeAnimation).animateTo(eq(maxBlur), any())
116     }
117 
118     @Test
119     fun onPanelExpansionChanged_animatesBlurIn_ifShade() {
120         notificationShadeDepthController.onPanelExpansionChanged(0.01f /* expansion */,
121                 false /* tracking */)
122         verify(shadeAnimation).animateTo(eq(maxBlur), any())
123     }
124 
125     @Test
126     fun onPanelExpansionChanged_animatesBlurOut_ifShade() {
127         onPanelExpansionChanged_animatesBlurIn_ifShade()
128         clearInvocations(shadeAnimation)
129         notificationShadeDepthController.onPanelExpansionChanged(0f /* expansion */,
130                 false /* tracking */)
131         verify(shadeAnimation).animateTo(eq(0), any())
132     }
133 
134     @Test
135     fun onPanelExpansionChanged_animatesBlurOut_ifFlick() {
136         onPanelExpansionChanged_apliesBlur_ifShade()
137         clearInvocations(shadeAnimation)
138         notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
139                 true /* tracking */)
140         verify(shadeAnimation, never()).animateTo(anyInt(), any())
141 
142         notificationShadeDepthController.onPanelExpansionChanged(0.9f /* expansion */,
143                 true /* tracking */)
144         verify(shadeAnimation, never()).animateTo(anyInt(), any())
145 
146         notificationShadeDepthController.onPanelExpansionChanged(0.8f /* expansion */,
147                 false /* tracking */)
148         verify(shadeAnimation).animateTo(eq(0), any())
149     }
150 
151     @Test
152     fun onPanelExpansionChanged_animatesBlurIn_ifFlickCancelled() {
153         onPanelExpansionChanged_animatesBlurOut_ifFlick()
154         clearInvocations(shadeAnimation)
155         notificationShadeDepthController.onPanelExpansionChanged(0.6f /* expansion */,
156                 true /* tracking */)
157         verify(shadeAnimation).animateTo(eq(maxBlur), any())
158     }
159 
160     @Test
161     fun onStateChanged_reevalutesBlurs_ifSameRadiusAndNewState() {
162         onPanelExpansionChanged_apliesBlur_ifShade()
163         clearInvocations(shadeSpring)
164         clearInvocations(shadeAnimation)
165 
166         statusBarState = StatusBarState.KEYGUARD
167         statusBarStateListener.onStateChanged(statusBarState)
168         verify(shadeSpring).animateTo(eq(0), any())
169         verify(shadeAnimation).animateTo(eq(0), any())
170     }
171 
172     @Test
173     fun updateGlobalDialogVisibility_animatesBlur() {
174         notificationShadeDepthController.updateGlobalDialogVisibility(0.5f, root)
175         verify(globalActionsSpring).animateTo(eq(maxBlur / 2), eq(root))
176     }
177 
178     @Test
179     fun updateGlobalDialogVisibility_appliesBlur_withoutHomeControls() {
180         `when`(globalActionsSpring.radius).thenReturn(maxBlur)
181         notificationShadeDepthController.updateBlurCallback.doFrame(0)
182         verify(blurUtils).applyBlur(any(), eq(maxBlur))
183     }
184 
185     @Test
186     fun updateGlobalDialogVisibility_appliesBlur_unlessHomeControls() {
187         notificationShadeDepthController.showingHomeControls = true
188         `when`(globalActionsSpring.radius).thenReturn(maxBlur)
189         notificationShadeDepthController.updateBlurCallback.doFrame(0)
190         verify(blurUtils).applyBlur(any(), eq(0))
191     }
192 
193     @Test
194     fun updateBlurCallback_setsBlurAndZoom() {
195         notificationShadeDepthController.addListener(listener)
196         notificationShadeDepthController.updateBlurCallback.doFrame(0)
197         verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat())
198         verify(listener).onWallpaperZoomOutChanged(anyFloat())
199         verify(blurUtils).applyBlur(any(), anyInt())
200     }
201 
202     @Test
203     fun updateBlurCallback_setsBlur_whenExpanded() {
204         `when`(shadeSpring.radius).thenReturn(maxBlur)
205         `when`(shadeAnimation.radius).thenReturn(maxBlur)
206         notificationShadeDepthController.updateBlurCallback.doFrame(0)
207         verify(blurUtils).applyBlur(any(), eq(maxBlur))
208     }
209 
210     @Test
211     fun updateBlurCallback_appLaunchAnimation_overridesZoom() {
212         `when`(shadeSpring.radius).thenReturn(maxBlur)
213         `when`(shadeAnimation.radius).thenReturn(maxBlur)
214         val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
215         animProgress.linearProgress = 1f
216         notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
217         notificationShadeDepthController.updateBlurCallback.doFrame(0)
218         verify(blurUtils).applyBlur(any(), eq(0))
219     }
220 
221     @Test
222     fun updateBlurCallback_invalidWindow() {
223         doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
224                 .setWallpaperZoomOut(any(), anyFloat())
225         notificationShadeDepthController.updateBlurCallback.doFrame(0)
226         verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat())
227     }
228 
229     @Test
230     fun brightnessMirrorVisible_whenVisible() {
231         notificationShadeDepthController.brightnessMirrorVisible = true
232         verify(brightnessSpring).animateTo(eq(maxBlur), any())
233     }
234 
235     @Test
236     fun brightnessMirrorVisible_whenHidden() {
237         notificationShadeDepthController.brightnessMirrorVisible = false
238         verify(brightnessSpring).animateTo(eq(0), any())
239     }
240 
241     @Test
242     fun brightnessMirror_hidesShadeBlur() {
243         // Brightness mirror is fully visible
244         `when`(brightnessSpring.ratio).thenReturn(1f)
245         // And shade is blurred
246         `when`(shadeSpring.radius).thenReturn(maxBlur)
247         `when`(shadeAnimation.radius).thenReturn(maxBlur)
248 
249         notificationShadeDepthController.updateBlurCallback.doFrame(0)
250         verify(notificationShadeWindowController).setBackgroundBlurRadius(0)
251         verify(blurUtils).applyBlur(eq(viewRootImpl), eq(0))
252     }
253 
254     @Test
255     fun setNotificationLaunchAnimationParams_schedulesFrame() {
256         val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
257         animProgress.linearProgress = 0.5f
258         notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
259         verify(choreographer).postFrameCallback(
260                 eq(notificationShadeDepthController.updateBlurCallback))
261     }
262 
263     @Test
264     fun setNotificationLaunchAnimationParams_whennNull_ignoresIfShadeHasNoBlur() {
265         val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
266         animProgress.linearProgress = 0.5f
267         `when`(shadeSpring.radius).thenReturn(0)
268         `when`(shadeAnimation.radius).thenReturn(0)
269         notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
270         verify(shadeSpring, never()).animateTo(anyInt(), any())
271         verify(shadeAnimation, never()).animateTo(anyInt(), any())
272     }
273 }