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