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