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