• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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.scene.ui.viewmodel
18 
19 import android.view.MotionEvent
20 import android.view.MotionEvent.ACTION_DOWN
21 import android.view.MotionEvent.ACTION_OUTSIDE
22 import android.view.View
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.filters.SmallTest
25 import com.android.compose.animation.scene.DefaultEdgeDetector
26 import com.android.systemui.SysuiTestCase
27 import com.android.systemui.classifier.fakeFalsingManager
28 import com.android.systemui.flags.EnableSceneContainer
29 import com.android.systemui.kosmos.collectLastValue
30 import com.android.systemui.kosmos.runCurrent
31 import com.android.systemui.kosmos.runTest
32 import com.android.systemui.kosmos.testScope
33 import com.android.systemui.lifecycle.activateIn
34 import com.android.systemui.power.data.repository.fakePowerRepository
35 import com.android.systemui.scene.domain.interactor.sceneInteractor
36 import com.android.systemui.scene.fakeOverlaysByKeys
37 import com.android.systemui.scene.sceneContainerConfig
38 import com.android.systemui.scene.sceneContainerViewModelFactory
39 import com.android.systemui.scene.shared.model.Overlays
40 import com.android.systemui.scene.shared.model.Scenes
41 import com.android.systemui.scene.shared.model.fakeSceneDataSource
42 import com.android.systemui.shade.domain.interactor.enableDualShade
43 import com.android.systemui.shade.domain.interactor.enableSingleShade
44 import com.android.systemui.shade.domain.interactor.enableSplitShade
45 import com.android.systemui.shade.domain.interactor.shadeMode
46 import com.android.systemui.shade.shared.model.ShadeMode
47 import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
48 import com.android.systemui.testKosmos
49 import com.google.common.truth.Truth.assertThat
50 import com.google.common.truth.Truth.assertWithMessage
51 import kotlinx.coroutines.ExperimentalCoroutinesApi
52 import kotlinx.coroutines.Job
53 import org.junit.Before
54 import org.junit.Test
55 import org.junit.runner.RunWith
56 import org.mockito.kotlin.doReturn
57 import org.mockito.kotlin.mock
58 
59 @OptIn(ExperimentalCoroutinesApi::class)
60 @SmallTest
61 @RunWith(AndroidJUnit4::class)
62 @EnableSceneContainer
63 class SceneContainerViewModelTest : SysuiTestCase() {
64 
65     private val kosmos = testKosmos()
66     private val testScope by lazy { kosmos.testScope }
67     private val falsingManager by lazy { kosmos.fakeFalsingManager }
68     private val view = mock<View>()
69 
70     private lateinit var underTest: SceneContainerViewModel
71 
72     private lateinit var activationJob: Job
73     private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null
74 
75     @Before
76     fun setUp() {
77         underTest =
78             kosmos.sceneContainerViewModelFactory.create(
79                 view,
80                 { motionEventHandler ->
81                     this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
82                 },
83             )
84         activationJob = Job()
85         underTest.activateIn(testScope, activationJob)
86     }
87 
88     @Test
89     fun activate_setsMotionEventHandler() =
90         kosmos.runTest {
91             runCurrent()
92             assertThat(motionEventHandler).isNotNull()
93         }
94 
95     @Test
96     fun deactivate_clearsMotionEventHandler() =
97         kosmos.runTest {
98             activationJob.cancel()
99             runCurrent()
100 
101             assertThat(motionEventHandler).isNull()
102         }
103 
104     @Test
105     fun isVisible() =
106         kosmos.runTest {
107             assertThat(underTest.isVisible).isTrue()
108 
109             sceneInteractor.setVisible(false, "reason")
110             runCurrent()
111             assertThat(underTest.isVisible).isFalse()
112 
113             sceneInteractor.setVisible(true, "reason")
114             runCurrent()
115             assertThat(underTest.isVisible).isTrue()
116         }
117 
118     @Test
119     fun sceneTransition() =
120         kosmos.runTest {
121             val currentScene by collectLastValue(underTest.currentScene)
122             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
123 
124             fakeSceneDataSource.changeScene(Scenes.Shade)
125 
126             assertThat(currentScene).isEqualTo(Scenes.Shade)
127         }
128 
129     @Test
130     fun canChangeScene_whenAllowed_switchingFromGone_returnsTrue() =
131         kosmos.runTest {
132             val currentScene by collectLastValue(underTest.currentScene)
133             fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
134             runCurrent()
135             assertThat(currentScene).isEqualTo(Scenes.Gone)
136 
137             sceneContainerConfig.sceneKeys
138                 .filter { it != currentScene }
139                 .forEach { toScene ->
140                     assertWithMessage("Scene $toScene incorrectly protected when allowed")
141                         .that(underTest.canChangeScene(toScene = toScene))
142                         .isTrue()
143                 }
144         }
145 
146     @Test
147     fun canChangeScene_whenAllowed_switchingFromLockscreen_returnsTrue() =
148         kosmos.runTest {
149             val currentScene by collectLastValue(underTest.currentScene)
150             fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
151             runCurrent()
152             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
153 
154             sceneContainerConfig.sceneKeys
155                 .filter { it != currentScene }
156                 .forEach { toScene ->
157                     assertWithMessage("Scene $toScene incorrectly protected when allowed")
158                         .that(underTest.canChangeScene(toScene = toScene))
159                         .isTrue()
160                 }
161         }
162 
163     @Test
164     fun canChangeScene_whenNotAllowed_fromLockscreen_toFalsingProtectedScenes_returnsFalse() =
165         kosmos.runTest {
166             falsingManager.setIsFalseTouch(true)
167             val currentScene by collectLastValue(underTest.currentScene)
168             fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
169             runCurrent()
170             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
171 
172             sceneContainerConfig.sceneKeys
173                 .filter { it != currentScene }
174                 .filter {
175                     // Moving to the Communal and Dream scene is not currently falsing protected.
176                     it != Scenes.Communal && it != Scenes.Dream
177                 }
178                 .forEach { toScene ->
179                     assertWithMessage("Protected scene $toScene not properly protected")
180                         .that(underTest.canChangeScene(toScene = toScene))
181                         .isFalse()
182                 }
183         }
184 
185     @Test
186     fun canChangeScene_whenNotAllowed_fromLockscreen_toFalsingUnprotectedScenes_returnsTrue() =
187         kosmos.runTest {
188             falsingManager.setIsFalseTouch(true)
189             val currentScene by collectLastValue(underTest.currentScene)
190             fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
191             runCurrent()
192             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
193 
194             sceneContainerConfig.sceneKeys
195                 .filter {
196                     // Moving to the Communal scene is not currently falsing protected.
197                     it == Scenes.Communal
198                 }
199                 .forEach { toScene ->
200                     assertWithMessage("Unprotected scene $toScene is incorrectly protected")
201                         .that(underTest.canChangeScene(toScene = toScene))
202                         .isTrue()
203                 }
204         }
205 
206     @Test
207     fun canChangeScene_whenNotAllowed_fromGone_toAnyOtherScene_returnsTrue() =
208         kosmos.runTest {
209             falsingManager.setIsFalseTouch(true)
210             val currentScene by collectLastValue(underTest.currentScene)
211             fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
212             runCurrent()
213             assertThat(currentScene).isEqualTo(Scenes.Gone)
214 
215             sceneContainerConfig.sceneKeys
216                 .filter { it != currentScene }
217                 .forEach { toScene ->
218                     assertWithMessage("Protected scene $toScene not properly protected")
219                         .that(underTest.canChangeScene(toScene = toScene))
220                         .isTrue()
221                 }
222         }
223 
224     @Test
225     fun canShowOrReplaceOverlay_whenAllowed_showingWhileOnGone_returnsTrue() =
226         kosmos.runTest {
227             val currentScene by collectLastValue(underTest.currentScene)
228             fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
229             runCurrent()
230             assertThat(currentScene).isEqualTo(Scenes.Gone)
231 
232             sceneContainerConfig.overlayKeys.forEach { overlay ->
233                 assertWithMessage("Overlay $overlay incorrectly protected when allowed")
234                     .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
235                     .isTrue()
236             }
237         }
238 
239     @Test
240     fun canShowOrReplaceOverlay_whenAllowed_showingWhileOnLockscreen_returnsTrue() =
241         kosmos.runTest {
242             val currentScene by collectLastValue(underTest.currentScene)
243             fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
244             runCurrent()
245             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
246 
247             sceneContainerConfig.overlayKeys.forEach { overlay ->
248                 assertWithMessage("Overlay $overlay incorrectly protected when allowed")
249                     .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
250                     .isTrue()
251             }
252         }
253 
254     @Test
255     fun canShowOrReplaceOverlay_whenNotAllowed_whileOnLockscreen_returnsFalse() =
256         kosmos.runTest {
257             falsingManager.setIsFalseTouch(true)
258             val currentScene by collectLastValue(underTest.currentScene)
259             fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen)
260             runCurrent()
261             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
262 
263             sceneContainerConfig.overlayKeys.forEach { overlay ->
264                 assertWithMessage("Protected overlay $overlay not properly protected")
265                     .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
266                     .isFalse()
267             }
268         }
269 
270     @Test
271     fun canShowOrReplaceOverlay_whenNotAllowed_whileOnGone_returnsTrue() =
272         kosmos.runTest {
273             falsingManager.setIsFalseTouch(true)
274             val currentScene by collectLastValue(underTest.currentScene)
275             fakeSceneDataSource.changeScene(toScene = Scenes.Gone)
276             runCurrent()
277             assertThat(currentScene).isEqualTo(Scenes.Gone)
278 
279             sceneContainerConfig.overlayKeys.forEach { overlay ->
280                 assertWithMessage("Protected overlay $overlay not properly protected")
281                     .that(underTest.canShowOrReplaceOverlay(newlyShown = overlay))
282                     .isTrue()
283             }
284         }
285 
286     @Test
287     fun userInput() =
288         kosmos.runTest {
289             assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
290             underTest.onMotionEvent(mock())
291             assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
292         }
293 
294     @Test
295     fun userInputOnEmptySpace_insideEvent() =
296         kosmos.runTest {
297             assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
298             val insideMotionEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0f, 0f, 0)
299             underTest.onEmptySpaceMotionEvent(insideMotionEvent)
300             assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
301         }
302 
303     @Test
304     fun userInputOnEmptySpace_outsideEvent_remoteInputActive() =
305         kosmos.runTest {
306             fakeRemoteInputRepository.isRemoteInputActive.value = true
307             assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
308             val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0)
309             underTest.onEmptySpaceMotionEvent(outsideMotionEvent)
310             assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isTrue()
311         }
312 
313     @Test
314     fun userInputOnEmptySpace_outsideEvent_remoteInputInactive() =
315         kosmos.runTest {
316             fakeRemoteInputRepository.isRemoteInputActive.value = false
317             assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
318             val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0)
319             underTest.onEmptySpaceMotionEvent(outsideMotionEvent)
320             assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
321         }
322 
323     @Test
324     fun remoteUserInteraction_keepsContainerVisible() =
325         kosmos.runTest {
326             sceneInteractor.setVisible(false, "reason")
327             runCurrent()
328             assertThat(underTest.isVisible).isFalse()
329             sceneInteractor.onRemoteUserInputStarted("reason")
330             runCurrent()
331             assertThat(underTest.isVisible).isTrue()
332 
333             underTest.onMotionEvent(mock { on { actionMasked } doReturn MotionEvent.ACTION_UP })
334             runCurrent()
335 
336             assertThat(underTest.isVisible).isFalse()
337         }
338 
339     @Test
340     fun getActionableContentKey_noOverlays_returnsCurrentScene() =
341         kosmos.runTest {
342             val currentScene by collectLastValue(underTest.currentScene)
343             val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
344             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
345             assertThat(currentOverlays).isEmpty()
346 
347             val actionableContentKey =
348                 underTest.getActionableContentKey(
349                     currentScene = checkNotNull(currentScene),
350                     currentOverlays = checkNotNull(currentOverlays),
351                     overlayByKey = kosmos.fakeOverlaysByKeys,
352                 )
353 
354             assertThat(actionableContentKey).isEqualTo(Scenes.Lockscreen)
355         }
356 
357     @Test
358     fun getActionableContentKey_multipleOverlays_returnsTopOverlay() =
359         kosmos.runTest {
360             val currentScene by collectLastValue(underTest.currentScene)
361             val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
362             fakeSceneDataSource.showOverlay(Overlays.QuickSettingsShade)
363             fakeSceneDataSource.showOverlay(Overlays.NotificationsShade)
364             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
365             assertThat(currentOverlays)
366                 .containsExactly(Overlays.QuickSettingsShade, Overlays.NotificationsShade)
367 
368             val actionableContentKey =
369                 underTest.getActionableContentKey(
370                     currentScene = checkNotNull(currentScene),
371                     currentOverlays = checkNotNull(currentOverlays),
372                     overlayByKey = kosmos.fakeOverlaysByKeys,
373                 )
374 
375             assertThat(actionableContentKey).isEqualTo(Overlays.QuickSettingsShade)
376         }
377 
378     @Test
379     fun edgeDetector_singleShade_usesDefaultEdgeDetector() =
380         kosmos.runTest {
381             val shadeMode by collectLastValue(kosmos.shadeMode)
382             kosmos.enableSingleShade()
383 
384             assertThat(shadeMode).isEqualTo(ShadeMode.Single)
385             assertThat(underTest.swipeSourceDetector).isEqualTo(DefaultEdgeDetector)
386         }
387 
388     @Test
389     fun edgeDetector_splitShade_usesDefaultEdgeDetector() =
390         kosmos.runTest {
391             val shadeMode by collectLastValue(kosmos.shadeMode)
392             kosmos.enableSplitShade()
393 
394             assertThat(shadeMode).isEqualTo(ShadeMode.Split)
395             assertThat(underTest.swipeSourceDetector).isEqualTo(DefaultEdgeDetector)
396         }
397 
398     @Test
399     fun edgeDetector_dualShade_narrowScreen_usesSceneContainerSwipeDetector() =
400         kosmos.runTest {
401             val shadeMode by collectLastValue(kosmos.shadeMode)
402             kosmos.enableDualShade(wideLayout = false)
403 
404             assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
405             assertThat(underTest.swipeSourceDetector)
406                 .isInstanceOf(SceneContainerSwipeDetector::class.java)
407         }
408 
409     @Test
410     fun edgeDetector_dualShade_wideScreen_usesSceneContainerSwipeDetector() =
411         kosmos.runTest {
412             val shadeMode by collectLastValue(kosmos.shadeMode)
413             kosmos.enableDualShade(wideLayout = true)
414 
415             assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
416             assertThat(underTest.swipeSourceDetector)
417                 .isInstanceOf(SceneContainerSwipeDetector::class.java)
418         }
419 }
420