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