added optional button to restart game (door, panel and touch variants)
[rocksndiamonds.git] / build-projects / android / app / src / main / java / org / libsdl / app / HIDDeviceManager.java
1 package org.libsdl.app;
2
3 import android.app.Activity;
4 import android.app.AlertDialog;
5 import android.app.PendingIntent;
6 import android.bluetooth.BluetoothAdapter;
7 import android.bluetooth.BluetoothDevice;
8 import android.bluetooth.BluetoothManager;
9 import android.bluetooth.BluetoothProfile;
10 import android.os.Build;
11 import android.util.Log;
12 import android.content.BroadcastReceiver;
13 import android.content.Context;
14 import android.content.DialogInterface;
15 import android.content.Intent;
16 import android.content.IntentFilter;
17 import android.content.SharedPreferences;
18 import android.content.pm.PackageManager;
19 import android.hardware.usb.*;
20 import android.os.Handler;
21 import android.os.Looper;
22
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
27
28 public class HIDDeviceManager {
29     private static final String TAG = "hidapi";
30     private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
31
32     private static HIDDeviceManager sManager;
33     private static int sManagerRefCount = 0;
34
35     public static HIDDeviceManager acquire(Context context) {
36         if (sManagerRefCount == 0) {
37             sManager = new HIDDeviceManager(context);
38         }
39         ++sManagerRefCount;
40         return sManager;
41     }
42
43     public static void release(HIDDeviceManager manager) {
44         if (manager == sManager) {
45             --sManagerRefCount;
46             if (sManagerRefCount == 0) {
47                 sManager.close();
48                 sManager = null;
49             }
50         }
51     }
52
53     private Context mContext;
54     private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
55     private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
56     private int mNextDeviceId = 0;
57     private SharedPreferences mSharedPreferences = null;
58     private boolean mIsChromebook = false;
59     private UsbManager mUsbManager;
60     private Handler mHandler;
61     private BluetoothManager mBluetoothManager;
62     private List<BluetoothDevice> mLastBluetoothDevices;
63
64     private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
65         @Override
66         public void onReceive(Context context, Intent intent) {
67             String action = intent.getAction();
68             if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
69                 UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
70                 handleUsbDeviceAttached(usbDevice);
71             } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
72                 UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
73                 handleUsbDeviceDetached(usbDevice);
74             } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
75                 UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
76                 handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
77             }
78         }
79     };
80
81     private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
82         @Override
83         public void onReceive(Context context, Intent intent) {
84             String action = intent.getAction();
85             // Bluetooth device was connected. If it was a Steam Controller, handle it
86             if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
87                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
88                 Log.d(TAG, "Bluetooth device connected: " + device);
89
90                 if (isSteamController(device)) {
91                     connectBluetoothDevice(device);
92                 }
93             }
94
95             // Bluetooth device was disconnected, remove from controller manager (if any)
96             if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
97                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
98                 Log.d(TAG, "Bluetooth device disconnected: " + device);
99
100                 disconnectBluetoothDevice(device);
101             }
102         }
103     };
104
105     private HIDDeviceManager(final Context context) {
106         mContext = context;
107
108         HIDDeviceRegisterCallback();
109
110         mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
111         mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
112
113 //        if (shouldClear) {
114 //            SharedPreferences.Editor spedit = mSharedPreferences.edit();
115 //            spedit.clear();
116 //            spedit.commit();
117 //        }
118 //        else
119         {
120             mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
121         }
122     }
123
124     public Context getContext() {
125         return mContext;
126     }
127
128     public int getDeviceIDForIdentifier(String identifier) {
129         SharedPreferences.Editor spedit = mSharedPreferences.edit();
130
131         int result = mSharedPreferences.getInt(identifier, 0);
132         if (result == 0) {
133             result = mNextDeviceId++;
134             spedit.putInt("next_device_id", mNextDeviceId);
135         }
136
137         spedit.putInt(identifier, result);
138         spedit.commit();
139         return result;
140     }
141
142     private void initializeUSB() {
143         mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
144         if (mUsbManager == null) {
145             return;
146         }
147
148         /*
149         // Logging
150         for (UsbDevice device : mUsbManager.getDeviceList().values()) {
151             Log.i(TAG,"Path: " + device.getDeviceName());
152             Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
153             Log.i(TAG,"Product: " + device.getProductName());
154             Log.i(TAG,"ID: " + device.getDeviceId());
155             Log.i(TAG,"Class: " + device.getDeviceClass());
156             Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
157             Log.i(TAG,"Vendor ID " + device.getVendorId());
158             Log.i(TAG,"Product ID: " + device.getProductId());
159             Log.i(TAG,"Interface count: " + device.getInterfaceCount());
160             Log.i(TAG,"---------------------------------------");
161
162             // Get interface details
163             for (int index = 0; index < device.getInterfaceCount(); index++) {
164                 UsbInterface mUsbInterface = device.getInterface(index);
165                 Log.i(TAG,"  *****     *****");
166                 Log.i(TAG,"  Interface index: " + index);
167                 Log.i(TAG,"  Interface ID: " + mUsbInterface.getId());
168                 Log.i(TAG,"  Interface class: " + mUsbInterface.getInterfaceClass());
169                 Log.i(TAG,"  Interface subclass: " + mUsbInterface.getInterfaceSubclass());
170                 Log.i(TAG,"  Interface protocol: " + mUsbInterface.getInterfaceProtocol());
171                 Log.i(TAG,"  Endpoint count: " + mUsbInterface.getEndpointCount());
172
173                 // Get endpoint details 
174                 for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
175                 {
176                     UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
177                     Log.i(TAG,"    ++++   ++++   ++++");
178                     Log.i(TAG,"    Endpoint index: " + epi);
179                     Log.i(TAG,"    Attributes: " + mEndpoint.getAttributes());
180                     Log.i(TAG,"    Direction: " + mEndpoint.getDirection());
181                     Log.i(TAG,"    Number: " + mEndpoint.getEndpointNumber());
182                     Log.i(TAG,"    Interval: " + mEndpoint.getInterval());
183                     Log.i(TAG,"    Packet size: " + mEndpoint.getMaxPacketSize());
184                     Log.i(TAG,"    Type: " + mEndpoint.getType());
185                 }
186             }
187         }
188         Log.i(TAG," No more devices connected.");
189         */
190
191         // Register for USB broadcasts and permission completions
192         IntentFilter filter = new IntentFilter();
193         filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
194         filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
195         filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
196         mContext.registerReceiver(mUsbBroadcast, filter);
197
198         for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
199             handleUsbDeviceAttached(usbDevice);
200         }
201     }
202
203     UsbManager getUSBManager() {
204         return mUsbManager;
205     }
206
207     private void shutdownUSB() {
208         try {
209             mContext.unregisterReceiver(mUsbBroadcast);
210         } catch (Exception e) {
211             // We may not have registered, that's okay
212         }
213     }
214
215     private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
216         if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
217             return true;
218         }
219         if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
220             return true;
221         }
222         return false;
223     }
224
225     private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
226         final int XB360_IFACE_SUBCLASS = 93;
227         final int XB360_IFACE_PROTOCOL = 1; // Wired
228         final int XB360W_IFACE_PROTOCOL = 129; // Wireless
229         final int[] SUPPORTED_VENDORS = {
230             0x0079, // GPD Win 2
231             0x044f, // Thrustmaster
232             0x045e, // Microsoft
233             0x046d, // Logitech
234             0x056e, // Elecom
235             0x06a3, // Saitek
236             0x0738, // Mad Catz
237             0x07ff, // Mad Catz
238             0x0e6f, // PDP
239             0x0f0d, // Hori
240             0x1038, // SteelSeries
241             0x11c9, // Nacon
242             0x12ab, // Unknown
243             0x1430, // RedOctane
244             0x146b, // BigBen
245             0x1532, // Razer Sabertooth
246             0x15e4, // Numark
247             0x162e, // Joytech
248             0x1689, // Razer Onza
249             0x1949, // Lab126, Inc.
250             0x1bad, // Harmonix
251             0x24c6, // PowerA
252         };
253
254         if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
255             usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
256             (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||
257              usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {
258             int vendor_id = usbDevice.getVendorId();
259             for (int supportedVid : SUPPORTED_VENDORS) {
260                 if (vendor_id == supportedVid) {
261                     return true;
262                 }
263             }
264         }
265         return false;
266     }
267
268     private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
269         final int XB1_IFACE_SUBCLASS = 71;
270         final int XB1_IFACE_PROTOCOL = 208;
271         final int[] SUPPORTED_VENDORS = {
272             0x045e, // Microsoft
273             0x0738, // Mad Catz
274             0x0e6f, // PDP
275             0x0f0d, // Hori
276             0x1532, // Razer Wildcat
277             0x24c6, // PowerA
278             0x2e24, // Hyperkin
279         };
280
281         if (usbInterface.getId() == 0 &&
282             usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
283             usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
284             usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
285             int vendor_id = usbDevice.getVendorId();
286             for (int supportedVid : SUPPORTED_VENDORS) {
287                 if (vendor_id == supportedVid) {
288                     return true;
289                 }
290             }
291         }
292         return false;
293     }
294
295     private void handleUsbDeviceAttached(UsbDevice usbDevice) {
296         connectHIDDeviceUSB(usbDevice);
297     }
298
299     private void handleUsbDeviceDetached(UsbDevice usbDevice) {
300         List<Integer> devices = new ArrayList<Integer>();
301         for (HIDDevice device : mDevicesById.values()) {
302             if (usbDevice.equals(device.getDevice())) {
303                 devices.add(device.getId());
304             }
305         }
306         for (int id : devices) {
307             HIDDevice device = mDevicesById.get(id);
308             mDevicesById.remove(id);
309             device.shutdown();
310             HIDDeviceDisconnected(id);
311         }
312     }
313
314     private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
315         for (HIDDevice device : mDevicesById.values()) {
316             if (usbDevice.equals(device.getDevice())) {
317                 boolean opened = false;
318                 if (permission_granted) {
319                     opened = device.open();
320                 }
321                 HIDDeviceOpenResult(device.getId(), opened);
322             }
323         }
324     }
325
326     private void connectHIDDeviceUSB(UsbDevice usbDevice) {
327         synchronized (this) {
328             int interface_mask = 0;
329             for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
330                 UsbInterface usbInterface = usbDevice.getInterface(interface_index);
331                 if (isHIDDeviceInterface(usbDevice, usbInterface)) {
332                     // Check to see if we've already added this interface
333                     // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
334                     int interface_id = usbInterface.getId();
335                     if ((interface_mask & (1 << interface_id)) != 0) {
336                         continue;
337                     }
338                     interface_mask |= (1 << interface_id);
339
340                     HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
341                     int id = device.getId();
342                     mDevicesById.put(id, device);
343                     HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
344                 }
345             }
346         }
347     }
348
349     private void initializeBluetooth() {
350         Log.d(TAG, "Initializing Bluetooth");
351
352         if (Build.VERSION.SDK_INT <= 30 &&
353             mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
354             Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
355             return;
356         }
357
358         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) {
359             Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
360             return;
361         }
362
363         // Find bonded bluetooth controllers and create SteamControllers for them
364         mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
365         if (mBluetoothManager == null) {
366             // This device doesn't support Bluetooth.
367             return;
368         }
369
370         BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
371         if (btAdapter == null) {
372             // This device has Bluetooth support in the codebase, but has no available adapters.
373             return;
374         }
375
376         // Get our bonded devices.
377         for (BluetoothDevice device : btAdapter.getBondedDevices()) {
378
379             Log.d(TAG, "Bluetooth device available: " + device);
380             if (isSteamController(device)) {
381                 connectBluetoothDevice(device);
382             }
383
384         }
385
386         // NOTE: These don't work on Chromebooks, to my undying dismay.
387         IntentFilter filter = new IntentFilter();
388         filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
389         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
390         mContext.registerReceiver(mBluetoothBroadcast, filter);
391
392         if (mIsChromebook) {
393             mHandler = new Handler(Looper.getMainLooper());
394             mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
395
396             // final HIDDeviceManager finalThis = this;
397             // mHandler.postDelayed(new Runnable() {
398             //     @Override
399             //     public void run() {
400             //         finalThis.chromebookConnectionHandler();
401             //     }
402             // }, 5000);
403         }
404     }
405
406     private void shutdownBluetooth() {
407         try {
408             mContext.unregisterReceiver(mBluetoothBroadcast);
409         } catch (Exception e) {
410             // We may not have registered, that's okay
411         }
412     }
413
414     // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
415     // This function provides a sort of dummy version of that, watching for changes in the
416     // connected devices and attempting to add controllers as things change.
417     public void chromebookConnectionHandler() {
418         if (!mIsChromebook) {
419             return;
420         }
421
422         ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
423         ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
424
425         List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
426
427         for (BluetoothDevice bluetoothDevice : currentConnected) {
428             if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
429                 connected.add(bluetoothDevice);
430             }
431         }
432         for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
433             if (!currentConnected.contains(bluetoothDevice)) {
434                 disconnected.add(bluetoothDevice);
435             }
436         }
437
438         mLastBluetoothDevices = currentConnected;
439
440         for (BluetoothDevice bluetoothDevice : disconnected) {
441             disconnectBluetoothDevice(bluetoothDevice);
442         }
443         for (BluetoothDevice bluetoothDevice : connected) {
444             connectBluetoothDevice(bluetoothDevice);
445         }
446
447         final HIDDeviceManager finalThis = this;
448         mHandler.postDelayed(new Runnable() {
449             @Override
450             public void run() {
451                 finalThis.chromebookConnectionHandler();
452             }
453         }, 10000);
454     }
455
456     public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
457         Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
458         synchronized (this) {
459             if (mBluetoothDevices.containsKey(bluetoothDevice)) {
460                 Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
461
462                 HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
463                 device.reconnect();
464
465                 return false;
466             }
467             HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
468             int id = device.getId();
469             mBluetoothDevices.put(bluetoothDevice, device);
470             mDevicesById.put(id, device);
471
472             // The Steam Controller will mark itself connected once initialization is complete
473         }
474         return true;
475     }
476
477     public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
478         synchronized (this) {
479             HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
480             if (device == null)
481                 return;
482
483             int id = device.getId();
484             mBluetoothDevices.remove(bluetoothDevice);
485             mDevicesById.remove(id);
486             device.shutdown();
487             HIDDeviceDisconnected(id);
488         }
489     }
490
491     public boolean isSteamController(BluetoothDevice bluetoothDevice) {
492         // Sanity check.  If you pass in a null device, by definition it is never a Steam Controller.
493         if (bluetoothDevice == null) {
494             return false;
495         }
496
497         // If the device has no local name, we really don't want to try an equality check against it.
498         if (bluetoothDevice.getName() == null) {
499             return false;
500         }
501
502         return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
503     }
504
505     private void close() {
506         shutdownUSB();
507         shutdownBluetooth();
508         synchronized (this) {
509             for (HIDDevice device : mDevicesById.values()) {
510                 device.shutdown();
511             }
512             mDevicesById.clear();
513             mBluetoothDevices.clear();
514             HIDDeviceReleaseCallback();
515         }
516     }
517
518     public void setFrozen(boolean frozen) {
519         synchronized (this) {
520             for (HIDDevice device : mDevicesById.values()) {
521                 device.setFrozen(frozen);
522             }
523         }        
524     }
525
526     //////////////////////////////////////////////////////////////////////////////////////////////////////
527     //////////////////////////////////////////////////////////////////////////////////////////////////////
528     //////////////////////////////////////////////////////////////////////////////////////////////////////
529
530     private HIDDevice getDevice(int id) {
531         synchronized (this) {
532             HIDDevice result = mDevicesById.get(id);
533             if (result == null) {
534                 Log.v(TAG, "No device for id: " + id);
535                 Log.v(TAG, "Available devices: " + mDevicesById.keySet());
536             }
537             return result;
538         }
539     }
540
541     //////////////////////////////////////////////////////////////////////////////////////////////////////
542     ////////// JNI interface functions
543     //////////////////////////////////////////////////////////////////////////////////////////////////////
544
545     public boolean initialize(boolean usb, boolean bluetooth) {
546         Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
547
548         if (usb) {
549             initializeUSB();
550         }
551         if (bluetooth) {
552             initializeBluetooth();
553         }
554         return true;
555     }
556
557     public boolean openDevice(int deviceID) {
558         Log.v(TAG, "openDevice deviceID=" + deviceID);
559         HIDDevice device = getDevice(deviceID);
560         if (device == null) {
561             HIDDeviceDisconnected(deviceID);
562             return false;
563         }
564
565         // Look to see if this is a USB device and we have permission to access it
566         UsbDevice usbDevice = device.getDevice();
567         if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
568             HIDDeviceOpenPending(deviceID);
569             try {
570                 final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
571                 int flags;
572                 if (Build.VERSION.SDK_INT >= 31) {
573                     flags = FLAG_MUTABLE;
574                 } else {
575                     flags = 0;
576                 }
577                 mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
578             } catch (Exception e) {
579                 Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
580                 HIDDeviceOpenResult(deviceID, false);
581             }
582             return false;
583         }
584
585         try {
586             return device.open();
587         } catch (Exception e) {
588             Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
589         }
590         return false;
591     }
592
593     public int sendOutputReport(int deviceID, byte[] report) {
594         try {
595             //Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
596             HIDDevice device;
597             device = getDevice(deviceID);
598             if (device == null) {
599                 HIDDeviceDisconnected(deviceID);
600                 return -1;
601             }
602
603             return device.sendOutputReport(report);
604         } catch (Exception e) {
605             Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
606         }
607         return -1;
608     }
609
610     public int sendFeatureReport(int deviceID, byte[] report) {
611         try {
612             //Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
613             HIDDevice device;
614             device = getDevice(deviceID);
615             if (device == null) {
616                 HIDDeviceDisconnected(deviceID);
617                 return -1;
618             }
619
620             return device.sendFeatureReport(report);
621         } catch (Exception e) {
622             Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
623         }
624         return -1;
625     }
626
627     public boolean getFeatureReport(int deviceID, byte[] report) {
628         try {
629             //Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
630             HIDDevice device;
631             device = getDevice(deviceID);
632             if (device == null) {
633                 HIDDeviceDisconnected(deviceID);
634                 return false;
635             }
636
637             return device.getFeatureReport(report);
638         } catch (Exception e) {
639             Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
640         }
641         return false;
642     }
643
644     public void closeDevice(int deviceID) {
645         try {
646             Log.v(TAG, "closeDevice deviceID=" + deviceID);
647             HIDDevice device;
648             device = getDevice(deviceID);
649             if (device == null) {
650                 HIDDeviceDisconnected(deviceID);
651                 return;
652             }
653
654             device.close();
655         } catch (Exception e) {
656             Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
657         }
658     }
659
660
661     //////////////////////////////////////////////////////////////////////////////////////////////////////
662     /////////////// Native methods
663     //////////////////////////////////////////////////////////////////////////////////////////////////////
664
665     private native void HIDDeviceRegisterCallback();
666     private native void HIDDeviceReleaseCallback();
667
668     native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
669     native void HIDDeviceOpenPending(int deviceID);
670     native void HIDDeviceOpenResult(int deviceID, boolean opened);
671     native void HIDDeviceDisconnected(int deviceID);
672
673     native void HIDDeviceInputReport(int deviceID, byte[] report);
674     native void HIDDeviceFeatureReport(int deviceID, byte[] report);
675 }