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