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