blob: 9356729e409b4c885071eb3b16f15755a530b730 [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
18import android.content.Context;
19import android.content.Intent;
Vitalii Tomkive2142e52016-04-29 11:35:26 -070020import android.net.Uri;
Keun-young Parka28d7b22016-02-29 16:54:29 -080021import android.os.ParcelFileDescriptor;
22import android.os.SystemClock;
23import android.os.UserHandle;
Vitalii Tomkive2142e52016-04-29 11:35:26 -070024import android.provider.CallLog.Calls;
Keun-young Parka28d7b22016-02-29 16:54:29 -080025import android.speech.RecognizerIntent;
Vitalii Tomkive2142e52016-04-29 11:35:26 -070026import android.telecom.TelecomManager;
Keun-young Parka28d7b22016-02-29 16:54:29 -080027import android.util.Log;
28import android.view.KeyEvent;
29
30import com.android.car.hal.InputHalService;
31import com.android.car.hal.VehicleHal;
32
33import java.io.File;
34import java.io.FileNotFoundException;
35import java.io.IOException;
36import java.io.PrintWriter;
37
38public class CarInputService implements CarServiceBase, InputHalService.InputListener {
39
40 public interface KeyEventListener {
Yao Chenc4d442f2016-04-08 11:33:47 -070041 boolean onKeyEvent(KeyEvent event);
Keun-young Parka28d7b22016-02-29 16:54:29 -080042 }
43
Vitalii Tomkive2142e52016-04-29 11:35:26 -070044 private static final long LONG_PRESS_TIME_MS = 1000;
Keun-young Parka28d7b22016-02-29 16:54:29 -080045
46 private final Context mContext;
Vitalii Tomkive2142e52016-04-29 11:35:26 -070047 private final TelecomManager mTelecomManager;
Keun-young Parka28d7b22016-02-29 16:54:29 -080048
Pavel Maltseve11ad3a2016-03-25 15:40:24 -070049 private KeyEventListener mVoiceAssistantKeyListener;
50 private KeyEventListener mLongVoiceAssistantKeyListener;
Keun-young Parka28d7b22016-02-29 16:54:29 -080051 private long mLastVoiceKeyDownTime = 0;
52
Vitalii Tomkive2142e52016-04-29 11:35:26 -070053 private long mLastCallKeyDownTime = 0;
54
Pavel Maltseve11ad3a2016-03-25 15:40:24 -070055 private KeyEventListener mInstrumentClusterKeyListener;
Keun-young Parka28d7b22016-02-29 16:54:29 -080056
Keun-young Park07182c72016-03-18 18:01:29 -070057 private KeyEventListener mVolumeKeyListener;
58
Keun-young Parka28d7b22016-02-29 16:54:29 -080059 private ParcelFileDescriptor mInjectionDeviceFd;
60
61 private int mKeyEventCount = 0;
62
63 public CarInputService(Context context) {
64 mContext = context;
Vitalii Tomkive2142e52016-04-29 11:35:26 -070065 mTelecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
Keun-young Parka28d7b22016-02-29 16:54:29 -080066 }
67
68 /**
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -080069 * Set listener for listening voice assistant key event. Setting to null stops listening.
70 * If listener is not set, default behavior will be done for short press.
71 * If listener is set, short key press will lead into calling the listener.
72 * @param listener
73 */
Pavel Maltseve11ad3a2016-03-25 15:40:24 -070074 public void setVoiceAssistantKeyListener(KeyEventListener listener) {
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -080075 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -070076 mVoiceAssistantKeyListener = listener;
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -080077 }
78 }
79
80 /**
Keun-young Parka28d7b22016-02-29 16:54:29 -080081 * Set listener for listening long voice assistant key event. Setting to null stops listening.
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -080082 * If listener is not set, default behavior will be done for long press.
83 * If listener is set, short long press will lead into calling the listener.
Keun-young Parka28d7b22016-02-29 16:54:29 -080084 * @param listener
85 */
Pavel Maltseve11ad3a2016-03-25 15:40:24 -070086 public void setLongVoiceAssistantKeyListener(KeyEventListener listener) {
Keun-young Parka28d7b22016-02-29 16:54:29 -080087 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -070088 mLongVoiceAssistantKeyListener = listener;
Keun-young Parka28d7b22016-02-29 16:54:29 -080089 }
90 }
91
92 public void setInstrumentClusterKeyListener(KeyEventListener listener) {
93 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -070094 mInstrumentClusterKeyListener = listener;
Keun-young Parka28d7b22016-02-29 16:54:29 -080095 }
96 }
97
Keun-young Park07182c72016-03-18 18:01:29 -070098 public void setVolumeKeyListener(KeyEventListener listener) {
99 synchronized (this) {
100 mVolumeKeyListener = listener;
101 }
102 }
103
Keun-young Parka28d7b22016-02-29 16:54:29 -0800104 @Override
105 public void init() {
106 InputHalService hal = VehicleHal.getInstance().getInputHal();
107 if (!hal.isKeyInputSupported()) {
108 Log.w(CarLog.TAG_INPUT, "Hal does not support key input.");
109 return;
110 }
111 String injectionDevice = mContext.getResources().getString(
112 R.string.inputInjectionDeviceNode);
113 ParcelFileDescriptor file = null;
114 try {
115 file = ParcelFileDescriptor.open(new File(injectionDevice),
116 ParcelFileDescriptor.MODE_READ_WRITE);
117 } catch (FileNotFoundException e) {
Pavel Maltsevf61ae282016-03-25 17:33:50 +0000118 Log.w(CarLog.TAG_INPUT, "cannot open device for input injection:" + injectionDevice);
Keun-young Parka28d7b22016-02-29 16:54:29 -0800119 return;
120 }
121 synchronized (this) {
122 mInjectionDeviceFd = file;
123 }
124 hal.setInputListener(this);
125 }
126
127 @Override
128 public void release() {
129 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700130 mVoiceAssistantKeyListener = null;
131 mLongVoiceAssistantKeyListener = null;
132 mInstrumentClusterKeyListener = null;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800133 if (mInjectionDeviceFd != null) {
134 try {
135 mInjectionDeviceFd.close();
136 } catch (IOException e) {
137 }
138 }
139 mInjectionDeviceFd = null;
140 mKeyEventCount = 0;
141 }
142 }
143
144 @Override
145 public void onKeyEvent(KeyEvent event, int targetDisplay) {
146 synchronized (this) {
147 mKeyEventCount++;
148 }
149 int keyCode = event.getKeyCode();
150 switch (keyCode) {
151 case KeyEvent.KEYCODE_VOICE_ASSIST:
152 handleVoiceAssistKey(event);
153 return;
Vitalii Tomkive2142e52016-04-29 11:35:26 -0700154 case KeyEvent.KEYCODE_CALL:
155 handleCallKey(event);
156 return;
Keun-young Park07182c72016-03-18 18:01:29 -0700157 case KeyEvent.KEYCODE_VOLUME_UP:
158 case KeyEvent.KEYCODE_VOLUME_DOWN:
159 handleVolumeKey(event);
160 return;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800161 default:
162 break;
163 }
164 if (targetDisplay == InputHalService.DISPLAY_INSTRUMENT_CLUSTER) {
165 handleInstrumentClusterKey(event);
166 } else {
167 handleMainDisplayKey(event);
168 }
169 }
170
171 private void handleVoiceAssistKey(KeyEvent event) {
172 int action = event.getAction();
173 if (action == KeyEvent.ACTION_DOWN) {
174 long now = SystemClock.elapsedRealtime();
175 synchronized (this) {
176 mLastVoiceKeyDownTime = now;
177 }
178 } else if (action == KeyEvent.ACTION_UP) {
179 // if no listener, do not handle long press
180 KeyEventListener listener = null;
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -0800181 KeyEventListener shortPressListener = null;
182 KeyEventListener longPressListener = null;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800183 long downTime;
184 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700185 shortPressListener = mVoiceAssistantKeyListener;
186 longPressListener = mLongVoiceAssistantKeyListener;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800187 downTime = mLastVoiceKeyDownTime;
188 }
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -0800189 if (shortPressListener == null && longPressListener == null) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700190 launchDefaultVoiceAssistantHandler();
Keun-young Parka28d7b22016-02-29 16:54:29 -0800191 } else {
192 long duration = SystemClock.elapsedRealtime() - downTime;
Vitalii Tomkive2142e52016-04-29 11:35:26 -0700193 listener = (duration > LONG_PRESS_TIME_MS
Vitalii Tomkiv61e2c232016-03-08 10:48:51 -0800194 ? longPressListener : shortPressListener);
195 if (listener != null) {
Keun-young Parka28d7b22016-02-29 16:54:29 -0800196 listener.onKeyEvent(event);
197 } else {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700198 launchDefaultVoiceAssistantHandler();
Keun-young Parka28d7b22016-02-29 16:54:29 -0800199 }
200 }
201 }
202 }
203
Vitalii Tomkive2142e52016-04-29 11:35:26 -0700204 private void handleCallKey(KeyEvent event) {
205 int action = event.getAction();
206 if (action == KeyEvent.ACTION_DOWN) {
207 // Only handle if it's ringing when button down.
208 if (mTelecomManager != null && mTelecomManager.isRinging()) {
209 Log.i(CarLog.TAG_INPUT, "call key while rinning. Answer the call!");
210 mTelecomManager.acceptRingingCall();
211 return;
212 }
213
214 long now = SystemClock.elapsedRealtime();
215 synchronized (this) {
216 mLastCallKeyDownTime = now;
217 }
218 } else if (action == KeyEvent.ACTION_UP) {
219 long downTime;
220 synchronized (this) {
221 downTime = mLastCallKeyDownTime;
222 }
223 long duration = SystemClock.elapsedRealtime() - downTime;
224 if (duration > LONG_PRESS_TIME_MS) {
225 dialLastCallHandler();
226 } else {
227 launchDialerHandler();
228 }
229 }
230 }
231
232 private void launchDialerHandler() {
233 Log.i(CarLog.TAG_INPUT, "call key, launch dialer intent");
234 Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
235 mContext.startActivityAsUser(dialerIntent, null, UserHandle.CURRENT_OR_SELF);
236 }
237
238 private void dialLastCallHandler() {
239 Log.i(CarLog.TAG_INPUT, "call key, dialing last call");
240
241 String lastNumber = Calls.getLastOutgoingCall(mContext);
242 Log.d(CarLog.TAG_INPUT, "Last number dialed: " + lastNumber);
243 if (lastNumber != null && !lastNumber.isEmpty()) {
244 Intent callLastNumberIntent = new Intent(Intent.ACTION_CALL)
245 .setData(Uri.fromParts("tel", lastNumber, null))
246 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
247 mContext.startActivityAsUser(callLastNumberIntent, null, UserHandle.CURRENT_OR_SELF);
248 }
249 }
250
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700251 private void launchDefaultVoiceAssistantHandler() {
Keun-young Parka28d7b22016-02-29 16:54:29 -0800252 Log.i(CarLog.TAG_INPUT, "voice key, launch default intent");
253 Intent voiceIntent =
254 new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
255 mContext.startActivityAsUser(voiceIntent, null, UserHandle.CURRENT_OR_SELF);
256 }
257
258 private void handleInstrumentClusterKey(KeyEvent event) {
259 KeyEventListener listener = null;
260 synchronized (this) {
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700261 listener = mInstrumentClusterKeyListener;
Keun-young Parka28d7b22016-02-29 16:54:29 -0800262 }
263 if (listener == null) {
264 return;
265 }
266 listener.onKeyEvent(event);
267 }
268
Keun-young Park07182c72016-03-18 18:01:29 -0700269 private void handleVolumeKey(KeyEvent event) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700270 KeyEventListener listener;
Keun-young Park07182c72016-03-18 18:01:29 -0700271 synchronized (this) {
272 listener = mVolumeKeyListener;
273 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700274 if (listener != null) {
275 listener.onKeyEvent(event);
Keun-young Park07182c72016-03-18 18:01:29 -0700276 }
Keun-young Park07182c72016-03-18 18:01:29 -0700277 }
278
Keun-young Parka28d7b22016-02-29 16:54:29 -0800279 private void handleMainDisplayKey(KeyEvent event) {
280 int fd;
281 synchronized (this) {
282 fd = mInjectionDeviceFd.getFd();
283 }
284 int action = event.getAction();
285 boolean isDown = (action == KeyEvent.ACTION_DOWN);
286 int keyCode = event.getKeyCode();
287 int r = nativeInjectKeyEvent(fd, keyCode, isDown);
288 if (r != 0) {
289 Log.e(CarLog.TAG_INPUT, "cannot inject key event, failed with:" + r);
290 }
291 }
292
293 @Override
294 public void dump(PrintWriter writer) {
295 writer.println("*Input Service*");
296 writer.println("mInjectionDeviceFd:" + mInjectionDeviceFd);
297 writer.println("mLastVoiceKeyDownTime:" + mLastVoiceKeyDownTime +
298 ",mKeyEventCount:" + mKeyEventCount);
299 }
300
301 private native int nativeInjectKeyEvent(int fd, int keyCode, boolean isDown);
302}