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