1 /*
2  * Copyright (C) 2020 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 
17 package com.android.commands.uinput;
18 
19 import android.os.Handler;
20 import android.os.HandlerThread;
21 import android.os.Looper;
22 import android.os.Message;
23 import android.os.Parcel;
24 import android.os.SystemClock;
25 import android.util.Log;
26 import android.util.SparseArray;
27 
28 import com.android.internal.os.SomeArgs;
29 
30 import org.json.JSONException;
31 import org.json.JSONObject;
32 
33 import java.io.IOException;
34 import java.io.OutputStream;
35 
36 import src.com.android.commands.uinput.InputAbsInfo;
37 
38 /**
39  * Device class defines uinput device interfaces of device operations, for device open, close,
40  * configuration, events injection.
41  */
42 public class Device {
43     private static final String TAG = "UinputDevice";
44 
45     private static final int MSG_OPEN_UINPUT_DEVICE = 1;
46     private static final int MSG_CLOSE_UINPUT_DEVICE = 2;
47     private static final int MSG_INJECT_EVENT = 3;
48     private static final int MSG_SYNC_EVENT = 4;
49 
50     private final int mId;
51     private final HandlerThread mThread;
52     private final DeviceHandler mHandler;
53     // mConfiguration is sparse array of ioctl code and array of values.
54     private final SparseArray<int[]> mConfiguration;
55     private final SparseArray<InputAbsInfo> mAbsInfo;
56     private final OutputStream mOutputStream;
57     private final Object mCond = new Object();
58     private long mTimeToSendNanos;
59 
60     static {
61         System.loadLibrary("uinputcommand_jni");
62     }
63 
nativeOpenUinputDevice(String name, int id, int vendorId, int productId, int versionId, int bus, int ffEffectsMax, String port, DeviceCallback callback)64     private static native long nativeOpenUinputDevice(String name, int id, int vendorId,
65             int productId, int versionId, int bus, int ffEffectsMax, String port,
66             DeviceCallback callback);
nativeCloseUinputDevice(long ptr)67     private static native void nativeCloseUinputDevice(long ptr);
nativeInjectEvent(long ptr, long timestampMicros, int type, int code, int value)68     private static native void nativeInjectEvent(long ptr, long timestampMicros, int type, int code,
69                                                  int value);
nativeConfigure(int handle, int code, int[] configs)70     private static native void nativeConfigure(int handle, int code, int[] configs);
nativeSetAbsInfo(int handle, int axisCode, Parcel axisParcel)71     private static native void nativeSetAbsInfo(int handle, int axisCode, Parcel axisParcel);
nativeGetEvdevEventTypeByLabel(String label)72     private static native int nativeGetEvdevEventTypeByLabel(String label);
nativeGetEvdevEventCodeByLabel(int type, String label)73     private static native int nativeGetEvdevEventCodeByLabel(int type, String label);
nativeGetEvdevInputPropByLabel(String label)74     private static native int nativeGetEvdevInputPropByLabel(String label);
75 
Device(int id, String name, int vendorId, int productId, int versionId, int bus, SparseArray<int[]> configuration, int ffEffectsMax, SparseArray<InputAbsInfo> absInfo, String port)76     public Device(int id, String name, int vendorId, int productId, int versionId, int bus,
77             SparseArray<int[]> configuration, int ffEffectsMax,
78             SparseArray<InputAbsInfo> absInfo, String port) {
79         mId = id;
80         mThread = new HandlerThread("UinputDeviceHandler");
81         mThread.start();
82         mHandler = new DeviceHandler(mThread.getLooper());
83         mConfiguration = configuration;
84         mAbsInfo = absInfo;
85         mOutputStream = System.out;
86         SomeArgs args = SomeArgs.obtain();
87         args.argi1 = id;
88         args.argi2 = vendorId;
89         args.argi3 = productId;
90         args.argi4 = versionId;
91         args.argi5 = bus;
92         args.argi6 = ffEffectsMax;
93         if (name != null) {
94             args.arg1 = name;
95         } else {
96             args.arg1 = id + ":" + vendorId + ":" + productId;
97         }
98         if (port != null) {
99             args.arg2 = port;
100         } else {
101             args.arg2 = "uinput:" + id + ":" + vendorId + ":" + productId;
102         }
103 
104         mHandler.obtainMessage(MSG_OPEN_UINPUT_DEVICE, args).sendToTarget();
105         updateTimeBase();
106     }
107 
getTimeToSendMillis()108     private long getTimeToSendMillis() {
109         // Since we can only specify delays in milliseconds but evemu timestamps are in
110         // microseconds, we have to round up the delays to avoid setting event timestamps
111         // which are in the future (which the kernel would silently reject and replace with
112         // the current time).
113         //
114         // This should be the same as (long) Math.ceil(mTimeToSendNanos / 1_000_000.0), except
115         // without the precision loss that comes from converting from long to double and back.
116         return mTimeToSendNanos / 1_000_000 + ((mTimeToSendNanos % 1_000_000 > 0) ? 1 : 0);
117     }
118 
119     /**
120      * Inject uinput events to device
121      *
122      * @param events  Array of raw uinput events.
123      * @param offsetMicros The difference in microseconds between the timestamps of the previous
124      *                     batch of events injected and this batch. If set to -1, the current
125      *                     timestamp will be used.
126      */
injectEvent(int[] events, long offsetMicros)127     public void injectEvent(int[] events, long offsetMicros) {
128         // if two messages are sent at identical time, they will be processed in order received
129         SomeArgs args = SomeArgs.obtain();
130         args.arg1 = events;
131         args.argl1 = offsetMicros;
132         args.argl2 = SystemClock.uptimeNanos();
133         Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, args);
134         mHandler.sendMessageAtTime(msg, getTimeToSendMillis());
135     }
136 
137     /**
138      * Set the reference time from which future injections are scheduled to the current time.
139      */
updateTimeBase()140     public void updateTimeBase() {
141         mTimeToSendNanos = SystemClock.uptimeNanos();
142     }
143 
144     /**
145      * Delay subsequent device activity by the specified amount of time.
146      *
147      * <p>Note that although the delay is specified in nanoseconds, due to limitations of {@link
148      * Handler}'s API, scheduling only occurs with millisecond precision. When scheduling an
149      * injection or sync, the time at which it is scheduled will be rounded up to the nearest
150      * millisecond. While this means that a particular injection cannot be scheduled precisely,
151      * rounding errors will not accumulate over time. For example, if five injections are scheduled
152      * with a delay of 1,200,000ns before each one, the total delay will be 6ms, as opposed to the
153      * 10ms it would have been if each individual delay had been rounded up (as {@link EvemuParser}
154      * would otherwise have to do to avoid sending timestamps that are in the future).
155      *
156      * @param delayNanos  Time to delay in unit of nanoseconds.
157      */
addDelayNanos(long delayNanos)158     public void addDelayNanos(long delayNanos) {
159         mTimeToSendNanos += delayNanos;
160     }
161 
162     /**
163      * Synchronize the uinput command queue by writing a sync response with the provided syncToken
164      * to the output stream when this event is processed.
165      *
166      * @param syncToken  The token for this sync command.
167      */
syncEvent(String syncToken)168     public void syncEvent(String syncToken) {
169         mHandler.sendMessageAtTime(
170                 mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), getTimeToSendMillis());
171     }
172 
173     /**
174      * Close an uinput device.
175      *
176      */
close()177     public void close() {
178         Message msg = mHandler.obtainMessage(MSG_CLOSE_UINPUT_DEVICE);
179         mHandler.sendMessageAtTime(
180                 msg, Math.max(SystemClock.uptimeMillis(), getTimeToSendMillis()) + 1);
181         try {
182             synchronized (mCond) {
183                 mCond.wait();
184             }
185         } catch (InterruptedException ignore) {
186         }
187     }
188 
189     private class DeviceHandler extends Handler {
190         private long mPtr;
191         private long mLastInjectTimestampMicros = -1;
192         private int mBarrierToken;
193 
DeviceHandler(Looper looper)194         DeviceHandler(Looper looper) {
195             super(looper);
196         }
197 
198         @Override
handleMessage(Message msg)199         public void handleMessage(Message msg) {
200             switch (msg.what) {
201                 case MSG_OPEN_UINPUT_DEVICE: {
202                     SomeArgs args = (SomeArgs) msg.obj;
203                     String name = (String) args.arg1;
204                     mPtr = nativeOpenUinputDevice(name, args.argi1 /* id */,
205                             args.argi2 /* vendorId */, args.argi3 /* productId */,
206                             args.argi4 /* versionId */, args.argi5 /* bus */,
207                             args.argi6 /* ffEffectsMax */, (String) args.arg2 /* port */,
208                             new DeviceCallback());
209                     if (mPtr == 0) {
210                         RuntimeException ex = new RuntimeException(
211                                 "Could not create uinput device \"" + name + "\"");
212                         Log.e(TAG, "Couldn't create uinput device, exiting.", ex);
213                         args.recycle();
214                         throw ex;
215                     }
216                     args.recycle();
217                     break;
218                 }
219                 case MSG_INJECT_EVENT: {
220                     SomeArgs args = (SomeArgs) msg.obj;
221                     if (mPtr == 0) {
222                         args.recycle();
223                         break;
224                     }
225                     long offsetMicros = args.argl1;
226                     if (mLastInjectTimestampMicros == -1) {
227                         // There's often a delay of a few milliseconds between the time specified to
228                         // Handler.sendMessageAtTime and the handler actually being called, due to
229                         // the way threads are scheduled. We don't take this into account when
230                         // calling addDelayNanos between the first batch of event injections (when
231                         // we set the "base timestamp" from which all others will be offset) and the
232                         // second batch, meaning that the actual time between the handler calls for
233                         // those batches may be less than the offset between their timestamps. When
234                         // that happens, we would pass a timestamp for the second batch that's
235                         // actually in the future. The kernel's uinput API rejects timestamps that
236                         // are in the future and uses the current time instead, making the reported
237                         // timestamps inconsistent with the recording we're replaying.
238                         //
239                         // To prevent this, we need to use the time at which we scheduled this first
240                         // batch, rather than the actual current time.
241                         mLastInjectTimestampMicros = args.argl2 / 1000;
242                     } else if (offsetMicros == -1) {
243                         // No timestamp offset is specified for this event, so use the current time.
244                         mLastInjectTimestampMicros = SystemClock.uptimeNanos() / 1000;
245                     } else {
246                         mLastInjectTimestampMicros += offsetMicros;
247                     }
248 
249                     int[] events = (int[]) args.arg1;
250                     for (int pos = 0; pos + 2 < events.length; pos += 3) {
251                         nativeInjectEvent(mPtr, mLastInjectTimestampMicros, events[pos],
252                                 events[pos + 1], events[pos + 2]);
253                     }
254                     args.recycle();
255                     break;
256                 }
257                 case MSG_CLOSE_UINPUT_DEVICE: {
258                     if (mPtr != 0) {
259                         nativeCloseUinputDevice(mPtr);
260                         getLooper().quitSafely();
261                         mPtr = 0;
262                     } else {
263                         Log.e(TAG, "Tried to close already closed device.");
264                     }
265                     Log.i(TAG, "Device closed.");
266                     synchronized (mCond) {
267                         mCond.notify();
268                     }
269                     break;
270                 }
271                 case MSG_SYNC_EVENT: {
272                     handleSyncEvent((String) msg.obj);
273                     break;
274                 }
275                 default: {
276                     throw new IllegalArgumentException("Unknown device message");
277                 }
278             }
279         }
280 
pauseEvents()281         public void pauseEvents() {
282             mBarrierToken = getLooper().myQueue().postSyncBarrier();
283         }
284 
resumeEvents()285         public void resumeEvents() {
286             getLooper().myQueue().removeSyncBarrier(mBarrierToken);
287             mBarrierToken = 0;
288         }
289 
handleSyncEvent(String syncToken)290         private void handleSyncEvent(String syncToken) {
291             final JSONObject json = new JSONObject();
292             try {
293                 json.put("reason", "sync");
294                 json.put("id", mId);
295                 json.put("syncToken", syncToken);
296             } catch (JSONException e) {
297                 throw new RuntimeException("Could not create JSON object ", e);
298             }
299             writeOutputObject(json);
300         }
301     }
302 
303     private class DeviceCallback {
onDeviceOpen()304         public void onDeviceOpen() {
305             mHandler.resumeEvents();
306         }
307 
onDeviceConfigure(int handle)308         public void onDeviceConfigure(int handle) {
309             for (int i = 0; i < mConfiguration.size(); i++) {
310                 int key = mConfiguration.keyAt(i);
311                 int[] data = mConfiguration.get(key);
312                 nativeConfigure(handle, key, data);
313             }
314 
315             if (mAbsInfo != null) {
316                 for (int i = 0; i < mAbsInfo.size(); i++) {
317                     int key = mAbsInfo.keyAt(i);
318                     InputAbsInfo info = mAbsInfo.get(key);
319                     Parcel parcel = Parcel.obtain();
320                     info.writeToParcel(parcel, 0);
321                     parcel.setDataPosition(0);
322                     nativeSetAbsInfo(handle, key, parcel);
323                 }
324             }
325         }
326 
onDeviceVibrating(int value)327         public void onDeviceVibrating(int value) {
328             final JSONObject json = new JSONObject();
329             try {
330                 json.put("reason", "vibrating");
331                 json.put("id", mId);
332                 json.put("status", value);
333             } catch (JSONException e) {
334                 throw new RuntimeException("Could not create JSON object ", e);
335             }
336             writeOutputObject(json);
337         }
338 
onDeviceError()339         public void onDeviceError() {
340             Log.e(TAG, "Device error occurred, closing /dev/uinput");
341             Message msg = mHandler.obtainMessage(MSG_CLOSE_UINPUT_DEVICE);
342             msg.setAsynchronous(true);
343             msg.sendToTarget();
344         }
345     }
346 
writeOutputObject(JSONObject json)347     private void writeOutputObject(JSONObject json) {
348         try {
349             mOutputStream.write(json.toString().getBytes());
350             mOutputStream.flush();
351         } catch (IOException e) {
352             throw new RuntimeException(e);
353         }
354     }
355 
getEvdevEventTypeByLabel(String label)356     static int getEvdevEventTypeByLabel(String label) {
357         final var type = nativeGetEvdevEventTypeByLabel(label);
358         if (type < 0) {
359             throw new IllegalArgumentException(
360                     "Failed to get evdev event type from label: " + label);
361         }
362         return type;
363     }
364 
getEvdevEventCodeByLabel(int type, String label)365     static int getEvdevEventCodeByLabel(int type, String label) {
366         final var code = nativeGetEvdevEventCodeByLabel(type, label);
367         if (code < 0) {
368             throw new IllegalArgumentException(
369                     "Failed to get evdev event code for type " + type + " from label: " + label);
370         }
371         return code;
372 
373     }
374 
getEvdevInputPropByLabel(String label)375     static int getEvdevInputPropByLabel(String label) {
376         final var prop = nativeGetEvdevInputPropByLabel(label);
377         if (prop < 0) {
378             throw new IllegalArgumentException(
379                     "Failed to get evdev input prop from label: " + label);
380         }
381         return prop;
382     }
383 }
384