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 20 import android.car.input.CarInputHandlingService; 21 import android.car.input.CarInputHandlingService.InputFilter; 22 import android.car.input.ICarInputListener; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.ServiceConnection; 27 import android.hardware.input.InputManager; 28 import android.net.Uri; 29 import android.os.Binder; 30 import android.os.Bundle; 31 import android.os.IBinder; 32 import android.os.Parcel; 33 import android.os.ParcelFileDescriptor; 34 import android.os.RemoteException; 35 import android.os.SystemClock; 36 import android.os.UserHandle; 37 import android.provider.CallLog.Calls; 38 import android.speech.RecognizerIntent; 39 import android.telecom.TelecomManager; 40 import android.text.TextUtils; 41 import android.util.Log; 42 import android.view.KeyEvent; 43 44 import com.android.car.hal.InputHalService; 45 import com.android.car.hal.VehicleHal; 46 47 import java.io.PrintWriter; 48 import java.util.HashMap; 49 import java.util.HashSet; 50 import java.util.Map; 51 import java.util.Set; 52 53 public class CarInputService implements CarServiceBase, InputHalService.InputListener { 54 55 public interface KeyEventListener { onKeyEvent(KeyEvent event)56 boolean onKeyEvent(KeyEvent event); 57 } 58 59 private static final long LONG_PRESS_TIME_MS = 1000; 60 private static final boolean DBG = false; 61 62 private final Context mContext; 63 private final InputHalService mInputHalService; 64 private final TelecomManager mTelecomManager; 65 private final InputManager mInputManager; 66 67 private KeyEventListener mVoiceAssistantKeyListener; 68 private KeyEventListener mLongVoiceAssistantKeyListener; 69 private long mLastVoiceKeyDownTime = 0; 70 71 private long mLastCallKeyDownTime = 0; 72 73 private KeyEventListener mInstrumentClusterKeyListener; 74 75 private KeyEventListener mVolumeKeyListener; 76 77 private ICarInputListener mCarInputListener; 78 private boolean mCarInputListenerBound = false; 79 private final Map<Integer, Set<Integer>> mHandledKeys = new HashMap<>(); 80 81 private int mKeyEventCount = 0; 82 83 private final Binder mCallback = new Binder() { 84 @Override 85 protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) { 86 if (code == CarInputHandlingService.INPUT_CALLBACK_BINDER_CODE) { 87 data.setDataPosition(0); 88 InputFilter[] handledKeys = (InputFilter[]) data.createTypedArray( 89 InputFilter.CREATOR); 90 if (handledKeys != null) { 91 setHandledKeys(handledKeys); 92 } 93 return true; 94 } 95 return false; 96 } 97 }; 98 99 private final ServiceConnection mInputServiceConnection = new ServiceConnection() { 100 @Override 101 public void onServiceConnected(ComponentName name, IBinder binder) { 102 if (DBG) { 103 Log.d(CarLog.TAG_INPUT, "onServiceConnected, name: " 104 + name + ", binder: " + binder); 105 } 106 mCarInputListener = ICarInputListener.Stub.asInterface(binder); 107 108 try { 109 binder.linkToDeath(() -> CarServiceUtils.runOnMainSync(() -> { 110 Log.w(CarLog.TAG_INPUT, "Input service died. Trying to rebind..."); 111 mCarInputListener = null; 112 // Try to rebind with input service. 113 mCarInputListenerBound = bindCarInputService(); 114 }), 0); 115 } catch (RemoteException e) { 116 Log.e(CarLog.TAG_INPUT, e.getMessage(), e); 117 } 118 } 119 120 @Override 121 public void onServiceDisconnected(ComponentName name) { 122 Log.d(CarLog.TAG_INPUT, "onServiceDisconnected, name: " + name); 123 mCarInputListener = null; 124 // Try to rebind with input service. 125 mCarInputListenerBound = bindCarInputService(); 126 } 127 }; 128 CarInputService(Context context, InputHalService inputHalService)129 public CarInputService(Context context, InputHalService inputHalService) { 130 mContext = context; 131 mInputHalService = inputHalService; 132 mTelecomManager = context.getSystemService(TelecomManager.class); 133 mInputManager = context.getSystemService(InputManager.class); 134 } 135 setHandledKeys(InputFilter[] handledKeys)136 private synchronized void setHandledKeys(InputFilter[] handledKeys) { 137 mHandledKeys.clear(); 138 for (InputFilter handledKey : handledKeys) { 139 Set<Integer> displaySet = mHandledKeys.get(handledKey.mTargetDisplay); 140 if (displaySet == null) { 141 displaySet = new HashSet<Integer>(); 142 mHandledKeys.put(handledKey.mTargetDisplay, displaySet); 143 } 144 displaySet.add(handledKey.mKeyCode); 145 } 146 } 147 148 /** 149 * Set listener for listening voice assistant key event. Setting to null stops listening. 150 * If listener is not set, default behavior will be done for short press. 151 * If listener is set, short key press will lead into calling the listener. 152 * @param listener 153 */ setVoiceAssistantKeyListener(KeyEventListener listener)154 public void setVoiceAssistantKeyListener(KeyEventListener listener) { 155 synchronized (this) { 156 mVoiceAssistantKeyListener = listener; 157 } 158 } 159 160 /** 161 * Set listener for listening long voice assistant key event. Setting to null stops listening. 162 * If listener is not set, default behavior will be done for long press. 163 * If listener is set, short long press will lead into calling the listener. 164 * @param listener 165 */ setLongVoiceAssistantKeyListener(KeyEventListener listener)166 public void setLongVoiceAssistantKeyListener(KeyEventListener listener) { 167 synchronized (this) { 168 mLongVoiceAssistantKeyListener = listener; 169 } 170 } 171 setInstrumentClusterKeyListener(KeyEventListener listener)172 public void setInstrumentClusterKeyListener(KeyEventListener listener) { 173 synchronized (this) { 174 mInstrumentClusterKeyListener = listener; 175 } 176 } 177 setVolumeKeyListener(KeyEventListener listener)178 public void setVolumeKeyListener(KeyEventListener listener) { 179 synchronized (this) { 180 mVolumeKeyListener = listener; 181 } 182 } 183 184 @Override init()185 public void init() { 186 if (!mInputHalService.isKeyInputSupported()) { 187 Log.w(CarLog.TAG_INPUT, "Hal does not support key input."); 188 return; 189 } 190 191 192 mInputHalService.setInputListener(this); 193 mCarInputListenerBound = bindCarInputService(); 194 } 195 196 @Override release()197 public void release() { 198 synchronized (this) { 199 mVoiceAssistantKeyListener = null; 200 mLongVoiceAssistantKeyListener = null; 201 mInstrumentClusterKeyListener = null; 202 mKeyEventCount = 0; 203 if (mCarInputListenerBound) { 204 mContext.unbindService(mInputServiceConnection); 205 mCarInputListenerBound = false; 206 } 207 } 208 } 209 210 @Override onKeyEvent(KeyEvent event, int targetDisplay)211 public void onKeyEvent(KeyEvent event, int targetDisplay) { 212 synchronized (this) { 213 mKeyEventCount++; 214 } 215 if (handleSystemEvent(event)) { 216 // System event handled, nothing more to do here. 217 return; 218 } 219 if (mCarInputListener != null && isCustomEventHandler(event, targetDisplay)) { 220 try { 221 mCarInputListener.onKeyEvent(event, targetDisplay); 222 } catch (RemoteException e) { 223 Log.e(CarLog.TAG_INPUT, "Error while calling car input service", e); 224 } 225 // Custom input service handled the event, nothing more to do here. 226 return; 227 } 228 229 switch (event.getKeyCode()) { 230 case KeyEvent.KEYCODE_VOICE_ASSIST: 231 handleVoiceAssistKey(event); 232 return; 233 case KeyEvent.KEYCODE_CALL: 234 handleCallKey(event); 235 return; 236 default: 237 break; 238 } 239 if (targetDisplay == InputHalService.DISPLAY_INSTRUMENT_CLUSTER) { 240 handleInstrumentClusterKey(event); 241 } else { 242 handleMainDisplayKey(event); 243 } 244 } 245 isCustomEventHandler(KeyEvent event, int targetDisplay)246 private synchronized boolean isCustomEventHandler(KeyEvent event, int targetDisplay) { 247 Set<Integer> displaySet = mHandledKeys.get(targetDisplay); 248 if (displaySet == null) { 249 return false; 250 } 251 return displaySet.contains(event.getKeyCode()); 252 } 253 handleSystemEvent(KeyEvent event)254 private boolean handleSystemEvent(KeyEvent event) { 255 switch (event.getKeyCode()) { 256 case KeyEvent.KEYCODE_VOLUME_UP: 257 case KeyEvent.KEYCODE_VOLUME_DOWN: 258 handleVolumeKey(event); 259 return true; 260 default: 261 return false; 262 } 263 } 264 handleVoiceAssistKey(KeyEvent event)265 private void handleVoiceAssistKey(KeyEvent event) { 266 int action = event.getAction(); 267 if (action == KeyEvent.ACTION_DOWN) { 268 long now = SystemClock.elapsedRealtime(); 269 synchronized (this) { 270 mLastVoiceKeyDownTime = now; 271 } 272 } else if (action == KeyEvent.ACTION_UP) { 273 // if no listener, do not handle long press 274 KeyEventListener listener = null; 275 KeyEventListener shortPressListener = null; 276 KeyEventListener longPressListener = null; 277 long downTime; 278 synchronized (this) { 279 shortPressListener = mVoiceAssistantKeyListener; 280 longPressListener = mLongVoiceAssistantKeyListener; 281 downTime = mLastVoiceKeyDownTime; 282 } 283 if (shortPressListener == null && longPressListener == null) { 284 launchDefaultVoiceAssistantHandler(); 285 } else { 286 long duration = SystemClock.elapsedRealtime() - downTime; 287 listener = (duration > LONG_PRESS_TIME_MS 288 ? longPressListener : shortPressListener); 289 if (listener != null) { 290 listener.onKeyEvent(event); 291 } else { 292 launchDefaultVoiceAssistantHandler(); 293 } 294 } 295 } 296 } 297 handleCallKey(KeyEvent event)298 private void handleCallKey(KeyEvent event) { 299 int action = event.getAction(); 300 if (action == KeyEvent.ACTION_DOWN) { 301 // Only handle if it's ringing when button down. 302 if (mTelecomManager != null && mTelecomManager.isRinging()) { 303 Log.i(CarLog.TAG_INPUT, "call key while rinning. Answer the call!"); 304 mTelecomManager.acceptRingingCall(); 305 return; 306 } 307 308 long now = SystemClock.elapsedRealtime(); 309 synchronized (this) { 310 mLastCallKeyDownTime = now; 311 } 312 } else if (action == KeyEvent.ACTION_UP) { 313 long downTime; 314 synchronized (this) { 315 downTime = mLastCallKeyDownTime; 316 } 317 long duration = SystemClock.elapsedRealtime() - downTime; 318 if (duration > LONG_PRESS_TIME_MS) { 319 dialLastCallHandler(); 320 } else { 321 launchDialerHandler(); 322 } 323 } 324 } 325 launchDialerHandler()326 private void launchDialerHandler() { 327 Log.i(CarLog.TAG_INPUT, "call key, launch dialer intent"); 328 Intent dialerIntent = new Intent(Intent.ACTION_DIAL); 329 mContext.startActivityAsUser(dialerIntent, null, UserHandle.CURRENT_OR_SELF); 330 } 331 dialLastCallHandler()332 private void dialLastCallHandler() { 333 Log.i(CarLog.TAG_INPUT, "call key, dialing last call"); 334 335 String lastNumber = Calls.getLastOutgoingCall(mContext); 336 if (lastNumber != null && !lastNumber.isEmpty()) { 337 Intent callLastNumberIntent = new Intent(Intent.ACTION_CALL) 338 .setData(Uri.fromParts("tel", lastNumber, null)) 339 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 340 mContext.startActivityAsUser(callLastNumberIntent, null, UserHandle.CURRENT_OR_SELF); 341 } 342 } 343 launchDefaultVoiceAssistantHandler()344 private void launchDefaultVoiceAssistantHandler() { 345 Log.i(CarLog.TAG_INPUT, "voice key, launch default intent"); 346 Intent voiceIntent = 347 new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); 348 mContext.startActivityAsUser(voiceIntent, null, UserHandle.CURRENT_OR_SELF); 349 } 350 handleInstrumentClusterKey(KeyEvent event)351 private void handleInstrumentClusterKey(KeyEvent event) { 352 KeyEventListener listener = null; 353 synchronized (this) { 354 listener = mInstrumentClusterKeyListener; 355 } 356 if (listener == null) { 357 return; 358 } 359 listener.onKeyEvent(event); 360 } 361 handleVolumeKey(KeyEvent event)362 private void handleVolumeKey(KeyEvent event) { 363 KeyEventListener listener; 364 synchronized (this) { 365 listener = mVolumeKeyListener; 366 } 367 if (listener != null) { 368 listener.onKeyEvent(event); 369 } 370 } 371 handleMainDisplayKey(KeyEvent event)372 private void handleMainDisplayKey(KeyEvent event) { 373 mInputManager.injectInputEvent(event, INJECT_INPUT_EVENT_MODE_ASYNC); 374 } 375 376 @Override dump(PrintWriter writer)377 public void dump(PrintWriter writer) { 378 writer.println("*Input Service*"); 379 writer.println("mCarInputListenerBound:" + mCarInputListenerBound); 380 writer.println("mCarInputListener:" + mCarInputListener); 381 writer.println("mLastVoiceKeyDownTime:" + mLastVoiceKeyDownTime + 382 ",mKeyEventCount:" + mKeyEventCount); 383 } 384 bindCarInputService()385 private boolean bindCarInputService() { 386 String carInputService = mContext.getString(R.string.inputService); 387 if (TextUtils.isEmpty(carInputService)) { 388 Log.i(CarLog.TAG_INPUT, "Custom input service was not configured"); 389 return false; 390 } 391 392 Log.d(CarLog.TAG_INPUT, "bindCarInputService, component: " + carInputService); 393 394 Intent intent = new Intent(); 395 Bundle extras = new Bundle(); 396 extras.putBinder(CarInputHandlingService.INPUT_CALLBACK_BINDER_KEY, mCallback); 397 intent.putExtras(extras); 398 intent.setComponent(ComponentName.unflattenFromString(carInputService)); 399 return mContext.bindService(intent, mInputServiceConnection, Context.BIND_AUTO_CREATE); 400 } 401 } 402