Device.java revision 1f2c7688c1f673790d61645632ae5e1838f021a4
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
17package com.android.commands.hid;
18
19import android.os.Handler;
20import android.os.HandlerThread;
21import android.os.Looper;
22import android.os.Message;
23import android.os.MessageQueue;
24import android.os.SystemClock;
25import android.util.Log;
26
27import com.android.internal.os.SomeArgs;
28
29public 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
55    private static native long nativeOpenDevice(String name, int id, int vid, int pid,
56            byte[] descriptor, MessageQueue queue, DeviceCallback callback);
57    private static native void nativeSendReport(long ptr, byte[] data);
58    private static native void nativeCloseDevice(long ptr);
59
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
80    public void sendReport(byte[] report) {
81        Message msg = mHandler.obtainMessage(MSG_SEND_REPORT, report);
82        mHandler.sendMessageAtTime(msg, mEventTime);
83    }
84
85    public void addDelay(int delay) {
86        mEventTime += delay;
87    }
88
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
104        public DeviceHandler(Looper looper) {
105            super(looper);
106        }
107
108        @Override
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
142        public void pauseEvents() {
143            mBarrierToken = getLooper().myQueue().postSyncBarrier();
144        }
145
146        public void resumeEvents() {
147            getLooper().myQueue().removeSyncBarrier(mBarrierToken);
148            mBarrierToken = 0;
149        }
150    }
151
152    private class DeviceCallback {
153        public void onDeviceOpen() {
154            mHandler.resumeEvents();
155        }
156
157        public void onDeviceError() {
158            Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
159            msg.setAsynchronous(true);
160            msg.sendToTarget();
161        }
162    }
163}
164