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