• 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 android.content.Context;
19 import android.content.Intent;
20 import android.os.ParcelFileDescriptor;
21 import android.os.SystemClock;
22 import android.os.UserHandle;
23 import android.speech.RecognizerIntent;
24 import android.util.Log;
25 import android.view.KeyEvent;
26 
27 import com.android.car.hal.InputHalService;
28 import com.android.car.hal.VehicleHal;
29 
30 import java.io.File;
31 import java.io.FileNotFoundException;
32 import java.io.IOException;
33 import java.io.PrintWriter;
34 
35 public class CarInputService implements CarServiceBase, InputHalService.InputListener {
36 
37     public interface KeyEventListener {
onKeyEvent(KeyEvent event)38         void onKeyEvent(KeyEvent event);
39     }
40 
41     private static final long VOICE_LONG_PRESS_TIME_MS = 1000;
42 
43     private final Context mContext;
44 
45     private KeyEventListener mVoiceAssitantKeyListener;
46     private KeyEventListener mLongVoiceAssitantKeyListener;
47     private long mLastVoiceKeyDownTime = 0;
48 
49     private KeyEventListener mInstumentClusterKeyListener;
50 
51     private ParcelFileDescriptor mInjectionDeviceFd;
52 
53     private int mKeyEventCount = 0;
54 
CarInputService(Context context)55     public CarInputService(Context context) {
56         mContext = context;
57     }
58 
59     /**
60      * Set listener for listening voice assistant key event. Setting to null stops listening.
61      * If listener is not set, default behavior will be done for short press.
62      * If listener is set, short key press will lead into calling the listener.
63      * @param listener
64      */
setVoiceAssitantKeyListener(KeyEventListener listener)65     public void setVoiceAssitantKeyListener(KeyEventListener listener) {
66         synchronized (this) {
67             mVoiceAssitantKeyListener = listener;
68         }
69     }
70 
71     /**
72      * Set listener for listening long voice assistant key event. Setting to null stops listening.
73      * If listener is not set, default behavior will be done for long press.
74      * If listener is set, short long press will lead into calling the listener.
75      * @param listener
76      */
setLongVoiceAssitantKeyListener(KeyEventListener listener)77     public void setLongVoiceAssitantKeyListener(KeyEventListener listener) {
78         synchronized (this) {
79             mLongVoiceAssitantKeyListener = listener;
80         }
81     }
82 
setInstrumentClusterKeyListener(KeyEventListener listener)83     public void setInstrumentClusterKeyListener(KeyEventListener listener) {
84         synchronized (this) {
85             mInstumentClusterKeyListener = listener;
86         }
87     }
88 
89     @Override
init()90     public void init() {
91         InputHalService hal = VehicleHal.getInstance().getInputHal();
92         if (!hal.isKeyInputSupported()) {
93             Log.w(CarLog.TAG_INPUT, "Hal does not support key input.");
94             return;
95         }
96         String injectionDevice = mContext.getResources().getString(
97                 R.string.inputInjectionDeviceNode);
98         ParcelFileDescriptor file = null;
99         try {
100             file = ParcelFileDescriptor.open(new File(injectionDevice),
101                     ParcelFileDescriptor.MODE_READ_WRITE);
102         } catch (FileNotFoundException e) {
103             Log.w(CarLog.TAG_INPUT, "cannot open device for input injection:" + injectionDevice);
104             return;
105         }
106         synchronized (this) {
107             mInjectionDeviceFd = file;
108         }
109         hal.setInputListener(this);
110     }
111 
112     @Override
release()113     public void release() {
114         synchronized (this) {
115             mVoiceAssitantKeyListener = null;
116             mLongVoiceAssitantKeyListener = null;
117             mInstumentClusterKeyListener = null;
118             if (mInjectionDeviceFd != null) {
119                 try {
120                     mInjectionDeviceFd.close();
121                 } catch (IOException e) {
122                 }
123             }
124             mInjectionDeviceFd = null;
125             mKeyEventCount = 0;
126         }
127     }
128 
129     @Override
onKeyEvent(KeyEvent event, int targetDisplay)130     public void onKeyEvent(KeyEvent event, int targetDisplay) {
131         synchronized (this) {
132             mKeyEventCount++;
133         }
134         int keyCode = event.getKeyCode();
135         switch (keyCode) {
136             case KeyEvent.KEYCODE_VOICE_ASSIST:
137                 handleVoiceAssistKey(event);
138                 return;
139             default:
140                 break;
141         }
142         if (targetDisplay == InputHalService.DISPLAY_INSTRUMENT_CLUSTER) {
143             handleInstrumentClusterKey(event);
144         } else {
145             handleMainDisplayKey(event);
146         }
147     }
148 
handleVoiceAssistKey(KeyEvent event)149     private void handleVoiceAssistKey(KeyEvent event) {
150         int action = event.getAction();
151         if (action == KeyEvent.ACTION_DOWN) {
152             long now = SystemClock.elapsedRealtime();
153             synchronized (this) {
154                 mLastVoiceKeyDownTime = now;
155             }
156         } else if (action == KeyEvent.ACTION_UP) {
157             // if no listener, do not handle long press
158             KeyEventListener listener = null;
159             KeyEventListener shortPressListener = null;
160             KeyEventListener longPressListener = null;
161             long downTime;
162             synchronized (this) {
163                 shortPressListener = mVoiceAssitantKeyListener;
164                 longPressListener = mLongVoiceAssitantKeyListener;
165                 downTime = mLastVoiceKeyDownTime;
166             }
167             if (shortPressListener == null && longPressListener == null) {
168                 launchDefaultVoiceAssitantHandler();
169             } else {
170                 long duration = SystemClock.elapsedRealtime() - downTime;
171                 listener = (duration > VOICE_LONG_PRESS_TIME_MS
172                         ? longPressListener : shortPressListener);
173                 if (listener != null) {
174                     listener.onKeyEvent(event);
175                 } else {
176                     launchDefaultVoiceAssitantHandler();
177                 }
178             }
179         }
180     }
181 
launchDefaultVoiceAssitantHandler()182     private void launchDefaultVoiceAssitantHandler() {
183         Log.i(CarLog.TAG_INPUT, "voice key, launch default intent");
184         Intent voiceIntent =
185                 new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
186         mContext.startActivityAsUser(voiceIntent, null, UserHandle.CURRENT_OR_SELF);
187     }
188 
handleInstrumentClusterKey(KeyEvent event)189     private void handleInstrumentClusterKey(KeyEvent event) {
190         KeyEventListener listener = null;
191         synchronized (this) {
192             listener = mInstumentClusterKeyListener;
193         }
194         if (listener == null) {
195             return;
196         }
197         listener.onKeyEvent(event);
198     }
199 
handleMainDisplayKey(KeyEvent event)200     private void handleMainDisplayKey(KeyEvent event) {
201         int fd;
202         synchronized (this) {
203             fd = mInjectionDeviceFd.getFd();
204         }
205         int action = event.getAction();
206         boolean isDown = (action == KeyEvent.ACTION_DOWN);
207         int keyCode = event.getKeyCode();
208         int r = nativeInjectKeyEvent(fd, keyCode, isDown);
209         if (r != 0) {
210             Log.e(CarLog.TAG_INPUT, "cannot inject key event, failed with:" + r);
211         }
212     }
213 
214     @Override
dump(PrintWriter writer)215     public void dump(PrintWriter writer) {
216         writer.println("*Input Service*");
217         writer.println("mInjectionDeviceFd:" + mInjectionDeviceFd);
218         writer.println("mLastVoiceKeyDownTime:" + mLastVoiceKeyDownTime +
219                 ",mKeyEventCount:" + mKeyEventCount);
220     }
221 
nativeInjectKeyEvent(int fd, int keyCode, boolean isDown)222     private native int nativeInjectKeyEvent(int fd, int keyCode, boolean isDown);
223 }
224