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 class KeyPressTimer { 60 private static final long LONG_PRESS_TIME_MS = 1000; 61 62 private boolean mDown = false; 63 private long mDuration = -1; 64 keyDown()65 synchronized void keyDown() { 66 mDown = true; 67 mDuration = SystemClock.elapsedRealtime(); 68 } 69 keyUp()70 synchronized void keyUp() { 71 if (!mDown) { 72 throw new IllegalStateException("key can't go up without being down"); 73 } 74 mDuration = SystemClock.elapsedRealtime() - mDuration; 75 mDown = false; 76 } 77 isLongPress()78 synchronized boolean isLongPress() { 79 if (mDown) { 80 throw new IllegalStateException("can't query press length during key down"); 81 } 82 return mDuration >= LONG_PRESS_TIME_MS; 83 } 84 } 85 86 private static final boolean DBG = false; 87 88 private final Context mContext; 89 private final InputHalService mInputHalService; 90 private final TelecomManager mTelecomManager; 91 private final InputManager mInputManager; 92 93 private KeyEventListener mVoiceAssistantKeyListener; 94 private KeyEventListener mLongVoiceAssistantKeyListener; 95 96 private final KeyPressTimer mVoiceKeyTimer = new KeyPressTimer(); 97 private final KeyPressTimer mCallKeyTimer = new KeyPressTimer(); 98 99 private KeyEventListener mInstrumentClusterKeyListener; 100 101 private ICarInputListener mCarInputListener; 102 private boolean mCarInputListenerBound = false; 103 private final Map<Integer, Set<Integer>> mHandledKeys = new HashMap<>(); 104 105 private final Binder mCallback = new Binder() { 106 @Override 107 protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) { 108 if (code == CarInputHandlingService.INPUT_CALLBACK_BINDER_CODE) { 109 data.setDataPosition(0); 110 InputFilter[] handledKeys = (InputFilter[]) data.createTypedArray( 111 InputFilter.CREATOR); 112 if (handledKeys != null) { 113 setHandledKeys(handledKeys); 114 } 115 return true; 116 } 117 return false; 118 } 119 }; 120 121 private final ServiceConnection mInputServiceConnection = new ServiceConnection() { 122 @Override 123 public void onServiceConnected(ComponentName name, IBinder binder) { 124 if (DBG) { 125 Log.d(CarLog.TAG_INPUT, "onServiceConnected, name: " 126 + name + ", binder: " + binder); 127 } 128 mCarInputListener = ICarInputListener.Stub.asInterface(binder); 129 130 try { 131 binder.linkToDeath(() -> CarServiceUtils.runOnMainSync(() -> { 132 Log.w(CarLog.TAG_INPUT, "Input service died. Trying to rebind..."); 133 mCarInputListener = null; 134 // Try to rebind with input service. 135 mCarInputListenerBound = bindCarInputService(); 136 }), 0); 137 } catch (RemoteException e) { 138 Log.e(CarLog.TAG_INPUT, e.getMessage(), e); 139 } 140 } 141 142 @Override 143 public void onServiceDisconnected(ComponentName name) { 144 Log.d(CarLog.TAG_INPUT, "onServiceDisconnected, name: " + name); 145 mCarInputListener = null; 146 // Try to rebind with input service. 147 mCarInputListenerBound = bindCarInputService(); 148 } 149 }; 150 CarInputService(Context context, InputHalService inputHalService)151 public CarInputService(Context context, InputHalService inputHalService) { 152 mContext = context; 153 mInputHalService = inputHalService; 154 mTelecomManager = context.getSystemService(TelecomManager.class); 155 mInputManager = context.getSystemService(InputManager.class); 156 } 157 setHandledKeys(InputFilter[] handledKeys)158 private synchronized void setHandledKeys(InputFilter[] handledKeys) { 159 mHandledKeys.clear(); 160 for (InputFilter handledKey : handledKeys) { 161 Set<Integer> displaySet = mHandledKeys.get(handledKey.mTargetDisplay); 162 if (displaySet == null) { 163 displaySet = new HashSet<Integer>(); 164 mHandledKeys.put(handledKey.mTargetDisplay, displaySet); 165 } 166 displaySet.add(handledKey.mKeyCode); 167 } 168 } 169 170 /** 171 * Set listener for listening voice assistant key event. Setting to null stops listening. 172 * If listener is not set, default behavior will be done for short press. 173 * If listener is set, short key press will lead into calling the listener. 174 * @param listener 175 */ setVoiceAssistantKeyListener(KeyEventListener listener)176 public void setVoiceAssistantKeyListener(KeyEventListener listener) { 177 synchronized (this) { 178 mVoiceAssistantKeyListener = listener; 179 } 180 } 181 182 /** 183 * Set listener for listening long voice assistant key event. Setting to null stops listening. 184 * If listener is not set, default behavior will be done for long press. 185 * If listener is set, short long press will lead into calling the listener. 186 * @param listener 187 */ setLongVoiceAssistantKeyListener(KeyEventListener listener)188 public void setLongVoiceAssistantKeyListener(KeyEventListener listener) { 189 synchronized (this) { 190 mLongVoiceAssistantKeyListener = listener; 191 } 192 } 193 setInstrumentClusterKeyListener(KeyEventListener listener)194 public void setInstrumentClusterKeyListener(KeyEventListener listener) { 195 synchronized (this) { 196 mInstrumentClusterKeyListener = listener; 197 } 198 } 199 200 @Override init()201 public void init() { 202 if (!mInputHalService.isKeyInputSupported()) { 203 Log.w(CarLog.TAG_INPUT, "Hal does not support key input."); 204 return; 205 } else if (DBG) { 206 Log.d(CarLog.TAG_INPUT, "Hal supports key input."); 207 } 208 209 210 mInputHalService.setInputListener(this); 211 mCarInputListenerBound = bindCarInputService(); 212 } 213 214 @Override release()215 public void release() { 216 synchronized (this) { 217 mVoiceAssistantKeyListener = null; 218 mLongVoiceAssistantKeyListener = null; 219 mInstrumentClusterKeyListener = null; 220 if (mCarInputListenerBound) { 221 mContext.unbindService(mInputServiceConnection); 222 mCarInputListenerBound = false; 223 } 224 } 225 } 226 227 @Override onKeyEvent(KeyEvent event, int targetDisplay)228 public void onKeyEvent(KeyEvent event, int targetDisplay) { 229 // Give a car specific input listener the opportunity to intercept any input from the car 230 if (mCarInputListener != null && isCustomEventHandler(event, targetDisplay)) { 231 try { 232 mCarInputListener.onKeyEvent(event, targetDisplay); 233 } catch (RemoteException e) { 234 Log.e(CarLog.TAG_INPUT, "Error while calling car input service", e); 235 } 236 // Custom input service handled the event, nothing more to do here. 237 return; 238 } 239 240 // Special case key code that have special "long press" handling for automotive 241 switch (event.getKeyCode()) { 242 case KeyEvent.KEYCODE_VOICE_ASSIST: 243 handleVoiceAssistKey(event); 244 return; 245 case KeyEvent.KEYCODE_CALL: 246 handleCallKey(event); 247 return; 248 default: 249 break; 250 } 251 252 // Allow specifically targeted keys to be routed to the cluster 253 if (targetDisplay == InputHalService.DISPLAY_INSTRUMENT_CLUSTER) { 254 handleInstrumentClusterKey(event); 255 } else { 256 handleMainDisplayKey(event); 257 } 258 } 259 isCustomEventHandler(KeyEvent event, int targetDisplay)260 private synchronized boolean isCustomEventHandler(KeyEvent event, int targetDisplay) { 261 Set<Integer> displaySet = mHandledKeys.get(targetDisplay); 262 if (displaySet == null) { 263 return false; 264 } 265 return displaySet.contains(event.getKeyCode()); 266 } 267 handleVoiceAssistKey(KeyEvent event)268 private void handleVoiceAssistKey(KeyEvent event) { 269 int action = event.getAction(); 270 if (action == KeyEvent.ACTION_DOWN) { 271 mVoiceKeyTimer.keyDown(); 272 } else if (action == KeyEvent.ACTION_UP) { 273 mVoiceKeyTimer.keyUp(); 274 final KeyEventListener listener; 275 276 synchronized (this) { 277 listener = (mVoiceKeyTimer.isLongPress() 278 ? mLongVoiceAssistantKeyListener : mVoiceAssistantKeyListener); 279 } 280 281 if (listener != null) { 282 listener.onKeyEvent(event); 283 } else { 284 launchDefaultVoiceAssistantHandler(); 285 } 286 } 287 } 288 handleCallKey(KeyEvent event)289 private void handleCallKey(KeyEvent event) { 290 int action = event.getAction(); 291 if (action == KeyEvent.ACTION_DOWN) { 292 mCallKeyTimer.keyDown(); 293 } else if (action == KeyEvent.ACTION_UP) { 294 mCallKeyTimer.keyUp(); 295 296 // Handle a phone call regardless of press length. 297 if (mTelecomManager != null && mTelecomManager.isRinging()) { 298 Log.i(CarLog.TAG_INPUT, "call key while ringing. Answer the call!"); 299 mTelecomManager.acceptRingingCall(); 300 } else if (mCallKeyTimer.isLongPress()) { 301 dialLastCallHandler(); 302 } else { 303 launchDialerHandler(); 304 } 305 } 306 } 307 launchDialerHandler()308 private void launchDialerHandler() { 309 Log.i(CarLog.TAG_INPUT, "call key, launch dialer intent"); 310 Intent dialerIntent = new Intent(Intent.ACTION_DIAL); 311 mContext.startActivityAsUser(dialerIntent, null, UserHandle.CURRENT_OR_SELF); 312 } 313 dialLastCallHandler()314 private void dialLastCallHandler() { 315 Log.i(CarLog.TAG_INPUT, "call key, dialing last call"); 316 317 String lastNumber = Calls.getLastOutgoingCall(mContext); 318 if (lastNumber != null && !lastNumber.isEmpty()) { 319 Intent callLastNumberIntent = new Intent(Intent.ACTION_CALL) 320 .setData(Uri.fromParts("tel", lastNumber, null)) 321 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 322 mContext.startActivityAsUser(callLastNumberIntent, null, UserHandle.CURRENT_OR_SELF); 323 } 324 } 325 launchDefaultVoiceAssistantHandler()326 private void launchDefaultVoiceAssistantHandler() { 327 Log.i(CarLog.TAG_INPUT, "voice key, launch default intent"); 328 Intent voiceIntent = 329 new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); 330 mContext.startActivityAsUser(voiceIntent, null, UserHandle.CURRENT_OR_SELF); 331 } 332 handleInstrumentClusterKey(KeyEvent event)333 private void handleInstrumentClusterKey(KeyEvent event) { 334 KeyEventListener listener = null; 335 synchronized (this) { 336 listener = mInstrumentClusterKeyListener; 337 } 338 if (listener == null) { 339 return; 340 } 341 listener.onKeyEvent(event); 342 } 343 handleMainDisplayKey(KeyEvent event)344 private void handleMainDisplayKey(KeyEvent event) { 345 mInputManager.injectInputEvent(event, INJECT_INPUT_EVENT_MODE_ASYNC); 346 } 347 348 @Override dump(PrintWriter writer)349 public void dump(PrintWriter writer) { 350 writer.println("*Input Service*"); 351 writer.println("mCarInputListenerBound:" + mCarInputListenerBound); 352 writer.println("mCarInputListener:" + mCarInputListener); 353 } 354 bindCarInputService()355 private boolean bindCarInputService() { 356 String carInputService = mContext.getString(R.string.inputService); 357 if (TextUtils.isEmpty(carInputService)) { 358 Log.i(CarLog.TAG_INPUT, "Custom input service was not configured"); 359 return false; 360 } 361 362 Log.d(CarLog.TAG_INPUT, "bindCarInputService, component: " + carInputService); 363 364 Intent intent = new Intent(); 365 Bundle extras = new Bundle(); 366 extras.putBinder(CarInputHandlingService.INPUT_CALLBACK_BINDER_KEY, mCallback); 367 intent.putExtras(extras); 368 intent.setComponent(ComponentName.unflattenFromString(carInputService)); 369 return mContext.bindService(intent, mInputServiceConnection, Context.BIND_AUTO_CREATE); 370 } 371 } 372