1 /* 2 * Copyright (C) 2022 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.unfold 18 19 import android.hardware.devicestate.DeviceStateManager 20 import android.hardware.devicestate.DeviceStateManager.FoldStateListener 21 import android.os.PowerManager 22 import android.testing.AndroidTestingRunner 23 import android.view.ViewGroup 24 import android.view.ViewTreeObserver 25 import androidx.test.filters.SmallTest 26 import com.android.internal.util.LatencyTracker 27 import com.android.systemui.SysuiTestCase 28 import com.android.systemui.flags.FakeFeatureFlags 29 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR 30 import com.android.systemui.keyguard.WakefulnessLifecycle 31 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository 32 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory 33 import com.android.systemui.shade.ShadeFoldAnimator 34 import com.android.systemui.shade.ShadeViewController 35 import com.android.systemui.statusbar.LightRevealScrim 36 import com.android.systemui.statusbar.phone.CentralSurfaces 37 import com.android.systemui.unfold.util.FoldableDeviceStates 38 import com.android.systemui.unfold.util.FoldableTestUtils 39 import com.android.systemui.util.concurrency.FakeExecutor 40 import com.android.systemui.util.mockito.any 41 import com.android.systemui.util.settings.GlobalSettings 42 import com.android.systemui.util.time.FakeSystemClock 43 import kotlinx.coroutines.Dispatchers 44 import kotlinx.coroutines.runBlocking 45 import kotlinx.coroutines.yield 46 import org.junit.Before 47 import org.junit.Test 48 import org.junit.runner.RunWith 49 import org.mockito.ArgumentCaptor 50 import org.mockito.Captor 51 import org.mockito.Mock 52 import org.mockito.Mockito.never 53 import org.mockito.Mockito.reset 54 import org.mockito.Mockito.verify 55 import org.mockito.Mockito.verifyNoMoreInteractions 56 import org.mockito.Mockito.`when` as whenever 57 import org.mockito.MockitoAnnotations 58 59 @RunWith(AndroidTestingRunner::class) 60 @SmallTest 61 class FoldAodAnimationControllerTest : SysuiTestCase() { 62 63 @Mock lateinit var deviceStateManager: DeviceStateManager 64 65 @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle 66 67 @Mock lateinit var globalSettings: GlobalSettings 68 69 @Mock lateinit var latencyTracker: LatencyTracker 70 71 @Mock lateinit var centralSurfaces: CentralSurfaces 72 73 @Mock lateinit var lightRevealScrim: LightRevealScrim 74 75 @Mock lateinit var shadeViewController: ShadeViewController 76 77 @Mock lateinit var viewGroup: ViewGroup 78 79 @Mock lateinit var viewTreeObserver: ViewTreeObserver 80 81 @Mock lateinit var shadeFoldAnimator: ShadeFoldAnimator 82 83 @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener> 84 85 private lateinit var deviceStates: FoldableDeviceStates 86 87 lateinit var keyguardRepository: FakeKeyguardRepository 88 89 lateinit var underTest: FoldAodAnimationController 90 private val fakeExecutor = FakeExecutor(FakeSystemClock()) 91 92 @Before 93 fun setup() { 94 MockitoAnnotations.initMocks(this) 95 96 deviceStates = FoldableTestUtils.findDeviceStates(context) 97 98 // TODO(b/254878364): remove this call to NPVC.getView() 99 whenever(shadeViewController.shadeFoldAnimator).thenReturn(shadeFoldAnimator) 100 whenever(shadeFoldAnimator.view).thenReturn(viewGroup) 101 whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver) 102 whenever(wakefulnessLifecycle.lastSleepReason) 103 .thenReturn(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD) 104 whenever(shadeFoldAnimator.startFoldToAodAnimation(any(), any(), any())).then { 105 val onActionStarted = it.arguments[0] as Runnable 106 onActionStarted.run() 107 } 108 109 val featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) } 110 val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags) 111 val keyguardInteractor = withDeps.keyguardInteractor 112 keyguardRepository = withDeps.repository 113 114 // Needs to be run on the main thread 115 runBlocking(IMMEDIATE) { 116 underTest = 117 FoldAodAnimationController( 118 fakeExecutor, 119 context, 120 deviceStateManager, 121 wakefulnessLifecycle, 122 globalSettings, 123 latencyTracker, 124 { keyguardInteractor }, 125 ) 126 .apply { initialize(centralSurfaces, shadeViewController, lightRevealScrim) } 127 128 verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture()) 129 130 setAodEnabled(enabled = true) 131 sendFoldEvent(folded = false) 132 } 133 } 134 135 @Test 136 fun onFolded_aodDisabled_doesNotLogLatency() = 137 runBlocking(IMMEDIATE) { 138 val job = underTest.listenForDozing(this) 139 keyguardRepository.setIsDozing(true) 140 setAodEnabled(enabled = false) 141 142 yield() 143 144 fold() 145 simulateScreenTurningOn() 146 147 verifyNoMoreInteractions(latencyTracker) 148 149 job.cancel() 150 } 151 152 @Test 153 fun onFolded_aodEnabled_logsLatency() = 154 runBlocking(IMMEDIATE) { 155 val job = underTest.listenForDozing(this) 156 keyguardRepository.setIsDozing(true) 157 setAodEnabled(enabled = true) 158 159 yield() 160 161 fold() 162 simulateScreenTurningOn() 163 164 verify(latencyTracker).onActionStart(any()) 165 verify(latencyTracker).onActionEnd(any()) 166 167 job.cancel() 168 } 169 170 @Test 171 fun onFolded_onScreenTurningOnInvokedTwice_doesNotLogLatency() = 172 runBlocking(IMMEDIATE) { 173 val job = underTest.listenForDozing(this) 174 keyguardRepository.setIsDozing(true) 175 setAodEnabled(enabled = true) 176 177 yield() 178 179 fold() 180 simulateScreenTurningOn() 181 reset(latencyTracker) 182 183 // This can happen > 1 time if the prox sensor is covered 184 simulateScreenTurningOn() 185 186 verify(latencyTracker, never()).onActionStart(any()) 187 verify(latencyTracker, never()).onActionEnd(any()) 188 189 job.cancel() 190 } 191 192 @Test 193 fun onFolded_onScreenTurningOnWithoutDozingThenWithDozing_doesNotLogLatency() = 194 runBlocking(IMMEDIATE) { 195 val job = underTest.listenForDozing(this) 196 keyguardRepository.setIsDozing(false) 197 setAodEnabled(enabled = true) 198 199 yield() 200 201 fold() 202 simulateScreenTurningOn() 203 reset(latencyTracker) 204 205 // Now enable dozing and trigger a second run through the aod animation code. It should 206 // not rerun the animation 207 keyguardRepository.setIsDozing(true) 208 yield() 209 simulateScreenTurningOn() 210 211 verify(latencyTracker, never()).onActionStart(any()) 212 verify(latencyTracker, never()).onActionEnd(any()) 213 214 job.cancel() 215 } 216 217 @Test 218 fun onFolded_animationCancelled_doesNotLogLatency() = 219 runBlocking(IMMEDIATE) { 220 val job = underTest.listenForDozing(this) 221 keyguardRepository.setIsDozing(true) 222 setAodEnabled(enabled = true) 223 224 yield() 225 226 fold() 227 underTest.onScreenTurningOn({}) 228 // The body of onScreenTurningOn is executed on fakeExecutor, 229 // run all pending tasks before calling the next method 230 fakeExecutor.runAllReady() 231 underTest.onStartedWakingUp() 232 233 verify(latencyTracker).onActionStart(any()) 234 verify(latencyTracker).onActionCancel(any()) 235 236 job.cancel() 237 } 238 239 private fun simulateScreenTurningOn() { 240 underTest.onScreenTurningOn({}) 241 underTest.onScreenTurnedOn() 242 fakeExecutor.runAllReady() 243 } 244 245 private fun fold() = sendFoldEvent(folded = true) 246 247 private fun setAodEnabled(enabled: Boolean) = underTest.onAlwaysOnChanged(alwaysOn = enabled) 248 249 private fun sendFoldEvent(folded: Boolean) { 250 val state = if (folded) deviceStates.folded else deviceStates.unfolded 251 foldStateListenerCaptor.value.onStateChanged(state) 252 } 253 254 companion object { 255 private val IMMEDIATE = Dispatchers.Main.immediate 256 } 257 } 258