blob: 31c07d5feedf9a93e3b6573c17dbdddad368a96a [file] [log] [blame]
Keun-young Parka28d7b22016-02-29 16:54:29 -08001/*
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 */
16package com.android.car;
17
Pavel Maltsev338cef82016-07-26 16:04:15 -070018import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
19
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -070020import android.car.input.CarInputHandlingService;
21import android.car.input.CarInputHandlingService.InputFilter;
22import android.car.input.ICarInputListener;
23import android.content.ComponentName;
Keun-young Parka28d7b22016-02-29 16:54:29 -080024import android.content.Context;
25import android.content.Intent;
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -070026import android.content.ServiceConnection;
Pavel Maltsev338cef82016-07-26 16:04:15 -070027import android.hardware.input.InputManager;
Vitalii Tomkive2142e52016-04-29 11:35:26 -070028import android.net.Uri;
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -070029import android.os.Binder;
30import android.os.Bundle;
31import android.os.IBinder;
32import android.os.Parcel;
Keun-young Parka28d7b22016-02-29 16:54:29 -080033import android.os.ParcelFileDescriptor;
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -070034import android.os.RemoteException;
Keun-young Parka28d7b22016-02-29 16:54:29 -080035import android.os.SystemClock;
36import android.os.UserHandle;
Vitalii Tomkive2142e52016-04-29 11:35:26 -070037import android.provider.CallLog.Calls;
Keun-young Parka28d7b22016-02-29 16:54:29 -080038import android.speech.RecognizerIntent;
Vitalii Tomkive2142e52016-04-29 11:35:26 -070039import android.telecom.TelecomManager;
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -070040import android.text.TextUtils;
Keun-young Parka28d7b22016-02-29 16:54:29 -080041import android.util.Log;
42import android.view.KeyEvent;
43
44import com.android.car.hal.InputHalService;
45import com.android.car.hal.VehicleHal;
46
Keun-young Parka28d7b22016-02-29 16:54:29 -080047import java.io.PrintWriter;
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -070048import java.util.HashMap;
49import java.util.HashSet;
50import java.util.Map;
51import java.util.Set;
Keun-young Parka28d7b22016-02-29 16:54:29 -080052
53public class CarInputService implements CarServiceBase, InputHalService.InputListener {
54
55 public interface KeyEventListener {
Yao Chenc4d442f2016-04-08 11:33:47 -070056 boolean onKeyEvent(KeyEvent event);
Keun-young Parka28d7b22016-02-29 16:54:29 -080057 }
58
Vitalii Tomkive2142e52016-04-29 11:35:26 -070059 private static final long LONG_PRESS_TIME_MS = 1000;
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -070060 private static final boolean DBG = false;
Keun-young Parka28d7b22016-02-29 16:54:29 -080061
62 private final Context mContext;
Vitalii Tomkive2142e52016-04-29 11:35:26 -070063 private final TelecomManager mTelecomManager;
Pavel Maltsev338cef82016-07-26 16:04:15 -070064 private final InputManager mInputManager;
Keun-young Parka28d7b22016-02-29 16:54:29 -080065
Pavel Maltseve11ad3a2016-03-25 15:40:24 -070066 private KeyEventListener mVoiceAssistantKeyListener;
67 private KeyEventListener mLongVoiceAssistantKeyListener;
Keun-young Parka28d7b22016-02-29 16:54:29 -080068 private long mLastVoiceKeyDownTime = 0;
69
Vitalii Tomkive2142e52016-04-29 11:35:26 -070070 private long mLastCallKeyDownTime = 0;
71
Pavel Maltseve11ad3a2016-03-25 15:40:24 -070072 private KeyEventListener mInstrumentClusterKeyListener;
Keun-young Parka28d7b22016-02-29 16:54:29 -080073
Keun-young Park07182c72016-03-18 18:01:29 -070074 private KeyEventListener mVolumeKeyListener;
75
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -070076 private ICarInputListener mCarInputListener;
77 private boolean mCarInputListenerBound = false;
78 private final Map<Integer, Set<Integer>> mHandledKeys = new HashMap<>();
79
Keun-young Parka28d7b22016-02-29 16:54:29 -080080 private int mKeyEventCount = 0;
81
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -070082 private final Binder mCallback = new Binder() {
83 @Override
84 protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
85 if (code == CarInputHandlingService.INPUT_CALLBACK_BINDER_CODE) {
86 data.setDataPosition(0);
87 InputFilter[] handledKeys = (InputFilter[]) data.createTypedArray(
88 InputFilter.CREATOR);
89 if (handledKeys != null) {
90 setHandledKeys(handledKeys);
91 }
92 return true;
93 }
94 return false;
95 }
96 };
97
98 private final ServiceConnection mInputServiceConnection = new ServiceConnection() {
99 @Override
100 public void onServiceConnected(ComponentName name, IBinder binder) {
101 if (DBG) {
102 Log.d(CarLog.TAG_INPUT, "onServiceConnected, name: "
103 + name + ", binder: " + binder);
104 }
105 mCarInputListener = ICarInputListener.Stub.asInterface(binder);
106
107 try {
108 binder.linkToDeath(() -> CarServiceUtils.runOnMainSync(() -> {
109 Log.w(CarLog.TAG_INPUT, "Input service died. Trying to rebind...");
110 mCarInputListener = null;
111 // Try to rebind with input service.
112 mCarInputListenerBound = bindCarInputService();
113 }), 0);
114 } catch (RemoteException e) {
115 Log.e(CarLog.TAG_INPUT, e.getMessage(), e);
116 }
117 }
118
119 @Override
120 public void onServiceDisconnected(ComponentName name) {
121 Log.d(CarLog.TAG_INPUT, "onServiceDisconnected, name: " + name);
122 mCarInputListener = null;
123 // Try to rebind with input service.
124 mCarInputListenerBound = bindCarInputService();
125 }
126 };
127
Keun-young Parka28d7b22016-02-29 16:54:29 -0800128 public CarInputService(Context context) {
129 mContext = context;
Pavel Maltsev338cef82016-07-26 16:04:15 -0700130 mTelecomManager = context.getSystemService(TelecomManager.class);
131 mInputManager = context.getSystemService(InputManager.class);
Keun-young Parka28d7b22016-02-29 16:54:29 -0800132 }
133
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -0700134 private synchronized void setHandledKeys(InputFilter[] handledKeys) {
135 mHandledKeys.clear();
136 for (InputFilter handledKey : handledKeys) {
137 Set<Integer> displaySet = mHandledKeys.get(handledKey.mTargetDisplay);
138 if (displaySet == null) {
139 displaySet = new HashSet<Integer>();
140 mHandledKeys.put(handledKey.mTargetDisplay, displaySet);
141 }
142 displaySet.add(handledKey.mKeyCode);
143 }
144 }
145
Keun-young Parka28d7b22016-02-29 16:54:29 -0800146 /**
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -0800147 * Set listener for listening voice assistant key event. Setting to null stops listening.
148 * If listener is not set, default behavior will be done for short press.
149 * If listener is set, short key press will lead into calling the listener.
150 * @param listener
151 */
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700152 public void setVoiceAssistantKeyListener(KeyEventListener listener) {
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -0800153 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700154 mVoiceAssistantKeyListener = listener;
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -0800155 }
156 }
157
158 /**
Keun-young Parka28d7b22016-02-29 16:54:29 -0800159 * Set listener for listening long voice assistant key event. Setting to null stops listening.
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -0800160 * If listener is not set, default behavior will be done for long press.
161 * If listener is set, short long press will lead into calling the listener.
Keun-young Parka28d7b22016-02-29 16:54:29 -0800162 * @param listener
163 */
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700164 public void setLongVoiceAssistantKeyListener(KeyEventListener listener) {
Keun-young Parka28d7b22016-02-29 16:54:29 -0800165 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700166 mLongVoiceAssistantKeyListener = listener;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800167 }
168 }
169
170 public void setInstrumentClusterKeyListener(KeyEventListener listener) {
171 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700172 mInstrumentClusterKeyListener = listener;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800173 }
174 }
175
Keun-young Park07182c72016-03-18 18:01:29 -0700176 public void setVolumeKeyListener(KeyEventListener listener) {
177 synchronized (this) {
178 mVolumeKeyListener = listener;
179 }
180 }
181
Keun-young Parka28d7b22016-02-29 16:54:29 -0800182 @Override
183 public void init() {
184 InputHalService hal = VehicleHal.getInstance().getInputHal();
185 if (!hal.isKeyInputSupported()) {
186 Log.w(CarLog.TAG_INPUT, "Hal does not support key input.");
187 return;
188 }
Pavel Maltsev338cef82016-07-26 16:04:15 -0700189
190
Keun-young Parka28d7b22016-02-29 16:54:29 -0800191 hal.setInputListener(this);
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -0700192 mCarInputListenerBound = bindCarInputService();
Keun-young Parka28d7b22016-02-29 16:54:29 -0800193 }
194
195 @Override
196 public void release() {
197 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700198 mVoiceAssistantKeyListener = null;
199 mLongVoiceAssistantKeyListener = null;
200 mInstrumentClusterKeyListener = null;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800201 mKeyEventCount = 0;
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -0700202 if (mCarInputListenerBound) {
203 mContext.unbindService(mInputServiceConnection);
204 mCarInputListenerBound = false;
205 }
Keun-young Parka28d7b22016-02-29 16:54:29 -0800206 }
207 }
208
209 @Override
210 public void onKeyEvent(KeyEvent event, int targetDisplay) {
211 synchronized (this) {
212 mKeyEventCount++;
213 }
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -0700214 if (handleSystemEvent(event)) {
215 // System event handled, nothing more to do here.
216 return;
217 }
218 if (mCarInputListener != null && isCustomEventHandler(event, targetDisplay)) {
219 try {
220 mCarInputListener.onKeyEvent(event, targetDisplay);
221 } catch (RemoteException e) {
222 Log.e(CarLog.TAG_INPUT, "Error while calling car input service", e);
223 }
224 // Custom input service handled the event, nothing more to do here.
225 return;
226 }
227
228 switch (event.getKeyCode()) {
Keun-young Parka28d7b22016-02-29 16:54:29 -0800229 case KeyEvent.KEYCODE_VOICE_ASSIST:
230 handleVoiceAssistKey(event);
231 return;
Vitalii Tomkive2142e52016-04-29 11:35:26 -0700232 case KeyEvent.KEYCODE_CALL:
233 handleCallKey(event);
234 return;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800235 default:
236 break;
237 }
238 if (targetDisplay == InputHalService.DISPLAY_INSTRUMENT_CLUSTER) {
239 handleInstrumentClusterKey(event);
240 } else {
241 handleMainDisplayKey(event);
242 }
243 }
244
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -0700245 private synchronized boolean isCustomEventHandler(KeyEvent event, int targetDisplay) {
246 Set<Integer> displaySet = mHandledKeys.get(targetDisplay);
247 if (displaySet == null) {
248 return false;
249 }
250 return displaySet.contains(event.getKeyCode());
251 }
252
253 private boolean handleSystemEvent(KeyEvent event) {
254 switch (event.getKeyCode()) {
255 case KeyEvent.KEYCODE_VOLUME_UP:
256 case KeyEvent.KEYCODE_VOLUME_DOWN:
257 handleVolumeKey(event);
258 return true;
259 default:
260 return false;
261 }
262 }
263
Keun-young Parka28d7b22016-02-29 16:54:29 -0800264 private void handleVoiceAssistKey(KeyEvent event) {
265 int action = event.getAction();
266 if (action == KeyEvent.ACTION_DOWN) {
267 long now = SystemClock.elapsedRealtime();
268 synchronized (this) {
269 mLastVoiceKeyDownTime = now;
270 }
271 } else if (action == KeyEvent.ACTION_UP) {
272 // if no listener, do not handle long press
273 KeyEventListener listener = null;
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -0800274 KeyEventListener shortPressListener = null;
275 KeyEventListener longPressListener = null;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800276 long downTime;
277 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700278 shortPressListener = mVoiceAssistantKeyListener;
279 longPressListener = mLongVoiceAssistantKeyListener;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800280 downTime = mLastVoiceKeyDownTime;
281 }
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -0800282 if (shortPressListener == null && longPressListener == null) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700283 launchDefaultVoiceAssistantHandler();
Keun-young Parka28d7b22016-02-29 16:54:29 -0800284 } else {
285 long duration = SystemClock.elapsedRealtime() - downTime;
Vitalii Tomkive2142e52016-04-29 11:35:26 -0700286 listener = (duration > LONG_PRESS_TIME_MS
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -0800287 ? longPressListener : shortPressListener);
288 if (listener != null) {
Keun-young Parka28d7b22016-02-29 16:54:29 -0800289 listener.onKeyEvent(event);
290 } else {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700291 launchDefaultVoiceAssistantHandler();
Keun-young Parka28d7b22016-02-29 16:54:29 -0800292 }
293 }
294 }
295 }
296
Vitalii Tomkive2142e52016-04-29 11:35:26 -0700297 private void handleCallKey(KeyEvent event) {
298 int action = event.getAction();
299 if (action == KeyEvent.ACTION_DOWN) {
300 // Only handle if it's ringing when button down.
301 if (mTelecomManager != null && mTelecomManager.isRinging()) {
302 Log.i(CarLog.TAG_INPUT, "call key while rinning. Answer the call!");
303 mTelecomManager.acceptRingingCall();
304 return;
305 }
306
307 long now = SystemClock.elapsedRealtime();
308 synchronized (this) {
309 mLastCallKeyDownTime = now;
310 }
311 } else if (action == KeyEvent.ACTION_UP) {
312 long downTime;
313 synchronized (this) {
314 downTime = mLastCallKeyDownTime;
315 }
316 long duration = SystemClock.elapsedRealtime() - downTime;
317 if (duration > LONG_PRESS_TIME_MS) {
318 dialLastCallHandler();
319 } else {
320 launchDialerHandler();
321 }
322 }
323 }
324
325 private void launchDialerHandler() {
326 Log.i(CarLog.TAG_INPUT, "call key, launch dialer intent");
327 Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
328 mContext.startActivityAsUser(dialerIntent, null, UserHandle.CURRENT_OR_SELF);
329 }
330
331 private void dialLastCallHandler() {
332 Log.i(CarLog.TAG_INPUT, "call key, dialing last call");
333
334 String lastNumber = Calls.getLastOutgoingCall(mContext);
Vitalii Tomkive2142e52016-04-29 11:35:26 -0700335 if (lastNumber != null && !lastNumber.isEmpty()) {
336 Intent callLastNumberIntent = new Intent(Intent.ACTION_CALL)
337 .setData(Uri.fromParts("tel", lastNumber, null))
338 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
339 mContext.startActivityAsUser(callLastNumberIntent, null, UserHandle.CURRENT_OR_SELF);
340 }
341 }
342
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700343 private void launchDefaultVoiceAssistantHandler() {
Keun-young Parka28d7b22016-02-29 16:54:29 -0800344 Log.i(CarLog.TAG_INPUT, "voice key, launch default intent");
345 Intent voiceIntent =
346 new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
347 mContext.startActivityAsUser(voiceIntent, null, UserHandle.CURRENT_OR_SELF);
348 }
349
350 private void handleInstrumentClusterKey(KeyEvent event) {
351 KeyEventListener listener = null;
352 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700353 listener = mInstrumentClusterKeyListener;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800354 }
355 if (listener == null) {
356 return;
357 }
358 listener.onKeyEvent(event);
359 }
360
Keun-young Park07182c72016-03-18 18:01:29 -0700361 private void handleVolumeKey(KeyEvent event) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700362 KeyEventListener listener;
Keun-young Park07182c72016-03-18 18:01:29 -0700363 synchronized (this) {
364 listener = mVolumeKeyListener;
365 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700366 if (listener != null) {
367 listener.onKeyEvent(event);
Keun-young Park07182c72016-03-18 18:01:29 -0700368 }
Keun-young Park07182c72016-03-18 18:01:29 -0700369 }
370
Keun-young Parka28d7b22016-02-29 16:54:29 -0800371 private void handleMainDisplayKey(KeyEvent event) {
Pavel Maltsev338cef82016-07-26 16:04:15 -0700372 mInputManager.injectInputEvent(event, INJECT_INPUT_EVENT_MODE_ASYNC);
Keun-young Parka28d7b22016-02-29 16:54:29 -0800373 }
374
375 @Override
376 public void dump(PrintWriter writer) {
377 writer.println("*Input Service*");
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -0700378 writer.println("mCarInputListenerBound:" + mCarInputListenerBound);
379 writer.println("mCarInputListener:" + mCarInputListener);
Keun-young Parka28d7b22016-02-29 16:54:29 -0800380 writer.println("mLastVoiceKeyDownTime:" + mLastVoiceKeyDownTime +
381 ",mKeyEventCount:" + mKeyEventCount);
382 }
383
Vitalii Tomkiv2bd06922016-07-26 11:07:48 -0700384 private boolean bindCarInputService() {
385 String carInputService = mContext.getString(R.string.inputService);
386 if (TextUtils.isEmpty(carInputService)) {
387 Log.i(CarLog.TAG_INPUT, "Custom input service was not configured");
388 return false;
389 }
390
391 Log.d(CarLog.TAG_INPUT, "bindCarInputService, component: " + carInputService);
392
393 Intent intent = new Intent();
394 Bundle extras = new Bundle();
395 extras.putBinder(CarInputHandlingService.INPUT_CALLBACK_BINDER_KEY, mCallback);
396 intent.putExtras(extras);
397 intent.setComponent(ComponentName.unflattenFromString(carInputService));
398 // Explicitly start service as we do not use BIND_AUTO_CREATE flag to handle service crash.
399 mContext.startService(intent);
400 return mContext.bindService(intent, mInputServiceConnection, Context.BIND_IMPORTANT);
401 }
Keun-young Parka28d7b22016-02-29 16:54:29 -0800402}