• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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