package com.limelight.binding.input.driver; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import com.limelight.LimeLog; import com.limelight.binding.video.MediaCodecHelper; import java.nio.ByteBuffer; import java.nio.ByteOrder; public abstract class AbstractXboxController extends AbstractController { protected final UsbDevice device; protected final UsbDeviceConnection connection; private Thread inputThread; private boolean stopped; protected UsbEndpoint inEndpt, outEndpt; public AbstractXboxController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) { super(deviceId, listener); this.device = device; this.connection = connection; } private Thread createInputThread() { return new Thread() { public void run() { while (!isInterrupted() && !stopped) { byte[] buffer = new byte[64]; int res; // // There's no way that I can tell to determine if a device has failed // or if the timeout has simply expired. We'll check how long the transfer // took to fail and assume the device failed if it happened before the timeout // expired. // do { // Read the next input state packet long lastMillis = MediaCodecHelper.getMonotonicMillis(); res = connection.bulkTransfer(inEndpt, buffer, buffer.length, 3000); // If we get a zero length response, treat it as an error if (res == 0) { res = -1; } if (res == -1 && MediaCodecHelper.getMonotonicMillis() - lastMillis < 1000) { LimeLog.warning("Detected device I/O error"); AbstractXboxController.this.stop(); break; } } while (res == -1 && !isInterrupted() && !stopped); if (res == -1 || stopped) { break; } if (handleRead(ByteBuffer.wrap(buffer, 0, res).order(ByteOrder.LITTLE_ENDIAN))) { // Report input if handleRead() returns true reportInput(); } } } }; } public boolean start() { // Force claim all interfaces for (int i = 0; i < device.getInterfaceCount(); i++) { UsbInterface iface = device.getInterface(i); if (!connection.claimInterface(iface, true)) { LimeLog.warning("Failed to claim interfaces"); return false; } } // Find the endpoints UsbInterface iface = device.getInterface(0); for (int i = 0; i < iface.getEndpointCount(); i++) { UsbEndpoint endpt = iface.getEndpoint(i); if (endpt.getDirection() == UsbConstants.USB_DIR_IN) { if (inEndpt != null) { LimeLog.warning("Found duplicate IN endpoint"); return false; } inEndpt = endpt; } else if (endpt.getDirection() == UsbConstants.USB_DIR_OUT) { if (outEndpt != null) { LimeLog.warning("Found duplicate OUT endpoint"); return false; } outEndpt = endpt; } } // Make sure the required endpoints were present if (inEndpt == null || outEndpt == null) { LimeLog.warning("Missing required endpoint"); return false; } // Run the init function if (!doInit()) { return false; } // Report that we're added _before_ starting the input thread notifyDeviceAdded(); // Start listening for controller input inputThread = createInputThread(); inputThread.start(); return true; } public void stop() { if (stopped) { return; } stopped = true; // Stop the input thread if (inputThread != null) { inputThread.interrupt(); inputThread = null; } // Close the USB connection connection.close(); // Report the device removed notifyDeviceRemoved(); } protected abstract boolean handleRead(ByteBuffer buffer); protected abstract boolean doInit(); }