• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 package com.android.car;
17 
18 import static android.car.CarOccupantZoneManager.DisplayTypeEnum;
19 import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
20 import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_PUSH_TO_TALK;
21 
22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
23 
24 import android.annotation.Nullable;
25 import android.annotation.UserIdInt;
26 import android.app.ActivityManager;
27 import android.bluetooth.BluetoothAdapter;
28 import android.bluetooth.BluetoothDevice;
29 import android.bluetooth.BluetoothHeadsetClient;
30 import android.bluetooth.BluetoothProfile;
31 import android.car.CarOccupantZoneManager;
32 import android.car.CarProjectionManager;
33 import android.car.input.CarInputManager;
34 import android.car.input.CustomInputEvent;
35 import android.car.input.ICarInput;
36 import android.car.input.ICarInputCallback;
37 import android.car.input.RotaryEvent;
38 import android.car.user.CarUserManager;
39 import android.content.ContentResolver;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.pm.PackageManager;
43 import android.content.res.Resources;
44 import android.hardware.input.InputManager;
45 import android.net.Uri;
46 import android.os.Binder;
47 import android.os.Bundle;
48 import android.os.Handler;
49 import android.os.Looper;
50 import android.os.UserHandle;
51 import android.provider.CallLog.Calls;
52 import android.provider.Settings;
53 import android.telecom.TelecomManager;
54 import android.text.TextUtils;
55 import android.util.IndentingPrintWriter;
56 import android.view.InputDevice;
57 import android.view.KeyEvent;
58 import android.view.ViewConfiguration;
59 
60 import com.android.car.hal.InputHalService;
61 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
62 import com.android.car.internal.common.UserHelperLite;
63 import com.android.car.user.CarUserService;
64 import com.android.internal.annotations.GuardedBy;
65 import com.android.internal.annotations.VisibleForTesting;
66 import com.android.internal.app.AssistUtils;
67 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
68 import com.android.internal.os.BackgroundThread;
69 import com.android.server.utils.Slogf;
70 
71 import java.util.ArrayList;
72 import java.util.BitSet;
73 import java.util.Collections;
74 import java.util.List;
75 import java.util.function.BooleanSupplier;
76 import java.util.function.IntSupplier;
77 import java.util.function.Supplier;
78 
79 /**
80  * CarInputService monitors and handles input event through vehicle HAL.
81  */
82 public class CarInputService extends ICarInput.Stub
83         implements CarServiceBase, InputHalService.InputListener {
84 
85     private static final String TAG = CarLog.TAG_INPUT;
86 
87     /** An interface to receive {@link KeyEvent}s as they occur. */
88     public interface KeyEventListener {
89         /** Called when a key event occurs. */
onKeyEvent(KeyEvent event)90         void onKeyEvent(KeyEvent event);
91     }
92 
93     private final class KeyPressTimer {
94         private final Runnable mLongPressRunnable;
95         private final Runnable mCallback = this::onTimerExpired;
96         private final IntSupplier mLongPressDelaySupplier;
97 
98         @GuardedBy("CarInputService.this.mLock")
99         private final Handler mHandler;
100         @GuardedBy("CarInputService.this.mLock")
101         private boolean mDown;
102         @GuardedBy("CarInputService.this.mLock")
103         private boolean mLongPress = false;
104 
KeyPressTimer( Handler handler, IntSupplier longPressDelaySupplier, Runnable longPressRunnable)105         KeyPressTimer(
106                 Handler handler, IntSupplier longPressDelaySupplier, Runnable longPressRunnable) {
107             mHandler = handler;
108             mLongPressRunnable = longPressRunnable;
109             mLongPressDelaySupplier = longPressDelaySupplier;
110         }
111 
112         /** Marks that a key was pressed, and starts the long-press timer. */
keyDown()113         void keyDown() {
114             synchronized (mLock) {
115                 mDown = true;
116                 mLongPress = false;
117                 mHandler.removeCallbacks(mCallback);
118                 mHandler.postDelayed(mCallback, mLongPressDelaySupplier.getAsInt());
119             }
120         }
121 
122         /**
123          * Marks that a key was released, and stops the long-press timer.
124          *
125          * Returns true if the press was a long-press.
126          */
keyUp()127         boolean keyUp() {
128             synchronized (mLock) {
129                 mHandler.removeCallbacks(mCallback);
130                 mDown = false;
131                 return mLongPress;
132             }
133         }
134 
onTimerExpired()135         private void onTimerExpired() {
136             synchronized (mLock) {
137                 // If the timer expires after key-up, don't retroactively make the press long.
138                 if (!mDown) {
139                     return;
140                 }
141                 mLongPress = true;
142             }
143             mLongPressRunnable.run();
144         }
145     }
146 
147     private final IVoiceInteractionSessionShowCallback mShowCallback =
148             new IVoiceInteractionSessionShowCallback.Stub() {
149                 @Override
150                 public void onFailed() {
151                     Slogf.w(TAG, "Failed to show VoiceInteractionSession");
152                 }
153 
154                 @Override
155                 public void onShown() {
156                     Slogf.d(TAG, "IVoiceInteractionSessionShowCallback onShown()");
157                 }
158             };
159 
160     @VisibleForTesting
161     static final String EXTRA_CAR_PUSH_TO_TALK =
162             "com.android.car.input.EXTRA_CAR_PUSH_TO_TALK";
163 
164     private final Context mContext;
165     private final InputHalService mInputHalService;
166     private final CarUserService mUserService;
167     private final CarOccupantZoneService mCarOccupantZoneService;
168     private final TelecomManager mTelecomManager;
169     private final AssistUtils mAssistUtils;
170 
171     // The default handler for main-display input events. By default, injects the events into
172     // the input queue via InputManager, but can be overridden for testing.
173     private final KeyEventListener mMainDisplayHandler;
174     // The supplier for the last-called number. By default, gets the number from the call log.
175     // May be overridden for testing.
176     private final Supplier<String> mLastCalledNumberSupplier;
177     // The supplier for the system long-press delay, in milliseconds. By default, gets the value
178     // from Settings.Secure for the current user, falling back to the system-wide default
179     // long-press delay defined in ViewConfiguration. May be overridden for testing.
180     private final IntSupplier mLongPressDelaySupplier;
181     // ComponentName of the RotaryService.
182     private final String mRotaryServiceComponentName;
183 
184     private final BooleanSupplier mShouldCallButtonEndOngoingCallSupplier;
185 
186     private final Object mLock = new Object();
187 
188     @GuardedBy("mLock")
189     private CarProjectionManager.ProjectionKeyEventHandler mProjectionKeyEventHandler;
190 
191     @GuardedBy("mLock")
192     private final BitSet mProjectionKeyEventsSubscribed = new BitSet();
193 
194     private final KeyPressTimer mVoiceKeyTimer;
195     private final KeyPressTimer mCallKeyTimer;
196 
197     @GuardedBy("mLock")
198     private KeyEventListener mInstrumentClusterKeyListener;
199 
200     private final InputCaptureClientController mCaptureController;
201 
202     private final BluetoothAdapter mBluetoothAdapter;
203 
204     // BluetoothHeadsetClient set through mBluetoothProfileServiceListener, and used by
205     // launchBluetoothVoiceRecognition().
206     @GuardedBy("mLock")
207     private BluetoothHeadsetClient mBluetoothHeadsetClient;
208 
209     private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
210             new BluetoothProfile.ServiceListener() {
211         @Override
212         public void onServiceConnected(int profile, BluetoothProfile proxy) {
213             if (profile == BluetoothProfile.HEADSET_CLIENT) {
214                 Slogf.d(TAG, "Bluetooth proxy connected for HEADSET_CLIENT profile");
215                 synchronized (mLock) {
216                     mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
217                 }
218             }
219         }
220 
221         @Override
222         public void onServiceDisconnected(int profile) {
223             if (profile == BluetoothProfile.HEADSET_CLIENT) {
224                 Slogf.d(TAG, "Bluetooth proxy disconnected for HEADSET_CLIENT profile");
225                 synchronized (mLock) {
226                     mBluetoothHeadsetClient = null;
227                 }
228             }
229         }
230     };
231 
232     private final CarUserManager.UserLifecycleListener mUserLifecycleListener = event -> {
233         Slogf.d(TAG, "CarInputService.onEvent(%s)", event);
234         if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
235             updateRotaryServiceSettings(event.getUserId());
236         }
237     };
238 
getViewLongPressDelay(ContentResolver cr)239     private static int getViewLongPressDelay(ContentResolver cr) {
240         return Settings.Secure.getIntForUser(
241                 cr,
242                 Settings.Secure.LONG_PRESS_TIMEOUT,
243                 ViewConfiguration.getLongPressTimeout(),
244                 UserHandle.USER_CURRENT);
245     }
246 
CarInputService(Context context, InputHalService inputHalService, CarUserService userService, CarOccupantZoneService occupantZoneService)247     public CarInputService(Context context, InputHalService inputHalService,
248             CarUserService userService, CarOccupantZoneService occupantZoneService) {
249         this(context, inputHalService, userService, occupantZoneService,
250                 new Handler(Looper.getMainLooper()),
251                 context.getSystemService(TelecomManager.class), new AssistUtils(context),
252                 event ->
253                         context.getSystemService(InputManager.class)
254                                 .injectInputEvent(event, INJECT_INPUT_EVENT_MODE_ASYNC),
255                 () -> Calls.getLastOutgoingCall(context),
256                 () -> getViewLongPressDelay(context.getContentResolver()),
257                 () -> context.getResources().getBoolean(R.bool.config_callButtonEndsOngoingCall),
258                 new InputCaptureClientController(context),
259                 BluetoothAdapter.getDefaultAdapter());
260     }
261 
262     @VisibleForTesting
CarInputService(Context context, InputHalService inputHalService, CarUserService userService, CarOccupantZoneService occupantZoneService, Handler handler, TelecomManager telecomManager, AssistUtils assistUtils, KeyEventListener mainDisplayHandler, Supplier<String> lastCalledNumberSupplier, IntSupplier longPressDelaySupplier, BooleanSupplier shouldCallButtonEndOngoingCallSupplier, InputCaptureClientController captureController, BluetoothAdapter bluetoothAdapter)263     CarInputService(Context context, InputHalService inputHalService, CarUserService userService,
264             CarOccupantZoneService occupantZoneService, Handler handler,
265             TelecomManager telecomManager, AssistUtils assistUtils,
266             KeyEventListener mainDisplayHandler,
267             Supplier<String> lastCalledNumberSupplier, IntSupplier longPressDelaySupplier,
268             BooleanSupplier shouldCallButtonEndOngoingCallSupplier,
269             InputCaptureClientController captureController, BluetoothAdapter bluetoothAdapter) {
270         mContext = context;
271         mCaptureController = captureController;
272         mInputHalService = inputHalService;
273         mUserService = userService;
274         mCarOccupantZoneService = occupantZoneService;
275         mTelecomManager = telecomManager;
276         mAssistUtils = assistUtils;
277         mMainDisplayHandler = mainDisplayHandler;
278         mLastCalledNumberSupplier = lastCalledNumberSupplier;
279         mLongPressDelaySupplier = longPressDelaySupplier;
280 
281         mVoiceKeyTimer =
282                 new KeyPressTimer(
283                         handler, longPressDelaySupplier, this::handleVoiceAssistLongPress);
284         mCallKeyTimer =
285                 new KeyPressTimer(handler, longPressDelaySupplier, this::handleCallLongPress);
286 
287         mRotaryServiceComponentName = mContext.getString(R.string.rotaryService);
288         mShouldCallButtonEndOngoingCallSupplier = shouldCallButtonEndOngoingCallSupplier;
289         mBluetoothAdapter = bluetoothAdapter;
290     }
291 
292     /**
293      * Set projection key event listener. If null, unregister listener.
294      */
setProjectionKeyEventHandler( @ullable CarProjectionManager.ProjectionKeyEventHandler listener, @Nullable BitSet events)295     public void setProjectionKeyEventHandler(
296             @Nullable CarProjectionManager.ProjectionKeyEventHandler listener,
297             @Nullable BitSet events) {
298         synchronized (mLock) {
299             mProjectionKeyEventHandler = listener;
300             mProjectionKeyEventsSubscribed.clear();
301             if (events != null) {
302                 mProjectionKeyEventsSubscribed.or(events);
303             }
304         }
305     }
306 
307     /**
308      * Sets the instrument cluster key event listener.
309      */
setInstrumentClusterKeyListener(KeyEventListener listener)310     public void setInstrumentClusterKeyListener(KeyEventListener listener) {
311         synchronized (mLock) {
312             mInstrumentClusterKeyListener = listener;
313         }
314     }
315 
316     @Override
init()317     public void init() {
318         if (!mInputHalService.isKeyInputSupported()) {
319             Slogf.w(TAG, "Hal does not support key input.");
320             return;
321         }
322         Slogf.d(TAG, "Hal supports key input.");
323         mInputHalService.setInputListener(this);
324         if (mBluetoothAdapter != null) {
325             BackgroundThread.getHandler().post(() -> {
326                 mBluetoothAdapter.getProfileProxy(mContext,
327                         mBluetoothProfileServiceListener, BluetoothProfile.HEADSET_CLIENT);
328             });
329         }
330         if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
331             mUserService.addUserLifecycleListener(mUserLifecycleListener);
332         }
333     }
334 
335     @Override
release()336     public void release() {
337         synchronized (mLock) {
338             mProjectionKeyEventHandler = null;
339             mProjectionKeyEventsSubscribed.clear();
340             mInstrumentClusterKeyListener = null;
341             if (mBluetoothHeadsetClient != null) {
342                 mBluetoothAdapter.closeProfileProxy(
343                         BluetoothProfile.HEADSET_CLIENT, mBluetoothHeadsetClient);
344                 mBluetoothHeadsetClient = null;
345             }
346         }
347         if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
348             mUserService.removeUserLifecycleListener(mUserLifecycleListener);
349         }
350     }
351 
352     @Override
onKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType)353     public void onKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType) {
354         // Special case key code that have special "long press" handling for automotive
355         switch (event.getKeyCode()) {
356             case KeyEvent.KEYCODE_VOICE_ASSIST:
357                 handleVoiceAssistKey(event);
358                 return;
359             case KeyEvent.KEYCODE_CALL:
360                 handleCallKey(event);
361                 return;
362             default:
363                 break;
364         }
365 
366         assignDisplayId(event, targetDisplayType);
367 
368         // Allow specifically targeted keys to be routed to the cluster
369         if (targetDisplayType == CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER
370                 && handleInstrumentClusterKey(event)) {
371             return;
372         }
373         if (mCaptureController.onKeyEvent(targetDisplayType, event)) {
374             return;
375         }
376         mMainDisplayHandler.onKeyEvent(event);
377     }
378 
assignDisplayId(KeyEvent event, @DisplayTypeEnum int targetDisplayType)379     private void assignDisplayId(KeyEvent event, @DisplayTypeEnum int targetDisplayType) {
380         // Setting display id for driver user id (currently MAIN and CLUSTER display types are
381         // linked to driver user only)
382         int newDisplayId = mCarOccupantZoneService.getDisplayIdForDriver(targetDisplayType);
383 
384         // Display id is overridden even if already set.
385         event.setDisplayId(newDisplayId);
386     }
387 
388     @Override
onRotaryEvent(RotaryEvent event, @DisplayTypeEnum int targetDisplay)389     public void onRotaryEvent(RotaryEvent event, @DisplayTypeEnum int targetDisplay) {
390         if (!mCaptureController.onRotaryEvent(targetDisplay, event)) {
391             List<KeyEvent> keyEvents = rotaryEventToKeyEvents(event);
392             for (KeyEvent keyEvent : keyEvents) {
393                 onKeyEvent(keyEvent, targetDisplay);
394             }
395         }
396     }
397 
398     @Override
onCustomInputEvent(CustomInputEvent event)399     public void onCustomInputEvent(CustomInputEvent event) {
400         if (!mCaptureController.onCustomInputEvent(event)) {
401             Slogf.w(TAG, "Failed to propagate (%s)", event);
402             return;
403         }
404         Slogf.d(TAG, "Succeed injecting (%s)", event);
405     }
406 
rotaryEventToKeyEvents(RotaryEvent event)407     private static List<KeyEvent> rotaryEventToKeyEvents(RotaryEvent event) {
408         int numClicks = event.getNumberOfClicks();
409         int numEvents = numClicks * 2; // up / down per each click
410         boolean clockwise = event.isClockwise();
411         int keyCode;
412         switch (event.getInputType()) {
413             case CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION:
414                 keyCode = clockwise
415                         ? KeyEvent.KEYCODE_NAVIGATE_NEXT
416                         : KeyEvent.KEYCODE_NAVIGATE_PREVIOUS;
417                 break;
418             case CarInputManager.INPUT_TYPE_ROTARY_VOLUME:
419                 keyCode = clockwise
420                         ? KeyEvent.KEYCODE_VOLUME_UP
421                         : KeyEvent.KEYCODE_VOLUME_DOWN;
422                 break;
423             default:
424                 Slogf.e(TAG, "Unknown rotary input type: %d", event.getInputType());
425                 return Collections.EMPTY_LIST;
426         }
427         ArrayList<KeyEvent> keyEvents = new ArrayList<>(numEvents);
428         for (int i = 0; i < numClicks; i++) {
429             long uptime = event.getUptimeMillisForClick(i);
430             KeyEvent downEvent = createKeyEvent(/* down= */ true, uptime, uptime, keyCode);
431             KeyEvent upEvent = createKeyEvent(/* down= */ false, uptime, uptime, keyCode);
432             keyEvents.add(downEvent);
433             keyEvents.add(upEvent);
434         }
435         return keyEvents;
436     }
437 
createKeyEvent(boolean down, long downTime, long eventTime, int keyCode)438     private static KeyEvent createKeyEvent(boolean down, long downTime, long eventTime,
439             int keyCode) {
440         return new KeyEvent(
441                 downTime,
442                 eventTime,
443                 /* action= */ down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP,
444                 keyCode,
445                 /* repeat= */ 0,
446                 /* metaState= */ 0,
447                 /* deviceId= */ 0,
448                 /* scancode= */ 0,
449                 /* flags= */ 0,
450                 InputDevice.SOURCE_CLASS_BUTTON);
451     }
452 
453     @Override
requestInputEventCapture(ICarInputCallback callback, @DisplayTypeEnum int targetDisplayType, int[] inputTypes, int requestFlags)454     public int requestInputEventCapture(ICarInputCallback callback,
455             @DisplayTypeEnum int targetDisplayType,
456             int[] inputTypes, int requestFlags) {
457         return mCaptureController.requestInputEventCapture(callback, targetDisplayType, inputTypes,
458                 requestFlags);
459     }
460 
461     @Override
releaseInputEventCapture(ICarInputCallback callback, @DisplayTypeEnum int targetDisplayType)462     public void releaseInputEventCapture(ICarInputCallback callback,
463             @DisplayTypeEnum int targetDisplayType) {
464         mCaptureController.releaseInputEventCapture(callback, targetDisplayType);
465     }
466 
467     /**
468      * Injects the {@link KeyEvent} passed as parameter against Car Input API.
469      * <p>
470      * The event's display id will be overridden accordingly to the display type (it will be
471      * retrieved from {@link CarOccupantZoneService}).
472      *
473      * @param event             the event to inject
474      * @param targetDisplayType the display type associated with the event
475      * @throws SecurityException when caller doesn't have INJECT_EVENTS permission granted
476      */
477     @Override
injectKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType)478     public void injectKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType) {
479         // Permission check
480         if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
481                 android.Manifest.permission.INJECT_EVENTS)) {
482             throw new SecurityException("Injecting KeyEvent requires INJECT_EVENTS permission");
483         }
484 
485         long token = Binder.clearCallingIdentity();
486         try {
487             // Redirect event to onKeyEvent
488             onKeyEvent(event, targetDisplayType);
489         } finally {
490             Binder.restoreCallingIdentity(token);
491         }
492     }
493 
handleVoiceAssistKey(KeyEvent event)494     private void handleVoiceAssistKey(KeyEvent event) {
495         int action = event.getAction();
496         if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
497             mVoiceKeyTimer.keyDown();
498             dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_KEY_DOWN);
499         } else if (action == KeyEvent.ACTION_UP) {
500             if (mVoiceKeyTimer.keyUp()) {
501                 // Long press already handled by handleVoiceAssistLongPress(), nothing more to do.
502                 // Hand it off to projection, if it's interested, otherwise we're done.
503                 dispatchProjectionKeyEvent(
504                         CarProjectionManager.KEY_EVENT_VOICE_SEARCH_LONG_PRESS_KEY_UP);
505                 return;
506             }
507 
508             if (dispatchProjectionKeyEvent(
509                     CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP)) {
510                 return;
511             }
512 
513             launchDefaultVoiceAssistantHandler();
514         }
515     }
516 
handleVoiceAssistLongPress()517     private void handleVoiceAssistLongPress() {
518         // If projection wants this event, let it take it.
519         if (dispatchProjectionKeyEvent(
520                 CarProjectionManager.KEY_EVENT_VOICE_SEARCH_LONG_PRESS_KEY_DOWN)) {
521             return;
522         }
523         // Otherwise, try to launch voice recognition on a BT device.
524         if (launchBluetoothVoiceRecognition()) {
525             return;
526         }
527         // Finally, fallback to the default voice assist handling.
528         launchDefaultVoiceAssistantHandler();
529     }
530 
handleCallKey(KeyEvent event)531     private void handleCallKey(KeyEvent event) {
532         int action = event.getAction();
533         if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
534             mCallKeyTimer.keyDown();
535             dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_CALL_KEY_DOWN);
536         } else if (action == KeyEvent.ACTION_UP) {
537             if (mCallKeyTimer.keyUp()) {
538                 // Long press already handled by handleCallLongPress(), nothing more to do.
539                 // Hand it off to projection, if it's interested, otherwise we're done.
540                 dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_UP);
541                 return;
542             }
543 
544             if (acceptCallIfRinging()) {
545                 // Ringing call answered, nothing more to do.
546                 return;
547             }
548 
549             if (mShouldCallButtonEndOngoingCallSupplier.getAsBoolean() && endCall()) {
550                 // On-going call ended, nothing more to do.
551                 return;
552             }
553 
554             if (dispatchProjectionKeyEvent(
555                     CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP)) {
556                 return;
557             }
558 
559             launchDialerHandler();
560         }
561     }
562 
handleCallLongPress()563     private void handleCallLongPress() {
564         // Long-press answers call if ringing, same as short-press.
565         if (acceptCallIfRinging()) {
566             return;
567         }
568 
569         if (mShouldCallButtonEndOngoingCallSupplier.getAsBoolean() && endCall()) {
570             return;
571         }
572 
573         if (dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN)) {
574             return;
575         }
576 
577         dialLastCallHandler();
578     }
579 
dispatchProjectionKeyEvent(@arProjectionManager.KeyEventNum int event)580     private boolean dispatchProjectionKeyEvent(@CarProjectionManager.KeyEventNum int event) {
581         CarProjectionManager.ProjectionKeyEventHandler projectionKeyEventHandler;
582         synchronized (mLock) {
583             projectionKeyEventHandler = mProjectionKeyEventHandler;
584             if (projectionKeyEventHandler == null || !mProjectionKeyEventsSubscribed.get(event)) {
585                 // No event handler, or event handler doesn't want this event - we're done.
586                 return false;
587             }
588         }
589 
590         projectionKeyEventHandler.onKeyEvent(event);
591         return true;
592     }
593 
launchDialerHandler()594     private void launchDialerHandler() {
595         Slogf.i(TAG, "call key, launch dialer intent");
596         Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
597         mContext.startActivityAsUser(dialerIntent, null, UserHandle.CURRENT_OR_SELF);
598     }
599 
dialLastCallHandler()600     private void dialLastCallHandler() {
601         Slogf.i(TAG, "call key, dialing last call");
602 
603         String lastNumber = mLastCalledNumberSupplier.get();
604         if (!TextUtils.isEmpty(lastNumber)) {
605             Intent callLastNumberIntent = new Intent(Intent.ACTION_CALL)
606                     .setData(Uri.fromParts("tel", lastNumber, null))
607                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
608             mContext.startActivityAsUser(callLastNumberIntent, null, UserHandle.CURRENT_OR_SELF);
609         }
610     }
611 
acceptCallIfRinging()612     private boolean acceptCallIfRinging() {
613         if (mTelecomManager != null && mTelecomManager.isRinging()) {
614             Slogf.i(TAG, "call key while ringing. Answer the call!");
615             mTelecomManager.acceptRingingCall();
616             return true;
617         }
618         return false;
619     }
620 
endCall()621     private boolean endCall() {
622         if (mTelecomManager != null && mTelecomManager.isInCall()) {
623             Slogf.i(TAG, "End the call!");
624             mTelecomManager.endCall();
625             return true;
626         }
627         return false;
628     }
629 
isBluetoothVoiceRecognitionEnabled()630     private boolean isBluetoothVoiceRecognitionEnabled() {
631         Resources res = mContext.getResources();
632         return res.getBoolean(R.bool.enableLongPressBluetoothVoiceRecognition);
633     }
634 
launchBluetoothVoiceRecognition()635     private boolean launchBluetoothVoiceRecognition() {
636         synchronized (mLock) {
637             if (mBluetoothHeadsetClient == null || !isBluetoothVoiceRecognitionEnabled()) {
638                 return false;
639             }
640             // getConnectedDevices() does not make any guarantees about the order of the returned
641             // list. As of 2019-02-26, this code is only triggered through a long-press of the
642             // voice recognition key, so handling of multiple connected devices that support voice
643             // recognition is not expected to be a primary use case.
644             List<BluetoothDevice> devices = mBluetoothHeadsetClient.getConnectedDevices();
645             if (devices != null) {
646                 for (BluetoothDevice device : devices) {
647                     Bundle bundle = mBluetoothHeadsetClient.getCurrentAgFeatures(device);
648                     if (bundle == null || !bundle.getBoolean(
649                             BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION)) {
650                         continue;
651                     }
652                     if (mBluetoothHeadsetClient.startVoiceRecognition(device)) {
653                         Slogf.d(TAG, "started voice recognition on BT device at (%s)",
654                                 device.getAddress());
655                         return true;
656                     }
657                 }
658             }
659         }
660         return false;
661     }
662 
launchDefaultVoiceAssistantHandler()663     private void launchDefaultVoiceAssistantHandler() {
664         Slogf.i(TAG, "voice key, invoke AssistUtils");
665 
666         if (mAssistUtils.getAssistComponentForUser(ActivityManager.getCurrentUser()) == null) {
667             Slogf.w(TAG, "Unable to retrieve assist component for current user");
668             return;
669         }
670 
671         final Bundle args = new Bundle();
672         args.putBoolean(EXTRA_CAR_PUSH_TO_TALK, true);
673 
674         mAssistUtils.showSessionForActiveService(args,
675                 SHOW_SOURCE_PUSH_TO_TALK, mShowCallback, null /*activityToken*/);
676     }
677 
678     /**
679      * @return false if the KeyEvent isn't consumed because there is no
680      * InstrumentClusterKeyListener.
681      */
handleInstrumentClusterKey(KeyEvent event)682     private boolean handleInstrumentClusterKey(KeyEvent event) {
683         KeyEventListener listener = null;
684         synchronized (mLock) {
685             listener = mInstrumentClusterKeyListener;
686         }
687         if (listener == null) {
688             return false;
689         }
690         listener.onKeyEvent(event);
691         return true;
692     }
693 
694     @Override
695     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)696     public void dump(IndentingPrintWriter writer) {
697         writer.println("*Input Service*");
698         writer.println("Long-press delay: " + mLongPressDelaySupplier.getAsInt() + "ms");
699         writer.println("Call button ends ongoing call: "
700                 + mShouldCallButtonEndOngoingCallSupplier.getAsBoolean());
701         mCaptureController.dump(writer);
702     }
703 
updateRotaryServiceSettings(@serIdInt int userId)704     private void updateRotaryServiceSettings(@UserIdInt int userId) {
705         if (UserHelperLite.isHeadlessSystemUser(userId)) {
706             return;
707         }
708         ContentResolver contentResolver = mContext.getContentResolver();
709         Settings.Secure.putStringForUser(contentResolver,
710                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
711                 mRotaryServiceComponentName,
712                 userId);
713         Settings.Secure.putStringForUser(contentResolver,
714                 Settings.Secure.ACCESSIBILITY_ENABLED,
715                 "1",
716                 userId);
717     }
718 }
719