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