• 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.hal;
17 
18 import static android.car.CarOccupantZoneManager.DisplayTypeEnum;
19 import static android.hardware.automotive.vehicle.RotaryInputType.ROTARY_INPUT_TYPE_AUDIO_VOLUME;
20 import static android.hardware.automotive.vehicle.RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION;
21 import static android.hardware.automotive.vehicle.VehicleProperty.HW_CUSTOM_INPUT;
22 import static android.hardware.automotive.vehicle.VehicleProperty.HW_KEY_INPUT;
23 import static android.hardware.automotive.vehicle.VehicleProperty.HW_ROTARY_INPUT;
24 
25 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
26 
27 import android.car.CarOccupantZoneManager;
28 import android.car.builtin.util.Slogf;
29 import android.car.input.CarInputManager;
30 import android.car.input.CustomInputEvent;
31 import android.car.input.RotaryEvent;
32 import android.hardware.automotive.vehicle.VehicleDisplay;
33 import android.hardware.automotive.vehicle.VehicleHwKeyInputAction;
34 import android.os.SystemClock;
35 import android.util.SparseArray;
36 import android.view.InputDevice;
37 import android.view.KeyEvent;
38 
39 import com.android.car.CarLog;
40 import com.android.car.CarServiceUtils;
41 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
42 import com.android.internal.annotations.GuardedBy;
43 import com.android.internal.annotations.VisibleForTesting;
44 
45 import java.io.PrintWriter;
46 import java.util.Collection;
47 import java.util.List;
48 import java.util.concurrent.TimeUnit;
49 import java.util.function.LongSupplier;
50 
51 /**
52  * Translates HAL input events to higher-level semantic information.
53  */
54 public class InputHalService extends HalServiceBase {
55 
56     private static final String TAG = CarLog.TAG_INPUT;
57 
58     private static final int[] SUPPORTED_PROPERTIES = new int[]{
59             HW_KEY_INPUT,
60             HW_ROTARY_INPUT,
61             HW_CUSTOM_INPUT
62     };
63 
64     private final VehicleHal mHal;
65 
66     /**
67      * A function to retrieve the current system uptime in milliseconds - replaceable for testing.
68      */
69     private final LongSupplier mUptimeSupplier;
70 
71     /**
72      * Interface used to act upon HAL incoming key events.
73      */
74     public interface InputListener {
75         /** Called for key event */
onKeyEvent(KeyEvent event, int targetDisplay)76         void onKeyEvent(KeyEvent event, int targetDisplay);
77 
78         /** Called for rotary event */
onRotaryEvent(RotaryEvent event, int targetDisplay)79         void onRotaryEvent(RotaryEvent event, int targetDisplay);
80 
81         /** Called for OEM custom input event */
onCustomInputEvent(CustomInputEvent event)82         void onCustomInputEvent(CustomInputEvent event);
83     }
84 
85     /** The current press state of a key. */
86     private static class KeyState {
87         /** The timestamp (uptimeMillis) of the last ACTION_DOWN event for this key. */
88         public long mLastKeyDownTimestamp = -1;
89         /** The number of ACTION_DOWN events that have been sent for this keypress. */
90         public int mRepeatCount = 0;
91     }
92 
93     private final Object mLock = new Object();
94 
95     @GuardedBy("mLock")
96     private boolean mKeyInputSupported;
97 
98     @GuardedBy("mLock")
99     private boolean mRotaryInputSupported;
100 
101     @GuardedBy("mLock")
102     private boolean mCustomInputSupported;
103 
104     @GuardedBy("mLock")
105     private InputListener mListener;
106 
107     @GuardedBy("mKeyStates")
108     private final SparseArray<KeyState> mKeyStates = new SparseArray<>();
109 
InputHalService(VehicleHal hal)110     public InputHalService(VehicleHal hal) {
111         this(hal, SystemClock::uptimeMillis);
112     }
113 
114     @VisibleForTesting
InputHalService(VehicleHal hal, LongSupplier uptimeSupplier)115     InputHalService(VehicleHal hal, LongSupplier uptimeSupplier) {
116         mHal = hal;
117         mUptimeSupplier = uptimeSupplier;
118     }
119 
120     /**
121      * Sets the input event listener.
122      */
setInputListener(InputListener listener)123     public void setInputListener(InputListener listener) {
124         boolean keyInputSupported;
125         boolean rotaryInputSupported;
126         boolean customInputSupported;
127         synchronized (mLock) {
128             if (!mKeyInputSupported && !mRotaryInputSupported && !mCustomInputSupported) {
129                 Slogf.w(TAG, "input listener set while rotary and key input not supported");
130                 return;
131             }
132             mListener = listener;
133             keyInputSupported = mKeyInputSupported;
134             rotaryInputSupported = mRotaryInputSupported;
135             customInputSupported = mCustomInputSupported;
136         }
137         if (keyInputSupported) {
138             mHal.subscribeProperty(this, HW_KEY_INPUT);
139         }
140         if (rotaryInputSupported) {
141             mHal.subscribeProperty(this, HW_ROTARY_INPUT);
142         }
143         if (customInputSupported) {
144             mHal.subscribeProperty(this, HW_CUSTOM_INPUT);
145         }
146     }
147 
148     /** Returns whether {@code HW_KEY_INPUT} is supported. */
isKeyInputSupported()149     public boolean isKeyInputSupported() {
150         synchronized (mLock) {
151             return mKeyInputSupported;
152         }
153     }
154 
155     /** Returns whether {@code HW_ROTARY_INPUT} is supported. */
isRotaryInputSupported()156     public boolean isRotaryInputSupported() {
157         synchronized (mLock) {
158             return mRotaryInputSupported;
159         }
160     }
161 
162     /** Returns whether {@code HW_CUSTOM_INPUT} is supported. */
isCustomInputSupported()163     public boolean isCustomInputSupported() {
164         synchronized (mLock) {
165             return mCustomInputSupported;
166         }
167     }
168 
169     @Override
init()170     public void init() {
171     }
172 
173     @Override
release()174     public void release() {
175         synchronized (mLock) {
176             mListener = null;
177             mKeyInputSupported = false;
178             mRotaryInputSupported = false;
179             mCustomInputSupported = false;
180         }
181     }
182 
183     @Override
getAllSupportedProperties()184     public int[] getAllSupportedProperties() {
185         return SUPPORTED_PROPERTIES;
186     }
187 
188     @Override
takeProperties(Collection<HalPropConfig> properties)189     public void takeProperties(Collection<HalPropConfig> properties) {
190         for (HalPropConfig property : properties) {
191             switch (property.getPropId()) {
192                 case HW_KEY_INPUT:
193                     synchronized (mLock) {
194                         mKeyInputSupported = true;
195                     }
196                     break;
197                 case HW_ROTARY_INPUT:
198                     synchronized (mLock) {
199                         mRotaryInputSupported = true;
200                     }
201                     break;
202                 case HW_CUSTOM_INPUT:
203                     synchronized (mLock) {
204                         mCustomInputSupported = true;
205                     }
206                     break;
207             }
208         }
209     }
210 
211     @Override
onHalEvents(List<HalPropValue> values)212     public void onHalEvents(List<HalPropValue> values) {
213         InputListener listener;
214         synchronized (mLock) {
215             listener = mListener;
216         }
217         if (listener == null) {
218             Slogf.w(TAG, "Input event while listener is null");
219             return;
220         }
221         for (int i = 0; i < values.size(); i++) {
222             HalPropValue value = values.get(i);
223             switch (value.getPropId()) {
224                 case HW_KEY_INPUT:
225                     dispatchKeyInput(listener, value);
226                     break;
227                 case HW_ROTARY_INPUT:
228                     dispatchRotaryInput(listener, value);
229                     break;
230                 case HW_CUSTOM_INPUT:
231                     dispatchCustomInput(listener, value);
232                     break;
233                 default:
234                     Slogf.e(TAG, "Wrong event dispatched, prop:0x%x", value.getPropId());
235                     break;
236             }
237         }
238     }
239 
dispatchKeyInput(InputListener listener, HalPropValue value)240     private void dispatchKeyInput(InputListener listener, HalPropValue value) {
241         int action;
242         int code;
243         int vehicleDisplay;
244         int indentsCount;
245         try {
246             action = (value.getInt32Value(0) == VehicleHwKeyInputAction.ACTION_DOWN)
247                     ? KeyEvent.ACTION_DOWN
248                     : KeyEvent.ACTION_UP;
249             code = value.getInt32Value(1);
250             vehicleDisplay = value.getInt32Value(2);
251             indentsCount = value.getInt32ValuesSize() < 4 ? 1 : value.getInt32Value(3);
252             Slogf.d(TAG, "hal event code: %d, action: %d, display: %d, number of indents: %d",
253                     code, action, vehicleDisplay, indentsCount);
254         } catch (IndexOutOfBoundsException e) {
255             Slogf.e(TAG, "Invalid hal key input event received, int32Values: "
256                     + value.dumpInt32Values(), e);
257             return;
258         }
259         while (indentsCount > 0) {
260             indentsCount--;
261             dispatchKeyEvent(listener, action, code, convertDisplayType(vehicleDisplay));
262         }
263     }
264 
265     private void dispatchRotaryInput(InputListener listener, HalPropValue value) {
266         int timeValuesIndex = 3;  // remaining values are time deltas in nanoseconds
267         if (value.getInt32ValuesSize() < timeValuesIndex) {
268             Slogf.e(TAG, "Wrong int32 array size for RotaryInput from vhal: %d",
269                     value.getInt32ValuesSize());
270             return;
271         }
272         int rotaryInputType = value.getInt32Value(0);
273         int detentCount = value.getInt32Value(1);
274         int vehicleDisplay = value.getInt32Value(2);
275         long timestamp = value.getTimestamp();  // for first detent, uptime nanoseconds
276         Slogf.d(TAG, "hal rotary input type: %d, number of detents: %d, display: %d",
277                 rotaryInputType, detentCount, vehicleDisplay);
278         boolean clockwise = detentCount > 0;
279         detentCount = Math.abs(detentCount);
280         if (detentCount == 0) { // at least there should be one event
281             Slogf.e(TAG, "Zero detentCount from vhal, ignore the event");
282             return;
283         }
284         // If count is Integer.MIN_VALUE, Math.abs(count) < 0.
285         if (detentCount < 0 || detentCount > Integer.MAX_VALUE - detentCount + 1) {
286             Slogf.e(TAG, "Invalid detentCount from vhal: %d, ignore the event", detentCount);
287         }
288         if (vehicleDisplay != VehicleDisplay.MAIN
289                 && vehicleDisplay != VehicleDisplay.INSTRUMENT_CLUSTER) {
290             Slogf.e(TAG, "Wrong display type for RotaryInput from vhal: %d",
291                     vehicleDisplay);
292             return;
293         }
294         if (value.getInt32ValuesSize() != (timeValuesIndex + detentCount - 1)) {
295             Slogf.e(TAG, "Wrong int32 array size for RotaryInput from vhal: %d",
296                     value.getInt32ValuesSize());
297             return;
298         }
299         int carInputManagerType;
300         switch (rotaryInputType) {
301             case ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION:
302                 carInputManagerType = CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION;
303                 break;
304             case ROTARY_INPUT_TYPE_AUDIO_VOLUME:
305                 carInputManagerType = CarInputManager.INPUT_TYPE_ROTARY_VOLUME;
306                 break;
307             default:
308                 Slogf.e(TAG, "Unknown rotary input type: %d", rotaryInputType);
309                 return;
310         }
311 
312         long[] timestamps = new long[detentCount];
313         // vhal returns elapsed time while rotary event is using uptime to be in line with KeyEvent.
314         long uptimeToElapsedTimeDelta = CarServiceUtils.getUptimeToElapsedTimeDeltaInMillis();
315         long startUptime = TimeUnit.NANOSECONDS.toMillis(timestamp) - uptimeToElapsedTimeDelta;
316         timestamps[0] = startUptime;
317         for (int i = 0; i < timestamps.length - 1; i++) {
318             timestamps[i + 1] = timestamps[i] + TimeUnit.NANOSECONDS.toMillis(
319                     value.getInt32Value(timeValuesIndex + i));
320         }
321         RotaryEvent event = new RotaryEvent(carInputManagerType, clockwise, timestamps);
322         listener.onRotaryEvent(event, convertDisplayType(vehicleDisplay));
323     }
324 
325     /**
326      * Dispatches a KeyEvent using {@link #mUptimeSupplier} for the event time.
327      *
328      * @param listener listener to dispatch the event to
329      * @param action action for the KeyEvent
330      * @param code keycode for the KeyEvent
331      * @param display target display the event is associated with
332      */
333     private void dispatchKeyEvent(InputListener listener, int action, int code,
334             @DisplayTypeEnum int display) {
335         dispatchKeyEvent(listener, action, code, display, mUptimeSupplier.getAsLong());
336     }
337 
338     /**
339      * Dispatches a KeyEvent.
340      *
341      * @param listener listener to dispatch the event to
342      * @param action action for the KeyEvent
343      * @param code keycode for the KeyEvent
344      * @param display target display the event is associated with
345      * @param eventTime uptime in milliseconds when the event occurred
346      */
347     private void dispatchKeyEvent(InputListener listener, int action, int code,
348             @DisplayTypeEnum int display, long eventTime) {
349         long downTime;
350         int repeat;
351 
352         synchronized (mKeyStates) {
353             KeyState state = mKeyStates.get(code);
354             if (state == null) {
355                 state = new KeyState();
356                 mKeyStates.put(code, state);
357             }
358 
359             if (action == KeyEvent.ACTION_DOWN) {
360                 downTime = eventTime;
361                 repeat = state.mRepeatCount++;
362                 state.mLastKeyDownTimestamp = eventTime;
363             } else {
364                 // Handle key up events without any matching down event by setting the down time to
365                 // the event time. This shouldn't happen in practice - keys should be pressed
366                 // before they can be released! - but this protects us against HAL weirdness.
367                 downTime =
368                         (state.mLastKeyDownTimestamp == -1)
369                                 ? eventTime
370                                 : state.mLastKeyDownTimestamp;
371                 repeat = 0;
372                 state.mRepeatCount = 0;
373             }
374         }
375 
376         KeyEvent event = new KeyEvent(
377                 downTime,
378                 eventTime,
379                 action,
380                 code,
381                 repeat,
382                 0 /* deviceId */,
383                 0 /* scancode */,
384                 0 /* flags */,
385                 InputDevice.SOURCE_CLASS_BUTTON);
386 
387         // event.displayId will be set in CarInputService#onKeyEvent
388         listener.onKeyEvent(event, display);
389     }
390 
391     private void dispatchCustomInput(InputListener listener, HalPropValue value) {
392         Slogf.d(TAG, "Dispatching CustomInputEvent for listener: %s and value: %s",
393                 listener, value);
394         int inputCode;
395         int targetDisplayType;
396         int repeatCounter;
397         try {
398             inputCode = value.getInt32Value(0);
399             targetDisplayType = convertDisplayType(value.getInt32Value(1));
400             repeatCounter = value.getInt32Value(2);
401         } catch (IndexOutOfBoundsException e) {
402             Slogf.e(TAG, "Invalid hal custom input event received", e);
403             return;
404         }
405         CustomInputEvent event = new CustomInputEvent(inputCode, targetDisplayType, repeatCounter);
406         listener.onCustomInputEvent(event);
407     }
408 
409     /**
410      * Converts the vehicle display type ({@link VehicleDisplay#MAIN} and
411      * {@link VehicleDisplay#INSTRUMENT_CLUSTER}) to their corresponding types in
412      * {@link CarOccupantZoneManager} ({@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN} and
413      * {@link CarOccupantZoneManager#DISPLAY_TYPE_INSTRUMENT_CLUSTER}).
414      *
415      * @param vehicleDisplayType the vehicle display type
416      * @return the corresponding display type (defined in {@link CarOccupantZoneManager}) or
417      * {@link CarOccupantZoneManager#DISPLAY_TYPE_UNKNOWN} if the value passed as parameter doesn't
418      * correspond to a driver's display type
419      * @hide
420      */
421     @DisplayTypeEnum
422     public static int convertDisplayType(int vehicleDisplayType) {
423         switch (vehicleDisplayType) {
424             case VehicleDisplay.MAIN:
425                 return CarOccupantZoneManager.DISPLAY_TYPE_MAIN;
426             case VehicleDisplay.INSTRUMENT_CLUSTER:
427                 return CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER;
428             default:
429                 return CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN;
430         }
431     }
432 
433     @Override
434     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
435     public void dump(PrintWriter writer) {
436         synchronized (mLock) {
437             writer.println("*Input HAL*");
438             writer.println("mKeyInputSupported:" + mKeyInputSupported);
439             writer.println("mRotaryInputSupported:" + mRotaryInputSupported);
440         }
441     }
442 }
443