Add device sensor fallback option

Correction for device orientation is not implemented yet
This commit is contained in:
Cameron Gutman 2023-09-16 20:25:54 -05:00
parent 08d509d831
commit 8f9a687872
6 changed files with 122 additions and 81 deletions

View File

@ -29,6 +29,12 @@
<uses-feature <uses-feature
android:name="android.software.leanback" android:name="android.software.leanback"
android:required="false" /> android:required="false" />
<uses-feature
android:name="android.hardware.sensor.accelerometer"
android:required="false" />
<uses-feature
android:name="android.hardware.sensor.gyroscope"
android:required="false" />
<!-- Disable legacy input emulation on ChromeOS --> <!-- Disable legacy input emulation on ChromeOS -->
<uses-feature <uses-feature

View File

@ -112,6 +112,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private final GameGestures gestures; private final GameGestures gestures;
private final Vibrator deviceVibrator; private final Vibrator deviceVibrator;
private final VibratorManager deviceVibratorManager; private final VibratorManager deviceVibratorManager;
private final SensorManager deviceSensorManager;
private final SceManager sceManager; private final SceManager sceManager;
private final Handler handler; private final Handler handler;
private boolean hasGameController; private boolean hasGameController;
@ -125,6 +126,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
this.gestures = gestures; this.gestures = gestures;
this.prefConfig = prefConfig; this.prefConfig = prefConfig;
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE); this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
this.deviceSensorManager = (SensorManager) activityContext.getSystemService(Context.SENSOR_SERVICE);
this.handler = new Handler(Looper.getMainLooper()); this.handler = new Handler(Looper.getMainLooper());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@ -416,6 +418,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
LimeLog.info("Not reserving a controller number"); LimeLog.info("Not reserving a controller number");
context.controllerNumber = 0; context.controllerNumber = 0;
} }
// If the gamepad doesn't have motion sensors, use the on-device sensors as a fallback for player 1
if (prefConfig.gamepadMotionSensorsFallbackToDevice && context.controllerNumber == 0 && devContext.sensorManager == null) {
devContext.sensorManager = deviceSensorManager;
}
} }
else { else {
if (prefConfig.multiController) { if (prefConfig.multiController) {
@ -648,6 +655,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
} }
} }
// On Android 12, we can try to use the InputDevice's sensors. This may not work if the
// Linux kernel version doesn't have motion sensor support, which is common for third-party
// gamepads.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && prefConfig.gamepadMotionSensors) {
if (dev.getSensorManager().getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null || dev.getSensorManager().getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null) {
context.sensorManager = dev.getSensorManager();
}
}
// Detect if the gamepad has Mode and Select buttons according to the Android key layouts. // Detect if the gamepad has Mode and Select buttons according to the Android key layouts.
// We do this first because other codepaths below may override these defaults. // We do this first because other codepaths below may override these defaults.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
@ -1955,11 +1971,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
} }
public void handleSetMotionEventState(final short controllerNumber, final byte motionType, short reportRateHz) { public void handleSetMotionEventState(final short controllerNumber, final byte motionType, short reportRateHz) {
// Don't use motion sensors if the user turned them off
if (!prefConfig.gamepadMotionSensors) {
return;
}
// Report rate is restricted to <= 200 Hz without the HIGH_SAMPLING_RATE_SENSORS permission // Report rate is restricted to <= 200 Hz without the HIGH_SAMPLING_RATE_SENSORS permission
reportRateHz = (short) Math.min(200, reportRateHz); reportRateHz = (short) Math.min(200, reportRateHz);
@ -2004,12 +2015,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public void onAccuracyChanged(Sensor sensor, int accuracy) {} public void onAccuracyChanged(Sensor sensor, int accuracy) {}
}; };
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
for (int i = 0; i < inputDeviceContexts.size(); i++) { for (int i = 0; i < inputDeviceContexts.size(); i++) {
InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i); InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i);
if (deviceContext.controllerNumber == controllerNumber) { if (deviceContext.controllerNumber == controllerNumber) {
SensorManager sm = deviceContext.inputDevice.getSensorManager(); SensorManager sm = deviceContext.sensorManager;
if (sm == null) {
continue;
}
switch (motionType) { switch (motionType) {
case MoonBridge.LI_MOTION_TYPE_ACCEL: case MoonBridge.LI_MOTION_TYPE_ACCEL:
@ -2051,7 +2064,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
} }
} }
} }
}
public void handleSetControllerLED(short controllerNumber, byte r, byte g, byte b) { public void handleSetControllerLED(short controllerNumber, byte r, byte g, byte b) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@ -2663,6 +2675,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public short lowFreqMotor, highFreqMotor; public short lowFreqMotor, highFreqMotor;
public short leftTriggerMotor, rightTriggerMotor; public short leftTriggerMotor, rightTriggerMotor;
public SensorManager sensorManager;
public SensorEventListener gyroListener; public SensorEventListener gyroListener;
public short gyroReportRateHz; public short gyroReportRateHz;
public SensorEventListener accelListener; public SensorEventListener accelListener;
@ -2743,14 +2756,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
vibrator.cancel(); vibrator.cancel();
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (gyroListener != null) { if (gyroListener != null) {
inputDevice.getSensorManager().unregisterListener(gyroListener); sensorManager.unregisterListener(gyroListener);
} }
if (accelListener != null) { if (accelListener != null) {
inputDevice.getSensorManager().unregisterListener(accelListener); sensorManager.unregisterListener(accelListener);
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (lightsSession != null) { if (lightsSession != null) {
lightsSession.close(); lightsSession.close();
} }
@ -2820,13 +2833,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
capabilities |= MoonBridge.LI_CCAP_RUMBLE; capabilities |= MoonBridge.LI_CCAP_RUMBLE;
} }
if (inputDevice.getSensorManager().getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null) {
capabilities |= MoonBridge.LI_CCAP_ACCEL;
}
if (inputDevice.getSensorManager().getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null) {
capabilities |= MoonBridge.LI_CCAP_GYRO;
}
if (inputDevice.getBatteryState().isPresent()) { if (inputDevice.getBatteryState().isPresent()) {
capabilities |= MoonBridge.LI_CCAP_BATTERY_STATE; capabilities |= MoonBridge.LI_CCAP_BATTERY_STATE;
} }
@ -2848,6 +2854,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
capabilities |= MoonBridge.LI_CCAP_ANALOG_TRIGGERS; capabilities |= MoonBridge.LI_CCAP_ANALOG_TRIGGERS;
} }
// Report sensors if the input device has them or we're using built-in sensors for a built-in controller
if (sensorManager != null && sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null) {
capabilities |= MoonBridge.LI_CCAP_ACCEL;
}
if (sensorManager != null && sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null) {
capabilities |= MoonBridge.LI_CCAP_GYRO;
}
// We can perform basic rumble with any vibrator // We can perform basic rumble with any vibrator
if (vibrator != null) { if (vibrator != null) {
capabilities |= MoonBridge.LI_CCAP_RUMBLE; capabilities |= MoonBridge.LI_CCAP_RUMBLE;
@ -2878,15 +2892,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public void migrateContext(InputDeviceContext oldContext) { public void migrateContext(InputDeviceContext oldContext) {
// Take ownership of the sensor and light sessions // Take ownership of the sensor and light sessions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
this.gyroReportRateHz = oldContext.gyroReportRateHz;
this.accelReportRateHz = oldContext.accelReportRateHz;
this.lightsSession = oldContext.lightsSession; this.lightsSession = oldContext.lightsSession;
oldContext.lightsSession = null;
}
this.gyroListener = oldContext.gyroListener; this.gyroListener = oldContext.gyroListener;
this.accelListener = oldContext.accelListener; this.accelListener = oldContext.accelListener;
oldContext.lightsSession = null; this.gyroReportRateHz = oldContext.gyroReportRateHz;
this.accelReportRateHz = oldContext.accelReportRateHz;
oldContext.gyroListener = null; oldContext.gyroListener = null;
oldContext.accelListener = null; oldContext.accelListener = null;
}
// Don't release the controller number, because we will carry it over if it is present. // Don't release the controller number, because we will carry it over if it is present.
// We also want to make sure the change is invisible to the host PC to avoid an add/remove // We also want to make sure the change is invisible to the host PC to avoid an add/remove
@ -2898,6 +2912,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
this.reservedControllerNumber = oldContext.reservedControllerNumber; this.reservedControllerNumber = oldContext.reservedControllerNumber;
this.controllerNumber = oldContext.controllerNumber; this.controllerNumber = oldContext.controllerNumber;
// We may have set this device to use the built-in sensor manager. If so, do that again.
if (oldContext.sensorManager == deviceSensorManager) {
this.sensorManager = deviceSensorManager;
}
// Refresh battery state and start the battery state polling again // Refresh battery state and start the battery state polling again
sendControllerBatteryPacket(this); sendControllerBatteryPacket(this);
handler.postDelayed(batteryStateUpdateRunnable, BATTERY_RECHECK_INTERVAL_MS); handler.postDelayed(batteryStateUpdateRunnable, BATTERY_RECHECK_INTERVAL_MS);
@ -2905,22 +2924,20 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public void disableSensors() { public void disableSensors() {
// Unregister all sensor listeners // Unregister all sensor listeners
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (gyroListener != null) { if (gyroListener != null) {
inputDevice.getSensorManager().unregisterListener(gyroListener); sensorManager.unregisterListener(gyroListener);
gyroListener = null; gyroListener = null;
// Send a gyro event to ensure the virtual controller is stationary // Send a gyro event to ensure the virtual controller is stationary
conn.sendControllerMotionEvent((byte) controllerNumber, MoonBridge.LI_MOTION_TYPE_GYRO, 0.f, 0.f, 0.f); conn.sendControllerMotionEvent((byte) controllerNumber, MoonBridge.LI_MOTION_TYPE_GYRO, 0.f, 0.f, 0.f);
} }
if (accelListener != null) { if (accelListener != null) {
inputDevice.getSensorManager().unregisterListener(accelListener); sensorManager.unregisterListener(accelListener);
accelListener = null; accelListener = null;
// We leave the acceleration as-is to preserve the attitude of the controller // We leave the acceleration as-is to preserve the attitude of the controller
} }
} }
}
public void enableSensors() { public void enableSensors() {
// Turn back on any sensors that should be reporting but are currently unregistered // Turn back on any sensors that should be reporting but are currently unregistered

View File

@ -58,6 +58,7 @@ public class PreferenceConfiguration {
private static final String FULL_RANGE_PREF_STRING = "checkbox_full_range"; private static final String FULL_RANGE_PREF_STRING = "checkbox_full_range";
private static final String GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING = "checkbox_gamepad_touchpad_as_mouse"; private static final String GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING = "checkbox_gamepad_touchpad_as_mouse";
private static final String GAMEPAD_MOTION_SENSORS_PREF_STRING = "checkbox_gamepad_motion_sensors"; private static final String GAMEPAD_MOTION_SENSORS_PREF_STRING = "checkbox_gamepad_motion_sensors";
private static final String GAMEPAD_MOTION_FALLBACK_PREF_STRING = "checkbox_gamepad_motion_fallback";
static final String DEFAULT_RESOLUTION = "1280x720"; static final String DEFAULT_RESOLUTION = "1280x720";
static final String DEFAULT_FPS = "60"; static final String DEFAULT_FPS = "60";
@ -94,6 +95,7 @@ public class PreferenceConfiguration {
private static final boolean DEFAULT_FULL_RANGE = false; private static final boolean DEFAULT_FULL_RANGE = false;
private static final boolean DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE = false; private static final boolean DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE = false;
private static final boolean DEFAULT_GAMEPAD_MOTION_SENSORS = true; private static final boolean DEFAULT_GAMEPAD_MOTION_SENSORS = true;
private static final boolean DEFAULT_GAMEPAD_MOTION_FALLBACK = false;
public static final int FRAME_PACING_MIN_LATENCY = 0; public static final int FRAME_PACING_MIN_LATENCY = 0;
public static final int FRAME_PACING_BALANCED = 1; public static final int FRAME_PACING_BALANCED = 1;
@ -137,6 +139,7 @@ public class PreferenceConfiguration {
public boolean fullRange; public boolean fullRange;
public boolean gamepadMotionSensors; public boolean gamepadMotionSensors;
public boolean gamepadTouchpadAsMouse; public boolean gamepadTouchpadAsMouse;
public boolean gamepadMotionSensorsFallbackToDevice;
public static boolean isNativeResolution(int width, int height) { public static boolean isNativeResolution(int width, int height) {
// It's not a native resolution if it matches an existing resolution option // It's not a native resolution if it matches an existing resolution option
@ -525,6 +528,7 @@ public class PreferenceConfiguration {
config.fullRange = prefs.getBoolean(FULL_RANGE_PREF_STRING, DEFAULT_FULL_RANGE); config.fullRange = prefs.getBoolean(FULL_RANGE_PREF_STRING, DEFAULT_FULL_RANGE);
config.gamepadTouchpadAsMouse = prefs.getBoolean(GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING, DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE); config.gamepadTouchpadAsMouse = prefs.getBoolean(GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING, DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE);
config.gamepadMotionSensors = prefs.getBoolean(GAMEPAD_MOTION_SENSORS_PREF_STRING, DEFAULT_GAMEPAD_MOTION_SENSORS); config.gamepadMotionSensors = prefs.getBoolean(GAMEPAD_MOTION_SENSORS_PREF_STRING, DEFAULT_GAMEPAD_MOTION_SENSORS);
config.gamepadMotionSensorsFallbackToDevice = prefs.getBoolean(GAMEPAD_MOTION_FALLBACK_PREF_STRING, DEFAULT_GAMEPAD_MOTION_FALLBACK);
return config; return config;
} }

View File

@ -296,6 +296,14 @@ public class StreamSettings extends Activity {
category.removePreference(findPreference("checkbox_gamepad_motion_sensors")); category.removePreference(findPreference("checkbox_gamepad_motion_sensors"));
} }
// Hide gamepad motion sensor fallback option if the device has no gyro or accelerometer
if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_ACCELEROMETER) &&
!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_GYROSCOPE)) {
PreferenceCategory category =
(PreferenceCategory) findPreference("category_gamepad_settings");
category.removePreference(findPreference("checkbox_gamepad_motion_fallback"));
}
// Remove PiP mode on devices pre-Oreo, where the feature is not available (some low RAM devices), // Remove PiP mode on devices pre-Oreo, where the feature is not available (some low RAM devices),
// and on Fire OS where it violates the Amazon App Store guidelines for some reason. // and on Fire OS where it violates the Amazon App Store guidelines for some reason.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||

View File

@ -187,6 +187,8 @@
<string name="summary_checkbox_gamepad_touchpad_as_mouse">Forces gamepad touchpad input to control the host mouse, even when emulating a gamepad with a touchpad.</string> <string name="summary_checkbox_gamepad_touchpad_as_mouse">Forces gamepad touchpad input to control the host mouse, even when emulating a gamepad with a touchpad.</string>
<string name="title_checkbox_gamepad_motion_sensors">Allow use of gamepad motion sensors</string> <string name="title_checkbox_gamepad_motion_sensors">Allow use of gamepad motion sensors</string>
<string name="summary_checkbox_gamepad_motion_sensors">Enables supported hosts to request motion sensor data when emulating a gamepad with motion sensors. Disabling may slightly reduce power and network usage if motion sensors are not being used in game.</string> <string name="summary_checkbox_gamepad_motion_sensors">Enables supported hosts to request motion sensor data when emulating a gamepad with motion sensors. Disabling may slightly reduce power and network usage if motion sensors are not being used in game.</string>
<string name="title_checkbox_gamepad_motion_fallback">Emulate gamepad motion support using device sensors</string>
<string name="summary_checkbox_gamepad_motion_fallback">Uses your device\'s built-in motion sensors if gamepad sensors are not supported by your connected gamepad or your Android version.\nNote: Enabling this option may cause your controller to appear on the host as a PlayStation controller.</string>
<string name="category_input_settings">Input Settings</string> <string name="category_input_settings">Input Settings</string>
<string name="title_checkbox_touchscreen_trackpad">Use the touchscreen as a trackpad</string> <string name="title_checkbox_touchscreen_trackpad">Use the touchscreen as a trackpad</string>

View File

@ -105,7 +105,11 @@
android:title="@string/title_checkbox_gamepad_motion_sensors" android:title="@string/title_checkbox_gamepad_motion_sensors"
android:summary="@string/summary_checkbox_gamepad_motion_sensors" android:summary="@string/summary_checkbox_gamepad_motion_sensors"
android:defaultValue="true" /> android:defaultValue="true" />
<CheckBoxPreference
android:key="checkbox_gamepad_motion_fallback"
android:title="@string/title_checkbox_gamepad_motion_fallback"
android:summary="@string/summary_checkbox_gamepad_motion_fallback"
android:defaultValue="false" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/category_input_settings" <PreferenceCategory android:title="@string/category_input_settings"
android:key="category_input_settings"> android:key="category_input_settings">