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.VehicleProperty.HW_KEY_INPUT; 19 20 import android.hardware.automotive.vehicle.V2_0.VehicleDisplay; 21 import android.hardware.automotive.vehicle.V2_0.VehicleHwKeyInputAction; 22 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig; 23 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; 24 import android.os.SystemClock; 25 import android.util.Log; 26 import android.util.SparseArray; 27 import android.view.InputDevice; 28 import android.view.KeyEvent; 29 30 import com.android.car.CarLog; 31 import com.android.internal.annotations.GuardedBy; 32 import com.android.internal.annotations.VisibleForTesting; 33 34 import java.io.PrintWriter; 35 import java.util.Collection; 36 import java.util.LinkedList; 37 import java.util.List; 38 import java.util.function.LongSupplier; 39 40 public class InputHalService extends HalServiceBase { 41 42 public static final int DISPLAY_MAIN = VehicleDisplay.MAIN; 43 public static final int DISPLAY_INSTRUMENT_CLUSTER = VehicleDisplay.INSTRUMENT_CLUSTER; 44 private final VehicleHal mHal; 45 /** A function to retrieve the current system uptime - replaceable for testing. */ 46 private final LongSupplier mUptimeSupplier; 47 48 public interface InputListener { onKeyEvent(KeyEvent event, int targetDisplay)49 void onKeyEvent(KeyEvent event, int targetDisplay); 50 } 51 52 /** The current press state of a key. */ 53 private static class KeyState { 54 /** The timestamp (uptimeMillis) of the last ACTION_DOWN event for this key. */ 55 public long mLastKeyDownTimestamp = -1; 56 /** The number of ACTION_DOWN events that have been sent for this keypress. */ 57 public int mRepeatCount = 0; 58 } 59 60 private static final boolean DBG = false; 61 62 @GuardedBy("this") 63 private boolean mKeyInputSupported = false; 64 65 @GuardedBy("this") 66 private InputListener mListener; 67 68 @GuardedBy("mKeyStates") 69 private final SparseArray<KeyState> mKeyStates = new SparseArray<>(); 70 InputHalService(VehicleHal hal)71 public InputHalService(VehicleHal hal) { 72 this(hal, SystemClock::uptimeMillis); 73 } 74 75 @VisibleForTesting InputHalService(VehicleHal hal, LongSupplier uptimeSupplier)76 InputHalService(VehicleHal hal, LongSupplier uptimeSupplier) { 77 mHal = hal; 78 mUptimeSupplier = uptimeSupplier; 79 } 80 setInputListener(InputListener listener)81 public void setInputListener(InputListener listener) { 82 synchronized (this) { 83 if (!mKeyInputSupported) { 84 Log.w(CarLog.TAG_INPUT, "input listener set while key input not supported"); 85 return; 86 } 87 mListener = listener; 88 } 89 mHal.subscribeProperty(this, HW_KEY_INPUT); 90 } 91 isKeyInputSupported()92 public synchronized boolean isKeyInputSupported() { 93 return mKeyInputSupported; 94 } 95 96 @Override init()97 public void init() { 98 } 99 100 @Override release()101 public void release() { 102 synchronized (this) { 103 mListener = null; 104 mKeyInputSupported = false; 105 } 106 } 107 108 @Override takeSupportedProperties( Collection<VehiclePropConfig> allProperties)109 public Collection<VehiclePropConfig> takeSupportedProperties( 110 Collection<VehiclePropConfig> allProperties) { 111 List<VehiclePropConfig> supported = new LinkedList<>(); 112 for (VehiclePropConfig p: allProperties) { 113 if (p.prop == HW_KEY_INPUT) { 114 supported.add(p); 115 synchronized (this) { 116 mKeyInputSupported = true; 117 } 118 } 119 } 120 return supported; 121 } 122 123 @Override handleHalEvents(List<VehiclePropValue> values)124 public void handleHalEvents(List<VehiclePropValue> values) { 125 InputListener listener; 126 synchronized (this) { 127 listener = mListener; 128 } 129 if (listener == null) { 130 Log.w(CarLog.TAG_INPUT, "Input event while listener is null"); 131 return; 132 } 133 for (VehiclePropValue v : values) { 134 if (v.prop != HW_KEY_INPUT) { 135 Log.e(CarLog.TAG_INPUT, "Wrong event dispatched, prop:0x" + 136 Integer.toHexString(v.prop)); 137 continue; 138 } 139 int action = (v.value.int32Values.get(0) == VehicleHwKeyInputAction.ACTION_DOWN) ? 140 KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP; 141 int code = v.value.int32Values.get(1); 142 int display = v.value.int32Values.get(2); 143 int indentsCount = v.value.int32Values.size() < 4 ? 1 : v.value.int32Values.get(3); 144 if (DBG) { 145 Log.i(CarLog.TAG_INPUT, new StringBuilder() 146 .append("hal event code:").append(code) 147 .append(", action:").append(action) 148 .append(", display: ").append(display) 149 .append(", number of indents: ").append(indentsCount) 150 .toString()); 151 } 152 while (indentsCount > 0) { 153 indentsCount--; 154 dispatchKeyEvent(listener, action, code, display); 155 } 156 } 157 } 158 159 private void dispatchKeyEvent(InputListener listener, int action, int code, int display) { 160 long eventTime = mUptimeSupplier.getAsLong(); 161 162 long downTime; 163 int repeat; 164 165 synchronized (mKeyStates) { 166 KeyState state = mKeyStates.get(code); 167 if (state == null) { 168 state = new KeyState(); 169 mKeyStates.put(code, state); 170 } 171 172 if (action == KeyEvent.ACTION_DOWN) { 173 downTime = eventTime; 174 repeat = state.mRepeatCount++; 175 state.mLastKeyDownTimestamp = eventTime; 176 } else { 177 // Handle key up events without any matching down event by setting the down time to 178 // the event time. This shouldn't happen in practice - keys should be pressed 179 // before they can be released! - but this protects us against HAL weirdness. 180 downTime = 181 (state.mLastKeyDownTimestamp == -1) 182 ? eventTime 183 : state.mLastKeyDownTimestamp; 184 repeat = 0; 185 state.mRepeatCount = 0; 186 } 187 } 188 189 KeyEvent event = KeyEvent.obtain( 190 downTime, 191 eventTime, 192 action, 193 code, 194 repeat, 195 0 /* meta state */, 196 0 /* deviceId */, 197 0 /* scancode */, 198 0 /* flags */, 199 InputDevice.SOURCE_CLASS_BUTTON, 200 null /* characters */); 201 202 listener.onKeyEvent(event, display); 203 event.recycle(); 204 } 205 206 @Override 207 public void dump(PrintWriter writer) { 208 writer.println("*Input HAL*"); 209 writer.println("mKeyInputSupported:" + mKeyInputSupported); 210 } 211 212 } 213