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