1 /*
2  * Copyright (C) 2021 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.systemui.controls.ui
18 
19 import android.test.suitebuilder.annotation.SmallTest
20 import android.testing.AndroidTestingRunner
21 import android.view.HapticFeedbackConstants
22 import com.android.systemui.SysuiTestCase
23 import com.android.systemui.broadcast.BroadcastSender
24 import com.android.systemui.controls.ControlsMetricsLogger
25 import com.android.systemui.controls.settings.ControlsSettingsDialogManager
26 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
27 import com.android.systemui.flags.FakeFeatureFlags
28 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
29 import com.android.systemui.plugins.ActivityStarter
30 import com.android.systemui.statusbar.VibratorHelper
31 import com.android.systemui.statusbar.policy.KeyguardStateController
32 import com.android.systemui.util.concurrency.DelayableExecutor
33 import com.android.wm.shell.taskview.TaskViewFactory
34 import org.junit.Before
35 import org.junit.Test
36 import org.junit.runner.RunWith
37 import org.mockito.Answers
38 import org.mockito.ArgumentMatchers.eq
39 import org.mockito.Mock
40 import org.mockito.Mockito
41 import org.mockito.Mockito.anyBoolean
42 import org.mockito.Mockito.doNothing
43 import org.mockito.Mockito.doReturn
44 import org.mockito.Mockito.never
45 import org.mockito.Mockito.reset
46 import org.mockito.Mockito.spy
47 import org.mockito.Mockito.verify
48 import org.mockito.Mockito.`when`
49 import org.mockito.MockitoAnnotations
50 import java.util.Optional
51 
52 @SmallTest
53 @RunWith(AndroidTestingRunner::class)
54 class ControlActionCoordinatorImplTest : SysuiTestCase() {
55     @Mock
56     private lateinit var vibratorHelper: VibratorHelper
57     @Mock
58     private lateinit var keyguardStateController: KeyguardStateController
59     @Mock
60     private lateinit var bgExecutor: DelayableExecutor
61     @Mock
62     private lateinit var uiExecutor: DelayableExecutor
63     @Mock
64     private lateinit var activityStarter: ActivityStarter
65     @Mock
66     private lateinit var broadcastSender: BroadcastSender
67     @Mock
68     private lateinit var taskViewFactory: Optional<TaskViewFactory>
69     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
70     private lateinit var cvh: ControlViewHolder
71     @Mock
72     private lateinit var metricsLogger: ControlsMetricsLogger
73     @Mock
74     private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
75 
76     companion object {
77         fun <T> any(): T = Mockito.any<T>()
78 
79         private val ID = "id"
80     }
81 
82     private lateinit var coordinator: ControlActionCoordinatorImpl
83     private lateinit var action: ControlActionCoordinatorImpl.Action
84     private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
85 
86     private val featureFlags = FakeFeatureFlags()
87 
88     @Before
89     fun setUp() {
90         MockitoAnnotations.initMocks(this)
91 
92         controlsSettingsRepository = FakeControlsSettingsRepository()
93         controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
94         controlsSettingsRepository.setCanShowControlsInLockscreen(true)
95 
96         coordinator = spy(ControlActionCoordinatorImpl(
97                 mContext,
98                 bgExecutor,
99                 uiExecutor,
100                 activityStarter,
101                 broadcastSender,
102                 keyguardStateController,
103                 taskViewFactory,
104                 metricsLogger,
105                 vibratorHelper,
106                 controlsSettingsRepository,
107                 featureFlags
108         ))
109         coordinator.activityContext = mContext
110 
111         `when`(cvh.cws.ci.controlId).thenReturn(ID)
112         `when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
113 
114         action = spy(coordinator.Action(ID, {}, false, true))
115         doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
116     }
117 
118     @Test
119     fun testToggleRunsWhenUnlocked() {
120         `when`(keyguardStateController.isShowing()).thenReturn(false)
121 
122         coordinator.toggle(cvh, "", true)
123         verify(coordinator).bouncerOrRun(action)
124         verify(action).invoke()
125     }
126 
127     @Test
128     fun testToggleDoesNotRunWhenLockedThenRunsWhenUnlocked() {
129         `when`(keyguardStateController.isShowing()).thenReturn(true)
130         `when`(keyguardStateController.isUnlocked()).thenReturn(false)
131 
132         coordinator.toggle(cvh, "", true)
133         verify(coordinator).bouncerOrRun(action)
134         verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean())
135         verify(action, never()).invoke()
136 
137         // Simulate a refresh call from a Publisher, which will trigger a call to runPendingAction
138         reset(action)
139         coordinator.runPendingAction(ID)
140         verify(action, never()).invoke()
141 
142         `when`(keyguardStateController.isUnlocked()).thenReturn(true)
143         reset(action)
144         coordinator.runPendingAction(ID)
145         verify(action).invoke()
146     }
147 
148     @Test
149     fun testToggleRunsWhenLockedAndAuthNotRequired() {
150         action = spy(coordinator.Action(ID, {}, false, false))
151         doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
152 
153         `when`(keyguardStateController.isShowing()).thenReturn(true)
154 
155         coordinator.toggle(cvh, "", true)
156 
157         verify(coordinator).bouncerOrRun(action)
158         verify(action).invoke()
159     }
160 
161     @Test
162     fun testToggleWhenLockedDoesNotTriggerDialog_featureFlagEnabled() {
163         action = spy(coordinator.Action(ID, {}, false, false))
164         doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
165 
166         `when`(keyguardStateController.isShowing()).thenReturn(true)
167         `when`(keyguardStateController.isUnlocked()).thenReturn(false)
168         doNothing().`when`(controlsSettingsDialogManager).maybeShowDialog(any(), any())
169 
170         coordinator.toggle(cvh, "", true)
171 
172         verify(coordinator).bouncerOrRun(action)
173         verify(controlsSettingsDialogManager, never()).maybeShowDialog(any(), any())
174     }
175 
176     @Test
177     fun testToggleDoesNotRunsWhenLockedAndAuthRequired() {
178         action = spy(coordinator.Action(ID, {}, false, true))
179         doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
180 
181         `when`(keyguardStateController.isShowing()).thenReturn(true)
182         `when`(keyguardStateController.isUnlocked()).thenReturn(false)
183 
184         coordinator.toggle(cvh, "", true)
185 
186         verify(coordinator).bouncerOrRun(action)
187         verify(action, never()).invoke()
188     }
189 
190     @Test
191     fun testNullControl() {
192         `when`(cvh.cws.control).thenReturn(null)
193 
194         `when`(keyguardStateController.isShowing()).thenReturn(true)
195 
196         coordinator.toggle(cvh, "", true)
197 
198         verify(coordinator).bouncerOrRun(action)
199         verify(action, never()).invoke()
200     }
201 
202     @Test
203     fun drag_isEdge_oneWayHapticsDisabled_usesVibrate() {
204         featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
205 
206         coordinator.drag(cvh, true)
207 
208         verify(vibratorHelper).vibrate(
209             Vibrations.rangeEdgeEffect
210         )
211     }
212 
213     @Test
214     fun drag_isNotEdge_oneWayHapticsDisabled_usesVibrate() {
215         featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
216 
217         coordinator.drag(cvh, false)
218 
219         verify(vibratorHelper).vibrate(
220             Vibrations.rangeMiddleEffect
221         )
222     }
223 
224     @Test
225     fun drag_isEdge_oneWayHapticsEnabled_usesPerformHapticFeedback() {
226         featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
227 
228         coordinator.drag(cvh, true)
229 
230         verify(vibratorHelper).performHapticFeedback(
231             any(),
232             eq(HapticFeedbackConstants.SEGMENT_TICK)
233         )
234     }
235 
236     @Test
237     fun drag_isNotEdge_oneWayHapticsEnabled_usesPerformHapticFeedback() {
238         featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
239 
240         coordinator.drag(cvh, false)
241 
242         verify(vibratorHelper).performHapticFeedback(
243             any(),
244             eq(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK)
245         )
246     }
247 }
248