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