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 package com.android.keyguard 18 19 import android.testing.TestableLooper 20 import android.view.View 21 import android.view.ViewGroup 22 import androidx.constraintlayout.widget.ConstraintLayout 23 import androidx.constraintlayout.widget.ConstraintSet 24 import androidx.test.ext.junit.runners.AndroidJUnit4 25 import androidx.test.filters.SmallTest 26 import com.android.internal.logging.UiEventLogger 27 import com.android.internal.util.LatencyTracker 28 import com.android.internal.widget.LockPatternUtils 29 import com.android.internal.widget.LockscreenCredential 30 import com.android.keyguard.KeyguardPinViewController.PinBouncerUiEvent 31 import com.android.keyguard.KeyguardSecurityModel.SecurityMode 32 import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor 33 import com.android.systemui.SysuiTestCase 34 import com.android.systemui.classifier.FalsingCollector 35 import com.android.systemui.classifier.FalsingCollectorFake 36 import com.android.systemui.flags.FeatureFlags 37 import com.android.systemui.flags.Flags 38 import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository 39 import com.android.systemui.res.R 40 import com.android.systemui.statusbar.policy.DevicePostureController 41 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED 42 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED 43 import com.android.systemui.user.domain.interactor.SelectedUserInteractor 44 import com.android.systemui.util.mockito.whenever 45 import com.google.common.truth.Truth.assertThat 46 import org.junit.Before 47 import org.junit.Test 48 import org.junit.runner.RunWith 49 import org.mockito.ArgumentCaptor 50 import org.mockito.ArgumentMatchers.anyBoolean 51 import org.mockito.ArgumentMatchers.anyInt 52 import org.mockito.ArgumentMatchers.anyString 53 import org.mockito.Captor 54 import org.mockito.Mock 55 import org.mockito.Mockito 56 import org.mockito.Mockito.any 57 import org.mockito.Mockito.verify 58 import org.mockito.Mockito.`when` 59 import org.mockito.MockitoAnnotations 60 61 @SmallTest 62 @RunWith(AndroidJUnit4::class) 63 // collectFlow in KeyguardPinBasedInputViewController.onViewAttached calls JavaAdapter.CollectFlow, 64 // which calls View.onRepeatWhenAttached, which requires being run on main thread. 65 @TestableLooper.RunWithLooper(setAsMainLooper = true) 66 class KeyguardPinViewControllerTest : SysuiTestCase() { 67 68 private lateinit var objectKeyguardPINView: KeyguardPINView 69 70 @Mock private lateinit var mockKeyguardPinView: KeyguardPINView 71 72 @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea 73 74 @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor 75 76 @Mock private lateinit var securityMode: SecurityMode 77 78 @Mock private lateinit var lockPatternUtils: LockPatternUtils 79 80 @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback 81 82 @Mock 83 private lateinit var keyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory 84 85 @Mock 86 private lateinit var keyguardMessageAreaController: 87 KeyguardMessageAreaController<BouncerKeyguardMessageArea> 88 89 @Mock private lateinit var mLatencyTracker: LatencyTracker 90 91 @Mock private lateinit var liftToActivateListener: LiftToActivateListener 92 93 @Mock private val mEmergencyButtonController: EmergencyButtonController? = null 94 private val falsingCollector: FalsingCollector = FalsingCollectorFake() 95 private val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository()) 96 private val passwordTextViewLayoutParams = 97 ViewGroup.LayoutParams(/* width= */ 0, /* height= */ 0) 98 @Mock lateinit var postureController: DevicePostureController 99 @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor 100 101 @Mock lateinit var featureFlags: FeatureFlags 102 @Mock lateinit var passwordTextView: PasswordTextView 103 @Mock lateinit var deleteButton: NumPadButton 104 @Mock lateinit var enterButton: View 105 @Mock lateinit var uiEventLogger: UiEventLogger 106 107 @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback> 108 109 @Before setupnull110 fun setup() { 111 MockitoAnnotations.initMocks(this) 112 `when`(mockKeyguardPinView.requireViewById<View>(R.id.bouncer_message_area)) 113 .thenReturn(keyguardMessageArea) 114 `when`(keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))) 115 .thenReturn(keyguardMessageAreaController) 116 `when`(mockKeyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry) 117 `when`(mockKeyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry)) 118 .thenReturn(passwordTextView) 119 `when`(mockKeyguardPinView.resources).thenReturn(context.resources) 120 `when`(mockKeyguardPinView.findViewById<NumPadButton>(R.id.delete_button)) 121 .thenReturn(deleteButton) 122 `when`(mockKeyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton) 123 // For posture tests: 124 `when`(mockKeyguardPinView.buttons).thenReturn(arrayOf()) 125 `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) 126 `when`(featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)).thenReturn(false) 127 `when`(passwordTextView.layoutParams).thenReturn(passwordTextViewLayoutParams) 128 129 objectKeyguardPINView = 130 View.inflate(mContext, R.layout.keyguard_pin_view, null) 131 .requireViewById(R.id.keyguard_pin_view) as KeyguardPINView 132 } 133 constructPinViewControllernull134 private fun constructPinViewController( 135 mKeyguardPinView: KeyguardPINView 136 ): KeyguardPinViewController { 137 return KeyguardPinViewController( 138 mKeyguardPinView, 139 keyguardUpdateMonitor, 140 securityMode, 141 lockPatternUtils, 142 mKeyguardSecurityCallback, 143 keyguardMessageAreaControllerFactory, 144 mLatencyTracker, 145 liftToActivateListener, 146 mEmergencyButtonController, 147 falsingCollector, 148 postureController, 149 featureFlags, 150 mSelectedUserInteractor, 151 uiEventLogger, 152 keyguardKeyboardInteractor 153 ) 154 } 155 156 @Test onViewAttached_deviceHalfFolded_propagatedToPatternViewnull157 fun onViewAttached_deviceHalfFolded_propagatedToPatternView() { 158 val pinViewController = constructPinViewController(objectKeyguardPINView) 159 overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f) 160 whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) 161 162 pinViewController.onViewAttached() 163 164 assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) 165 } 166 167 @Test onDevicePostureChanged_deviceOpened_propagatedToPatternViewnull168 fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() { 169 val pinViewController = constructPinViewController(objectKeyguardPINView) 170 overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f) 171 172 whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) 173 pinViewController.onViewAttached() 174 175 // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED 176 assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) 177 178 // Simulate posture change to state DEVICE_POSTURE_OPENED with callback 179 verify(postureController).addCallback(postureCallbackCaptor.capture()) 180 val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value 181 postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) 182 183 // Verify view is now in posture state DEVICE_POSTURE_OPENED 184 assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) 185 186 // Simulate posture change to same state with callback 187 postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) 188 189 // Verify view is still in posture state DEVICE_POSTURE_OPENED 190 assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) 191 } 192 getPinTopGuidelinenull193 private fun getPinTopGuideline(): Float { 194 val cs = ConstraintSet() 195 val container = 196 objectKeyguardPINView.requireViewById(R.id.pin_container) as ConstraintLayout 197 cs.clone(container) 198 return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent 199 } 200 getHalfOpenedBouncerHeightRationull201 private fun getHalfOpenedBouncerHeightRatio(): Float { 202 return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio) 203 } 204 205 @Test testOnViewAttachednull206 fun testOnViewAttached() { 207 val pinViewController = constructPinViewController(mockKeyguardPinView) 208 209 pinViewController.onViewAttached() 210 211 verify(keyguardMessageAreaController) 212 .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false) 213 } 214 215 @Test testOnViewAttached_withExistingMessagenull216 fun testOnViewAttached_withExistingMessage() { 217 val pinViewController = constructPinViewController(mockKeyguardPinView) 218 Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.") 219 220 pinViewController.onViewAttached() 221 222 verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean()) 223 } 224 225 @Test testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsLessThan5null226 fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() { 227 val pinViewController = constructPinViewController(mockKeyguardPinView) 228 `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) 229 `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) 230 `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) 231 `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3) 232 `when`(passwordTextView.text).thenReturn("") 233 234 pinViewController.onViewAttached() 235 236 verify(deleteButton).visibility = View.INVISIBLE 237 verify(enterButton).visibility = View.INVISIBLE 238 verify(passwordTextView).setUsePinShapes(true) 239 verify(passwordTextView).setIsPinHinting(true) 240 } 241 242 @Test testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5null243 fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() { 244 val pinViewController = constructPinViewController(mockKeyguardPinView) 245 `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) 246 `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) 247 `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) 248 `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6) 249 `when`(passwordTextView.text).thenReturn("") 250 251 pinViewController.onViewAttached() 252 253 verify(deleteButton).visibility = View.VISIBLE 254 verify(enterButton).visibility = View.VISIBLE 255 verify(passwordTextView).setUsePinShapes(true) 256 verify(passwordTextView).setIsPinHinting(false) 257 } 258 259 @Test handleLockout_readsNumberOfErrorAttemptsnull260 fun handleLockout_readsNumberOfErrorAttempts() { 261 val pinViewController = constructPinViewController(mockKeyguardPinView) 262 263 pinViewController.handleAttemptLockout(0) 264 265 verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt()) 266 } 267 268 @Test onUserInput_autoConfirmation_attemptsUnlocknull269 fun onUserInput_autoConfirmation_attemptsUnlock() { 270 val pinViewController = constructPinViewController(mockKeyguardPinView) 271 whenever(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) 272 whenever(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) 273 whenever(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) 274 whenever(passwordTextView.text).thenReturn("000000") 275 whenever(enterButton.visibility).thenReturn(View.INVISIBLE) 276 whenever(mockKeyguardPinView.enteredCredential) 277 .thenReturn(LockscreenCredential.createPin("000000")) 278 279 pinViewController.onUserInput() 280 281 verify(uiEventLogger).log(PinBouncerUiEvent.ATTEMPT_UNLOCK_WITH_AUTO_CONFIRM_FEATURE) 282 verify(keyguardUpdateMonitor).setCredentialAttempted() 283 } 284 } 285