• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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