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