Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0dc14517cd | |||
| 04713c007b | |||
| 1cac7660b8 | |||
| edb286f9af | |||
| fb15ff99ca | |||
| a455e75e37 | |||
| 2b452e51f9 | |||
| 9d2b6f8854 | |||
| 3be10a1b59 | |||
| 01950c25a8 | |||
| 7ad1ebd0e8 | |||
| ee01a8b5a0 | |||
| 23c54f6813 | |||
| ceef4510fb |
+2
-2
@@ -11,8 +11,8 @@ android {
|
|||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
|
|
||||||
versionName "4.5"
|
versionName "4.5.2"
|
||||||
versionCode = 84
|
versionCode = 86
|
||||||
}
|
}
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
|
|||||||
@@ -12,17 +12,18 @@
|
|||||||
<uses-feature android:name="android.hardware.wifi" android:required="false" />
|
<uses-feature android:name="android.hardware.wifi" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
|
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
||||||
|
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:banner="@drawable/atv_banner"
|
||||||
android:theme="@style/AppTheme" >
|
android:theme="@style/AppTheme" >
|
||||||
|
|
||||||
<!-- Samsung multi-window support -->
|
<!-- Samsung multi-window support -->
|
||||||
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false" />
|
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false" />
|
||||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||||
|
|
||||||
<!-- Launcher for traditional devices -->
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".PcView"
|
android:name=".PcView"
|
||||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
||||||
@@ -30,22 +31,10 @@
|
|||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
<category android:name="tv.ouya.intent.category.APP" />
|
<category android:name="tv.ouya.intent.category.APP" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!-- Launcher for Android TV devices -->
|
|
||||||
<activity
|
|
||||||
android:name=".PcViewTv"
|
|
||||||
android:logo="@drawable/atv_banner"
|
|
||||||
android:icon="@drawable/atv_banner"
|
|
||||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".AppView"
|
android:name=".AppView"
|
||||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" >
|
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" >
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package com.limelight;
|
|||||||
import com.limelight.binding.PlatformBinding;
|
import com.limelight.binding.PlatformBinding;
|
||||||
import com.limelight.binding.input.ControllerHandler;
|
import com.limelight.binding.input.ControllerHandler;
|
||||||
import com.limelight.binding.input.KeyboardTranslator;
|
import com.limelight.binding.input.KeyboardTranslator;
|
||||||
|
import com.limelight.binding.input.NvMouseHelper;
|
||||||
import com.limelight.binding.input.TouchContext;
|
import com.limelight.binding.input.TouchContext;
|
||||||
import com.limelight.binding.input.driver.UsbDriverService;
|
import com.limelight.binding.input.driver.UsbDriverService;
|
||||||
import com.limelight.binding.input.evdev.EvdevHandler;
|
import com.limelight.binding.input.evdev.EvdevHandler;
|
||||||
@@ -422,11 +423,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private final Runnable toggleGrab = new Runnable() {
|
private final Runnable toggleGrab = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (evdevHandler != null) {
|
if (grabbedInput) {
|
||||||
if (grabbedInput) {
|
NvMouseHelper.setCursorVisibility(Game.this, true);
|
||||||
|
if (evdevHandler != null) {
|
||||||
evdevHandler.ungrabAll();
|
evdevHandler.ungrabAll();
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
|
else {
|
||||||
|
NvMouseHelper.setCursorVisibility(Game.this, false);
|
||||||
|
if (evdevHandler != null) {
|
||||||
evdevHandler.regrabAll();
|
evdevHandler.regrabAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -616,7 +621,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
||||||
{
|
{
|
||||||
// This case is for mice
|
// This case is for mice
|
||||||
if (event.getSource() == InputDevice.SOURCE_MOUSE)
|
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||||
|
(event.getPointerCount() >= 1 &&
|
||||||
|
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE))
|
||||||
{
|
{
|
||||||
int changedButtons = event.getButtonState() ^ lastButtonState;
|
int changedButtons = event.getButtonState() ^ lastButtonState;
|
||||||
|
|
||||||
@@ -653,13 +660,26 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// First process the history
|
// Get relative axis values if we can
|
||||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
if (NvMouseHelper.eventHasRelativeMouseAxes(event)) {
|
||||||
updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i));
|
// Send the deltas straight from the motion event
|
||||||
}
|
conn.sendMouseMove((short)NvMouseHelper.getRelativeAxisX(event),
|
||||||
|
(short)NvMouseHelper.getRelativeAxisY(event));
|
||||||
|
|
||||||
// Now process the current values
|
// We have to also update the position Android thinks the cursor is at
|
||||||
updateMousePosition((int)event.getX(), (int)event.getY());
|
// in order to avoid jumping when we stop moving or click.
|
||||||
|
lastMouseX = (int)event.getX();
|
||||||
|
lastMouseY = (int)event.getY();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// First process the history
|
||||||
|
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||||
|
updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now process the current values
|
||||||
|
updateMousePosition((int)event.getX(), (int)event.getY());
|
||||||
|
}
|
||||||
|
|
||||||
lastButtonState = event.getButtonState();
|
lastButtonState = event.getButtonState();
|
||||||
}
|
}
|
||||||
@@ -826,6 +846,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
evdevHandler.stop();
|
evdevHandler.stop();
|
||||||
evdevHandler = null;
|
evdevHandler = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable cursor visibility again
|
||||||
|
NvMouseHelper.setCursorVisibility(this, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -865,6 +888,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
connecting = false;
|
connecting = false;
|
||||||
connected = true;
|
connected = true;
|
||||||
|
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Hide the mouse cursor now. Doing it before
|
||||||
|
// dismissing the spinner seems to be undone
|
||||||
|
// when the spinner gets displayed.
|
||||||
|
NvMouseHelper.setCursorVisibility(Game.this, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
hideSystemUi(1000);
|
hideSystemUi(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.limelight;
|
|
||||||
|
|
||||||
/* This is a dummy class to allow for a separate icon
|
|
||||||
* and launcher for TV.
|
|
||||||
*/
|
|
||||||
public class PcViewTv extends PcView {}
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.limelight.binding.input;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.input.InputManager;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
// NVIDIA extended the Android input APIs with support for using an attached mouse in relative
|
||||||
|
// mode without having to grab the input device (which requires root). The data comes in the form
|
||||||
|
// of new AXIS_RELATIVE_X and AXIS_RELATIVE_Y constants in the mouse's MotionEvent objects and
|
||||||
|
// a new function, InputManager.setCursorVisibility(), that allows the cursor to be hidden.
|
||||||
|
//
|
||||||
|
// http://docs.nvidia.com/gameworks/index.html#technologies/mobile/game_controller_handling_mouse.htm
|
||||||
|
|
||||||
|
public class NvMouseHelper {
|
||||||
|
private static boolean nvExtensionSupported;
|
||||||
|
private static Method methodSetCursorVisibility;
|
||||||
|
private static int AXIS_RELATIVE_X;
|
||||||
|
private static int AXIS_RELATIVE_Y;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
methodSetCursorVisibility = InputManager.class.getMethod("setCursorVisibility", boolean.class);
|
||||||
|
|
||||||
|
Field fieldRelX = MotionEvent.class.getField("AXIS_RELATIVE_X");
|
||||||
|
Field fieldRelY = MotionEvent.class.getField("AXIS_RELATIVE_Y");
|
||||||
|
|
||||||
|
AXIS_RELATIVE_X = (Integer) fieldRelX.get(null);
|
||||||
|
AXIS_RELATIVE_Y = (Integer) fieldRelY.get(null);
|
||||||
|
|
||||||
|
nvExtensionSupported = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LimeLog.info("NvMouseHelper not supported");
|
||||||
|
nvExtensionSupported = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean setCursorVisibility(Context context, boolean visible) {
|
||||||
|
if (!nvExtensionSupported) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
methodSetCursorVisibility.invoke(context.getSystemService(Context.INPUT_SERVICE), visible);
|
||||||
|
return true;
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||||
|
if (!nvExtensionSupported) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return event.getAxisValue(AXIS_RELATIVE_X) != 0 ||
|
||||||
|
event.getAxisValue(AXIS_RELATIVE_Y) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float getRelativeAxisX(MotionEvent event) {
|
||||||
|
return event.getAxisValue(AXIS_RELATIVE_X);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float getRelativeAxisY(MotionEvent event) {
|
||||||
|
return event.getAxisValue(AXIS_RELATIVE_Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.limelight.binding.input.driver;
|
||||||
|
|
||||||
|
import android.hardware.usb.UsbEndpoint;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.binding.video.MediaCodecHelper;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public abstract class AbstractController {
|
||||||
|
|
||||||
|
private final int deviceId;
|
||||||
|
|
||||||
|
private UsbDriverListener listener;
|
||||||
|
|
||||||
|
protected short buttonFlags;
|
||||||
|
protected float leftTrigger, rightTrigger;
|
||||||
|
protected float rightStickX, rightStickY;
|
||||||
|
protected float leftStickX, leftStickY;
|
||||||
|
|
||||||
|
public int getControllerId() {
|
||||||
|
return deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setButtonFlag(int buttonFlag, int data) {
|
||||||
|
if (data != 0) {
|
||||||
|
buttonFlags |= buttonFlag;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buttonFlags &= ~buttonFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void reportInput() {
|
||||||
|
listener.reportControllerState(deviceId, buttonFlags, leftStickX, leftStickY,
|
||||||
|
rightStickX, rightStickY, leftTrigger, rightTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean start();
|
||||||
|
public abstract void stop();
|
||||||
|
|
||||||
|
public AbstractController(int deviceId, UsbDriverListener listener) {
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void notifyDeviceRemoved() {
|
||||||
|
listener.deviceRemoved(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void notifyDeviceAdded() {
|
||||||
|
listener.deviceAdded(deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening for controller input
|
||||||
|
inputThread = createInputThread();
|
||||||
|
inputThread.start();
|
||||||
|
|
||||||
|
// Now report we're added
|
||||||
|
notifyDeviceAdded();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (stopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopped = true;
|
||||||
|
|
||||||
|
// Stop the input thread
|
||||||
|
if (inputThread != null) {
|
||||||
|
inputThread.interrupt();
|
||||||
|
inputThread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report the device removed
|
||||||
|
notifyDeviceRemoved();
|
||||||
|
|
||||||
|
// Close the USB connection
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean handleRead(ByteBuffer buffer);
|
||||||
|
protected abstract boolean doInit();
|
||||||
|
}
|
||||||
@@ -10,7 +10,9 @@ import android.hardware.usb.UsbDevice;
|
|||||||
import android.hardware.usb.UsbDeviceConnection;
|
import android.hardware.usb.UsbDeviceConnection;
|
||||||
import android.hardware.usb.UsbManager;
|
import android.hardware.usb.UsbManager;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
|
|
||||||
@@ -26,10 +28,10 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
||||||
private final UsbDriverBinder binder = new UsbDriverBinder();
|
private final UsbDriverBinder binder = new UsbDriverBinder();
|
||||||
|
|
||||||
private final ArrayList<XboxOneController> controllers = new ArrayList<>();
|
private final ArrayList<AbstractController> controllers = new ArrayList<>();
|
||||||
|
|
||||||
private UsbDriverListener listener;
|
private UsbDriverListener listener;
|
||||||
private static int nextDeviceId;
|
private int nextDeviceId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reportControllerState(int controllerId, short buttonFlags, float leftStickX, float leftStickY, float rightStickX, float rightStickY, float leftTrigger, float rightTrigger) {
|
public void reportControllerState(int controllerId, short buttonFlags, float leftStickX, float leftStickY, float rightStickX, float rightStickY, float leftTrigger, float rightTrigger) {
|
||||||
@@ -42,7 +44,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
@Override
|
@Override
|
||||||
public void deviceRemoved(int controllerId) {
|
public void deviceRemoved(int controllerId) {
|
||||||
// Remove the the controller from our list (if not removed already)
|
// Remove the the controller from our list (if not removed already)
|
||||||
for (XboxOneController controller : controllers) {
|
for (AbstractController controller : controllers) {
|
||||||
if (controller.getControllerId() == controllerId) {
|
if (controller.getControllerId() == controllerId) {
|
||||||
controllers.remove(controller);
|
controllers.remove(controller);
|
||||||
break;
|
break;
|
||||||
@@ -93,7 +95,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
|
|
||||||
// Report all controllerMap that already exist
|
// Report all controllerMap that already exist
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
for (XboxOneController controller : controllers) {
|
for (AbstractController controller : controllers) {
|
||||||
listener.deviceAdded(controller.getControllerId());
|
listener.deviceAdded(controller.getControllerId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +104,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
|
|
||||||
private void handleUsbDeviceState(UsbDevice device) {
|
private void handleUsbDeviceState(UsbDevice device) {
|
||||||
// Are we able to operate it?
|
// Are we able to operate it?
|
||||||
if (XboxOneController.canClaimDevice(device)) {
|
if (shouldClaimDevice(device)) {
|
||||||
// Do we have permission yet?
|
// Do we have permission yet?
|
||||||
if (!usbManager.hasPermission(device)) {
|
if (!usbManager.hasPermission(device)) {
|
||||||
// Let's ask for permission
|
// Let's ask for permission
|
||||||
@@ -117,8 +119,21 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to initialize it
|
|
||||||
XboxOneController controller = new XboxOneController(device, connection, nextDeviceId++, this);
|
AbstractController controller;
|
||||||
|
|
||||||
|
if (XboxOneController.canClaimDevice(device)) {
|
||||||
|
controller = new XboxOneController(device, connection, nextDeviceId++, this);
|
||||||
|
}
|
||||||
|
else if (Xbox360Controller.canClaimDevice(device)) {
|
||||||
|
controller = new Xbox360Controller(device, connection, nextDeviceId++, this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Unreachable
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the controller
|
||||||
if (!controller.start()) {
|
if (!controller.start()) {
|
||||||
connection.close();
|
connection.close();
|
||||||
return;
|
return;
|
||||||
@@ -129,6 +144,34 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isRecognizedInputDevice(UsbDevice device) {
|
||||||
|
// On KitKat and later, we can determine if this VID and PID combo
|
||||||
|
// matches an existing input device and defer to the built-in controller
|
||||||
|
// support in that case. Prior to KitKat, we'll always return true to be safe.
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
for (int id : InputDevice.getDeviceIds()) {
|
||||||
|
InputDevice inputDev = InputDevice.getDevice(id);
|
||||||
|
|
||||||
|
if (inputDev.getVendorId() == device.getVendorId() &&
|
||||||
|
inputDev.getProductId() == device.getProductId()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldClaimDevice(UsbDevice device) {
|
||||||
|
// We always bind to XB1 controllers but only bind to XB360 controllers
|
||||||
|
// if we know the kernel isn't already driving this device.
|
||||||
|
return XboxOneController.canClaimDevice(device) ||
|
||||||
|
(!isRecognizedInputDevice(device) && Xbox360Controller.canClaimDevice(device));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||||
@@ -141,7 +184,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
|
|
||||||
// Enumerate existing devices
|
// Enumerate existing devices
|
||||||
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
||||||
if (XboxOneController.canClaimDevice(dev)) {
|
if (shouldClaimDevice(dev)) {
|
||||||
// Start the process of claiming this device
|
// Start the process of claiming this device
|
||||||
handleUsbDeviceState(dev);
|
handleUsbDeviceState(dev);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package com.limelight.binding.input.driver;
|
||||||
|
|
||||||
|
import android.hardware.usb.UsbConstants;
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
import android.hardware.usb.UsbDeviceConnection;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.nvstream.input.ControllerPacket;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class Xbox360Controller extends AbstractXboxController {
|
||||||
|
|
||||||
|
// This list is taken from the Xpad driver in the Linux kernel.
|
||||||
|
// I've excluded the devices that aren't "controllers" in the traditional sense, but
|
||||||
|
// if people really want to use their dancepads or fight sticks with Moonlight, I can
|
||||||
|
// put them in.
|
||||||
|
private static final DeviceIdTuple[] supportedDeviceTuples = {
|
||||||
|
new DeviceIdTuple(0x045e, 0x028e, "Microsoft X-Box 360 pad"),
|
||||||
|
new DeviceIdTuple(0x044f, 0xb326, "Thrustmaster Gamepad GP XID"),
|
||||||
|
new DeviceIdTuple(0x046d, 0xc21d, "Logitech Gamepad F310"),
|
||||||
|
new DeviceIdTuple(0x046d, 0xc21e, "Logitech Gamepad F510"),
|
||||||
|
new DeviceIdTuple(0x046d, 0xc21f, "Logitech Gamepad F710"),
|
||||||
|
new DeviceIdTuple(0x046d, 0xc242, "Logitech Chillstream Controller"),
|
||||||
|
new DeviceIdTuple(0x0738, 0x4716, "Mad Catz Wired Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x0738, 0x4726, "Mad Catz Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x0738, 0xb726, "Mad Catz Xbox controller - MW2"),
|
||||||
|
new DeviceIdTuple(0x0738, 0xbeef, "Mad Catz JOYTECH NEO SE Advanced GamePad"),
|
||||||
|
new DeviceIdTuple(0x0738, 0xcb02, "Saitek Cyborg Rumble Pad - PC/Xbox 360"),
|
||||||
|
new DeviceIdTuple(0x0738, 0xcb03, "Saitek P3200 Rumble Pad - PC/Xbox 360"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x0113, "Afterglow AX.1 Gamepad for Xbox 360"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x0201, "Pelican PL-3601 'TSZ' Wired Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x0301, "Logic3 Controller"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x0401, "Logic3 Controller"),
|
||||||
|
new DeviceIdTuple(0x12ab, 0x0301, "PDP AFTERGLOW AX.1"),
|
||||||
|
new DeviceIdTuple(0x146b, 0x0601, "BigBen Interactive XBOX 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x1532, 0x0037, "Razer Sabertooth"),
|
||||||
|
new DeviceIdTuple(0x15e4, 0x3f00, "Power A Mini Pro Elite"),
|
||||||
|
new DeviceIdTuple(0x15e4, 0x3f0a, "Xbox Airflo wired controller"),
|
||||||
|
new DeviceIdTuple(0x15e4, 0x3f10, "Batarang Xbox 360 controller"),
|
||||||
|
new DeviceIdTuple(0x162e, 0xbeef, "Joytech Neo-Se Take2"),
|
||||||
|
new DeviceIdTuple(0x1689, 0xfd00, "Razer Onza Tournament Edition"),
|
||||||
|
new DeviceIdTuple(0x1689, 0xfd01, "Razer Onza Classic Edition"),
|
||||||
|
new DeviceIdTuple(0x24c6, 0x5d04, "Razer Sabertooth"),
|
||||||
|
new DeviceIdTuple(0x1bad, 0xf016, "Mad Catz Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x1bad, 0xf023, "MLG Pro Circuit Controller (Xbox)"),
|
||||||
|
new DeviceIdTuple(0x1bad, 0xf900, "Harmonix Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x1bad, 0xf901, "Gamestop Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x1bad, 0xf903, "Tron Xbox 360 controller"),
|
||||||
|
new DeviceIdTuple(0x24c6, 0x5300, "PowerA MINI PROEX Controller"),
|
||||||
|
new DeviceIdTuple(0x24c6, 0x5303, "Xbox Airflo wired controller"),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static boolean canClaimDevice(UsbDevice device) {
|
||||||
|
for (DeviceIdTuple tuple : supportedDeviceTuples) {
|
||||||
|
if (device.getVendorId() == tuple.vid && device.getProductId() == tuple.pid) {
|
||||||
|
LimeLog.info("XB360 can claim device: " + tuple.name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Xbox360Controller(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
|
||||||
|
super(device, connection, deviceId, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int unsignByte(byte b) {
|
||||||
|
if (b < 0) {
|
||||||
|
return b + 256;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean handleRead(ByteBuffer buffer) {
|
||||||
|
if (buffer.limit() < 14) {
|
||||||
|
LimeLog.severe("Read too small: "+buffer.limit());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip first short
|
||||||
|
buffer.position(buffer.position() + 2);
|
||||||
|
|
||||||
|
// DPAD
|
||||||
|
byte b = buffer.get();
|
||||||
|
setButtonFlag(ControllerPacket.LEFT_FLAG, b & 0x04);
|
||||||
|
setButtonFlag(ControllerPacket.RIGHT_FLAG, b & 0x08);
|
||||||
|
setButtonFlag(ControllerPacket.UP_FLAG, b & 0x01);
|
||||||
|
setButtonFlag(ControllerPacket.DOWN_FLAG, b & 0x02);
|
||||||
|
|
||||||
|
// Start/Select
|
||||||
|
setButtonFlag(ControllerPacket.PLAY_FLAG, b & 0x10);
|
||||||
|
setButtonFlag(ControllerPacket.BACK_FLAG, b & 0x20);
|
||||||
|
|
||||||
|
// LS/RS
|
||||||
|
setButtonFlag(ControllerPacket.LS_CLK_FLAG, b & 0x40);
|
||||||
|
setButtonFlag(ControllerPacket.RS_CLK_FLAG, b & 0x80);
|
||||||
|
|
||||||
|
// ABXY buttons
|
||||||
|
b = buffer.get();
|
||||||
|
setButtonFlag(ControllerPacket.A_FLAG, b & 0x10);
|
||||||
|
setButtonFlag(ControllerPacket.B_FLAG, b & 0x20);
|
||||||
|
setButtonFlag(ControllerPacket.X_FLAG, b & 0x40);
|
||||||
|
setButtonFlag(ControllerPacket.Y_FLAG, b & 0x80);
|
||||||
|
|
||||||
|
// LB/RB
|
||||||
|
setButtonFlag(ControllerPacket.LB_FLAG, b & 0x01);
|
||||||
|
setButtonFlag(ControllerPacket.RB_FLAG, b & 0x02);
|
||||||
|
|
||||||
|
// Xbox button
|
||||||
|
setButtonFlag(ControllerPacket.SPECIAL_BUTTON_FLAG, b & 0x04);
|
||||||
|
|
||||||
|
// Triggers
|
||||||
|
leftTrigger = unsignByte(buffer.get()) / 255.0f;
|
||||||
|
rightTrigger = unsignByte(buffer.get()) / 255.0f;
|
||||||
|
|
||||||
|
// Left stick
|
||||||
|
leftStickX = buffer.getShort() / 32767.0f;
|
||||||
|
leftStickY = ~buffer.getShort() / 32767.0f;
|
||||||
|
|
||||||
|
// Right stick
|
||||||
|
rightStickX = buffer.getShort() / 32767.0f;
|
||||||
|
rightStickY = ~buffer.getShort() / 32767.0f;
|
||||||
|
|
||||||
|
// Return true to send input
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sendLedCommand(byte command) {
|
||||||
|
byte[] commandBuffer = {0x01, 0x03, command};
|
||||||
|
|
||||||
|
int res = connection.bulkTransfer(outEndpt, commandBuffer, commandBuffer.length, 3000);
|
||||||
|
if (res != commandBuffer.length) {
|
||||||
|
LimeLog.warning("LED set transfer failed: "+res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doInit() {
|
||||||
|
// Turn the LED on corresponding to our device ID
|
||||||
|
sendLedCommand((byte)(2 + (getControllerId() % 4)));
|
||||||
|
|
||||||
|
// No need to fail init if the LED command fails
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DeviceIdTuple {
|
||||||
|
public final int vid;
|
||||||
|
public final int pid;
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
public DeviceIdTuple(int vid, int pid, String name) {
|
||||||
|
this.vid = vid;
|
||||||
|
this.pid = pid;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,19 +13,7 @@ import com.limelight.nvstream.input.ControllerPacket;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
public class XboxOneController {
|
public class XboxOneController extends AbstractXboxController {
|
||||||
private final UsbDevice device;
|
|
||||||
private final UsbDeviceConnection connection;
|
|
||||||
private final int deviceId;
|
|
||||||
|
|
||||||
private Thread inputThread;
|
|
||||||
private UsbDriverListener listener;
|
|
||||||
private boolean stopped;
|
|
||||||
|
|
||||||
private short buttonFlags;
|
|
||||||
private float leftTrigger, rightTrigger;
|
|
||||||
private float rightStickX, rightStickY;
|
|
||||||
private float leftStickX, leftStickY;
|
|
||||||
|
|
||||||
private static final int MICROSOFT_VID = 0x045e;
|
private static final int MICROSOFT_VID = 0x045e;
|
||||||
private static final int XB1_IFACE_SUBCLASS = 71;
|
private static final int XB1_IFACE_SUBCLASS = 71;
|
||||||
@@ -35,28 +23,7 @@ public class XboxOneController {
|
|||||||
private static final byte[] XB1_INIT_DATA = {0x05, 0x20, 0x00, 0x01, 0x00};
|
private static final byte[] XB1_INIT_DATA = {0x05, 0x20, 0x00, 0x01, 0x00};
|
||||||
|
|
||||||
public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
|
public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
|
||||||
this.device = device;
|
super(device, connection, deviceId, listener);
|
||||||
this.connection = connection;
|
|
||||||
this.deviceId = deviceId;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getControllerId() {
|
|
||||||
return this.deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setButtonFlag(int buttonFlag, int data) {
|
|
||||||
if (data != 0) {
|
|
||||||
buttonFlags |= buttonFlag;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
buttonFlags &= ~buttonFlag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reportInput() {
|
|
||||||
listener.reportControllerState(deviceId, buttonFlags, leftStickX, leftStickY,
|
|
||||||
rightStickX, rightStickY, leftTrigger, rightTrigger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processButtons(ByteBuffer buffer) {
|
private void processButtons(ByteBuffer buffer) {
|
||||||
@@ -90,143 +57,24 @@ public class XboxOneController {
|
|||||||
|
|
||||||
rightStickX = buffer.getShort() / 32767.0f;
|
rightStickX = buffer.getShort() / 32767.0f;
|
||||||
rightStickY = ~buffer.getShort() / 32767.0f;
|
rightStickY = ~buffer.getShort() / 32767.0f;
|
||||||
|
|
||||||
reportInput();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processPacket(ByteBuffer buffer) {
|
@Override
|
||||||
|
protected boolean handleRead(ByteBuffer buffer) {
|
||||||
switch (buffer.get())
|
switch (buffer.get())
|
||||||
{
|
{
|
||||||
case 0x20:
|
case 0x20:
|
||||||
buffer.position(buffer.position()+3);
|
buffer.position(buffer.position()+3);
|
||||||
processButtons(buffer);
|
processButtons(buffer);
|
||||||
break;
|
return true;
|
||||||
|
|
||||||
case 0x07:
|
case 0x07:
|
||||||
buffer.position(buffer.position() + 3);
|
buffer.position(buffer.position() + 3);
|
||||||
setButtonFlag(ControllerPacket.SPECIAL_BUTTON_FLAG, buffer.get() & 0x01);
|
setButtonFlag(ControllerPacket.SPECIAL_BUTTON_FLAG, buffer.get() & 0x01);
|
||||||
reportInput();
|
return true;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startInputThread(final UsbEndpoint inEndpt) {
|
|
||||||
inputThread = 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");
|
|
||||||
XboxOneController.this.stop();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (res == -1 && !isInterrupted() && !stopped);
|
|
||||||
|
|
||||||
if (res == -1 || stopped) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
processPacket(ByteBuffer.wrap(buffer, 0, res).order(ByteOrder.LITTLE_ENDIAN));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
inputThread.setName("Xbox One Controller - Input Thread");
|
|
||||||
inputThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
return false;
|
||||||
UsbEndpoint outEndpt = null;
|
|
||||||
UsbEndpoint inEndpt = null;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the initialization packet
|
|
||||||
int res = connection.bulkTransfer(outEndpt, XB1_INIT_DATA, XB1_INIT_DATA.length, 3000);
|
|
||||||
if (res != XB1_INIT_DATA.length) {
|
|
||||||
LimeLog.warning("Initialization transfer failed: "+res);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start listening for controller input
|
|
||||||
startInputThread(inEndpt);
|
|
||||||
|
|
||||||
// Report this device added via the listener
|
|
||||||
listener.deviceAdded(deviceId);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
if (stopped) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stopped = true;
|
|
||||||
|
|
||||||
// Stop the input thread
|
|
||||||
if (inputThread != null) {
|
|
||||||
inputThread.interrupt();
|
|
||||||
inputThread = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report the device removed
|
|
||||||
listener.deviceRemoved(deviceId);
|
|
||||||
|
|
||||||
// Close the USB connection
|
|
||||||
connection.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean canClaimDevice(UsbDevice device) {
|
public static boolean canClaimDevice(UsbDevice device) {
|
||||||
@@ -236,4 +84,16 @@ public class XboxOneController {
|
|||||||
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||||
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL;
|
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doInit() {
|
||||||
|
// Send the initialization packet
|
||||||
|
int res = connection.bulkTransfer(outEndpt, XB1_INIT_DATA, XB1_INIT_DATA.length, 3000);
|
||||||
|
if (res != XB1_INIT_DATA.length) {
|
||||||
|
LimeLog.warning("Initialization transfer failed: "+res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public class ComputerManagerService extends Service {
|
|||||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||||
private static final int FAST_POLL_TIMEOUT = 500;
|
private static final int FAST_POLL_TIMEOUT = 500;
|
||||||
private static final int OFFLINE_POLL_TRIES = 5;
|
private static final int OFFLINE_POLL_TRIES = 5;
|
||||||
|
private static final int EMPTY_LIST_THRESHOLD = 3;
|
||||||
|
|
||||||
private final ComputerManagerBinder binder = new ComputerManagerBinder();
|
private final ComputerManagerBinder binder = new ComputerManagerBinder();
|
||||||
|
|
||||||
@@ -232,8 +233,10 @@ public class ComputerManagerService extends Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onUnbind(Intent intent) {
|
public boolean onUnbind(Intent intent) {
|
||||||
// Stop mDNS autodiscovery
|
if (discoveryBinder != null) {
|
||||||
discoveryBinder.stopDiscovery();
|
// Stop mDNS autodiscovery
|
||||||
|
discoveryBinder.stopDiscovery();
|
||||||
|
}
|
||||||
|
|
||||||
// Stop polling
|
// Stop polling
|
||||||
pollingActive = false;
|
pollingActive = false;
|
||||||
@@ -661,6 +664,7 @@ public class ComputerManagerService extends Service {
|
|||||||
thread = new Thread() {
|
thread = new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
int emptyAppListResponses = 0;
|
||||||
do {
|
do {
|
||||||
InetAddress selectedAddr;
|
InetAddress selectedAddr;
|
||||||
|
|
||||||
@@ -705,7 +709,15 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<NvApp> list = NvHTTP.getAppListByReader(new StringReader(appList));
|
List<NvApp> list = NvHTTP.getAppListByReader(new StringReader(appList));
|
||||||
if (appList != null && !appList.isEmpty() && !list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
|
LimeLog.warning("Empty app list received from "+computer.uuid);
|
||||||
|
|
||||||
|
// The app list might actually be empty, so if we get an empty response a few times
|
||||||
|
// in a row, we'll go ahead and believe it.
|
||||||
|
emptyAppListResponses++;
|
||||||
|
}
|
||||||
|
if (appList != null && !appList.isEmpty() &&
|
||||||
|
(!list.isEmpty() || emptyAppListResponses >= EMPTY_LIST_THRESHOLD)) {
|
||||||
// Open the cache file
|
// Open the cache file
|
||||||
OutputStream cacheOut = null;
|
OutputStream cacheOut = null;
|
||||||
try {
|
try {
|
||||||
@@ -721,6 +733,11 @@ public class ComputerManagerService extends Service {
|
|||||||
} catch (IOException ignored) {}
|
} catch (IOException ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset empty count if it wasn't empty this time
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
emptyAppListResponses = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the computer
|
// Update the computer
|
||||||
computer.rawAppList = appList;
|
computer.rawAppList = appList;
|
||||||
receivedAppList = true;
|
receivedAppList = true;
|
||||||
@@ -731,8 +748,8 @@ public class ComputerManagerService extends Service {
|
|||||||
listener.notifyComputerUpdated(computer);
|
listener.notifyComputerUpdated(computer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if (appList == null || appList.isEmpty()) {
|
||||||
LimeLog.warning("Empty app list received from "+computer.uuid);
|
LimeLog.warning("Null app list received from "+computer.uuid);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
@@ -103,8 +103,8 @@
|
|||||||
<string name="summary_checkbox_multi_controller">When unchecked, all controllers appear as one</string>
|
<string name="summary_checkbox_multi_controller">When unchecked, all controllers appear as one</string>
|
||||||
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
|
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
|
||||||
<string name="suffix_seekbar_deadzone">%</string>
|
<string name="suffix_seekbar_deadzone">%</string>
|
||||||
<string name="title_checkbox_xb1_driver">Xbox One controller driver</string>
|
<string name="title_checkbox_xb1_driver">Xbox 360/One controller driver</string>
|
||||||
<string name="summary_checkbox_xb1_driver">Enables a built-in USB driver for devices without native Xbox One controller support.</string>
|
<string name="summary_checkbox_xb1_driver">Enables a built-in USB driver for devices without native Xbox controller support.</string>
|
||||||
|
|
||||||
<string name="category_on_screen_controls_settings">On-screen Controls Settings</string>
|
<string name="category_on_screen_controls_settings">On-screen Controls Settings</string>
|
||||||
<string name="title_checkbox_show_onscreen_controls">Show on-screen controls</string>
|
<string name="title_checkbox_show_onscreen_controls">Show on-screen controls</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user