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 18 package com.android.systemui.keyguard.domain.interactor 19 20 import android.app.admin.DevicePolicyManager 21 import android.content.Intent 22 import android.os.UserHandle 23 import androidx.test.filters.SmallTest 24 import com.android.internal.widget.LockPatternUtils 25 import com.android.systemui.SysuiTestCase 26 import com.android.systemui.animation.ActivityLaunchAnimator 27 import com.android.systemui.animation.DialogLaunchAnimator 28 import com.android.systemui.animation.Expandable 29 import com.android.systemui.common.shared.model.ContentDescription 30 import com.android.systemui.common.shared.model.Icon 31 import com.android.systemui.dock.DockManagerFake 32 import com.android.systemui.flags.FakeFeatureFlags 33 import com.android.systemui.flags.Flags 34 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys 35 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig 36 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory 37 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig 38 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer 39 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager 40 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager 41 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository 42 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository 43 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition 44 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger 45 import com.android.systemui.plugins.ActivityStarter 46 import com.android.systemui.settings.FakeUserTracker 47 import com.android.systemui.settings.UserFileManager 48 import com.android.systemui.settings.UserTracker 49 import com.android.systemui.statusbar.policy.KeyguardStateController 50 import com.android.systemui.util.FakeSharedPreferences 51 import com.android.systemui.util.mockito.any 52 import com.android.systemui.util.mockito.mock 53 import com.android.systemui.util.mockito.whenever 54 import com.android.systemui.util.settings.FakeSettings 55 import kotlinx.coroutines.CoroutineScope 56 import kotlinx.coroutines.Dispatchers 57 import kotlinx.coroutines.ExperimentalCoroutinesApi 58 import kotlinx.coroutines.test.StandardTestDispatcher 59 import kotlinx.coroutines.test.TestScope 60 import kotlinx.coroutines.test.runTest 61 import org.junit.Before 62 import org.junit.Test 63 import org.junit.runner.RunWith 64 import org.junit.runners.Parameterized 65 import org.junit.runners.Parameterized.Parameter 66 import org.junit.runners.Parameterized.Parameters 67 import org.mockito.ArgumentMatchers.anyInt 68 import org.mockito.ArgumentMatchers.anyString 69 import org.mockito.ArgumentMatchers.eq 70 import org.mockito.ArgumentMatchers.same 71 import org.mockito.Mock 72 import org.mockito.Mockito.verify 73 import org.mockito.Mockito.verifyZeroInteractions 74 import org.mockito.MockitoAnnotations 75 76 @OptIn(ExperimentalCoroutinesApi::class) 77 @SmallTest 78 @RunWith(Parameterized::class) 79 class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { 80 81 companion object { 82 private val INTENT = Intent("some.intent.action") 83 private val DRAWABLE = 84 mock<Icon> { 85 whenever(this.contentDescription) 86 .thenReturn( 87 ContentDescription.Resource( 88 res = CONTENT_DESCRIPTION_RESOURCE_ID, 89 ) 90 ) 91 } 92 private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337 93 94 @Parameters( 95 name = 96 "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," + 97 " keyguardIsUnlocked={2}, needsToUnlockFirst={3}, startActivity={4}" 98 ) 99 @JvmStatic 100 fun data() = 101 listOf( 102 arrayOf( 103 /* needStrongAuthAfterBoot= */ false, 104 /* canShowWhileLocked= */ false, 105 /* keyguardIsUnlocked= */ false, 106 /* needsToUnlockFirst= */ true, 107 /* startActivity= */ false, 108 ), 109 arrayOf( 110 /* needStrongAuthAfterBoot= */ false, 111 /* canShowWhileLocked= */ false, 112 /* keyguardIsUnlocked= */ true, 113 /* needsToUnlockFirst= */ false, 114 /* startActivity= */ false, 115 ), 116 arrayOf( 117 /* needStrongAuthAfterBoot= */ false, 118 /* canShowWhileLocked= */ true, 119 /* keyguardIsUnlocked= */ false, 120 /* needsToUnlockFirst= */ false, 121 /* startActivity= */ false, 122 ), 123 arrayOf( 124 /* needStrongAuthAfterBoot= */ false, 125 /* canShowWhileLocked= */ true, 126 /* keyguardIsUnlocked= */ true, 127 /* needsToUnlockFirst= */ false, 128 /* startActivity= */ false, 129 ), 130 arrayOf( 131 /* needStrongAuthAfterBoot= */ true, 132 /* canShowWhileLocked= */ false, 133 /* keyguardIsUnlocked= */ false, 134 /* needsToUnlockFirst= */ true, 135 /* startActivity= */ false, 136 ), 137 arrayOf( 138 /* needStrongAuthAfterBoot= */ true, 139 /* canShowWhileLocked= */ false, 140 /* keyguardIsUnlocked= */ true, 141 /* needsToUnlockFirst= */ true, 142 /* startActivity= */ false, 143 ), 144 arrayOf( 145 /* needStrongAuthAfterBoot= */ true, 146 /* canShowWhileLocked= */ true, 147 /* keyguardIsUnlocked= */ false, 148 /* needsToUnlockFirst= */ true, 149 /* startActivity= */ false, 150 ), 151 arrayOf( 152 /* needStrongAuthAfterBoot= */ true, 153 /* canShowWhileLocked= */ true, 154 /* keyguardIsUnlocked= */ true, 155 /* needsToUnlockFirst= */ true, 156 /* startActivity= */ false, 157 ), 158 arrayOf( 159 /* needStrongAuthAfterBoot= */ false, 160 /* canShowWhileLocked= */ false, 161 /* keyguardIsUnlocked= */ false, 162 /* needsToUnlockFirst= */ true, 163 /* startActivity= */ true, 164 ), 165 arrayOf( 166 /* needStrongAuthAfterBoot= */ false, 167 /* canShowWhileLocked= */ false, 168 /* keyguardIsUnlocked= */ true, 169 /* needsToUnlockFirst= */ false, 170 /* startActivity= */ true, 171 ), 172 arrayOf( 173 /* needStrongAuthAfterBoot= */ false, 174 /* canShowWhileLocked= */ true, 175 /* keyguardIsUnlocked= */ false, 176 /* needsToUnlockFirst= */ false, 177 /* startActivity= */ true, 178 ), 179 arrayOf( 180 /* needStrongAuthAfterBoot= */ false, 181 /* canShowWhileLocked= */ true, 182 /* keyguardIsUnlocked= */ true, 183 /* needsToUnlockFirst= */ false, 184 /* startActivity= */ true, 185 ), 186 arrayOf( 187 /* needStrongAuthAfterBoot= */ true, 188 /* canShowWhileLocked= */ false, 189 /* keyguardIsUnlocked= */ false, 190 /* needsToUnlockFirst= */ true, 191 /* startActivity= */ true, 192 ), 193 arrayOf( 194 /* needStrongAuthAfterBoot= */ true, 195 /* canShowWhileLocked= */ false, 196 /* keyguardIsUnlocked= */ true, 197 /* needsToUnlockFirst= */ true, 198 /* startActivity= */ true, 199 ), 200 arrayOf( 201 /* needStrongAuthAfterBoot= */ true, 202 /* canShowWhileLocked= */ true, 203 /* keyguardIsUnlocked= */ false, 204 /* needsToUnlockFirst= */ true, 205 /* startActivity= */ true, 206 ), 207 arrayOf( 208 /* needStrongAuthAfterBoot= */ true, 209 /* canShowWhileLocked= */ true, 210 /* keyguardIsUnlocked= */ true, 211 /* needsToUnlockFirst= */ true, 212 /* startActivity= */ true, 213 ), 214 ) 215 216 private val IMMEDIATE = Dispatchers.Main.immediate 217 } 218 219 @Mock private lateinit var lockPatternUtils: LockPatternUtils 220 @Mock private lateinit var keyguardStateController: KeyguardStateController 221 @Mock private lateinit var activityStarter: ActivityStarter 222 @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller 223 @Mock private lateinit var expandable: Expandable 224 @Mock private lateinit var launchAnimator: DialogLaunchAnimator 225 @Mock private lateinit var devicePolicyManager: DevicePolicyManager 226 @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger 227 228 private lateinit var underTest: KeyguardQuickAffordanceInteractor 229 private lateinit var testScope: TestScope 230 231 @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false 232 @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false 233 @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false 234 @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false 235 @JvmField @Parameter(4) var startActivity: Boolean = false 236 private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig 237 private lateinit var dockManager: DockManagerFake 238 private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository 239 private lateinit var userTracker: UserTracker 240 241 @Before 242 fun setUp() { 243 MockitoAnnotations.initMocks(this) 244 whenever(expandable.activityLaunchController()).thenReturn(animationController) 245 246 userTracker = FakeUserTracker() 247 homeControls = 248 FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS) 249 dockManager = DockManagerFake() 250 biometricSettingsRepository = FakeBiometricSettingsRepository() 251 val quickAccessWallet = 252 FakeKeyguardQuickAffordanceConfig( 253 BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET 254 ) 255 val qrCodeScanner = 256 FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER) 257 val scope = CoroutineScope(IMMEDIATE) 258 val localUserSelectionManager = 259 KeyguardQuickAffordanceLocalUserSelectionManager( 260 context = context, 261 userFileManager = 262 mock<UserFileManager>().apply { 263 whenever( 264 getSharedPreferences( 265 anyString(), 266 anyInt(), 267 anyInt(), 268 ) 269 ) 270 .thenReturn(FakeSharedPreferences()) 271 }, 272 userTracker = userTracker, 273 broadcastDispatcher = fakeBroadcastDispatcher, 274 ) 275 val remoteUserSelectionManager = 276 KeyguardQuickAffordanceRemoteUserSelectionManager( 277 scope = scope, 278 userTracker = userTracker, 279 clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker), 280 userHandle = UserHandle.SYSTEM, 281 ) 282 val quickAffordanceRepository = 283 KeyguardQuickAffordanceRepository( 284 appContext = context, 285 scope = scope, 286 localUserSelectionManager = localUserSelectionManager, 287 remoteUserSelectionManager = remoteUserSelectionManager, 288 userTracker = userTracker, 289 legacySettingSyncer = 290 KeyguardQuickAffordanceLegacySettingSyncer( 291 scope = scope, 292 backgroundDispatcher = IMMEDIATE, 293 secureSettings = FakeSettings(), 294 selectionsManager = localUserSelectionManager, 295 ), 296 configs = setOf(homeControls, quickAccessWallet, qrCodeScanner), 297 dumpManager = mock(), 298 userHandle = UserHandle.SYSTEM, 299 ) 300 val featureFlags = 301 FakeFeatureFlags().apply { 302 set(Flags.FACE_AUTH_REFACTOR, true) 303 } 304 val testDispatcher = StandardTestDispatcher() 305 testScope = TestScope(testDispatcher) 306 underTest = 307 KeyguardQuickAffordanceInteractor( 308 keyguardInteractor = 309 KeyguardInteractorFactory.create( 310 featureFlags = featureFlags, 311 ) 312 .keyguardInteractor, 313 lockPatternUtils = lockPatternUtils, 314 keyguardStateController = keyguardStateController, 315 userTracker = userTracker, 316 activityStarter = activityStarter, 317 featureFlags = featureFlags, 318 repository = { quickAffordanceRepository }, 319 launchAnimator = launchAnimator, 320 logger = logger, 321 devicePolicyManager = devicePolicyManager, 322 dockManager = dockManager, 323 biometricSettingsRepository = biometricSettingsRepository, 324 backgroundDispatcher = testDispatcher, 325 appContext = mContext, 326 ) 327 } 328 329 @Test 330 fun onQuickAffordanceTriggered() = 331 testScope.runTest { 332 val key = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS 333 setUpMocks( 334 needStrongAuthAfterBoot = needStrongAuthAfterBoot, 335 keyguardIsUnlocked = keyguardIsUnlocked, 336 ) 337 338 homeControls.setState( 339 lockScreenState = 340 KeyguardQuickAffordanceConfig.LockScreenState.Visible( 341 icon = DRAWABLE, 342 ) 343 ) 344 homeControls.onTriggeredResult = 345 if (startActivity) { 346 KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity( 347 intent = INTENT, 348 canShowWhileLocked = canShowWhileLocked, 349 ) 350 } else { 351 KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled 352 } 353 354 underTest.onQuickAffordanceTriggered( 355 configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::${key}", 356 expandable = expandable, 357 slotId = "", 358 ) 359 360 if (startActivity) { 361 if (needsToUnlockFirst) { 362 verify(activityStarter) 363 .postStartActivityDismissingKeyguard( 364 any(), 365 /* delay= */ eq(0), 366 same(animationController), 367 ) 368 } else { 369 verify(activityStarter) 370 .startActivity( 371 any(), 372 /* dismissShade= */ eq(true), 373 same(animationController), 374 /* showOverLockscreenWhenLocked= */ eq(true), 375 ) 376 } 377 } else { 378 verifyZeroInteractions(activityStarter) 379 } 380 } 381 382 private fun setUpMocks( 383 needStrongAuthAfterBoot: Boolean = true, 384 keyguardIsUnlocked: Boolean = false, 385 ) { 386 whenever(lockPatternUtils.getStrongAuthForUser(any())) 387 .thenReturn( 388 if (needStrongAuthAfterBoot) { 389 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT 390 } else { 391 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED 392 } 393 ) 394 whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked) 395 } 396 } 397