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