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