1 /* 2 * Copyright (C) 2024 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.os.PowerManager 20 import android.os.SystemProperties 21 import android.testing.AndroidTestingRunner 22 import android.testing.TestableLooper 23 import androidx.test.filters.SmallTest 24 import com.android.internal.foldables.FoldLockSettingAvailabilityProvider 25 import com.android.systemui.SysuiTestCase 26 import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState 27 import com.android.systemui.display.data.repository.fakeDeviceStateRepository 28 import com.android.systemui.kosmos.Kosmos 29 import com.android.systemui.kosmos.testDispatcher 30 import com.android.systemui.kosmos.testScope 31 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest 32 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState 33 import com.android.systemui.power.domain.interactor.powerInteractor 34 import com.android.systemui.power.shared.model.ScreenPowerState 35 import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository 36 import com.android.systemui.util.mockito.any 37 import com.android.systemui.util.mockito.mock 38 import com.android.systemui.util.mockito.whenever 39 import kotlinx.coroutines.ExperimentalCoroutinesApi 40 import kotlinx.coroutines.test.TestScope 41 import kotlinx.coroutines.test.advanceTimeBy 42 import kotlinx.coroutines.test.runTest 43 import org.junit.Before 44 import org.junit.Test 45 import org.junit.runner.RunWith 46 import org.mockito.Mockito.atLeast 47 import org.mockito.Mockito.never 48 import org.mockito.Mockito.verify 49 import org.mockito.MockitoAnnotations 50 51 @SmallTest 52 @TestableLooper.RunWithLooper(setAsMainLooper = true) 53 @RunWith(AndroidTestingRunner::class) 54 @OptIn(ExperimentalCoroutinesApi::class) 55 class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { 56 private val kosmos = Kosmos() 57 private val testScope: TestScope = kosmos.testScope 58 private val fakeDeviceStateRepository = kosmos.fakeDeviceStateRepository 59 private val powerInteractor = kosmos.powerInteractor 60 private val fakeAnimationStatusRepository = kosmos.fakeAnimationStatusRepository 61 private val mockControllerFactory = kosmos.fullscreenLightRevealAnimationControllerFactory 62 private val mockFullScreenController = kosmos.fullscreenLightRevealAnimationController 63 private val mockFoldLockSettingAvailabilityProvider = 64 mock<FoldLockSettingAvailabilityProvider>() 65 private val onOverlayReady = mock<Runnable>() 66 private lateinit var foldLightRevealAnimation: FoldLightRevealOverlayAnimation 67 68 @Before setupnull69 fun setup() { 70 MockitoAnnotations.initMocks(this) 71 whenever(mockFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable) 72 .thenReturn(true) 73 fakeAnimationStatusRepository.onAnimationStatusChanged(true) 74 75 foldLightRevealAnimation = 76 FoldLightRevealOverlayAnimation( 77 kosmos.testDispatcher, 78 fakeDeviceStateRepository, 79 powerInteractor, 80 testScope.backgroundScope, 81 fakeAnimationStatusRepository, 82 mockControllerFactory, 83 mockFoldLockSettingAvailabilityProvider 84 ) 85 foldLightRevealAnimation.init() 86 } 87 88 @Test foldToScreenOn_playFoldAnimationnull89 fun foldToScreenOn_playFoldAnimation() = 90 testScope.runTest { 91 foldDeviceToScreenOff() 92 turnScreenOn() 93 94 verifyFoldAnimationPlayed() 95 } 96 97 @Test foldToAod_doNotPlayFoldAnimationnull98 fun foldToAod_doNotPlayFoldAnimation() = 99 testScope.runTest { 100 foldDeviceToScreenOff() 101 emitLastWakefulnessEventStartingToSleep() 102 advanceTimeBy(SHORT_DELAY_MS) 103 turnScreenOn() 104 advanceTimeBy(ANIMATION_DURATION) 105 106 verifyFoldAnimationDidNotPlay() 107 } 108 109 @Test foldToScreenOff_doNotPlayFoldAnimationnull110 fun foldToScreenOff_doNotPlayFoldAnimation() = 111 testScope.runTest { 112 foldDeviceToScreenOff() 113 emitLastWakefulnessEventStartingToSleep() 114 advanceTimeBy(SHORT_DELAY_MS) 115 advanceTimeBy(ANIMATION_DURATION) 116 117 verifyFoldAnimationDidNotPlay() 118 } 119 120 @Test foldToScreenOnWithDelay_doNotPlayFoldAnimationnull121 fun foldToScreenOnWithDelay_doNotPlayFoldAnimation() = 122 testScope.runTest { 123 foldDeviceToScreenOff() 124 foldLightRevealAnimation.onScreenTurningOn {} 125 advanceTimeBy(WAIT_FOR_ANIMATION_TIMEOUT_MS) 126 powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) 127 advanceTimeBy(SHORT_DELAY_MS) 128 advanceTimeBy(ANIMATION_DURATION) 129 130 verifyFoldAnimationDidNotPlay() 131 } 132 133 @Test immediateUnfoldAfterFold_removeOverlayAfterCancellationnull134 fun immediateUnfoldAfterFold_removeOverlayAfterCancellation() = 135 testScope.runTest { 136 foldDeviceToScreenOff() 137 foldLightRevealAnimation.onScreenTurningOn {} 138 advanceTimeBy(SHORT_DELAY_MS) 139 advanceTimeBy(ANIMATION_DURATION) 140 fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED) 141 advanceTimeBy(SHORT_DELAY_MS) 142 powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) 143 144 verifyOverlayWasRemoved() 145 } 146 147 @Test foldToScreenOn_removeOverlayAfterCompletionnull148 fun foldToScreenOn_removeOverlayAfterCompletion() = 149 testScope.runTest { 150 foldDeviceToScreenOff() 151 turnScreenOn() 152 advanceTimeBy(ANIMATION_DURATION) 153 154 verifyOverlayWasRemoved() 155 } 156 157 @Test unfold_immediatelyRunRunnablenull158 fun unfold_immediatelyRunRunnable() = 159 testScope.runTest { 160 foldLightRevealAnimation.onScreenTurningOn(onOverlayReady) 161 162 verify(onOverlayReady).run() 163 } 164 foldDeviceToScreenOffnull165 private suspend fun TestScope.foldDeviceToScreenOff() { 166 fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED) 167 powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) 168 advanceTimeBy(SHORT_DELAY_MS) 169 fakeDeviceStateRepository.emit(DeviceState.FOLDED) 170 advanceTimeBy(SHORT_DELAY_MS) 171 powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_OFF) 172 advanceTimeBy(SHORT_DELAY_MS) 173 } 174 TestScopenull175 private fun TestScope.turnScreenOn() { 176 foldLightRevealAnimation.onScreenTurningOn {} 177 advanceTimeBy(SHORT_DELAY_MS) 178 powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) 179 advanceTimeBy(SHORT_DELAY_MS) 180 } 181 emitLastWakefulnessEventStartingToSleepnull182 private fun emitLastWakefulnessEventStartingToSleep() = 183 powerInteractor.setAsleepForTest(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD) 184 185 private fun verifyFoldAnimationPlayed() = 186 verify(mockFullScreenController, atLeast(1)).updateRevealAmount(any()) 187 188 private fun verifyFoldAnimationDidNotPlay() = 189 verify(mockFullScreenController, never()).updateRevealAmount(any()) 190 191 private fun verifyOverlayWasRemoved() = 192 verify(mockFullScreenController, atLeast(1)).ensureOverlayRemoved() 193 194 private companion object { 195 const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L 196 val ANIMATION_DURATION: Long 197 get() = SystemProperties.getLong("persist.fold_animation_duration", 200L) 198 const val SHORT_DELAY_MS = 50L 199 } 200 } 201