From 262d562dd949b6f609eeeafc587e5c4338859f56 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 14 Jul 2021 20:18:35 -0500 Subject: [PATCH] Implement enhanced rumble support for Android 12 devices This allows independent control of large and small motors which was not possible with the old single Vibrator API. Currently untested on real hardware. --- .../binding/input/ControllerHandler.java | 64 +++++++++++++++++-- 1 file changed, 57 insertions(+), 7 deletions(-) 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 50ecaece..28fcf4bc 100644 --- a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java +++ b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java @@ -1,5 +1,6 @@ package com.limelight.binding.input; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.hardware.input.InputManager; @@ -7,9 +8,11 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.media.AudioAttributes; import android.os.Build; +import android.os.CombinedVibration; import android.os.SystemClock; import android.os.VibrationEffect; import android.os.Vibrator; +import android.os.VibratorManager; import android.util.SparseArray; import android.view.InputDevice; import android.view.InputEvent; @@ -487,7 +490,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD context.productId = dev.getProductId(); } - if (dev.getVibrator().hasVibrator()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && hasDualAmplitudeControlledRumbleVibrators(dev.getVibratorManager())) { + context.vibratorManager = dev.getVibratorManager(); + } + else if (dev.getVibrator().hasVibrator()) { context.vibrator = dev.getVibrator(); } @@ -1283,7 +1289,43 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD } } - private void rumbleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) { + @TargetApi(31) + private boolean hasDualAmplitudeControlledRumbleVibrators(VibratorManager vm) { + int[] vibratorIds = vm.getVibratorIds(); + + // There must be exactly 2 vibrators on this device + if (vibratorIds.length != 2) { + return false; + } + + // Both vibrators must have amplitude control + for (int vid : vibratorIds) { + if (!vm.getVibrator(vid).hasAmplitudeControl()) { + return false; + } + } + + return true; + } + + // This must only be called if hasDualAmplitudeControlledRumbleVibrators() is true! + @TargetApi(31) + private void rumbleDualVibrators(VibratorManager vm, short lowFreqMotor, short highFreqMotor) { + if (lowFreqMotor == 0 && highFreqMotor == 0) { + vm.cancel(); + return; + } + + int[] vibratorIds = vm.getVibratorIds(); + + CombinedVibration.ParallelCombination combo = CombinedVibration.startParallel(); + combo.addVibrator(vibratorIds[0], VibrationEffect.createOneShot(60000, (lowFreqMotor >> 8) & 0xFF)); + combo.addVibrator(vibratorIds[1], VibrationEffect.createOneShot(60000, (highFreqMotor >> 8) & 0xFF)); + + vm.vibrate(combo.combine()); + } + + private void rumbleSingleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) { // Since we can only use a single amplitude value, compute the desired amplitude // by taking 80% of the big motor and 33% of the small motor, then capping to 255. // NB: This value is now 0-255 as required by VibrationEffect. @@ -1339,9 +1381,13 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD if (deviceContext.controllerNumber == controllerNumber) { foundMatchingDevice = true; - if (deviceContext.vibrator != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && deviceContext.vibratorManager != null) { vibrated = true; - rumbleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor); + rumbleDualVibrators(deviceContext.vibratorManager, lowFreqMotor, highFreqMotor); + } + else if (deviceContext.vibrator != null) { + vibrated = true; + rumbleSingleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor); } } } @@ -1361,12 +1407,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD // controls that triggered the rumble. Vibrate the device if // the user has requested that behavior. if (!foundMatchingDevice && prefConfig.onscreenController && !prefConfig.onlyL3R3 && prefConfig.vibrateOsc) { - rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor); + rumbleSingleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor); } else if (foundMatchingDevice && !vibrated && prefConfig.vibrateFallbackToDevice) { // We found a device to vibrate but it didn't have rumble support. The user // has requested us to vibrate the device in this case. - rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor); + rumbleSingleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor); } } } @@ -1803,6 +1849,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD class InputDeviceContext extends GenericControllerContext { public String name; + public VibratorManager vibratorManager; public Vibrator vibrator; public int leftStickXAxis = -1; @@ -1849,7 +1896,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD public void destroy() { super.destroy(); - if (vibrator != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && vibratorManager != null) { + vibratorManager.cancel(); + } + else if (vibrator != null) { vibrator.cancel(); } }