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.keyguard.ui.viewmodel 18 19 import androidx.test.ext.junit.runners.AndroidJUnit4 20 import androidx.test.filters.SmallTest 21 import com.android.systemui.RoboPilotTest 22 import com.android.systemui.SysuiTestCase 23 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository 24 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory 25 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD 26 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING 27 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING 28 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE 29 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN 30 import com.android.systemui.keyguard.shared.model.TransitionState 31 import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED 32 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED 33 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING 34 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED 35 import com.android.systemui.keyguard.shared.model.TransitionStep 36 import com.android.systemui.util.mockito.mock 37 import com.google.common.collect.Range 38 import com.google.common.truth.Truth.assertThat 39 import kotlinx.coroutines.flow.launchIn 40 import kotlinx.coroutines.flow.onEach 41 import kotlinx.coroutines.test.TestScope 42 import kotlinx.coroutines.test.UnconfinedTestDispatcher 43 import kotlinx.coroutines.test.runTest 44 import org.junit.Before 45 import org.junit.Test 46 import org.junit.runner.RunWith 47 48 @SmallTest 49 @RoboPilotTest 50 @RunWith(AndroidJUnit4::class) 51 class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { 52 private lateinit var underTest: DreamingToLockscreenTransitionViewModel 53 private lateinit var repository: FakeKeyguardTransitionRepository 54 55 @Before 56 fun setUp() { 57 repository = FakeKeyguardTransitionRepository() 58 val interactor = 59 KeyguardTransitionInteractorFactory.create( 60 scope = TestScope().backgroundScope, 61 repository = repository, 62 ) 63 .keyguardTransitionInteractor 64 underTest = DreamingToLockscreenTransitionViewModel(interactor, mock()) 65 } 66 67 @Test 68 fun dreamOverlayTranslationY() = 69 runTest(UnconfinedTestDispatcher()) { 70 val values = mutableListOf<Float>() 71 72 val pixels = 100 73 val job = 74 underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this) 75 76 repository.sendTransitionStep(step(0f, STARTED)) 77 repository.sendTransitionStep(step(0f)) 78 repository.sendTransitionStep(step(0.3f)) 79 repository.sendTransitionStep(step(0.5f)) 80 repository.sendTransitionStep(step(0.6f)) 81 repository.sendTransitionStep(step(0.8f)) 82 repository.sendTransitionStep(step(1f)) 83 84 assertThat(values.size).isEqualTo(7) 85 values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } 86 87 job.cancel() 88 } 89 90 @Test 91 fun dreamOverlayFadeOut() = 92 runTest(UnconfinedTestDispatcher()) { 93 val values = mutableListOf<Float>() 94 95 val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this) 96 97 // Should start running here... 98 repository.sendTransitionStep(step(0f, STARTED)) 99 repository.sendTransitionStep(step(0f)) 100 repository.sendTransitionStep(step(0.1f)) 101 repository.sendTransitionStep(step(0.5f)) 102 // ...up to here 103 repository.sendTransitionStep(step(1f)) 104 105 // Only two values should be present, since the dream overlay runs for a small fraction 106 // of the overall animation time 107 assertThat(values.size).isEqualTo(4) 108 values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } 109 110 job.cancel() 111 } 112 113 @Test 114 fun lockscreenFadeIn() = 115 runTest(UnconfinedTestDispatcher()) { 116 val values = mutableListOf<Float>() 117 118 val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) 119 120 repository.sendTransitionStep(step(0f, STARTED)) 121 repository.sendTransitionStep(step(0f)) 122 repository.sendTransitionStep(step(0.1f)) 123 repository.sendTransitionStep(step(0.2f)) 124 repository.sendTransitionStep(step(0.3f)) 125 repository.sendTransitionStep(step(1f)) 126 127 assertThat(values.size).isEqualTo(4) 128 values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } 129 130 job.cancel() 131 } 132 133 @Test 134 fun lockscreenTranslationY() = 135 runTest(UnconfinedTestDispatcher()) { 136 val values = mutableListOf<Float>() 137 138 val pixels = 100 139 val job = 140 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) 141 142 repository.sendTransitionStep(step(0f, STARTED)) 143 repository.sendTransitionStep(step(0f)) 144 repository.sendTransitionStep(step(0.3f)) 145 repository.sendTransitionStep(step(0.5f)) 146 repository.sendTransitionStep(step(1f)) 147 148 assertThat(values.size).isEqualTo(5) 149 values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) } 150 151 job.cancel() 152 } 153 154 @Test 155 fun transitionEnded() = 156 runTest(UnconfinedTestDispatcher()) { 157 val values = mutableListOf<TransitionStep>() 158 159 val job = underTest.transitionEnded.onEach { values.add(it) }.launchIn(this) 160 161 repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 0.0f, STARTED)) 162 repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 1.0f, FINISHED)) 163 164 repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED)) 165 repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING)) 166 repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED)) 167 168 repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED)) 169 repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING)) 170 repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED)) 171 172 repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.0f, STARTED)) 173 repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.5f, RUNNING)) 174 repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 1.0f, CANCELED)) 175 176 repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 0.0f, STARTED)) 177 repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 1.0f, FINISHED)) 178 179 assertThat(values.size).isEqualTo(3) 180 values.forEach { 181 assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED) 182 .isTrue() 183 } 184 185 job.cancel() 186 } 187 188 private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { 189 return TransitionStep( 190 from = DREAMING, 191 to = LOCKSCREEN, 192 value = value, 193 transitionState = state, 194 ownerName = "DreamingToLockscreenTransitionViewModelTest" 195 ) 196 } 197 } 198