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 private static final int MSG_OPEN_DEVICE = 1; 33 private static final int MSG_SEND_REPORT = 2; 34 private static final int MSG_CLOSE_DEVICE = 3; 35 36 private final int mId; 37 private final HandlerThread mThread; 38 private final DeviceHandler mHandler; 39 private long mTimeToSend; 40 41 private final Object mCond = new Object(); 42 43 static { 44 System.loadLibrary("hidcommand_jni"); 45 } 46 47 private static native long nativeOpenDevice(String name, int id, int vid, int pid, 48 byte[] descriptor, DeviceCallback callback); 49 private static native void nativeSendReport(long ptr, byte[] data); 50 private static native void nativeCloseDevice(long ptr); 51 52 public Device(int id, String name, int vid, int pid, byte[] descriptor, byte[] report) { 53 mId = id; 54 mThread = new HandlerThread("HidDeviceHandler"); 55 mThread.start(); 56 mHandler = new DeviceHandler(mThread.getLooper()); 57 SomeArgs args = SomeArgs.obtain(); 58 args.argi1 = id; 59 args.argi2 = vid; 60 args.argi3 = pid; 61 if (name != null) { 62 args.arg1 = name; 63 } else { 64 args.arg1 = id + ":" + vid + ":" + pid; 65 } 66 args.arg2 = descriptor; 67 args.arg3 = report; 68 mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget(); 69 mTimeToSend = SystemClock.uptimeMillis(); 70 } 71 72 public void sendReport(byte[] report) { 73 Message msg = mHandler.obtainMessage(MSG_SEND_REPORT, report); 74 // if two messages are sent at identical time, they will be processed in order received 75 mHandler.sendMessageAtTime(msg, mTimeToSend); 76 } 77 78 public void addDelay(int delay) { 79 mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay; 80 } 81 82 public void close() { 83 Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE); 84 mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1); 85 try { 86 synchronized (mCond) { 87 mCond.wait(); 88 } 89 } catch (InterruptedException ignore) {} 90 } 91 92 private class DeviceHandler extends Handler { 93 private long mPtr; 94 private int mBarrierToken; 95 96 public DeviceHandler(Looper looper) { 97 super(looper); 98 } 99 100 @Override 101 public void handleMessage(Message msg) { 102 switch (msg.what) { 103 case MSG_OPEN_DEVICE: 104 SomeArgs args = (SomeArgs) msg.obj; 105 mPtr = nativeOpenDevice((String) args.arg1, args.argi1, args.argi2, args.argi3, 106 (byte[]) args.arg2, new DeviceCallback()); 107 pauseEvents(); 108 break; 109 case MSG_SEND_REPORT: 110 if (mPtr != 0) { 111 nativeSendReport(mPtr, (byte[]) msg.obj); 112 } else { 113 Log.e(TAG, "Tried to send report to closed device."); 114 } 115 break; 116 case MSG_CLOSE_DEVICE: 117 if (mPtr != 0) { 118 nativeCloseDevice(mPtr); 119 getLooper().quitSafely(); 120 mPtr = 0; 121 } else { 122 Log.e(TAG, "Tried to close already closed device."); 123 } 124 synchronized (mCond) { 125 mCond.notify(); 126 } 127 break; 128 default: 129 throw new IllegalArgumentException("Unknown device message"); 130 } 131 } 132 133 public void pauseEvents() { 134 mBarrierToken = getLooper().myQueue().postSyncBarrier(); 135 } 136 137 public void resumeEvents() { 138 getLooper().myQueue().removeSyncBarrier(mBarrierToken); 139 mBarrierToken = 0; 140 } 141 } 142 143 private class DeviceCallback { 144 public void onDeviceOpen() { 145 mHandler.resumeEvents(); 146 } 147 148 public void onDeviceError() { 149 Log.e(TAG, "Device error occurred, closing /dev/uhid"); 150 Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE); 151 msg.setAsynchronous(true); 152 msg.sendToTarget(); 153 } 154 } 155 } 156