diff --git a/app/build.gradle b/app/build.gradle index 923d3e28..7364548f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -130,4 +130,5 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.12.13' implementation 'com.squareup.okio:okio:1.17.5' implementation 'org.jmdns:jmdns:3.5.7' + implementation 'com.github.cgutman:ShieldControllerExtensions:1.0' } diff --git a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java index 019ece32..b33d9008 100644 --- a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java +++ b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java @@ -25,7 +25,6 @@ import com.limelight.LimeLog; import com.limelight.binding.input.driver.AbstractController; import com.limelight.binding.input.driver.UsbDriverListener; import com.limelight.binding.input.driver.UsbDriverService; -import com.limelight.binding.input.shield.ShieldControllerExtensionsHandler; import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.input.ControllerPacket; import com.limelight.nvstream.input.MouseButtonPacket; @@ -33,6 +32,8 @@ import com.limelight.preferences.PreferenceConfiguration; import com.limelight.ui.GameGestures; import com.limelight.utils.Vector2d; +import org.cgutman.shieldcontrollerextensions.SceManager; + import java.lang.reflect.InvocationTargetException; import java.util.Timer; import java.util.TimerTask; @@ -62,7 +63,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD private final InputDeviceContext defaultContext = new InputDeviceContext(); private final GameGestures gestures; private final Vibrator deviceVibrator; - private final ShieldControllerExtensionsHandler shieldControllerExtensionsHandler; + private final SceManager sceManager; private boolean hasGameController; private final PreferenceConfiguration prefConfig; @@ -74,7 +75,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD this.gestures = gestures; this.prefConfig = prefConfig; this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE); - this.shieldControllerExtensionsHandler = new ShieldControllerExtensionsHandler(activityContext); + + this.sceManager = new SceManager(activityContext); + this.sceManager.start(); int deadzonePercentage = prefConfig.deadzonePercentage; @@ -203,7 +206,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD deviceContext.destroy(); } - shieldControllerExtensionsHandler.destroy(); + sceManager.stop(); deviceVibrator.cancel(); } @@ -1445,32 +1448,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD if (deviceContext.controllerNumber == controllerNumber) { foundMatchingDevice = true; - // Cancel pending rumble repeat timer if one exists - if (deviceContext.rumbleRepeatTimer != null) { - deviceContext.rumbleRepeatTimer.cancel(); - deviceContext.rumbleRepeatTimer = null; - } - // Prefer the documented Android 12 rumble API which can handle dual vibrators on PS/Xbox controllers if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && deviceContext.vibratorManager != null) { vibrated = true; rumbleDualVibrators(deviceContext.vibratorManager, lowFreqMotor, highFreqMotor); } // On Shield devices, we can use their special API to rumble Shield controllers - else if (shieldControllerExtensionsHandler.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor)) { + else if (sceManager.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor)) { vibrated = true; - - // The Shield controller can only rumble up to 1 second at a time, so we will call rumble again - // every 500 ms until the host PC gives us another rumble value. - if (lowFreqMotor != 0 || highFreqMotor != 0) { - deviceContext.rumbleRepeatTimer = new Timer("Rumble Repeat - "+deviceContext.name, true); - deviceContext.rumbleRepeatTimer.schedule(new TimerTask() { - @Override - public void run() { - shieldControllerExtensionsHandler.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor); - } - }, 500, 500); - } } // If all else fails, we have to try the old Vibrator API else if (deviceContext.vibrator != null) { @@ -1961,7 +1946,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD public VibratorManager vibratorManager; public Vibrator vibrator; public InputDevice inputDevice; - public Timer rumbleRepeatTimer; public int leftStickXAxis = -1; public int leftStickYAxis = -1; @@ -2013,10 +1997,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD else if (vibrator != null) { vibrator.cancel(); } - - if (rumbleRepeatTimer != null) { - rumbleRepeatTimer.cancel(); - } } } diff --git a/app/src/main/java/com/limelight/binding/input/shield/IExposedControllerManagerListener.java b/app/src/main/java/com/limelight/binding/input/shield/IExposedControllerManagerListener.java deleted file mode 100644 index 68a34f72..00000000 --- a/app/src/main/java/com/limelight/binding/input/shield/IExposedControllerManagerListener.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.limelight.binding.input.shield; - -import android.os.Binder; -import android.os.IBinder; -import android.os.IInterface; -import android.os.Parcel; -import android.os.RemoteException; - -public interface IExposedControllerManagerListener extends IInterface { - void onDeviceAdded(String controllerToken); - void onDeviceChanged(String controllerToken, int i); - void onDeviceRemoved(String controllerToken); - - public static abstract class Stub extends Binder implements IExposedControllerManagerListener { - public Stub() { - attachInterface(this, "com.nvidia.blakepairing.IExposedControllerManagerListener"); - } - - @Override - public IBinder asBinder() { - return this; - } - - public boolean onTransact(int code, Parcel input, Parcel output, int flags) throws RemoteException { - switch (code) { - case 1: - input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener"); - onDeviceAdded(input.readString()); - break; - case 2: - input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener"); - onDeviceChanged(input.readString(), input.readInt()); - break; - case 3: - input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener"); - onDeviceRemoved(input.readString()); - break; - case 4: - case 5: - input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener"); - // Don't care - break; - - default: - return super.onTransact(code, input, output, flags); - } - - return true; - } - } -} diff --git a/app/src/main/java/com/limelight/binding/input/shield/ShieldControllerExtensionsHandler.java b/app/src/main/java/com/limelight/binding/input/shield/ShieldControllerExtensionsHandler.java deleted file mode 100644 index 45248355..00000000 --- a/app/src/main/java/com/limelight/binding/input/shield/ShieldControllerExtensionsHandler.java +++ /dev/null @@ -1,298 +0,0 @@ -package com.limelight.binding.input.shield; - -import android.app.Service; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.hardware.input.InputManager; -import android.os.IBinder; -import android.os.Parcel; -import android.os.RemoteException; -import android.view.InputDevice; - -import com.limelight.LimeLog; - -import java.util.HashMap; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; - -public class ShieldControllerExtensionsHandler implements InputManager.InputDeviceListener { - private Context context; - - private IBinder binder; - private ServiceConnection serviceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - binder = iBinder; - - try { - listenerId = registerListener(); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - listenerId = 0; - tokenToDeviceIdMap.clear(); - deviceIdToTokenMap.clear(); - - binder = null; - } - }; - - // ConcurrentHashMap handles synchronization between the Binder thread adding/removing - // entries and callers on arbitrary threads that are doing device lookups. - // - // Since these are separate maps, they can be temporarily inconsistent (only one-way - // of the two-way mapping present). This is fine for our purposes here. - private ConcurrentHashMap tokenToDeviceIdMap = new ConcurrentHashMap<>(); - private ConcurrentHashMap deviceIdToTokenMap = new ConcurrentHashMap<>(); - private AtomicBoolean needsRefresh = new AtomicBoolean(false); - - private int listenerId; - private IExposedControllerManagerListener.Stub controllerListener = new IExposedControllerManagerListener.Stub() { - @Override - public void onDeviceAdded(String controllerToken) { - try { - int inputDeviceId = getInputDeviceId(controllerToken); - - LimeLog.info("Shield controller added: " + controllerToken + " -> " + inputDeviceId); - - tokenToDeviceIdMap.put(controllerToken, inputDeviceId); - deviceIdToTokenMap.put(inputDeviceId, controllerToken); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - @Override - public void onDeviceChanged(String controllerToken, int i) { - LimeLog.info("Shield controller changed: " + controllerToken + " " + i); - } - - @Override - public void onDeviceRemoved(String controllerToken) { - LimeLog.info("Shield controller removed: " + controllerToken); - - Integer deviceId = tokenToDeviceIdMap.remove(controllerToken); - if (deviceId != null) { - deviceIdToTokenMap.remove(deviceId); - } - } - }; - - public ShieldControllerExtensionsHandler(Context context) { - this.context = context; - - InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); - inputManager.registerInputDeviceListener(this, null); - - Intent intent = new Intent(); - intent.setClassName("com.nvidia.blakepairing", "com.nvidia.blakepairing.AccessoryService"); - try { - // The docs say to call unbindService() even if the bindService() call returns false - // or throws a SecurityException. - if (!context.bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE)) { - LimeLog.info("com.nvidia.blakepairing.AccessoryService is not available on this device"); - context.unbindService(serviceConnection); - } - } catch (SecurityException e) { - context.unbindService(serviceConnection); - } - } - - private String getControllerToken(InputDevice device) { - // Refresh device ID <-> token mappings if one of our devices was removed - if (needsRefresh.compareAndSet(true, false)) { - try { - LimeLog.info("Refreshing controller token mappings"); - - // We have to enumerate tokenToDeviceIdMap rather than deviceIdToTokenMap - // because we remove the deviceIdToTokenMap entry when the device goes away. - HashMap newTokenToDeviceIdMap = new HashMap<>(); - HashMap newDeviceIdToTokenMap = new HashMap<>(); - for (String existingToken : tokenToDeviceIdMap.keySet()) { - int deviceId = getInputDeviceId(existingToken); - if (deviceId != 0) { - newTokenToDeviceIdMap.put(existingToken, deviceId); - newDeviceIdToTokenMap.put(deviceId, existingToken); - } - } - - tokenToDeviceIdMap.clear(); - deviceIdToTokenMap.clear(); - - tokenToDeviceIdMap.putAll(newTokenToDeviceIdMap); - deviceIdToTokenMap.putAll(newDeviceIdToTokenMap); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - return deviceIdToTokenMap.get(device.getId()); - } - - public boolean rumble(InputDevice device, int lowFreqMotor, int highFreqMotor) { - String controllerToken = getControllerToken(device); - if (controllerToken != null) { - try { - return rumble(controllerToken, lowFreqMotor, highFreqMotor); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - return false; - } - - public void destroy() { - InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); - inputManager.unregisterInputDeviceListener(this); - - tokenToDeviceIdMap.clear(); - deviceIdToTokenMap.clear(); - - if (listenerId != 0) { - try { - unregisterListener(listenerId); - } catch (RemoteException e) { - e.printStackTrace(); - } - listenerId = 0; - } - - if (binder != null) { - context.unbindService(serviceConnection); - binder = null; - } - } - - private int registerListener() throws RemoteException { - if (binder == null) { - return 0; - } - - Parcel input = Parcel.obtain(); - Parcel output = Parcel.obtain(); - try { - input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder"); - input.writeStrongBinder(controllerListener); - - binder.transact(20, input, output, 0); - - output.readException(); - return output.readInt(); - } finally { - input.recycle(); - output.recycle(); - } - } - - private boolean unregisterListener(int listenerId) throws RemoteException { - if (binder == null) { - return false; - } - - Parcel input = Parcel.obtain(); - Parcel output = Parcel.obtain(); - try { - input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder"); - input.writeInt(listenerId); - - binder.transact(21, input, output, 0); - - output.readException(); - return output.readInt() != 0; - } finally { - input.recycle(); - output.recycle(); - } - } - - private int getInputDeviceId(String controllerToken) throws RemoteException { - if (binder == null) { - return 0; - } - - Parcel input = Parcel.obtain(); - Parcel output = Parcel.obtain(); - try { - input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder"); - input.writeString(controllerToken); - - binder.transact(13, input, output, 0); - - output.readException(); - return output.readInt(); - } finally { - input.recycle(); - output.recycle(); - } - } - - // Rumble duration maximum of 1 second - private boolean rumble(String controllerToken, int lowFreqMotor, int highFreqMotor) throws RemoteException { - if (binder == null) { - return false; - } - - Parcel input = Parcel.obtain(); - Parcel output = Parcel.obtain(); - try { - input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder"); - input.writeString(controllerToken); - input.writeInt(lowFreqMotor); - input.writeInt(highFreqMotor); - - binder.transact(18, input, output, 0); - - output.readException(); - return output.readInt() != 0; - } finally { - input.recycle(); - output.recycle(); - } - } - - // Rumble duration maximum of 1.5 seconds - private boolean rumbleWithDuration(String controllerToken, int lowFreqMotor, int highFreqMotor, long durationMs) throws RemoteException { - if (binder == null) { - return false; - } - - Parcel input = Parcel.obtain(); - Parcel output = Parcel.obtain(); - try { - input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder"); - input.writeString(controllerToken); - input.writeInt(lowFreqMotor); - input.writeInt(highFreqMotor); - input.writeLong(durationMs); - - binder.transact(19, input, output, 0); - - output.readException(); - return output.readInt() != 0; - } finally { - input.recycle(); - output.recycle(); - } - } - - @Override - public void onInputDeviceAdded(int deviceId) {} - - @Override - public void onInputDeviceChanged(int deviceId) {} - - @Override - public void onInputDeviceRemoved(int deviceId) { - // Remove the device ID to token mapping, but leave the token mapping to device ID - // mapping so we will re-enumerate it when we next try to rumble a controller. - if (deviceIdToTokenMap.remove(deviceId) != null) { - needsRefresh.set(true); - } - } -} diff --git a/build.gradle b/build.gradle index f9943b9f..a0b450f8 100644 --- a/build.gradle +++ b/build.gradle @@ -13,5 +13,6 @@ allprojects { repositories { mavenCentral() google() + maven { url 'https://jitpack.io' } } }