added optional button to restart game (door, panel and touch variants)
[rocksndiamonds.git] / build-projects / android / app / src / main / java / org / libsdl / app / HIDDeviceUSB.java
1 package org.libsdl.app;
2
3 import android.hardware.usb.*;
4 import android.os.Build;
5 import android.util.Log;
6 import java.util.Arrays;
7
8 class HIDDeviceUSB implements HIDDevice {
9
10     private static final String TAG = "hidapi";
11
12     protected HIDDeviceManager mManager;
13     protected UsbDevice mDevice;
14     protected int mInterfaceIndex;
15     protected int mInterface;
16     protected int mDeviceId;
17     protected UsbDeviceConnection mConnection;
18     protected UsbEndpoint mInputEndpoint;
19     protected UsbEndpoint mOutputEndpoint;
20     protected InputThread mInputThread;
21     protected boolean mRunning;
22     protected boolean mFrozen;
23
24     public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
25         mManager = manager;
26         mDevice = usbDevice;
27         mInterfaceIndex = interface_index;
28         mInterface = mDevice.getInterface(mInterfaceIndex).getId();
29         mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
30         mRunning = false;
31     }
32
33     public String getIdentifier() {
34         return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
35     }
36
37     @Override
38     public int getId() {
39         return mDeviceId;
40     }
41
42     @Override
43     public int getVendorId() {
44         return mDevice.getVendorId();
45     }
46
47     @Override
48     public int getProductId() {
49         return mDevice.getProductId();
50     }
51
52     @Override
53     public String getSerialNumber() {
54         String result = null;
55         if (Build.VERSION.SDK_INT >= 21) {
56             try {
57                 result = mDevice.getSerialNumber();
58             }
59             catch (SecurityException exception) {
60                 //Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
61             }
62         }
63         if (result == null) {
64             result = "";
65         }
66         return result;
67     }
68
69     @Override
70     public int getVersion() {
71         return 0;
72     }
73
74     @Override
75     public String getManufacturerName() {
76         String result = null;
77         if (Build.VERSION.SDK_INT >= 21) {
78             result = mDevice.getManufacturerName();
79         }
80         if (result == null) {
81             result = String.format("%x", getVendorId());
82         }
83         return result;
84     }
85
86     @Override
87     public String getProductName() {
88         String result = null;
89         if (Build.VERSION.SDK_INT >= 21) {
90             result = mDevice.getProductName();
91         }
92         if (result == null) {
93             result = String.format("%x", getProductId());
94         }
95         return result;
96     }
97
98     @Override
99     public UsbDevice getDevice() {
100         return mDevice;
101     }
102
103     public String getDeviceName() {
104         return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
105     }
106
107     @Override
108     public boolean open() {
109         mConnection = mManager.getUSBManager().openDevice(mDevice);
110         if (mConnection == null) {
111             Log.w(TAG, "Unable to open USB device " + getDeviceName());
112             return false;
113         }
114
115         // Force claim our interface
116         UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
117         if (!mConnection.claimInterface(iface, true)) {
118             Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
119             close();
120             return false;
121         }
122
123         // Find the endpoints
124         for (int j = 0; j < iface.getEndpointCount(); j++) {
125             UsbEndpoint endpt = iface.getEndpoint(j);
126             switch (endpt.getDirection()) {
127             case UsbConstants.USB_DIR_IN:
128                 if (mInputEndpoint == null) {
129                     mInputEndpoint = endpt;
130                 }
131                 break;
132             case UsbConstants.USB_DIR_OUT:
133                 if (mOutputEndpoint == null) {
134                     mOutputEndpoint = endpt;
135                 }
136                 break;
137             }
138         }
139
140         // Make sure the required endpoints were present
141         if (mInputEndpoint == null || mOutputEndpoint == null) {
142             Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
143             close();
144             return false;
145         }
146
147         // Start listening for input
148         mRunning = true;
149         mInputThread = new InputThread();
150         mInputThread.start();
151
152         return true;
153     }
154
155     @Override
156     public int sendFeatureReport(byte[] report) {
157         int res = -1;
158         int offset = 0;
159         int length = report.length;
160         boolean skipped_report_id = false;
161         byte report_number = report[0];
162
163         if (report_number == 0x0) {
164             ++offset;
165             --length;
166             skipped_report_id = true;
167         }
168
169         res = mConnection.controlTransfer(
170             UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
171             0x09/*HID set_report*/,
172             (3/*HID feature*/ << 8) | report_number,
173             mInterface,
174             report, offset, length,
175             1000/*timeout millis*/);
176
177         if (res < 0) {
178             Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
179             return -1;
180         }
181
182         if (skipped_report_id) {
183             ++length;
184         }
185         return length;
186     }
187
188     @Override
189     public int sendOutputReport(byte[] report) {
190         int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
191         if (r != report.length) {
192             Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
193         }
194         return r;
195     }
196
197     @Override
198     public boolean getFeatureReport(byte[] report) {
199         int res = -1;
200         int offset = 0;
201         int length = report.length;
202         boolean skipped_report_id = false;
203         byte report_number = report[0];
204
205         if (report_number == 0x0) {
206             /* Offset the return buffer by 1, so that the report ID
207                will remain in byte 0. */
208             ++offset;
209             --length;
210             skipped_report_id = true;
211         }
212
213         res = mConnection.controlTransfer(
214             UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
215             0x01/*HID get_report*/,
216             (3/*HID feature*/ << 8) | report_number,
217             mInterface,
218             report, offset, length,
219             1000/*timeout millis*/);
220
221         if (res < 0) {
222             Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
223             return false;
224         }
225
226         if (skipped_report_id) {
227             ++res;
228             ++length;
229         }
230
231         byte[] data;
232         if (res == length) {
233             data = report;
234         } else {
235             data = Arrays.copyOfRange(report, 0, res);
236         }
237         mManager.HIDDeviceFeatureReport(mDeviceId, data);
238
239         return true;
240     }
241
242     @Override
243     public void close() {
244         mRunning = false;
245         if (mInputThread != null) {
246             while (mInputThread.isAlive()) {
247                 mInputThread.interrupt();
248                 try {
249                     mInputThread.join();
250                 } catch (InterruptedException e) {
251                     // Keep trying until we're done
252                 }
253             }
254             mInputThread = null;
255         }
256         if (mConnection != null) {
257             UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
258             mConnection.releaseInterface(iface);
259             mConnection.close();
260             mConnection = null;
261         }
262     }
263
264     @Override
265     public void shutdown() {
266         close();
267         mManager = null;
268     }
269
270     @Override
271     public void setFrozen(boolean frozen) {
272         mFrozen = frozen;
273     }
274
275     protected class InputThread extends Thread {
276         @Override
277         public void run() {
278             int packetSize = mInputEndpoint.getMaxPacketSize();
279             byte[] packet = new byte[packetSize];
280             while (mRunning) {
281                 int r;
282                 try
283                 {
284                     r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
285                 }
286                 catch (Exception e)
287                 {
288                     Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
289                     break;
290                 }
291                 if (r < 0) {
292                     // Could be a timeout or an I/O error
293                 }
294                 if (r > 0) {
295                     byte[] data;
296                     if (r == packetSize) {
297                         data = packet;
298                     } else {
299                         data = Arrays.copyOfRange(packet, 0, r);
300                     }
301
302                     if (!mFrozen) {
303                         mManager.HIDDeviceInputReport(mDeviceId, data);
304                     }
305                 }
306             }
307         }
308     }
309 }