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.hal;
17 
18 import static android.hardware.automotive.vehicle.V2_0.RotaryInputType.ROTARY_INPUT_TYPE_AUDIO_VOLUME;
19 import static android.hardware.automotive.vehicle.V2_0.RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION;
20 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.HW_KEY_INPUT;
21 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.HW_ROTARY_INPUT;
22 
23 import android.car.input.CarInputManager;
24 import android.car.input.RotaryEvent;
25 import android.hardware.automotive.vehicle.V2_0.VehicleDisplay;
26 import android.hardware.automotive.vehicle.V2_0.VehicleHwKeyInputAction;
27 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
28 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
29 import android.os.SystemClock;
30 import android.util.Log;
31 import android.util.SparseArray;
32 import android.view.InputDevice;
33 import android.view.KeyEvent;
34 
35 import com.android.car.CarLog;
36 import com.android.car.CarServiceUtils;
37 import com.android.internal.annotations.GuardedBy;
38 import com.android.internal.annotations.VisibleForTesting;
39 
40 import java.io.PrintWriter;
41 import java.util.Collection;
42 import java.util.List;
43 import java.util.concurrent.TimeUnit;
44 import java.util.function.LongSupplier;
45 
46 public class InputHalService extends HalServiceBase {
47 
48     public static final int DISPLAY_MAIN = VehicleDisplay.MAIN;
49     public static final int DISPLAY_INSTRUMENT_CLUSTER = VehicleDisplay.INSTRUMENT_CLUSTER;
50 
51     private static final int[] SUPPORTED_PROPERTIES = new int[] {
52             HW_KEY_INPUT,
53             HW_ROTARY_INPUT
54     };
55 
56     private final VehicleHal mHal;
57 
58     /**
59      * A function to retrieve the current system uptime in milliseconds - replaceable for testing.
60      */
61     private final LongSupplier mUptimeSupplier;
62 
63     public interface InputListener {
64         /** Called for key event */
onKeyEvent(KeyEvent event, int targetDisplay)65         void onKeyEvent(KeyEvent event, int targetDisplay);
66         /** Called for rotary event */
onRotaryEvent(RotaryEvent event, int targetDisplay)67         void onRotaryEvent(RotaryEvent event, int targetDisplay);
68     }
69 
70     /** The current press state of a key. */
71     private static class KeyState {
72         /** The timestamp (uptimeMillis) of the last ACTION_DOWN event for this key. */
73         public long mLastKeyDownTimestamp = -1;
74         /** The number of ACTION_DOWN events that have been sent for this keypress. */
75         public int mRepeatCount = 0;
76     }
77 
78     private static final boolean DBG = false;
79 
80     private final Object mLock = new Object();
81 
82     @GuardedBy("mLock")
83     private boolean mKeyInputSupported = false;
84 
85     @GuardedBy("mLock")
86     private boolean mRotaryInputSupported = false;
87 
88     @GuardedBy("mLock")
89     private InputListener mListener;
90 
91     @GuardedBy("mKeyStates")
92     private final SparseArray<KeyState> mKeyStates = new SparseArray<>();
93 
InputHalService(VehicleHal hal)94     public InputHalService(VehicleHal hal) {
95         this(hal, SystemClock::uptimeMillis);
96     }
97 
98     @VisibleForTesting
InputHalService(VehicleHal hal, LongSupplier uptimeSupplier)99     InputHalService(VehicleHal hal, LongSupplier uptimeSupplier) {
100         mHal = hal;
101         mUptimeSupplier = uptimeSupplier;
102     }
103 
setInputListener(InputListener listener)104     public void setInputListener(InputListener listener) {
105         boolean keyInputSupported;
106         boolean rotaryInputSupported;
107         synchronized (mLock) {
108             if (!mKeyInputSupported && !mRotaryInputSupported) {
109                 Log.w(CarLog.TAG_INPUT,
110                         "input listener set while rotary and key input not supported");
111                 return;
112             }
113             mListener = listener;
114             keyInputSupported = mKeyInputSupported;
115             rotaryInputSupported = mRotaryInputSupported;
116         }
117         if (keyInputSupported) {
118             mHal.subscribeProperty(this, HW_KEY_INPUT);
119         }
120         if (rotaryInputSupported) {
121             mHal.subscribeProperty(this, HW_ROTARY_INPUT);
122         }
123     }
124 
125     /** Returns whether {@code HW_KEY_INPUT} is supported. */
isKeyInputSupported()126     public boolean isKeyInputSupported() {
127         synchronized (mLock) {
128             return mKeyInputSupported;
129         }
130     }
131 
132     /** Returns whether {@code HW_ROTARY_INPUT} is supported. */
isRotaryInputSupported()133     public boolean isRotaryInputSupported() {
134         synchronized (mLock) {
135             return mRotaryInputSupported;
136         }
137     }
138 
139     @Override
init()140     public void init() {
141     }
142 
143     @Override
release()144     public void release() {
145         synchronized (mLock) {
146             mListener = null;
147             mKeyInputSupported = false;
148             mRotaryInputSupported = false;
149         }
150     }
151 
152     @Override
getAllSupportedProperties()153     public int[] getAllSupportedProperties() {
154         return SUPPORTED_PROPERTIES;
155     }
156 
157     @Override
takeProperties(Collection<VehiclePropConfig> properties)158     public void takeProperties(Collection<VehiclePropConfig> properties) {
159         for (VehiclePropConfig property : properties) {
160             switch (property.prop) {
161                 case HW_KEY_INPUT:
162                     synchronized (mLock) {
163                         mKeyInputSupported = true;
164                     }
165                     break;
166                 case HW_ROTARY_INPUT:
167                     synchronized (mLock) {
168                         mRotaryInputSupported = true;
169                     }
170                     break;
171             }
172         }
173     }
174 
175     @Override
onHalEvents(List<VehiclePropValue> values)176     public void onHalEvents(List<VehiclePropValue> values) {
177         InputListener listener;
178         synchronized (mLock) {
179             listener = mListener;
180         }
181         if (listener == null) {
182             Log.w(CarLog.TAG_INPUT, "Input event while listener is null");
183             return;
184         }
185         for (VehiclePropValue value : values) {
186             switch (value.prop) {
187                 case HW_KEY_INPUT:
188                     dispatchKeyInput(listener, value);
189                     break;
190                 case HW_ROTARY_INPUT:
191                     dispatchRotaryInput(listener, value);
192                     break;
193                 default:
194                     Log.e(CarLog.TAG_INPUT,
195                             "Wrong event dispatched, prop:0x" + Integer.toHexString(value.prop));
196                     break;
197             }
198         }
199     }
200 
dispatchKeyInput(InputListener listener, VehiclePropValue value)201     private void dispatchKeyInput(InputListener listener, VehiclePropValue value) {
202         int action = (value.value.int32Values.get(0) == VehicleHwKeyInputAction.ACTION_DOWN)
203                 ? KeyEvent.ACTION_DOWN
204                 : KeyEvent.ACTION_UP;
205         int code = value.value.int32Values.get(1);
206         int display = value.value.int32Values.get(2);
207         int indentsCount = value.value.int32Values.size() < 4 ? 1 : value.value.int32Values.get(3);
208         if (DBG) {
209             Log.i(CarLog.TAG_INPUT, new StringBuilder()
210                     .append("hal event code:").append(code)
211                     .append(", action:").append(action)
212                     .append(", display: ").append(display)
213                     .append(", number of indents: ").append(indentsCount)
214                     .toString());
215         }
216         while (indentsCount > 0) {
217             indentsCount--;
218             dispatchKeyEvent(listener, action, code, display);
219         }
220     }
221 
222     private void dispatchRotaryInput(InputListener listener, VehiclePropValue value) {
223         int timeValuesIndex = 3;  // remaining values are time deltas in nanoseconds
224         if (value.value.int32Values.size() < timeValuesIndex) {
225             Log.e(CarLog.TAG_INPUT, "Wrong int32 array size for RotaryInput from vhal:"
226                     + value.value.int32Values.size());
227             return;
228         }
229         int rotaryInputType = value.value.int32Values.get(0);
230         int detentCount = value.value.int32Values.get(1);
231         int display = value.value.int32Values.get(2);
232         long timestamp = value.timestamp;  // for first detent, uptime nanoseconds
233         if (DBG) {
234             Log.i(CarLog.TAG_INPUT, new StringBuilder()
235                     .append("hal rotary input type: ").append(rotaryInputType)
236                     .append(", number of detents:").append(detentCount)
237                     .append(", display: ").append(display)
238                     .toString());
239         }
240         boolean clockwise = detentCount > 0;
241         detentCount = Math.abs(detentCount);
242         if (detentCount == 0) { // at least there should be one event
243             Log.e(CarLog.TAG_INPUT, "Zero detentCount from vhal, ignore the event");
244             return;
245         }
246         if (display != DISPLAY_MAIN && display != DISPLAY_INSTRUMENT_CLUSTER) {
247             Log.e(CarLog.TAG_INPUT, "Wrong display type for RotaryInput from vhal:"
248                     + display);
249             return;
250         }
251         if (value.value.int32Values.size() != (timeValuesIndex + detentCount - 1)) {
252             Log.e(CarLog.TAG_INPUT, "Wrong int32 array size for RotaryInput from vhal:"
253                     + value.value.int32Values.size());
254             return;
255         }
256         int carInputManagerType;
257         switch (rotaryInputType) {
258             case ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION:
259                 carInputManagerType = CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION;
260                 break;
261             case ROTARY_INPUT_TYPE_AUDIO_VOLUME:
262                 carInputManagerType = CarInputManager.INPUT_TYPE_ROTARY_VOLUME;
263                 break;
264             default:
265                 Log.e(CarLog.TAG_INPUT, "Unknown rotary input type: " + rotaryInputType);
266                 return;
267         }
268 
269         long[] timestamps = new long[detentCount];
270         // vhal returns elapsed time while rotary event is using uptime to be in line with KeyEvent.
271         long uptimeToElapsedTimeDelta = CarServiceUtils.getUptimeToElapsedTimeDeltaInMillis();
272         long startUptime = TimeUnit.NANOSECONDS.toMillis(timestamp) - uptimeToElapsedTimeDelta;
273         timestamps[0] = startUptime;
274         for (int i = 0; i < timestamps.length - 1; i++) {
275             timestamps[i + 1] = timestamps[i] + TimeUnit.NANOSECONDS.toMillis(
276                     value.value.int32Values.get(timeValuesIndex + i));
277         }
278         RotaryEvent event = new RotaryEvent(carInputManagerType, clockwise, timestamps);
279         listener.onRotaryEvent(event, display);
280     }
281 
282     /**
283      * Dispatches a KeyEvent using {@link #mUptimeSupplier} for the event time.
284      *
285      * @param listener listener to dispatch the event to
286      * @param action action for the KeyEvent
287      * @param code keycode for the KeyEvent
288      * @param display target display the event is associated with
289      */
290     private void dispatchKeyEvent(InputListener listener, int action, int code, int display) {
291         dispatchKeyEvent(listener, action, code, display, mUptimeSupplier.getAsLong());
292     }
293 
294     /**
295      * Dispatches a KeyEvent.
296      *
297      * @param listener listener to dispatch the event to
298      * @param action action for the KeyEvent
299      * @param code keycode for the KeyEvent
300      * @param display target display the event is associated with
301      * @param eventTime uptime in milliseconds when the event occurred
302      */
303     private void dispatchKeyEvent(InputListener listener, int action, int code, int display,
304             long eventTime) {
305         long downTime;
306         int repeat;
307 
308         synchronized (mKeyStates) {
309             KeyState state = mKeyStates.get(code);
310             if (state == null) {
311                 state = new KeyState();
312                 mKeyStates.put(code, state);
313             }
314 
315             if (action == KeyEvent.ACTION_DOWN) {
316                 downTime = eventTime;
317                 repeat = state.mRepeatCount++;
318                 state.mLastKeyDownTimestamp = eventTime;
319             } else {
320                 // Handle key up events without any matching down event by setting the down time to
321                 // the event time. This shouldn't happen in practice - keys should be pressed
322                 // before they can be released! - but this protects us against HAL weirdness.
323                 downTime =
324                         (state.mLastKeyDownTimestamp == -1)
325                                 ? eventTime
326                                 : state.mLastKeyDownTimestamp;
327                 repeat = 0;
328                 state.mRepeatCount = 0;
329             }
330         }
331 
332         KeyEvent event = new KeyEvent(
333                 downTime,
334                 eventTime,
335                 action,
336                 code,
337                 repeat,
338                 0 /* meta state */,
339                 0 /* deviceId */,
340                 0 /* scancode */,
341                 0 /* flags */,
342                 InputDevice.SOURCE_CLASS_BUTTON);
343 
344         listener.onKeyEvent(event, display);
345     }
346 
347     @Override
348     public void dump(PrintWriter writer) {
349         writer.println("*Input HAL*");
350         writer.println("mKeyInputSupported:" + mKeyInputSupported);
351         writer.println("mRotaryInputSupported:" + mRotaryInputSupported);
352     }
353 }
354