1 /* 2 * Copyright (C) 2020 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.systemui.R 28 import com.android.systemui.SysuiTestCase 29 import com.android.systemui.classifier.FalsingCollector 30 import com.android.systemui.classifier.FalsingCollectorFake 31 import com.android.systemui.flags.FakeFeatureFlags 32 import com.android.systemui.flags.Flags 33 import com.android.systemui.statusbar.policy.DevicePostureController 34 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED 35 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED 36 import com.android.systemui.util.mockito.any 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.never 49 import org.mockito.Mockito.reset 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 KeyguardPatternViewControllerTest : SysuiTestCase() { 58 private lateinit var mKeyguardPatternView: KeyguardPatternView 59 60 @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor 61 62 @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode 63 64 @Mock private lateinit var mLockPatternUtils: LockPatternUtils 65 66 @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback 67 68 @Mock private lateinit var mLatencyTracker: LatencyTracker 69 private var mFalsingCollector: FalsingCollector = FalsingCollectorFake() 70 71 @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController 72 73 @Mock 74 private lateinit var mKeyguardMessageAreaControllerFactory: 75 KeyguardMessageAreaController.Factory 76 77 @Mock 78 private lateinit var mKeyguardMessageAreaController: 79 KeyguardMessageAreaController<BouncerKeyguardMessageArea> 80 81 @Mock private lateinit var mPostureController: DevicePostureController 82 83 private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController 84 private lateinit var fakeFeatureFlags: FakeFeatureFlags 85 86 @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback> 87 88 @Before 89 fun setup() { 90 MockitoAnnotations.initMocks(this) 91 whenever(mKeyguardMessageAreaControllerFactory.create(any())) 92 .thenReturn(mKeyguardMessageAreaController) 93 fakeFeatureFlags = FakeFeatureFlags() 94 fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false) 95 mKeyguardPatternView = 96 View.inflate(mContext, R.layout.keyguard_pattern_view, null) as KeyguardPatternView 97 98 mKeyguardPatternViewController = 99 KeyguardPatternViewController( 100 mKeyguardPatternView, 101 mKeyguardUpdateMonitor, 102 mSecurityMode, 103 mLockPatternUtils, 104 mKeyguardSecurityCallback, 105 mLatencyTracker, 106 mFalsingCollector, 107 mEmergencyButtonController, 108 mKeyguardMessageAreaControllerFactory, 109 mPostureController, 110 fakeFeatureFlags 111 ) 112 mKeyguardPatternView.onAttachedToWindow() 113 } 114 115 @Test 116 fun onViewAttached_deviceHalfFolded_propagatedToPatternView() { 117 overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f) 118 whenever(mPostureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) 119 120 mKeyguardPatternViewController.onViewAttached() 121 122 assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) 123 } 124 125 @Test 126 fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() { 127 overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f) 128 whenever(mPostureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) 129 130 mKeyguardPatternViewController.onViewAttached() 131 132 // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED 133 assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio()) 134 135 // Simulate posture change to state DEVICE_POSTURE_OPENED with callback 136 verify(mPostureController).addCallback(postureCallbackCaptor.capture()) 137 val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value 138 postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) 139 140 // Simulate posture change to same state with callback 141 assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) 142 143 postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) 144 145 // Verify view is still in posture state DEVICE_POSTURE_OPENED 146 assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio()) 147 } 148 149 private fun getPatternTopGuideline(): Float { 150 val cs = ConstraintSet() 151 val container = 152 mKeyguardPatternView.requireViewById(R.id.pattern_container) as ConstraintLayout 153 cs.clone(container) 154 return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent 155 } 156 157 private fun getHalfOpenedBouncerHeightRatio(): Float { 158 return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio) 159 } 160 161 @Test 162 fun withFeatureFlagOn_oldMessage_isHidden() { 163 fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) 164 165 mKeyguardPatternViewController.onViewAttached() 166 167 verify<KeyguardMessageAreaController<*>>(mKeyguardMessageAreaController).disable() 168 } 169 170 @Test 171 fun onPause_resetsText() { 172 mKeyguardPatternViewController.init() 173 mKeyguardPatternViewController.onPause() 174 verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern) 175 } 176 177 @Test 178 fun testOnViewAttached() { 179 reset(mKeyguardMessageAreaController) 180 reset(mLockPatternUtils) 181 mKeyguardPatternViewController.onViewAttached() 182 verify(mKeyguardMessageAreaController) 183 .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false) 184 verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt()) 185 } 186 187 @Test 188 fun testOnViewAttached_withExistingMessage() { 189 reset(mKeyguardMessageAreaController) 190 `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.") 191 mKeyguardPatternViewController.onViewAttached() 192 verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean()) 193 } 194 } 195