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