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