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