| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.commands.hid; |
| |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.MessageQueue; |
| import android.os.SystemClock; |
| import android.util.Log; |
| |
| import com.android.internal.os.SomeArgs; |
| |
| public class Device { |
| private static final String TAG = "HidDevice"; |
| |
| // Minimum amount of time to wait before sending input events to a device. Even though we're |
| // guaranteed that the device has been created and opened by the input system, there's still a |
| // window in which the system hasn't started reading events out of it. If a stream of events |
| // begins in during this window (like a button down event) and *then* we start reading, we're |
| // liable to ignore the whole stream. |
| private static final int MIN_WAIT_FOR_FIRST_EVENT = 150; |
| |
| private static final int MSG_OPEN_DEVICE = 1; |
| private static final int MSG_SEND_REPORT = 2; |
| private static final int MSG_CLOSE_DEVICE = 3; |
| |
| |
| private final int mId; |
| private final HandlerThread mThread; |
| private final DeviceHandler mHandler; |
| private long mEventTime; |
| |
| private final Object mCond = new Object(); |
| |
| static { |
| System.loadLibrary("hidcommand_jni"); |
| } |
| |
| private static native long nativeOpenDevice(String name, int id, int vid, int pid, |
| byte[] descriptor, MessageQueue queue, DeviceCallback callback); |
| private static native void nativeSendReport(long ptr, byte[] data); |
| private static native void nativeCloseDevice(long ptr); |
| |
| public Device(int id, String name, int vid, int pid, byte[] descriptor, byte[] report) { |
| mId = id; |
| mThread = new HandlerThread("HidDeviceHandler"); |
| mThread.start(); |
| mHandler = new DeviceHandler(mThread.getLooper()); |
| SomeArgs args = SomeArgs.obtain(); |
| args.argi1 = id; |
| args.argi2 = vid; |
| args.argi3 = pid; |
| if (name != null) { |
| args.arg1 = name; |
| } else { |
| args.arg1 = id + ":" + vid + ":" + pid; |
| } |
| args.arg2 = descriptor; |
| args.arg3 = report; |
| mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget(); |
| mEventTime = SystemClock.uptimeMillis() + MIN_WAIT_FOR_FIRST_EVENT; |
| } |
| |
| public void sendReport(byte[] report) { |
| Message msg = mHandler.obtainMessage(MSG_SEND_REPORT, report); |
| mHandler.sendMessageAtTime(msg, mEventTime); |
| } |
| |
| public void addDelay(int delay) { |
| mEventTime += delay; |
| } |
| |
| public void close() { |
| Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE); |
| msg.setAsynchronous(true); |
| mHandler.sendMessageAtTime(msg, mEventTime + 1); |
| try { |
| synchronized (mCond) { |
| mCond.wait(); |
| } |
| } catch (InterruptedException ignore) {} |
| } |
| |
| private class DeviceHandler extends Handler { |
| private long mPtr; |
| private int mBarrierToken; |
| |
| public DeviceHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_OPEN_DEVICE: |
| SomeArgs args = (SomeArgs) msg.obj; |
| mPtr = nativeOpenDevice((String) args.arg1, args.argi1, args.argi2, args.argi3, |
| (byte[]) args.arg2, getLooper().myQueue(), new DeviceCallback()); |
| nativeSendReport(mPtr, (byte[]) args.arg3); |
| pauseEvents(); |
| break; |
| case MSG_SEND_REPORT: |
| if (mPtr != 0) { |
| nativeSendReport(mPtr, (byte[]) msg.obj); |
| } else { |
| Log.e(TAG, "Tried to send report to closed device."); |
| } |
| break; |
| case MSG_CLOSE_DEVICE: |
| if (mPtr != 0) { |
| nativeCloseDevice(mPtr); |
| getLooper().quitSafely(); |
| mPtr = 0; |
| } else { |
| Log.e(TAG, "Tried to close already closed device."); |
| } |
| synchronized (mCond) { |
| mCond.notify(); |
| } |
| break; |
| default: |
| throw new IllegalArgumentException("Unknown device message"); |
| } |
| } |
| |
| public void pauseEvents() { |
| mBarrierToken = getLooper().myQueue().postSyncBarrier(); |
| } |
| |
| public void resumeEvents() { |
| getLooper().myQueue().removeSyncBarrier(mBarrierToken); |
| mBarrierToken = 0; |
| } |
| } |
| |
| private class DeviceCallback { |
| public void onDeviceOpen() { |
| mHandler.resumeEvents(); |
| } |
| |
| public void onDeviceError() { |
| Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE); |
| msg.setAsynchronous(true); |
| msg.sendToTarget(); |
| } |
| } |
| } |