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 mTimeToSend; 59 60 static { 61 System.loadLibrary("uinputcommand_jni"); 62 } 63 nativeOpenUinputDevice(String name, int id, int vid, int pid, int bus, int ffEffectsMax, String port, DeviceCallback callback)64 private static native long nativeOpenUinputDevice(String name, int id, int vid, int pid, 65 int bus, int ffEffectsMax, String port, DeviceCallback callback); nativeCloseUinputDevice(long ptr)66 private static native void nativeCloseUinputDevice(long ptr); nativeInjectEvent(long ptr, int type, int code, int value)67 private static native void nativeInjectEvent(long ptr, int type, int code, int value); nativeConfigure(int handle, int code, int[] configs)68 private static native void nativeConfigure(int handle, int code, int[] configs); nativeSetAbsInfo(int handle, int axisCode, Parcel axisParcel)69 private static native void nativeSetAbsInfo(int handle, int axisCode, Parcel axisParcel); 70 Device(int id, String name, int vid, int pid, int bus, SparseArray<int[]> configuration, int ffEffectsMax, SparseArray<InputAbsInfo> absInfo, String port)71 public Device(int id, String name, int vid, int pid, int bus, 72 SparseArray<int[]> configuration, int ffEffectsMax, 73 SparseArray<InputAbsInfo> absInfo, String port) { 74 mId = id; 75 mThread = new HandlerThread("UinputDeviceHandler"); 76 mThread.start(); 77 mHandler = new DeviceHandler(mThread.getLooper()); 78 mConfiguration = configuration; 79 mAbsInfo = absInfo; 80 mOutputStream = System.out; 81 SomeArgs args = SomeArgs.obtain(); 82 args.argi1 = id; 83 args.argi2 = vid; 84 args.argi3 = pid; 85 args.argi4 = bus; 86 args.argi5 = ffEffectsMax; 87 if (name != null) { 88 args.arg1 = name; 89 } else { 90 args.arg1 = id + ":" + vid + ":" + pid; 91 } 92 if (port != null) { 93 args.arg2 = port; 94 } else { 95 args.arg2 = "uinput:" + id + ":" + vid + ":" + pid; 96 } 97 98 mHandler.obtainMessage(MSG_OPEN_UINPUT_DEVICE, args).sendToTarget(); 99 mTimeToSend = SystemClock.uptimeMillis(); 100 } 101 102 /** 103 * Inject uinput events to device 104 * 105 * @param events Array of raw uinput events. 106 */ injectEvent(int[] events)107 public void injectEvent(int[] events) { 108 // if two messages are sent at identical time, they will be processed in order received 109 Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, events); 110 mHandler.sendMessageAtTime(msg, mTimeToSend); 111 } 112 113 /** 114 * Impose a delay to the device for execution. 115 * 116 * @param delay Time to delay in unit of milliseconds. 117 */ addDelay(int delay)118 public void addDelay(int delay) { 119 mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay; 120 } 121 122 /** 123 * Synchronize the uinput command queue by writing a sync response with the provided syncToken 124 * to the output stream when this event is processed. 125 * 126 * @param syncToken The token for this sync command. 127 */ syncEvent(String syncToken)128 public void syncEvent(String syncToken) { 129 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), mTimeToSend); 130 } 131 132 /** 133 * Close an uinput device. 134 * 135 */ close()136 public void close() { 137 Message msg = mHandler.obtainMessage(MSG_CLOSE_UINPUT_DEVICE); 138 mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1); 139 try { 140 synchronized (mCond) { 141 mCond.wait(); 142 } 143 } catch (InterruptedException ignore) { 144 } 145 } 146 147 private class DeviceHandler extends Handler { 148 private long mPtr; 149 private int mBarrierToken; 150 DeviceHandler(Looper looper)151 DeviceHandler(Looper looper) { 152 super(looper); 153 } 154 155 @Override handleMessage(Message msg)156 public void handleMessage(Message msg) { 157 switch (msg.what) { 158 case MSG_OPEN_UINPUT_DEVICE: 159 SomeArgs args = (SomeArgs) msg.obj; 160 mPtr = nativeOpenUinputDevice((String) args.arg1, args.argi1, args.argi2, 161 args.argi3, args.argi4, args.argi5, (String) args.arg2, 162 new DeviceCallback()); 163 break; 164 case MSG_INJECT_EVENT: 165 if (mPtr != 0) { 166 int[] events = (int[]) msg.obj; 167 for (int pos = 0; pos + 2 < events.length; pos += 3) { 168 nativeInjectEvent(mPtr, events[pos], events[pos + 1], events[pos + 2]); 169 } 170 } 171 break; 172 case MSG_CLOSE_UINPUT_DEVICE: 173 if (mPtr != 0) { 174 nativeCloseUinputDevice(mPtr); 175 getLooper().quitSafely(); 176 mPtr = 0; 177 } else { 178 Log.e(TAG, "Tried to close already closed device."); 179 } 180 Log.i(TAG, "Device closed."); 181 synchronized (mCond) { 182 mCond.notify(); 183 } 184 break; 185 case MSG_SYNC_EVENT: 186 handleSyncEvent((String) msg.obj); 187 break; 188 default: 189 throw new IllegalArgumentException("Unknown device message"); 190 } 191 } 192 pauseEvents()193 public void pauseEvents() { 194 mBarrierToken = getLooper().myQueue().postSyncBarrier(); 195 } 196 resumeEvents()197 public void resumeEvents() { 198 getLooper().myQueue().removeSyncBarrier(mBarrierToken); 199 mBarrierToken = 0; 200 } 201 handleSyncEvent(String syncToken)202 private void handleSyncEvent(String syncToken) { 203 final JSONObject json = new JSONObject(); 204 try { 205 json.put("reason", "sync"); 206 json.put("id", mId); 207 json.put("syncToken", syncToken); 208 } catch (JSONException e) { 209 throw new RuntimeException("Could not create JSON object ", e); 210 } 211 writeOutputObject(json); 212 } 213 } 214 215 private class DeviceCallback { onDeviceOpen()216 public void onDeviceOpen() { 217 mHandler.resumeEvents(); 218 } 219 onDeviceConfigure(int handle)220 public void onDeviceConfigure(int handle) { 221 for (int i = 0; i < mConfiguration.size(); i++) { 222 int key = mConfiguration.keyAt(i); 223 int[] data = mConfiguration.get(key); 224 nativeConfigure(handle, key, data); 225 } 226 227 if (mAbsInfo != null) { 228 for (int i = 0; i < mAbsInfo.size(); i++) { 229 int key = mAbsInfo.keyAt(i); 230 InputAbsInfo info = mAbsInfo.get(key); 231 Parcel parcel = Parcel.obtain(); 232 info.writeToParcel(parcel, 0); 233 parcel.setDataPosition(0); 234 nativeSetAbsInfo(handle, key, parcel); 235 } 236 } 237 } 238 onDeviceVibrating(int value)239 public void onDeviceVibrating(int value) { 240 final JSONObject json = new JSONObject(); 241 try { 242 json.put("reason", "vibrating"); 243 json.put("id", mId); 244 json.put("status", value); 245 } catch (JSONException e) { 246 throw new RuntimeException("Could not create JSON object ", e); 247 } 248 writeOutputObject(json); 249 } 250 onDeviceError()251 public void onDeviceError() { 252 Log.e(TAG, "Device error occurred, closing /dev/uinput"); 253 Message msg = mHandler.obtainMessage(MSG_CLOSE_UINPUT_DEVICE); 254 msg.setAsynchronous(true); 255 msg.sendToTarget(); 256 } 257 } 258 writeOutputObject(JSONObject json)259 private void writeOutputObject(JSONObject json) { 260 try { 261 mOutputStream.write(json.toString().getBytes()); 262 mOutputStream.flush(); 263 } catch (IOException e) { 264 throw new RuntimeException(e); 265 } 266 } 267 } 268