1 /* 2 * Copyright (C) 2019 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.car; 18 19 import static android.car.CarOccupantZoneManager.DisplayTypeEnum; 20 import static android.car.input.CustomInputEvent.INPUT_CODE_F1; 21 22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 24 25 import static com.google.common.truth.Truth.assertThat; 26 import static com.google.common.truth.Truth.assertWithMessage; 27 28 import static org.mockito.ArgumentMatchers.any; 29 import static org.mockito.ArgumentMatchers.anyInt; 30 import static org.mockito.ArgumentMatchers.anyLong; 31 import static org.mockito.ArgumentMatchers.eq; 32 import static org.mockito.ArgumentMatchers.same; 33 import static org.mockito.Mockito.atLeast; 34 import static org.mockito.Mockito.clearInvocations; 35 import static org.mockito.Mockito.doNothing; 36 import static org.mockito.Mockito.mock; 37 import static org.mockito.Mockito.never; 38 import static org.mockito.Mockito.times; 39 import static org.mockito.Mockito.when; 40 41 import android.car.CarOccupantZoneManager; 42 import android.car.CarProjectionManager; 43 import android.car.builtin.util.AssistUtilsHelper; 44 import android.car.input.CarInputManager; 45 import android.car.input.CustomInputEvent; 46 import android.car.input.ICarInputCallback; 47 import android.car.input.RotaryEvent; 48 import android.car.test.mocks.AbstractExtendedMockitoTestCase; 49 import android.content.Context; 50 import android.content.Intent; 51 import android.content.pm.PackageManager; 52 import android.content.res.Resources; 53 import android.net.Uri; 54 import android.os.Handler; 55 import android.os.Looper; 56 import android.os.Message; 57 import android.os.SystemClock; 58 import android.os.UserHandle; 59 import android.provider.Settings; 60 import android.telecom.TelecomManager; 61 import android.view.KeyEvent; 62 63 import androidx.test.core.app.ApplicationProvider; 64 65 import com.android.car.bluetooth.CarBluetoothService; 66 import com.android.car.hal.InputHalService; 67 import com.android.car.user.CarUserService; 68 69 import com.google.common.collect.Range; 70 71 import org.junit.After; 72 import org.junit.Before; 73 import org.junit.Test; 74 import org.mockito.ArgumentCaptor; 75 import org.mockito.Mock; 76 import org.mockito.Spy; 77 import org.testng.Assert; 78 79 import java.util.BitSet; 80 import java.util.function.BooleanSupplier; 81 import java.util.function.IntSupplier; 82 import java.util.function.Supplier; 83 84 public class CarInputServiceTest extends AbstractExtendedMockitoTestCase { 85 86 @Mock InputHalService mInputHalService; 87 @Mock TelecomManager mTelecomManager; 88 @Mock CarInputService.KeyEventListener mDefaultMainListener; 89 @Mock CarInputService.KeyEventListener mInstrumentClusterKeyListener; 90 @Mock Supplier<String> mLastCallSupplier; 91 @Mock IntSupplier mLongPressDelaySupplier; 92 @Mock BooleanSupplier mShouldCallButtonEndOngoingCallSupplier; 93 @Mock InputCaptureClientController mCaptureController; 94 @Mock CarOccupantZoneService mCarOccupantZoneService; 95 @Mock CarBluetoothService mCarBluetoothService; 96 97 @Spy Context mContext = ApplicationProvider.getApplicationContext(); 98 @Mock private Resources mMockResources; 99 @Spy Handler mHandler = new Handler(Looper.getMainLooper()); 100 101 @Mock CarUserService mCarUserService; 102 private CarInputService mCarInputService; 103 CarInputServiceTest()104 public CarInputServiceTest() { 105 super(CarInputService.TAG); 106 } 107 108 @Before setUp()109 public void setUp() { 110 mCarInputService = new CarInputService(mContext, mInputHalService, mCarUserService, 111 mCarOccupantZoneService, mCarBluetoothService, mHandler, mTelecomManager, 112 mDefaultMainListener, mLastCallSupplier, mLongPressDelaySupplier, 113 mShouldCallButtonEndOngoingCallSupplier, mCaptureController); 114 mCarInputService.setInstrumentClusterKeyListener(mInstrumentClusterKeyListener); 115 116 when(mInputHalService.isKeyInputSupported()).thenReturn(true); 117 mCarInputService.init(); 118 119 // Delay Handler callbacks until flushHandler() is called. 120 doReturn(true).when(mHandler).sendMessageAtTime(any(), anyLong()); 121 122 when(mShouldCallButtonEndOngoingCallSupplier.getAsBoolean()).thenReturn(false); 123 124 when(mContext.getResources()).thenReturn(mMockResources); 125 } 126 127 @Override onSessionBuilder(CustomMockitoSessionBuilder session)128 protected void onSessionBuilder(CustomMockitoSessionBuilder session) { 129 session.spyStatic(AssistUtilsHelper.class); 130 } 131 132 @After tearDown()133 public void tearDown() { 134 if (mCarInputService != null) { 135 mCarInputService.release(); 136 } 137 } 138 139 @Test testConstantValueMatching()140 public void testConstantValueMatching() { 141 assertWithMessage( 142 "CarInputService.LONG_PRESS_TIMEOUT ('%s') must match the string defined in " 143 + "Settings.Secure.LONG_PRESS_TIMEOUT", 144 CarInputService.LONG_PRESS_TIMEOUT).that( 145 CarInputService.LONG_PRESS_TIMEOUT).isEqualTo( 146 Settings.Secure.LONG_PRESS_TIMEOUT); 147 } 148 149 @Test testOnRotaryEvent_injectingRotaryNavigationEvent()150 public void testOnRotaryEvent_injectingRotaryNavigationEvent() { 151 RotaryEvent event = new RotaryEvent( 152 /* inputType= */ CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, 153 /* clockwise= */ true, 154 /* uptimeMillisForClicks= */ new long[]{1, 1}); 155 when(mCaptureController.onRotaryEvent( 156 same(CarOccupantZoneManager.DISPLAY_TYPE_MAIN), same(event))).thenReturn(true); 157 when(mCaptureController.onKeyEvent(anyInt(), any(KeyEvent.class))).thenReturn(true); 158 159 mCarInputService.onRotaryEvent(event, CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 160 161 // Since mCaptureController processed RotaryEvent, then no KeyEvent was generated or 162 // processed 163 verify(mCarOccupantZoneService, never()).getDisplayIdForDriver(anyInt()); 164 verify(mCaptureController, never()).onKeyEvent(anyInt(), any(KeyEvent.class)); 165 verify(mDefaultMainListener, never()).onKeyEvent(any(KeyEvent.class)); 166 } 167 168 @Test testOnRotaryEvent_injectingRotaryVolumeEvent()169 public void testOnRotaryEvent_injectingRotaryVolumeEvent() { 170 RotaryEvent event = new RotaryEvent( 171 /* inputType= */ CarInputManager.INPUT_TYPE_ROTARY_VOLUME, 172 /* clockwise= */ true, 173 /* uptimeMillisForClicks= */ new long[]{1, 1}); 174 when(mCaptureController.onRotaryEvent( 175 same(CarOccupantZoneManager.DISPLAY_TYPE_MAIN), same(event))).thenReturn(false); 176 when(mCaptureController.onKeyEvent(anyInt(), any(KeyEvent.class))).thenReturn(true); 177 178 mCarInputService.onRotaryEvent(event, CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 179 180 // Since mCaptureController processed RotaryEvent, then KeyEvent was generated or 181 // processed 182 int numberOfGeneratedKeyEvents = 4; 183 verify(mCarOccupantZoneService, times(numberOfGeneratedKeyEvents)).getDisplayIdForDriver( 184 eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN)); 185 verify(mCaptureController, times(numberOfGeneratedKeyEvents)).onKeyEvent(anyInt(), 186 any(KeyEvent.class)); 187 verify(mDefaultMainListener, never()).onKeyEvent(any(KeyEvent.class)); 188 } 189 190 @Test testOnRotaryEvent_injectingRotaryNavigation_notConsumedByCaptureController()191 public void testOnRotaryEvent_injectingRotaryNavigation_notConsumedByCaptureController() { 192 RotaryEvent event = new RotaryEvent( 193 /* inputType= */ CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, 194 /* clockwise= */ true, 195 /* uptimeMillisForClicks= */ new long[]{1, 1}); 196 when(mCaptureController.onRotaryEvent( 197 same(CarOccupantZoneManager.DISPLAY_TYPE_MAIN), same(event))).thenReturn(false); 198 199 mCarInputService.onRotaryEvent(event, CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 200 201 // Since mCaptureController processed RotaryEvent, then KeyEvent was generated or 202 // processed 203 int numberOfGeneratedKeyEvents = 4; 204 verify(mCarOccupantZoneService, times(numberOfGeneratedKeyEvents)).getDisplayIdForDriver( 205 eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN)); 206 verify(mCaptureController, times(numberOfGeneratedKeyEvents)).onKeyEvent(anyInt(), 207 any(KeyEvent.class)); 208 verify(mDefaultMainListener, times(numberOfGeneratedKeyEvents)).onKeyEvent( 209 any(KeyEvent.class)); 210 } 211 212 @Test testRequestInputEventCapture_delegatesToCaptureController()213 public void testRequestInputEventCapture_delegatesToCaptureController() { 214 ICarInputCallback callback = mock(ICarInputCallback.class); 215 int[] inputTypes = new int[]{CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT}; 216 int requestFlags = CarInputManager.CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT; 217 mCarInputService.requestInputEventCapture(callback, 218 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, inputTypes, requestFlags); 219 220 verify(mCaptureController).requestInputEventCapture(same(callback), 221 eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN), same(inputTypes), eq(requestFlags)); 222 } 223 224 @Test testOnCustomInputEvent_delegatesToCaptureController()225 public void testOnCustomInputEvent_delegatesToCaptureController() { 226 CustomInputEvent event = new CustomInputEvent(INPUT_CODE_F1, 227 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, /* repeatCounter= */ 1); 228 229 mCarInputService.onCustomInputEvent(event); 230 231 verify(mCaptureController).onCustomInputEvent(same(event)); 232 } 233 234 @Test testReleaseInputEventCapture_delegatesToCaptureController()235 public void testReleaseInputEventCapture_delegatesToCaptureController() { 236 ICarInputCallback callback = mock(ICarInputCallback.class); 237 mCarInputService.releaseInputEventCapture(callback, 238 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 239 240 verify(mCaptureController).releaseInputEventCapture(same(callback), 241 eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN)); 242 } 243 244 @Test ordinaryEvents_onMainDisplay_routedToInputManager()245 public void ordinaryEvents_onMainDisplay_routedToInputManager() { 246 KeyEvent event = send(Key.DOWN, KeyEvent.KEYCODE_ENTER, Display.MAIN); 247 248 verify(mDefaultMainListener).onKeyEvent(event); 249 } 250 251 @Test ordinaryEvents_onInstrumentClusterDisplay_notRoutedToInputManager()252 public void ordinaryEvents_onInstrumentClusterDisplay_notRoutedToInputManager() { 253 send(Key.DOWN, KeyEvent.KEYCODE_ENTER, Display.INSTRUMENT_CLUSTER); 254 255 verify(mDefaultMainListener, never()).onKeyEvent(any()); 256 } 257 258 @Test ordinaryEvents_onInstrumentClusterDisplay_routedToListener()259 public void ordinaryEvents_onInstrumentClusterDisplay_routedToListener() { 260 CarInputService.KeyEventListener listener = mock(CarInputService.KeyEventListener.class); 261 mCarInputService.setInstrumentClusterKeyListener(listener); 262 263 KeyEvent event = send(Key.DOWN, KeyEvent.KEYCODE_ENTER, Display.INSTRUMENT_CLUSTER); 264 verify(listener).onKeyEvent(event); 265 } 266 267 @Test customEventHandler_capturesDisplayMainEvent_capturedByInputController()268 public void customEventHandler_capturesDisplayMainEvent_capturedByInputController() { 269 CarInputService.KeyEventListener instrumentClusterListener = 270 setupInstrumentClusterListener(); 271 272 // Assume mCaptureController will consume every event. 273 when(mCaptureController.onKeyEvent(anyInt(), any(KeyEvent.class))).thenReturn(true); 274 275 KeyEvent event = send(Key.DOWN, KeyEvent.KEYCODE_ENTER, Display.MAIN); 276 verify(instrumentClusterListener, never()).onKeyEvent(any(KeyEvent.class)); 277 verify(mCaptureController).onKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, event); 278 verify(mDefaultMainListener, never()).onKeyEvent(any(KeyEvent.class)); 279 } 280 281 @Test customEventHandler_capturesDisplayMainEvent_missedByInputController()282 public void customEventHandler_capturesDisplayMainEvent_missedByInputController() { 283 CarInputService.KeyEventListener instrumentClusterListener = 284 setupInstrumentClusterListener(); 285 286 // Assume mCaptureController will consume every event. 287 when(mCaptureController.onKeyEvent(anyInt(), any(KeyEvent.class))).thenReturn(false); 288 289 KeyEvent event = send(Key.DOWN, KeyEvent.KEYCODE_ENTER, Display.MAIN); 290 verify(instrumentClusterListener, never()).onKeyEvent(any(KeyEvent.class)); 291 verify(mCaptureController).onKeyEvent(anyInt(), any(KeyEvent.class)); 292 verify(mDefaultMainListener).onKeyEvent(event); 293 } 294 295 @Test customEventHandler_capturesClusterEvents_capturedByInstrumentCluster()296 public void customEventHandler_capturesClusterEvents_capturedByInstrumentCluster() { 297 CarInputService.KeyEventListener instrumentClusterListener = 298 setupInstrumentClusterListener(); 299 300 // Assume mCaptureController will consume every event. 301 when(mCaptureController.onKeyEvent(anyInt(), any(KeyEvent.class))).thenReturn(true); 302 303 KeyEvent event = send(Key.DOWN, KeyEvent.KEYCODE_ENTER, Display.INSTRUMENT_CLUSTER); 304 verify(instrumentClusterListener).onKeyEvent(event); 305 verify(mCaptureController, never()).onKeyEvent(anyInt(), any(KeyEvent.class)); 306 verify(mDefaultMainListener, never()).onKeyEvent(any(KeyEvent.class)); 307 } 308 setupInstrumentClusterListener()309 private CarInputService.KeyEventListener setupInstrumentClusterListener() { 310 CarInputService.KeyEventListener instrumentClusterListener = 311 mock(CarInputService.KeyEventListener.class); 312 mCarInputService.setInstrumentClusterKeyListener(instrumentClusterListener); 313 return instrumentClusterListener; 314 } 315 316 @Test voiceKey_shortPress_withRegisteredEventHandler_triggersEventHandler()317 public void voiceKey_shortPress_withRegisteredEventHandler_triggersEventHandler() { 318 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 319 registerProjectionKeyEventHandler( 320 CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP); 321 322 send(Key.DOWN, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 323 send(Key.UP, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 324 325 verify(eventHandler) 326 .onKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP); 327 } 328 329 @Test voiceKey_longPress_withRegisteredEventHandler_triggersEventHandler()330 public void voiceKey_longPress_withRegisteredEventHandler_triggersEventHandler() { 331 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 332 registerProjectionKeyEventHandler( 333 CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP, 334 CarProjectionManager.KEY_EVENT_VOICE_SEARCH_LONG_PRESS_KEY_DOWN); 335 336 send(Key.DOWN, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 337 verify(eventHandler, never()).onKeyEvent(anyInt()); 338 339 // Simulate the long-press timer expiring. 340 flushHandler(); 341 verify(eventHandler) 342 .onKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_LONG_PRESS_KEY_DOWN); 343 344 // Ensure that the short-press handler is *not* called. 345 send(Key.UP, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 346 flushHandler(); 347 verify(eventHandler, never()) 348 .onKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP); 349 } 350 351 @Test voiceKey_shortPress_withoutRegisteredEventHandler_triggersAssistUtils()352 public void voiceKey_shortPress_withoutRegisteredEventHandler_triggersAssistUtils() { 353 doReturn(true).when( 354 () -> AssistUtilsHelper.showPushToTalkSessionForActiveService(eq(mContext), any())); 355 356 send(Key.DOWN, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 357 send(Key.UP, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 358 } 359 360 @Test voiceKey_longPress_withoutRegisteredEventHandler_triggersAssistUtils()361 public void voiceKey_longPress_withoutRegisteredEventHandler_triggersAssistUtils() { 362 doReturn(true).when( 363 () -> AssistUtilsHelper.showPushToTalkSessionForActiveService(eq(mContext), any())); 364 365 send(Key.DOWN, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 366 flushHandler(); 367 368 send(Key.UP, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 369 370 // NOTE: extend mockito doesn't provide verifyNoMoreInteractions(), so we'd need to 371 // explicitly call verify() and verify(never()). But since AssistUtilHelper only has one 372 // static method, we don't need the latter. 373 verify(() -> AssistUtilsHelper.showPushToTalkSessionForActiveService(eq(mContext), any())); 374 } 375 376 /** 377 * Testing long press triggers Bluetooth voice recognition. 378 * 379 * Based on current implementation of {@link CarInputService#handleVoiceAssistLongPress}, 380 * long press of the button should trigger Bluetooth voice recognition if: 381 * (a) {@link CarProjectionManager.ProjectionKeyEventHandler} did not subscribe for the 382 * event, or if the key event handler does not exit; and 383 * (b) Bluetooth voice recognition is enabled. 384 * 385 * Preconditions: 386 * - Bluetooth voice recognition is enabled. 387 * - No {@link CarProjectionManager.ProjectionKeyEventHandler} registered for the key event. 388 * Action: 389 * - Long press the voice assistant key. 390 * Results: 391 * - Bluetooth voice recognition is invoked. 392 */ 393 @Test voiceKey_longPress_bluetoothVoiceRecognitionIsEnabled_triggersBluetoothAssist()394 public void voiceKey_longPress_bluetoothVoiceRecognitionIsEnabled_triggersBluetoothAssist() { 395 mockEnableLongPressBluetoothVoiceRecognitionProperty(true); 396 397 send(Key.DOWN, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 398 flushHandler(); 399 400 send(Key.UP, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 401 402 verify(mCarBluetoothService, times(1)).startBluetoothVoiceRecognition(); 403 } 404 405 /** 406 * Testing short press does not trigger Bluetooth voice recognition. 407 * 408 * Based on current implementation of {@link CarInputService#handleVoiceAssistKey}, 409 * short press of the button should not trigger Bluetooth, and instead launch the default 410 * voice assistant handler. 411 * 412 * Preconditions: 413 * - Bluetooth voice recognition is enabled. 414 * Action: 415 * - Short press the voice assistant key. 416 * Results: 417 * - Bluetooth voice recognition is not invoked. 418 * - Default assistant handler is invoked instead. 419 */ 420 @Test voiceKey_shortPress_bluetoothVoiceRecognitionIsEnabled_triggersAssistUtils()421 public void voiceKey_shortPress_bluetoothVoiceRecognitionIsEnabled_triggersAssistUtils() { 422 mockEnableLongPressBluetoothVoiceRecognitionProperty(true); 423 424 doReturn(true).when( 425 () -> AssistUtilsHelper.showPushToTalkSessionForActiveService(eq(mContext), any())); 426 427 send(Key.DOWN, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 428 send(Key.UP, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 429 430 verify(mCarBluetoothService, never()).startBluetoothVoiceRecognition(); 431 verify(() -> AssistUtilsHelper.showPushToTalkSessionForActiveService(eq(mContext), any())); 432 } 433 434 @Test voiceKey_keyDown_withEventHandler_triggersEventHandler()435 public void voiceKey_keyDown_withEventHandler_triggersEventHandler() { 436 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 437 registerProjectionKeyEventHandler( 438 CarProjectionManager.KEY_EVENT_VOICE_SEARCH_KEY_DOWN); 439 440 send(Key.DOWN, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 441 442 verify(eventHandler).onKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_KEY_DOWN); 443 } 444 445 @Test voiceKey_keyUp_afterLongPress_withEventHandler_triggersEventHandler()446 public void voiceKey_keyUp_afterLongPress_withEventHandler_triggersEventHandler() { 447 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 448 registerProjectionKeyEventHandler( 449 CarProjectionManager.KEY_EVENT_VOICE_SEARCH_LONG_PRESS_KEY_UP); 450 451 send(Key.DOWN, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 452 flushHandler(); 453 verify(eventHandler, never()) 454 .onKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_LONG_PRESS_KEY_UP); 455 456 send(Key.UP, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 457 verify(eventHandler) 458 .onKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_LONG_PRESS_KEY_UP); 459 } 460 461 @Test voiceKey_repeatedEvents_ignored()462 public void voiceKey_repeatedEvents_ignored() { 463 // Pressing a key starts the long-press timer. 464 send(Key.DOWN, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN); 465 verify(mHandler).sendMessageAtTime(any(), anyLong()); 466 clearInvocations(mHandler); 467 468 // Repeated KEY_DOWN events don't reset the timer. 469 sendWithRepeat(Key.DOWN, KeyEvent.KEYCODE_VOICE_ASSIST, Display.MAIN, 1); 470 verify(mHandler, never()).sendMessageAtTime(any(), anyLong()); 471 } 472 473 @Test callKey_shortPress_withoutEventHandler_launchesDialer()474 public void callKey_shortPress_withoutEventHandler_launchesDialer() { 475 ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); 476 477 doNothing().when(mContext).startActivityAsUser(any(), any()); 478 479 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 480 send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN); 481 482 verify(mContext).startActivityAsUser( 483 intentCaptor.capture(), eq(UserHandle.CURRENT)); 484 assertThat(intentCaptor.getValue().getAction()).isEqualTo(Intent.ACTION_DIAL); 485 } 486 487 @Test callKey_shortPress_withoutEventHandler_whenCallRinging_answersCall()488 public void callKey_shortPress_withoutEventHandler_whenCallRinging_answersCall() { 489 when(mTelecomManager.isRinging()).thenReturn(true); 490 491 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 492 send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN); 493 494 verify(mTelecomManager).acceptRingingCall(); 495 // Ensure default handler does not run. 496 verify(mContext, never()).startActivityAsUser(any(), any()); 497 } 498 499 @Test callKey_shortPress_withEventHandler_triggersEventHandler()500 public void callKey_shortPress_withEventHandler_triggersEventHandler() { 501 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 502 registerProjectionKeyEventHandler( 503 CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 504 505 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 506 send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN); 507 508 verify(eventHandler).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 509 // Ensure default handlers do not run. 510 verify(mTelecomManager, never()).acceptRingingCall(); 511 verify(mContext, never()).startActivityAsUser(any(), any()); 512 } 513 514 @Test callKey_shortPress_withEventHandler_whenCallRinging_answersCall()515 public void callKey_shortPress_withEventHandler_whenCallRinging_answersCall() { 516 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 517 registerProjectionKeyEventHandler( 518 CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP); 519 when(mTelecomManager.isRinging()).thenReturn(true); 520 521 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 522 send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN); 523 524 verify(mTelecomManager).acceptRingingCall(); 525 verify(eventHandler, never()).onKeyEvent(anyInt()); 526 } 527 528 @Test callKey_shortPress_duringCall_endCallViaCallButtonOn_endsCall()529 public void callKey_shortPress_duringCall_endCallViaCallButtonOn_endsCall() { 530 when(mShouldCallButtonEndOngoingCallSupplier.getAsBoolean()).thenReturn(true); 531 when(mTelecomManager.isInCall()).thenReturn(true); 532 533 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 534 send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN); 535 536 verify(mTelecomManager).endCall(); 537 } 538 539 @Test callKey_shortPress_duringCall_endCallViaCallButtonOff_doesNotEndCall()540 public void callKey_shortPress_duringCall_endCallViaCallButtonOff_doesNotEndCall() { 541 when(mTelecomManager.isInCall()).thenReturn(true); 542 543 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 544 send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN); 545 546 verify(mTelecomManager, never()).endCall(); 547 } 548 549 @Test callKey_longPress_withoutEventHandler_redialsLastCall()550 public void callKey_longPress_withoutEventHandler_redialsLastCall() { 551 ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); 552 553 when(mLastCallSupplier.get()).thenReturn("1234567890"); 554 doNothing().when(mContext).startActivityAsUser(any(), any()); 555 556 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 557 flushHandler(); 558 559 verify(mContext).startActivityAsUser( 560 intentCaptor.capture(), eq(UserHandle.CURRENT)); 561 562 Intent intent = intentCaptor.getValue(); 563 assertThat(intent.getAction()).isEqualTo(Intent.ACTION_CALL); 564 assertThat(intent.getData()).isEqualTo(Uri.parse("tel:1234567890")); 565 566 clearInvocations(mContext); 567 send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN); 568 verify(mContext, never()).startActivityAsUser(any(), any()); 569 } 570 571 @Test callKey_longPress_withoutEventHandler_withNoLastCall_doesNothing()572 public void callKey_longPress_withoutEventHandler_withNoLastCall_doesNothing() { 573 when(mLastCallSupplier.get()).thenReturn(""); 574 575 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 576 flushHandler(); 577 578 verify(mContext, never()).startActivityAsUser(any(), any()); 579 } 580 581 @Test callKey_longPress_withoutEventHandler_whenCallRinging_answersCall()582 public void callKey_longPress_withoutEventHandler_whenCallRinging_answersCall() { 583 when(mTelecomManager.isRinging()).thenReturn(true); 584 585 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 586 flushHandler(); 587 588 verify(mTelecomManager).acceptRingingCall(); 589 590 send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN); 591 // Ensure that default handler does not run, either after accepting ringing call, 592 // or as a result of key-up. 593 verify(mContext, never()).startActivityAsUser(any(), any()); 594 } 595 596 @Test callKey_longPress_withEventHandler_triggersEventHandler()597 public void callKey_longPress_withEventHandler_triggersEventHandler() { 598 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 599 registerProjectionKeyEventHandler( 600 CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN); 601 602 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 603 flushHandler(); 604 605 verify(eventHandler).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN); 606 verify(mContext, never()).startActivityAsUser(any(), any()); 607 } 608 609 @Test callKey_longPress_withEventHandler_whenCallRinging_answersCall()610 public void callKey_longPress_withEventHandler_whenCallRinging_answersCall() { 611 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 612 registerProjectionKeyEventHandler( 613 CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN); 614 when(mTelecomManager.isRinging()).thenReturn(true); 615 616 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 617 flushHandler(); 618 619 verify(mTelecomManager).acceptRingingCall(); 620 621 send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN); 622 // Ensure that event handler does not run, either after accepting ringing call, 623 // or as a result of key-up. 624 verify(eventHandler, never()).onKeyEvent(anyInt()); 625 } 626 627 @Test callKey_longPress_duringCall_endCallViaCallButtonOn_endsCall()628 public void callKey_longPress_duringCall_endCallViaCallButtonOn_endsCall() { 629 when(mShouldCallButtonEndOngoingCallSupplier.getAsBoolean()).thenReturn(true); 630 when(mTelecomManager.isInCall()).thenReturn(true); 631 632 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 633 flushHandler(); 634 635 verify(mTelecomManager).endCall(); 636 } 637 638 @Test callKey_longPress_duringCall_endCallViaCallButtonOff_doesNotEndCall()639 public void callKey_longPress_duringCall_endCallViaCallButtonOff_doesNotEndCall() { 640 when(mTelecomManager.isInCall()).thenReturn(true); 641 642 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 643 flushHandler(); 644 645 verify(mTelecomManager, never()).endCall(); 646 } 647 648 @Test callKey_keyDown_withEventHandler_triggersEventHandler()649 public void callKey_keyDown_withEventHandler_triggersEventHandler() { 650 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 651 registerProjectionKeyEventHandler( 652 CarProjectionManager.KEY_EVENT_CALL_KEY_DOWN); 653 654 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 655 656 verify(eventHandler).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_KEY_DOWN); 657 } 658 659 @Test callKey_keyUp_afterLongPress_withEventHandler_triggersEventHandler()660 public void callKey_keyUp_afterLongPress_withEventHandler_triggersEventHandler() { 661 CarProjectionManager.ProjectionKeyEventHandler eventHandler = 662 registerProjectionKeyEventHandler( 663 CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_UP); 664 665 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 666 flushHandler(); 667 verify(eventHandler, never()) 668 .onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_UP); 669 670 send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN); 671 verify(eventHandler).onKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_UP); 672 } 673 674 @Test callKey_repeatedEvents_ignored()675 public void callKey_repeatedEvents_ignored() { 676 // Pressing a key starts the long-press timer. 677 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 678 verify(mHandler).sendMessageAtTime(any(), anyLong()); 679 clearInvocations(mHandler); 680 681 // Repeated KEY_DOWN events don't reset the timer. 682 sendWithRepeat(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN, 1); 683 verify(mHandler, never()).sendMessageAtTime(any(), anyLong()); 684 } 685 686 @Test longPressDelay_obeysValueFromSystem()687 public void longPressDelay_obeysValueFromSystem() { 688 final int systemDelay = 4242; 689 690 when(mLongPressDelaySupplier.getAsInt()).thenReturn(systemDelay); 691 ArgumentCaptor<Long> timeCaptor = ArgumentCaptor.forClass(long.class); 692 693 long then = SystemClock.uptimeMillis(); 694 send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN); 695 long now = SystemClock.uptimeMillis(); 696 697 verify(mHandler).sendMessageAtTime(any(), timeCaptor.capture()); 698 699 // The message time must be the expected delay time (as provided by the supplier) after 700 // the time the message was sent to the handler. We don't know that exact time, but we 701 // can put a bound on it - it's no sooner than immediately before the call to send(), and no 702 // later than immediately afterwards. Check to make sure the actual observed message time is 703 // somewhere in the valid range. 704 705 assertThat(timeCaptor.getValue()).isIn(Range.closed(then + systemDelay, now + systemDelay)); 706 } 707 708 @Test injectKeyEvent_throwsSecurityExceptionWithoutInjectEventsPermission()709 public void injectKeyEvent_throwsSecurityExceptionWithoutInjectEventsPermission() { 710 // Arrange 711 doReturn(PackageManager.PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission( 712 android.Manifest.permission.INJECT_EVENTS); 713 714 long currentTime = SystemClock.uptimeMillis(); 715 KeyEvent event = new KeyEvent(/* downTime= */ currentTime, 716 /* eventTime= */ currentTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 717 /* repeat= */ 0); 718 719 // Act and assert 720 Assert.assertThrows(SecurityException.class, 721 () -> mCarInputService.injectKeyEvent(event, 722 CarOccupantZoneManager.DISPLAY_TYPE_MAIN)); 723 } 724 725 @Test injectKeyEvent_delegatesToOnKeyEvent()726 public void injectKeyEvent_delegatesToOnKeyEvent() { 727 long currentTime = SystemClock.uptimeMillis(); 728 KeyEvent event = new KeyEvent(/* downTime= */ currentTime, 729 /* eventTime= */ currentTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 730 /* repeat= */ 0); 731 event.setDisplayId(android.view.Display.INVALID_DISPLAY); 732 733 injectKeyEventAndVerify(event, CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 734 735 verify(mDefaultMainListener).onKeyEvent(event); 736 verify(mInstrumentClusterKeyListener, never()).onKeyEvent(event); 737 } 738 739 @Test injectKeyEvent_sendingKeyEventWithDefaultDisplayAgainstClusterDisplayType()740 public void injectKeyEvent_sendingKeyEventWithDefaultDisplayAgainstClusterDisplayType() { 741 long currentTime = SystemClock.uptimeMillis(); 742 KeyEvent event = new KeyEvent(/* downTime= */ currentTime, 743 /* eventTime= */ currentTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 744 /* repeat= */ 0); 745 event.setDisplayId(android.view.Display.INVALID_DISPLAY); 746 747 injectKeyEventAndVerify(event, CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER); 748 749 verify(mDefaultMainListener, never()).onKeyEvent(event); 750 verify(mInstrumentClusterKeyListener).onKeyEvent(event); 751 } 752 injectKeyEventAndVerify(KeyEvent event, @DisplayTypeEnum int displayType)753 private void injectKeyEventAndVerify(KeyEvent event, @DisplayTypeEnum int displayType) { 754 doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission( 755 android.Manifest.permission.INJECT_EVENTS); 756 int someDisplayId = Integer.MAX_VALUE; 757 when(mCarOccupantZoneService.getDisplayIdForDriver(anyInt())).thenReturn(someDisplayId); 758 assertThat(event.getDisplayId()).isNotEqualTo(someDisplayId); 759 760 mCarInputService.injectKeyEvent(event, displayType); 761 762 verify(mCarOccupantZoneService).getDisplayIdForDriver(displayType); 763 assertWithMessage("Event's display id not updated as expected").that( 764 event.getDisplayId()).isEqualTo(someDisplayId); 765 } 766 767 @Test onKey_assignDisplayId_mainDisplay()768 public void onKey_assignDisplayId_mainDisplay() { 769 // Act 770 KeyEvent event = send(Key.DOWN, KeyEvent.KEYCODE_HOME, Display.MAIN); 771 772 // Arrange 773 assertWithMessage("display id expected to be assigned with Display.DEFAULT_DISPLAY").that( 774 event.getDisplayId()).isEqualTo(android.view.Display.DEFAULT_DISPLAY); 775 } 776 777 @Test onKey_assignDisplayId_clusterDisplay()778 public void onKey_assignDisplayId_clusterDisplay() { 779 // Act 780 KeyEvent event = send(Key.DOWN, KeyEvent.KEYCODE_HOME, Display.INSTRUMENT_CLUSTER); 781 782 // Arrange 783 assertWithMessage("display id expected to be assigned with Display.DEFAULT_DISPLAY").that( 784 event.getDisplayId()).isEqualTo(android.view.Display.DEFAULT_DISPLAY); 785 } 786 787 private enum Key {DOWN, UP} 788 789 private enum Display {MAIN, INSTRUMENT_CLUSTER} 790 send(Key action, int keyCode, Display display)791 private KeyEvent send(Key action, int keyCode, Display display) { 792 return sendWithRepeat(action, keyCode, display, 0); 793 } 794 sendWithRepeat(Key action, int keyCode, Display display, int repeatCount)795 private KeyEvent sendWithRepeat(Key action, int keyCode, Display display, int repeatCount) { 796 KeyEvent event = new KeyEvent( 797 /* downTime= */ 0L, 798 /* eventTime= */ 0L, 799 action == Key.DOWN ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, 800 keyCode, 801 repeatCount); 802 event.setDisplayId(android.view.Display.INVALID_DISPLAY); 803 mCarInputService.onKeyEvent( 804 event, 805 display == Display.MAIN 806 ? CarOccupantZoneManager.DISPLAY_TYPE_MAIN 807 : CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER); 808 return event; 809 } 810 registerProjectionKeyEventHandler( int... events)811 private CarProjectionManager.ProjectionKeyEventHandler registerProjectionKeyEventHandler( 812 int... events) { 813 BitSet eventSet = new BitSet(); 814 for (int event : events) { 815 eventSet.set(event); 816 } 817 818 CarProjectionManager.ProjectionKeyEventHandler projectionKeyEventHandler = 819 mock(CarProjectionManager.ProjectionKeyEventHandler.class); 820 mCarInputService.setProjectionKeyEventHandler(projectionKeyEventHandler, eventSet); 821 return projectionKeyEventHandler; 822 } 823 flushHandler()824 private void flushHandler() { 825 ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); 826 827 verify(mHandler, atLeast(0)).sendMessageAtTime(messageCaptor.capture(), anyLong()); 828 829 for (Message message : messageCaptor.getAllValues()) { 830 mHandler.dispatchMessage(message); 831 } 832 833 clearInvocations(mHandler); 834 } 835 mockEnableLongPressBluetoothVoiceRecognitionProperty(boolean enabledOrNot)836 private void mockEnableLongPressBluetoothVoiceRecognitionProperty(boolean enabledOrNot) { 837 when(mMockResources.getBoolean(R.bool.enableLongPressBluetoothVoiceRecognition)) 838 .thenReturn(enabledOrNot); 839 } 840 } 841