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 }