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 18 package com.android.systemui.keyguard.domain.interactor 19 20 import android.content.Intent 21 import android.os.PowerManager 22 import android.platform.test.annotations.DisableFlags 23 import android.platform.test.annotations.EnableFlags 24 import android.platform.test.flag.junit.SetFlagsRule 25 import android.provider.Settings 26 import android.view.accessibility.accessibilityManagerWrapper 27 import androidx.test.ext.junit.runners.AndroidJUnit4 28 import androidx.test.filters.SmallTest 29 import com.android.internal.logging.testing.UiEventLoggerFake 30 import com.android.internal.logging.uiEventLogger 31 import com.android.systemui.Flags.FLAG_DOUBLE_TAP_TO_SLEEP 32 import com.android.systemui.SysuiTestCase 33 import com.android.systemui.coroutines.collectLastValue 34 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor 35 import com.android.systemui.deviceentry.shared.FaceAuthUiEvent 36 import com.android.systemui.flags.fakeFeatureFlagsClassic 37 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository 38 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository 39 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository 40 import com.android.systemui.keyguard.shared.model.KeyguardState 41 import com.android.systemui.kosmos.testScope 42 import com.android.systemui.res.R 43 import com.android.systemui.shade.pulsingGestureListener 44 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper 45 import com.android.systemui.testKosmos 46 import com.android.systemui.util.mockito.mock 47 import com.android.systemui.util.mockito.whenever 48 import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository 49 import com.android.systemui.util.time.fakeSystemClock 50 import com.google.common.truth.Truth.assertThat 51 import kotlinx.coroutines.runBlocking 52 import kotlinx.coroutines.test.advanceTimeBy 53 import kotlinx.coroutines.test.runCurrent 54 import kotlinx.coroutines.test.runTest 55 import org.junit.After 56 import org.junit.Before 57 import org.junit.Rule 58 import org.junit.Test 59 import org.junit.runner.RunWith 60 import org.mockito.Mock 61 import org.mockito.ArgumentMatchers.anyInt 62 import org.mockito.ArgumentMatchers.anyLong 63 import org.mockito.Mockito.never 64 import org.mockito.Mockito.verify 65 import org.mockito.MockitoAnnotations 66 67 @SmallTest 68 @RunWith(AndroidJUnit4::class) 69 @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) 70 class KeyguardTouchHandlingInteractorTest : SysuiTestCase() { 71 private val kosmos = 72 testKosmos().apply { 73 this.accessibilityManagerWrapper = mock<AccessibilityManagerWrapper>() 74 this.uiEventLogger = mock<UiEventLoggerFake>() 75 } 76 77 @get:Rule val setFlagsRule = SetFlagsRule() 78 79 private lateinit var underTest: KeyguardTouchHandlingInteractor 80 81 private val logger = kosmos.uiEventLogger 82 private val testScope = kosmos.testScope 83 private val keyguardRepository = kosmos.fakeKeyguardRepository 84 private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository 85 private val secureSettingsRepository = kosmos.userAwareSecureSettingsRepository 86 87 @Mock private lateinit var powerManager: PowerManager 88 89 @Before 90 fun setUp() { 91 MockitoAnnotations.initMocks(this) 92 overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, true) 93 overrideResource(com.android.internal.R.bool.config_supportDoubleTapSleep, true) 94 whenever(kosmos.accessibilityManagerWrapper.getRecommendedTimeoutMillis(anyInt(), anyInt())) 95 .thenAnswer { it.arguments[0] } 96 97 runBlocking { createUnderTest() } 98 } 99 100 @After 101 fun tearDown() { 102 val testableResource = mContext.getOrCreateTestableResources() 103 testableResource.removeOverride(R.bool.long_press_keyguard_customize_lockscreen_enabled) 104 testableResource.removeOverride(com.android.internal.R.bool.config_supportDoubleTapSleep) 105 } 106 107 @Test 108 fun isLongPressEnabled() = 109 testScope.runTest { 110 val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled) 111 KeyguardState.values().forEach { keyguardState -> 112 setUpState(keyguardState = keyguardState) 113 114 if (keyguardState == KeyguardState.LOCKSCREEN) { 115 assertThat(isEnabled()).isTrue() 116 } else { 117 assertThat(isEnabled()).isFalse() 118 } 119 } 120 } 121 122 @Test 123 fun isLongPressEnabled_alwaysFalseWhenQuickSettingsAreVisible() = 124 testScope.runTest { 125 val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled) 126 KeyguardState.values().forEach { keyguardState -> 127 setUpState(keyguardState = keyguardState, isQuickSettingsVisible = true) 128 129 assertThat(isEnabled()).isFalse() 130 } 131 } 132 133 @Test 134 fun isLongPressEnabled_alwaysFalseWhenConfigEnabledBooleanIsFalse() = 135 testScope.runTest { 136 overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, false) 137 createUnderTest() 138 val isEnabled by collectLastValue(underTest.isLongPressHandlingEnabled) 139 runCurrent() 140 141 assertThat(isEnabled).isFalse() 142 } 143 144 @Test 145 fun longPressed_menuClicked_showsSettings() = 146 testScope.runTest { 147 val isMenuVisible by collectLastValue(underTest.isMenuVisible) 148 val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings) 149 runCurrent() 150 151 underTest.onLongPress() 152 assertThat(isMenuVisible).isTrue() 153 154 underTest.onMenuTouchGestureEnded(/* isClick= */ true) 155 156 assertThat(isMenuVisible).isFalse() 157 assertThat(shouldOpenSettings).isTrue() 158 } 159 160 @Test 161 fun onSettingsShown_consumesSettingsShowEvent() = 162 testScope.runTest { 163 val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings) 164 runCurrent() 165 166 underTest.onLongPress() 167 underTest.onMenuTouchGestureEnded(/* isClick= */ true) 168 assertThat(shouldOpenSettings).isTrue() 169 170 underTest.onSettingsShown() 171 assertThat(shouldOpenSettings).isFalse() 172 } 173 174 @Test 175 fun onTouchedOutside_neverShowsSettings() = 176 testScope.runTest { 177 val isMenuVisible by collectLastValue(underTest.isMenuVisible) 178 val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings) 179 runCurrent() 180 181 underTest.onTouchedOutside() 182 183 assertThat(isMenuVisible).isFalse() 184 assertThat(shouldOpenSettings).isFalse() 185 } 186 187 @Test 188 fun longPressed_isA11yAction_doesNotShowMenu_opensSettings() = 189 testScope.runTest { 190 createUnderTest() 191 val isMenuVisible by collectLastValue(underTest.isMenuVisible) 192 val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings) 193 val isA11yAction = true 194 runCurrent() 195 196 underTest.onLongPress(isA11yAction) 197 198 assertThat(isMenuVisible).isFalse() 199 assertThat(shouldOpenSettings).isTrue() 200 } 201 202 @Test 203 fun longPressed_closeDialogsBroadcastReceived_popupDismissed() = 204 testScope.runTest { 205 val isMenuVisible by collectLastValue(underTest.isMenuVisible) 206 runCurrent() 207 208 underTest.onLongPress() 209 assertThat(isMenuVisible).isTrue() 210 211 fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( 212 context, 213 Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), 214 ) 215 216 assertThat(isMenuVisible).isFalse() 217 } 218 219 @Test 220 fun closesDialogAfterTimeout() = 221 testScope.runTest { 222 val isMenuVisible by collectLastValue(underTest.isMenuVisible) 223 runCurrent() 224 225 underTest.onLongPress() 226 assertThat(isMenuVisible).isTrue() 227 228 advanceTimeBy(KeyguardTouchHandlingInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) 229 230 assertThat(isMenuVisible).isFalse() 231 } 232 233 @Test 234 fun closesDialogAfterTimeout_onlyAfterTouchGestureEnded() = 235 testScope.runTest { 236 val isMenuVisible by collectLastValue(underTest.isMenuVisible) 237 runCurrent() 238 239 underTest.onLongPress() 240 assertThat(isMenuVisible).isTrue() 241 underTest.onMenuTouchGestureStarted() 242 243 advanceTimeBy(KeyguardTouchHandlingInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) 244 assertThat(isMenuVisible).isTrue() 245 246 underTest.onMenuTouchGestureEnded(/* isClick= */ false) 247 advanceTimeBy(KeyguardTouchHandlingInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) 248 assertThat(isMenuVisible).isFalse() 249 } 250 251 @Test 252 fun logsWhenMenuIsShown() = 253 testScope.runTest { 254 collectLastValue(underTest.isMenuVisible) 255 runCurrent() 256 257 underTest.onLongPress() 258 259 verify(logger) 260 .log(KeyguardTouchHandlingInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN) 261 } 262 263 @Test 264 fun logsWhenMenuIsClicked() = 265 testScope.runTest { 266 collectLastValue(underTest.isMenuVisible) 267 runCurrent() 268 269 underTest.onLongPress() 270 underTest.onMenuTouchGestureEnded(/* isClick= */ true) 271 272 verify(logger) 273 .log(KeyguardTouchHandlingInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED) 274 } 275 276 @Test 277 fun triggersFaceAuthWhenLockscreenIsClicked() = 278 testScope.runTest { 279 collectLastValue(underTest.isMenuVisible) 280 runCurrent() 281 kosmos.fakeDeviceEntryFaceAuthRepository.canRunFaceAuth.value = true 282 283 underTest.onClick(100.0f, 100.0f) 284 runCurrent() 285 286 val runningAuthRequest = 287 kosmos.fakeDeviceEntryFaceAuthRepository.runningAuthRequest.value 288 assertThat(runningAuthRequest?.first) 289 .isEqualTo(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED) 290 assertThat(runningAuthRequest?.second).isEqualTo(true) 291 } 292 293 @Test 294 fun showMenu_leaveLockscreen_returnToLockscreen_menuNotVisible() = 295 testScope.runTest { 296 val isMenuVisible by collectLastValue(underTest.isMenuVisible) 297 runCurrent() 298 underTest.onLongPress() 299 assertThat(isMenuVisible).isTrue() 300 301 keyguardTransitionRepository.sendTransitionSteps( 302 from = KeyguardState.LOCKSCREEN, 303 to = KeyguardState.GONE, 304 testScope, 305 ) 306 assertThat(isMenuVisible).isFalse() 307 308 keyguardTransitionRepository.sendTransitionSteps( 309 from = KeyguardState.GONE, 310 to = KeyguardState.LOCKSCREEN, 311 testScope, 312 ) 313 assertThat(isMenuVisible).isFalse() 314 } 315 316 @Test 317 @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) 318 fun isDoubleTapEnabled_flagEnabled_userSettingEnabled_onlyTrueInLockScreenState() { 319 testScope.runTest { 320 secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true) 321 322 val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled) 323 KeyguardState.entries.forEach { keyguardState -> 324 setUpState(keyguardState = keyguardState) 325 326 if (keyguardState == KeyguardState.LOCKSCREEN) { 327 assertThat(isEnabled()).isTrue() 328 } else { 329 assertThat(isEnabled()).isFalse() 330 } 331 } 332 } 333 } 334 335 @Test 336 @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) 337 fun isDoubleTapEnabled_flagEnabled_userSettingDisabled_alwaysFalse() { 338 testScope.runTest { 339 secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, false) 340 341 val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled) 342 KeyguardState.entries.forEach { keyguardState -> 343 setUpState(keyguardState = keyguardState) 344 345 assertThat(isEnabled()).isFalse() 346 } 347 } 348 } 349 350 @Test 351 @DisableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) 352 fun isDoubleTapEnabled_flagDisabled_userSettingEnabled_alwaysFalse() { 353 testScope.runTest { 354 secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true) 355 356 val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled) 357 KeyguardState.entries.forEach { keyguardState -> 358 setUpState(keyguardState = keyguardState) 359 360 assertThat(isEnabled()).isFalse() 361 } 362 } 363 } 364 365 366 367 @Test 368 @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) 369 fun isDoubleTapEnabled_flagEnabledAndConfigDisabled_alwaysFalse() { 370 testScope.runTest { 371 secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true) 372 overrideResource(com.android.internal.R.bool.config_supportDoubleTapSleep, false) 373 createUnderTest() 374 375 val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled) 376 KeyguardState.entries.forEach { keyguardState -> 377 setUpState(keyguardState = keyguardState) 378 379 assertThat(isEnabled()).isFalse() 380 } 381 } 382 } 383 384 @Test 385 @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) 386 fun isDoubleTapEnabled_quickSettingsVisible_alwaysFalse() { 387 testScope.runTest { 388 secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true) 389 390 val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled) 391 KeyguardState.entries.forEach { keyguardState -> 392 setUpState(keyguardState = keyguardState, isQuickSettingsVisible = true) 393 394 assertThat(isEnabled()).isFalse() 395 } 396 } 397 } 398 399 @Test 400 @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) 401 fun onDoubleClick_doubleTapEnabled() { 402 testScope.runTest { 403 secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true) 404 val isEnabled by collectLastValue(underTest.isDoubleTapHandlingEnabled) 405 runCurrent() 406 407 underTest.onDoubleClick() 408 409 assertThat(isEnabled).isTrue() 410 verify(powerManager).goToSleep(anyLong()) 411 } 412 } 413 414 @Test 415 @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) 416 fun onDoubleClick_doubleTapDisabled() { 417 testScope.runTest { 418 secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, false) 419 val isEnabled by collectLastValue(underTest.isDoubleTapHandlingEnabled) 420 runCurrent() 421 422 underTest.onDoubleClick() 423 424 assertThat(isEnabled).isFalse() 425 verify(powerManager, never()).goToSleep(anyLong()) 426 } 427 } 428 429 private suspend fun createUnderTest(isRevampedWppFeatureEnabled: Boolean = true) { 430 // This needs to be re-created for each test outside of kosmos since the flag values are 431 // read during initialization to set up flows. Maybe there is a better way to handle that. 432 underTest = 433 KeyguardTouchHandlingInteractor( 434 context = mContext, 435 scope = testScope.backgroundScope, 436 transitionInteractor = kosmos.keyguardTransitionInteractor, 437 repository = keyguardRepository, 438 logger = logger, 439 featureFlags = kosmos.fakeFeatureFlagsClassic, 440 broadcastDispatcher = fakeBroadcastDispatcher, 441 accessibilityManager = kosmos.accessibilityManagerWrapper, 442 pulsingGestureListener = kosmos.pulsingGestureListener, 443 faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor, 444 secureSettingsRepository = secureSettingsRepository, 445 powerManager = powerManager, 446 systemClock = kosmos.fakeSystemClock, 447 ) 448 setUpState() 449 } 450 451 private suspend fun setUpState( 452 keyguardState: KeyguardState = KeyguardState.LOCKSCREEN, 453 isQuickSettingsVisible: Boolean = false, 454 ) { 455 keyguardTransitionRepository.sendTransitionSteps( 456 from = KeyguardState.AOD, 457 to = keyguardState, 458 testScope = testScope, 459 ) 460 keyguardRepository.setQuickSettingsVisible(isVisible = isQuickSettingsVisible) 461 } 462 } 463