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