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