1 /*
2  * Copyright (C) 2015 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.hid;
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.MessageQueue;
24 import android.os.SystemClock;
25 import android.util.Log;
26 
27 import com.android.internal.os.SomeArgs;
28 
29 public class Device {
30     private static final String TAG = "HidDevice";
31 
32     // Minimum amount of time to wait before sending input events to a device. Even though we're
33     // guaranteed that the device has been created and opened by the input system, there's still a
34     // window in which the system hasn't started reading events out of it. If a stream of events
35     // begins in during this window (like a button down event) and *then* we start reading, we're
36     // liable to ignore the whole stream.
37     private static final int MIN_WAIT_FOR_FIRST_EVENT = 150;
38 
39     private static final int MSG_OPEN_DEVICE = 1;
40     private static final int MSG_SEND_REPORT = 2;
41     private static final int MSG_CLOSE_DEVICE = 3;
42 
43 
44     private final int mId;
45     private final HandlerThread mThread;
46     private final DeviceHandler mHandler;
47     private long mEventTime;
48 
49     private final Object mCond = new Object();
50 
51     static {
52         System.loadLibrary("hidcommand_jni");
53     }
54 
nativeOpenDevice(String name, int id, int vid, int pid, byte[] descriptor, MessageQueue queue, DeviceCallback callback)55     private static native long nativeOpenDevice(String name, int id, int vid, int pid,
56             byte[] descriptor, MessageQueue queue, DeviceCallback callback);
nativeSendReport(long ptr, byte[] data)57     private static native void nativeSendReport(long ptr, byte[] data);
nativeCloseDevice(long ptr)58     private static native void nativeCloseDevice(long ptr);
59 
Device(int id, String name, int vid, int pid, byte[] descriptor, byte[] report)60     public Device(int id, String name, int vid, int pid, byte[] descriptor, byte[] report) {
61         mId = id;
62         mThread = new HandlerThread("HidDeviceHandler");
63         mThread.start();
64         mHandler = new DeviceHandler(mThread.getLooper());
65         SomeArgs args = SomeArgs.obtain();
66         args.argi1 = id;
67         args.argi2 = vid;
68         args.argi3 = pid;
69         if (name != null) {
70             args.arg1 = name;
71         } else {
72             args.arg1 = id + ":" + vid + ":" + pid;
73         }
74         args.arg2 = descriptor;
75         args.arg3 = report;
76         mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget();
77         mEventTime = SystemClock.uptimeMillis() + MIN_WAIT_FOR_FIRST_EVENT;
78     }
79 
sendReport(byte[] report)80     public void sendReport(byte[] report) {
81         Message msg = mHandler.obtainMessage(MSG_SEND_REPORT, report);
82         mHandler.sendMessageAtTime(msg, mEventTime);
83     }
84 
addDelay(int delay)85     public void addDelay(int delay) {
86         mEventTime += delay;
87     }
88 
close()89     public void close() {
90         Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
91         msg.setAsynchronous(true);
92         mHandler.sendMessageAtTime(msg, mEventTime + 1);
93         try {
94             synchronized (mCond) {
95                 mCond.wait();
96             }
97         } catch (InterruptedException ignore) {}
98     }
99 
100     private class DeviceHandler extends Handler {
101         private long mPtr;
102         private int mBarrierToken;
103 
DeviceHandler(Looper looper)104         public DeviceHandler(Looper looper) {
105             super(looper);
106         }
107 
108         @Override
handleMessage(Message msg)109         public void handleMessage(Message msg) {
110             switch (msg.what) {
111                 case MSG_OPEN_DEVICE:
112                     SomeArgs args = (SomeArgs) msg.obj;
113                     mPtr = nativeOpenDevice((String) args.arg1, args.argi1, args.argi2, args.argi3,
114                             (byte[]) args.arg2, getLooper().myQueue(), new DeviceCallback());
115                     nativeSendReport(mPtr, (byte[]) args.arg3);
116                     pauseEvents();
117                     break;
118                 case MSG_SEND_REPORT:
119                     if (mPtr != 0) {
120                         nativeSendReport(mPtr, (byte[]) msg.obj);
121                     } else {
122                         Log.e(TAG, "Tried to send report to closed device.");
123                     }
124                     break;
125                 case MSG_CLOSE_DEVICE:
126                     if (mPtr != 0) {
127                         nativeCloseDevice(mPtr);
128                         getLooper().quitSafely();
129                         mPtr = 0;
130                     } else {
131                         Log.e(TAG, "Tried to close already closed device.");
132                     }
133                     synchronized (mCond) {
134                         mCond.notify();
135                     }
136                     break;
137                 default:
138                     throw new IllegalArgumentException("Unknown device message");
139             }
140         }
141 
pauseEvents()142         public void pauseEvents() {
143             mBarrierToken = getLooper().myQueue().postSyncBarrier();
144         }
145 
resumeEvents()146         public void resumeEvents() {
147             getLooper().myQueue().removeSyncBarrier(mBarrierToken);
148             mBarrierToken = 0;
149         }
150     }
151 
152     private class DeviceCallback {
onDeviceOpen()153         public void onDeviceOpen() {
154             mHandler.resumeEvents();
155         }
156 
onDeviceError()157         public void onDeviceError() {
158             Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
159             msg.setAsynchronous(true);
160             msg.sendToTarget();
161         }
162     }
163 }
164