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