1 /* <lambda>null2 * 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.keyguard.ui.viewmodel 18 19 import android.platform.test.flag.junit.FlagsParameterization 20 import androidx.compose.ui.Alignment 21 import androidx.test.filters.SmallTest 22 import com.android.systemui.SysuiTestCase 23 import com.android.systemui.biometrics.authController 24 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository 25 import com.android.systemui.flags.DisableSceneContainer 26 import com.android.systemui.flags.EnableSceneContainer 27 import com.android.systemui.flags.andSceneContainer 28 import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository 29 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository 30 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository 31 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor 32 import com.android.systemui.keyguard.shared.model.ClockSize 33 import com.android.systemui.keyguard.shared.model.KeyguardState 34 import com.android.systemui.keyguard.shared.transition.fakeKeyguardTransitionAnimationCallback 35 import com.android.systemui.keyguard.shared.transition.keyguardTransitionAnimationCallbackDelegator 36 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BelowClock 37 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BesideClock 38 import com.android.systemui.kosmos.Kosmos 39 import com.android.systemui.kosmos.collectLastValue 40 import com.android.systemui.kosmos.runCurrent 41 import com.android.systemui.kosmos.runTest 42 import com.android.systemui.kosmos.testScope 43 import com.android.systemui.lifecycle.activateIn 44 import com.android.systemui.res.R 45 import com.android.systemui.scene.domain.interactor.sceneInteractor 46 import com.android.systemui.scene.shared.model.Scenes 47 import com.android.systemui.shade.data.repository.shadeRepository 48 import com.android.systemui.shade.domain.interactor.enableDualShade 49 import com.android.systemui.shade.domain.interactor.enableSingleShade 50 import com.android.systemui.shade.domain.interactor.enableSplitShade 51 import com.android.systemui.shade.domain.interactor.shadeModeInteractor 52 import com.android.systemui.shade.shared.model.ShadeMode 53 import com.android.systemui.testKosmos 54 import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider 55 import com.android.systemui.util.mockito.whenever 56 import com.google.common.truth.Truth.assertThat 57 import java.util.Locale 58 import kotlinx.coroutines.Job 59 import org.junit.Before 60 import org.junit.Test 61 import org.junit.runner.RunWith 62 import platform.test.runner.parameterized.ParameterizedAndroidJunit4 63 import platform.test.runner.parameterized.Parameters 64 65 @SmallTest 66 @RunWith(ParameterizedAndroidJunit4::class) 67 class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { 68 69 private val kosmos: Kosmos = testKosmos() 70 71 private lateinit var underTest: LockscreenContentViewModel 72 private val activationJob = Job() 73 74 companion object { 75 @JvmStatic 76 @Parameters(name = "{0}") 77 fun getParams(): List<FlagsParameterization> { 78 return FlagsParameterization.allCombinationsOf().andSceneContainer() 79 } 80 } 81 82 init { 83 mSetFlagsRule.setFlagsParameterization(flags) 84 } 85 86 @Before 87 fun setup() { 88 with(kosmos) { 89 shadeRepository.setShadeLayoutWide(false) 90 underTest = 91 lockscreenContentViewModelFactory.create(fakeKeyguardTransitionAnimationCallback) 92 underTest.activateIn(testScope, activationJob) 93 } 94 } 95 96 @Test 97 fun isUdfpsVisible_withUdfps_true() = 98 kosmos.runTest { 99 whenever(authController.isUdfpsSupported).thenReturn(true) 100 assertThat(underTest.isUdfpsVisible).isTrue() 101 } 102 103 @Test 104 fun isUdfpsVisible_withoutUdfps_false() = 105 kosmos.runTest { 106 whenever(authController.isUdfpsSupported).thenReturn(false) 107 assertThat(underTest.isUdfpsVisible).isFalse() 108 } 109 110 @Test 111 fun notificationsPlacement_splitShade_topEnd() = 112 kosmos.runTest { 113 setupState(shadeMode = ShadeMode.Split, clockSize = ClockSize.SMALL) 114 115 assertThat(underTest.notificationsPlacement) 116 .isEqualTo(BesideClock(alignment = Alignment.TopEnd)) 117 } 118 119 @Test 120 fun notificationsPlacement_singleShade_below() = 121 kosmos.runTest { 122 setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.SMALL) 123 124 assertThat(underTest.notificationsPlacement).isEqualTo(BelowClock) 125 } 126 127 @Test 128 @EnableSceneContainer 129 fun notificationsPlacement_dualShadeSmallClock_below() = 130 kosmos.runTest { 131 setupState( 132 shadeMode = ShadeMode.Dual, 133 clockSize = ClockSize.SMALL, 134 shadeLayoutWide = true, 135 ) 136 137 assertThat(underTest.notificationsPlacement).isEqualTo(BelowClock) 138 } 139 140 @Test 141 @EnableSceneContainer 142 fun notificationsPlacement_dualShadeLargeClock_topStart() = 143 kosmos.runTest { 144 setupState( 145 shadeMode = ShadeMode.Dual, 146 clockSize = ClockSize.LARGE, 147 shadeLayoutWide = true, 148 ) 149 150 assertThat(underTest.notificationsPlacement) 151 .isEqualTo(BesideClock(alignment = Alignment.TopStart)) 152 } 153 154 @Test 155 fun areNotificationsVisible_splitShadeTrue_true() = 156 kosmos.runTest { 157 setupState(shadeMode = ShadeMode.Split, clockSize = ClockSize.LARGE) 158 159 assertThat(underTest.areNotificationsVisible).isTrue() 160 } 161 162 @Test 163 @EnableSceneContainer 164 fun areNotificationsVisible_dualShadeWideOnLockscreen_true() = 165 kosmos.runTest { 166 setupState( 167 shadeMode = ShadeMode.Dual, 168 clockSize = ClockSize.LARGE, 169 shadeLayoutWide = true, 170 ) 171 172 assertThat(underTest.areNotificationsVisible).isTrue() 173 } 174 175 @Test 176 @DisableSceneContainer 177 fun areNotificationsVisible_withSmallClock_true() = 178 kosmos.runTest { 179 setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.SMALL) 180 181 assertThat(underTest.areNotificationsVisible).isTrue() 182 } 183 184 @Test 185 @DisableSceneContainer 186 fun areNotificationsVisible_withLargeClock_false() = 187 kosmos.runTest { 188 setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.LARGE) 189 190 assertThat(underTest.areNotificationsVisible).isFalse() 191 } 192 193 @Test 194 fun unfoldTranslations() = 195 kosmos.runTest { 196 val maxTranslation = prepareConfiguration() 197 198 val unfoldProvider = fakeUnfoldTransitionProgressProvider 199 unfoldProvider.onTransitionStarted() 200 runCurrent() 201 assertThat(underTest.unfoldTranslations.start).isZero() 202 assertThat(underTest.unfoldTranslations.end).isZero() 203 204 repeat(10) { repetition -> 205 val transitionProgress = 0.1f * (repetition + 1) 206 unfoldProvider.onTransitionProgress(transitionProgress) 207 runCurrent() 208 assertThat(underTest.unfoldTranslations.start) 209 .isEqualTo((1 - transitionProgress) * maxTranslation) 210 assertThat(underTest.unfoldTranslations.end) 211 .isEqualTo(-(1 - transitionProgress) * maxTranslation) 212 } 213 214 unfoldProvider.onTransitionFinishing() 215 runCurrent() 216 assertThat(underTest.unfoldTranslations.start).isZero() 217 assertThat(underTest.unfoldTranslations.end).isZero() 218 219 unfoldProvider.onTransitionFinished() 220 runCurrent() 221 assertThat(underTest.unfoldTranslations.start).isZero() 222 assertThat(underTest.unfoldTranslations.end).isZero() 223 } 224 225 @Test 226 fun isContentVisible_whenNotOccluded_visible() = 227 kosmos.runTest { 228 keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, null) 229 runCurrent() 230 assertThat(underTest.isContentVisible).isTrue() 231 } 232 233 @Test 234 fun isContentVisible_whenOccluded_notVisible() = 235 kosmos.runTest { 236 keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null) 237 fakeKeyguardTransitionRepository.transitionTo( 238 KeyguardState.LOCKSCREEN, 239 KeyguardState.OCCLUDED, 240 ) 241 runCurrent() 242 assertThat(underTest.isContentVisible).isFalse() 243 } 244 245 @Test 246 fun isContentVisible_whenOccluded_notVisible_evenIfShadeShown() = 247 kosmos.runTest { 248 keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null) 249 fakeKeyguardTransitionRepository.transitionTo( 250 KeyguardState.LOCKSCREEN, 251 KeyguardState.OCCLUDED, 252 ) 253 runCurrent() 254 255 sceneInteractor.snapToScene(Scenes.Shade, "") 256 runCurrent() 257 assertThat(underTest.isContentVisible).isFalse() 258 } 259 260 @Test 261 fun activate_setsDelegate_onKeyguardTransitionAnimationCallbackDelegator() = 262 kosmos.runTest { 263 runCurrent() 264 assertThat(keyguardTransitionAnimationCallbackDelegator.delegate) 265 .isSameInstanceAs(fakeKeyguardTransitionAnimationCallback) 266 } 267 268 @Test 269 fun deactivate_clearsDelegate_onKeyguardTransitionAnimationCallbackDelegator() = 270 kosmos.runTest { 271 activationJob.cancel() 272 runCurrent() 273 assertThat(keyguardTransitionAnimationCallbackDelegator.delegate).isNull() 274 } 275 276 @Test 277 fun isContentVisible_whenOccluded_notVisibleInOccluded_visibleInAod() = 278 kosmos.runTest { 279 keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null) 280 fakeKeyguardTransitionRepository.transitionTo( 281 from = KeyguardState.LOCKSCREEN, 282 to = KeyguardState.OCCLUDED, 283 ) 284 runCurrent() 285 286 sceneInteractor.snapToScene(Scenes.Shade, "") 287 runCurrent() 288 assertThat(underTest.isContentVisible).isFalse() 289 290 fakeKeyguardTransitionRepository.transitionTo(KeyguardState.OCCLUDED, KeyguardState.AOD) 291 runCurrent() 292 293 sceneInteractor.snapToScene(Scenes.Lockscreen, "") 294 runCurrent() 295 296 assertThat(underTest.isContentVisible).isTrue() 297 } 298 299 private fun Kosmos.setupState( 300 shadeMode: ShadeMode, 301 clockSize: ClockSize, 302 shadeLayoutWide: Boolean? = null, 303 ) { 304 val isShadeLayoutWide by collectLastValue(kosmos.shadeRepository.isShadeLayoutWide) 305 val collectedClockSize by collectLastValue(kosmos.keyguardClockInteractor.clockSize) 306 val collectedShadeMode by collectLastValue(kosmos.shadeModeInteractor.shadeMode) 307 when (shadeMode) { 308 ShadeMode.Dual -> kosmos.enableDualShade(wideLayout = shadeLayoutWide) 309 ShadeMode.Single -> kosmos.enableSingleShade() 310 ShadeMode.Split -> kosmos.enableSplitShade() 311 } 312 fakeKeyguardClockRepository.setShouldForceSmallClock(clockSize == ClockSize.SMALL) 313 fakeKeyguardClockRepository.setClockSize(clockSize) 314 runCurrent() 315 if (shadeLayoutWide != null) { 316 assertThat(isShadeLayoutWide).isEqualTo(shadeLayoutWide) 317 } 318 assertThat(collectedShadeMode).isEqualTo(shadeMode) 319 assertThat(collectedClockSize).isEqualTo(clockSize) 320 } 321 322 private fun prepareConfiguration(): Int { 323 val configuration = context.resources.configuration 324 configuration.setLayoutDirection(Locale.US) 325 kosmos.fakeConfigurationRepository.onConfigurationChange(configuration) 326 val maxTranslation = 10 327 kosmos.fakeConfigurationRepository.setDimensionPixelSize( 328 R.dimen.notification_side_paddings, 329 maxTranslation, 330 ) 331 return maxTranslation 332 } 333 } 334