mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-21 21:30:19 -06:00
ControllerInterface/Android: Implement sensor input
This commit is contained in:
@ -11,6 +11,7 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
|
import android.view.Display;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@ -42,6 +43,7 @@ import org.dolphinemu.dolphinemu.databinding.DialogInputAdjustBinding;
|
|||||||
import org.dolphinemu.dolphinemu.databinding.DialogIrSensitivityBinding;
|
import org.dolphinemu.dolphinemu.databinding.DialogIrSensitivityBinding;
|
||||||
import org.dolphinemu.dolphinemu.databinding.DialogSkylandersManagerBinding;
|
import org.dolphinemu.dolphinemu.databinding.DialogSkylandersManagerBinding;
|
||||||
import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface;
|
import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface;
|
||||||
|
import org.dolphinemu.dolphinemu.features.input.model.SensorEventRequester;
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
|
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting;
|
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting;
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||||
@ -442,12 +444,15 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateOrientation();
|
updateOrientation();
|
||||||
|
|
||||||
|
ControllerInterface.enableSensorEvents(() -> getWindowManager().getDefaultDisplay());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause()
|
protected void onPause()
|
||||||
{
|
{
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
ControllerInterface.disableSensorEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -64,6 +64,26 @@ public final class ControllerInterface
|
|||||||
*/
|
*/
|
||||||
public static native boolean dispatchGenericMotionEvent(MotionEvent event);
|
public static native boolean dispatchGenericMotionEvent(MotionEvent event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link DolphinSensorEventListener} calls this for each axis of a received SensorEvent.
|
||||||
|
*/
|
||||||
|
public static native void dispatchSensorEvent(String axisName, float value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables delivering sensor events to native code.
|
||||||
|
*
|
||||||
|
* @param requester The activity or other component which is requesting sensor events to be
|
||||||
|
* delivered.
|
||||||
|
*/
|
||||||
|
public static native void enableSensorEvents(SensorEventRequester requester);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables delivering sensor events to native code.
|
||||||
|
*
|
||||||
|
* Calling this when sensor events are no longer needed will save battery.
|
||||||
|
*/
|
||||||
|
public static native void disableSensorEvents();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rescans for input devices.
|
* Rescans for input devices.
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,361 @@
|
|||||||
|
package org.dolphinemu.dolphinemu.features.input.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorEvent;
|
||||||
|
import android.hardware.SensorEventListener;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.view.Surface;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
|
import org.dolphinemu.dolphinemu.DolphinApplication;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DolphinSensorEventListener implements SensorEventListener
|
||||||
|
{
|
||||||
|
// Set of three axes. Creates a negative companion to each axis, and corrects for device rotation.
|
||||||
|
private static final int AXIS_SET_TYPE_DEVICE_COORDINATES = 0;
|
||||||
|
// Set of three axes. Creates a negative companion to each axis.
|
||||||
|
private static final int AXIS_SET_TYPE_OTHER_COORDINATES = 1;
|
||||||
|
|
||||||
|
private static class AxisSetDetails
|
||||||
|
{
|
||||||
|
public final int firstAxisOfSet;
|
||||||
|
public final int axisSetType;
|
||||||
|
|
||||||
|
public AxisSetDetails(int firstAxisOfSet, int axisSetType)
|
||||||
|
{
|
||||||
|
this.firstAxisOfSet = firstAxisOfSet;
|
||||||
|
this.axisSetType = axisSetType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SensorDetails
|
||||||
|
{
|
||||||
|
public final int sensorType;
|
||||||
|
public final String[] axisNames;
|
||||||
|
public final AxisSetDetails[] axisSetDetails;
|
||||||
|
|
||||||
|
public SensorDetails(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails)
|
||||||
|
{
|
||||||
|
this.sensorType = sensorType;
|
||||||
|
this.axisNames = axisNames;
|
||||||
|
this.axisSetDetails = axisSetDetails;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SensorManager mSensorManager;
|
||||||
|
|
||||||
|
private final HashMap<Sensor, SensorDetails> mSensorDetails = new HashMap<>();
|
||||||
|
|
||||||
|
private SensorEventRequester mRequester = null;
|
||||||
|
|
||||||
|
// The fastest sampling rate Android lets us use without declaring the HIGH_SAMPLING_RATE_SENSORS
|
||||||
|
// permission is 200 Hz. This is also the sampling rate of a Wii Remote, so it fits us perfectly.
|
||||||
|
private static final int SAMPLING_PERIOD_US = 1000000 / 200;
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public DolphinSensorEventListener()
|
||||||
|
{
|
||||||
|
mSensorManager = (SensorManager)
|
||||||
|
DolphinApplication.getAppContext().getSystemService(Context.SENSOR_SERVICE);
|
||||||
|
|
||||||
|
addSensors();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSensors()
|
||||||
|
{
|
||||||
|
tryAddSensor(Sensor.TYPE_ACCELEROMETER, new String[]{"Accel Right", "Accel Left",
|
||||||
|
"Accel Forward", "Accel Backward", "Accel Up", "Accel Down"},
|
||||||
|
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_GYROSCOPE, new String[]{"Gyro Pitch Up", "Gyro Pitch Down",
|
||||||
|
"Gyro Roll Right", "Gyro Roll Left", "Gyro Yaw Left", "Gyro Yaw Right"},
|
||||||
|
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_LIGHT, "Light");
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_PRESSURE, "Pressure");
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_TEMPERATURE, "Device Temperature");
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_PROXIMITY, "Proximity");
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_GRAVITY, new String[]{"Gravity Right", "Gravity Left",
|
||||||
|
"Gravity Forward", "Gravity Backward", "Gravity Up", "Gravity Down"},
|
||||||
|
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_LINEAR_ACCELERATION,
|
||||||
|
new String[]{"Linear Acceleration Right", "Linear Acceleration Left",
|
||||||
|
"Linear Acceleration Forward", "Linear Acceleration Backward",
|
||||||
|
"Linear Acceleration Up", "Linear Acceleration Down"},
|
||||||
|
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||||
|
|
||||||
|
// The values provided by this sensor can be interpreted as an Euler vector or a quaternion.
|
||||||
|
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||||
|
tryAddSensor(Sensor.TYPE_ROTATION_VECTOR,
|
||||||
|
new String[]{"Rotation Vector X-", "Rotation Vector X+", "Rotation Vector Y-",
|
||||||
|
"Rotation Vector Y+", "Rotation Vector Z+",
|
||||||
|
"Rotation Vector Z-", "Rotation Vector R", "Rotation Vector Heading Accuracy"},
|
||||||
|
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_RELATIVE_HUMIDITY, "Relative Humidity");
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_AMBIENT_TEMPERATURE, "Ambient Temperature");
|
||||||
|
|
||||||
|
// The values provided by this sensor can be interpreted as an Euler vector or a quaternion.
|
||||||
|
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||||
|
tryAddSensor(Sensor.TYPE_GAME_ROTATION_VECTOR,
|
||||||
|
new String[]{"Game Rotation Vector X-", "Game Rotation Vector X+",
|
||||||
|
"Game Rotation Vector Y-", "Game Rotation Vector Y+", "Game Rotation Vector Z+",
|
||||||
|
"Game Rotation Vector Z-", "Game Rotation Vector R"},
|
||||||
|
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED,
|
||||||
|
new String[]{"Gyro Uncalibrated Pitch Up", "Gyro Uncalibrated Pitch Down",
|
||||||
|
"Gyro Uncalibrated Roll Right", "Gyro Uncalibrated Roll Left",
|
||||||
|
"Gyro Uncalibrated Yaw Left", "Gyro Uncalibrated Yaw Right",
|
||||||
|
"Gyro Drift Pitch Up", "Gyro Drift Pitch Down", "Gyro Drift Roll Right",
|
||||||
|
"Gyro Drift Roll Left", "Gyro Drift Yaw Left", "Gyro Drift Yaw Right"},
|
||||||
|
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
|
||||||
|
new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_HEART_RATE, "Heart Rate");
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 24)
|
||||||
|
{
|
||||||
|
tryAddSensor(Sensor.TYPE_HEART_BEAT, "Heart Beat");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 26)
|
||||||
|
{
|
||||||
|
tryAddSensor(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED,
|
||||||
|
new String[]{"Accel Uncalibrated Right", "Accel Uncalibrated Left",
|
||||||
|
"Accel Uncalibrated Forward", "Accel Uncalibrated Backward",
|
||||||
|
"Accel Uncalibrated Up", "Accel Uncalibrated Down",
|
||||||
|
"Accel Bias Right", "Accel Bias Left", "Accel Bias Forward",
|
||||||
|
"Accel Bias Backward", "Accel Bias Up", "Accel Bias Down"},
|
||||||
|
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
|
||||||
|
new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 30)
|
||||||
|
{
|
||||||
|
tryAddSensor(Sensor.TYPE_HINGE_ANGLE, "Hinge Angle");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 33)
|
||||||
|
{
|
||||||
|
// The values provided by this sensor can be interpreted as an Euler vector.
|
||||||
|
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||||
|
tryAddSensor(Sensor.TYPE_HEAD_TRACKER,
|
||||||
|
new String[]{"Head Rotation Vector X-", "Head Rotation Vector X+",
|
||||||
|
"Head Rotation Vector Y-", "Head Rotation Vector Y+",
|
||||||
|
"Head Rotation Vector Z+", "Head Rotation Vector Z-",
|
||||||
|
"Head Pitch Up", "Head Pitch Down", "Head Roll Right", "Head Roll Left",
|
||||||
|
"Head Yaw Left", "Head Yaw Right"},
|
||||||
|
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_OTHER_COORDINATES),
|
||||||
|
new AxisSetDetails(3, AXIS_SET_TYPE_OTHER_COORDINATES)});
|
||||||
|
|
||||||
|
tryAddSensor(Sensor.TYPE_HEADING, new String[]{"Heading", "Heading Accuracy"},
|
||||||
|
new AxisSetDetails[]{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryAddSensor(int sensorType, String axisName)
|
||||||
|
{
|
||||||
|
tryAddSensor(sensorType, new String[]{axisName}, new AxisSetDetails[]{});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryAddSensor(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails)
|
||||||
|
{
|
||||||
|
Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
|
||||||
|
if (sensor != null)
|
||||||
|
{
|
||||||
|
mSensorDetails.put(sensor, new SensorDetails(sensorType, axisNames, axisSetDetails));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSensorChanged(SensorEvent sensorEvent)
|
||||||
|
{
|
||||||
|
final SensorDetails sensorDetails = mSensorDetails.get(sensorEvent.sensor);
|
||||||
|
|
||||||
|
final float[] values = sensorEvent.values;
|
||||||
|
final String[] axisNames = sensorDetails.axisNames;
|
||||||
|
final AxisSetDetails[] axisSetDetails = sensorDetails.axisSetDetails;
|
||||||
|
|
||||||
|
int eventAxisIndex = 0;
|
||||||
|
int detailsAxisIndex = 0;
|
||||||
|
int detailsAxisSetIndex = 0;
|
||||||
|
while (eventAxisIndex < values.length && detailsAxisIndex < axisNames.length)
|
||||||
|
{
|
||||||
|
if (detailsAxisSetIndex < axisSetDetails.length &&
|
||||||
|
axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex)
|
||||||
|
{
|
||||||
|
int rotation = Surface.ROTATION_0;
|
||||||
|
if (axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES)
|
||||||
|
{
|
||||||
|
rotation = mRequester.getDisplay().getRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
float x, y;
|
||||||
|
switch (rotation)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case Surface.ROTATION_0:
|
||||||
|
x = values[eventAxisIndex];
|
||||||
|
y = values[eventAxisIndex + 1];
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_90:
|
||||||
|
x = -values[eventAxisIndex + 1];
|
||||||
|
y = values[eventAxisIndex];
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_180:
|
||||||
|
x = -values[eventAxisIndex];
|
||||||
|
y = -values[eventAxisIndex + 1];
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_270:
|
||||||
|
x = values[eventAxisIndex + 1];
|
||||||
|
y = -values[eventAxisIndex];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
float z = values[eventAxisIndex + 2];
|
||||||
|
|
||||||
|
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex], x);
|
||||||
|
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex + 1], x);
|
||||||
|
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex + 2], y);
|
||||||
|
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex + 3], y);
|
||||||
|
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex + 4], z);
|
||||||
|
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex + 5], z);
|
||||||
|
|
||||||
|
eventAxisIndex += 3;
|
||||||
|
detailsAxisIndex += 6;
|
||||||
|
detailsAxisSetIndex++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex],
|
||||||
|
values[eventAxisIndex]);
|
||||||
|
|
||||||
|
eventAxisIndex++;
|
||||||
|
detailsAxisIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccuracyChanged(Sensor sensor, int i)
|
||||||
|
{
|
||||||
|
// We don't care about this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables delivering sensor events to native code.
|
||||||
|
*
|
||||||
|
* @param requester The activity or other component which is requesting sensor events to be
|
||||||
|
* delivered.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public void enableSensorEvents(SensorEventRequester requester)
|
||||||
|
{
|
||||||
|
if (mRequester != null)
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Attempted to enable sensor events when someone else" +
|
||||||
|
"had already enabled them");
|
||||||
|
}
|
||||||
|
|
||||||
|
mRequester = requester;
|
||||||
|
|
||||||
|
for (Sensor sensor : mSensorDetails.keySet())
|
||||||
|
{
|
||||||
|
mSensorManager.registerListener(this, sensor, SAMPLING_PERIOD_US);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables delivering sensor events to native code.
|
||||||
|
*
|
||||||
|
* Calling this when sensor events are no longer needed will save battery.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public void disableSensorEvents()
|
||||||
|
{
|
||||||
|
mRequester = null;
|
||||||
|
|
||||||
|
mSensorManager.unregisterListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public String[] getAxisNames()
|
||||||
|
{
|
||||||
|
ArrayList<String> axisNames = new ArrayList<>();
|
||||||
|
|
||||||
|
for (SensorDetails sensorDetails : getSensorDetailsSorted())
|
||||||
|
{
|
||||||
|
Collections.addAll(axisNames, sensorDetails.axisNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
return axisNames.toArray(new String[]{});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public boolean[] getNegativeAxes()
|
||||||
|
{
|
||||||
|
ArrayList<Boolean> negativeAxes = new ArrayList<>();
|
||||||
|
|
||||||
|
for (SensorDetails sensorDetails : getSensorDetailsSorted())
|
||||||
|
{
|
||||||
|
int eventAxisIndex = 0;
|
||||||
|
int detailsAxisIndex = 0;
|
||||||
|
int detailsAxisSetIndex = 0;
|
||||||
|
while (detailsAxisIndex < sensorDetails.axisNames.length)
|
||||||
|
{
|
||||||
|
if (detailsAxisSetIndex < sensorDetails.axisSetDetails.length &&
|
||||||
|
sensorDetails.axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex)
|
||||||
|
{
|
||||||
|
negativeAxes.add(false);
|
||||||
|
negativeAxes.add(true);
|
||||||
|
negativeAxes.add(false);
|
||||||
|
negativeAxes.add(true);
|
||||||
|
negativeAxes.add(false);
|
||||||
|
negativeAxes.add(true);
|
||||||
|
|
||||||
|
eventAxisIndex += 3;
|
||||||
|
detailsAxisIndex += 6;
|
||||||
|
detailsAxisSetIndex++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
negativeAxes.add(false);
|
||||||
|
|
||||||
|
eventAxisIndex++;
|
||||||
|
detailsAxisIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean[] result = new boolean[negativeAxes.size()];
|
||||||
|
for (int i = 0; i < result.length; i++)
|
||||||
|
{
|
||||||
|
result[i] = negativeAxes.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SensorDetails> getSensorDetailsSorted()
|
||||||
|
{
|
||||||
|
ArrayList<SensorDetails> sensorDetails = new ArrayList<>(mSensorDetails.values());
|
||||||
|
Collections.sort(sensorDetails, Comparator.comparingInt(s -> s.sensorType));
|
||||||
|
return sensorDetails;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package org.dolphinemu.dolphinemu.features.input.model;
|
||||||
|
|
||||||
|
import android.view.Display;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public interface SensorEventRequester
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the display the activity is shown on.
|
||||||
|
*
|
||||||
|
* This is used for getting the display orientation for rotating the axes of motion events.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Display getDisplay();
|
||||||
|
}
|
@ -17,6 +17,7 @@
|
|||||||
#include <android/keycodes.h>
|
#include <android/keycodes.h>
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
|
#include "Common/Assert.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
|
||||||
@ -62,12 +63,20 @@ jclass s_controller_interface_class;
|
|||||||
jmethodID s_controller_interface_register_input_device_listener;
|
jmethodID s_controller_interface_register_input_device_listener;
|
||||||
jmethodID s_controller_interface_unregister_input_device_listener;
|
jmethodID s_controller_interface_unregister_input_device_listener;
|
||||||
|
|
||||||
|
jclass s_sensor_event_listener_class;
|
||||||
|
jmethodID s_sensor_event_listener_constructor;
|
||||||
|
jmethodID s_sensor_event_listener_enable_sensor_events;
|
||||||
|
jmethodID s_sensor_event_listener_disable_sensor_events;
|
||||||
|
jmethodID s_sensor_event_listener_get_axis_names;
|
||||||
|
jmethodID s_sensor_event_listener_get_negative_axes;
|
||||||
|
|
||||||
jintArray s_keycodes_array;
|
jintArray s_keycodes_array;
|
||||||
|
|
||||||
using Clock = std::chrono::steady_clock;
|
using Clock = std::chrono::steady_clock;
|
||||||
constexpr Clock::duration ACTIVE_INPUT_TIMEOUT = std::chrono::milliseconds(1000);
|
constexpr Clock::duration ACTIVE_INPUT_TIMEOUT = std::chrono::milliseconds(1000);
|
||||||
|
|
||||||
std::unordered_map<jint, ciface::Core::DeviceQualifier> s_device_id_to_device_qualifier;
|
std::unordered_map<jint, ciface::Core::DeviceQualifier> s_device_id_to_device_qualifier;
|
||||||
|
ciface::Core::DeviceQualifier s_sensor_device_qualifier;
|
||||||
|
|
||||||
constexpr int MAX_KEYCODE = AKEYCODE_PROFILE_SWITCH; // Up to date as of SDK 31
|
constexpr int MAX_KEYCODE = AKEYCODE_PROFILE_SWITCH; // Up to date as of SDK 31
|
||||||
|
|
||||||
@ -460,7 +469,7 @@ public:
|
|||||||
explicit AndroidKey(int keycode) : AndroidInput(ConstructKeyName(keycode)) {}
|
explicit AndroidKey(int keycode) : AndroidInput(ConstructKeyName(keycode)) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class AndroidAxis final : public AndroidInput
|
class AndroidAxis : public AndroidInput
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AndroidAxis(int source, int axis, bool negative)
|
AndroidAxis(int source, int axis, bool negative)
|
||||||
@ -468,6 +477,10 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AndroidAxis(std::string name, bool negative) : AndroidInput(std::move(name)), m_negative(negative)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
ControlState GetState() const override
|
ControlState GetState() const override
|
||||||
{
|
{
|
||||||
return m_negative ? -AndroidInput::GetState() : AndroidInput::GetState();
|
return m_negative ? -AndroidInput::GetState() : AndroidInput::GetState();
|
||||||
@ -477,12 +490,21 @@ private:
|
|||||||
bool m_negative;
|
bool m_negative;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AndroidSensorAxis final : public AndroidAxis
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AndroidSensorAxis(std::string name, bool negative) : AndroidAxis(std::move(name), negative) {}
|
||||||
|
|
||||||
|
bool IsDetectable() const override { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
class AndroidDevice final : public Core::Device
|
class AndroidDevice final : public Core::Device
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AndroidDevice(JNIEnv* env, jobject input_device)
|
AndroidDevice(JNIEnv* env, jobject input_device)
|
||||||
: m_source(env->CallIntMethod(input_device, s_input_device_get_sources)),
|
: m_sensor_event_listener(nullptr),
|
||||||
m_controller_number(env->CallIntMethod(input_device, s_input_device_get_controller_number)
|
m_source(env->CallIntMethod(input_device, s_input_device_get_sources)),
|
||||||
|
m_controller_number(env->CallIntMethod(input_device, s_input_device_get_controller_number))
|
||||||
{
|
{
|
||||||
jstring j_name =
|
jstring j_name =
|
||||||
reinterpret_cast<jstring>(env->CallObjectMethod(input_device, s_input_device_get_name));
|
reinterpret_cast<jstring>(env->CallObjectMethod(input_device, s_input_device_get_name));
|
||||||
@ -495,6 +517,19 @@ public:
|
|||||||
AddAxes(env, input_device);
|
AddAxes(env, input_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constructor for the device added by Dolphin to contain sensor inputs
|
||||||
|
AndroidDevice(JNIEnv* env, std::string name)
|
||||||
|
: m_sensor_event_listener(AddSensors(env)), m_source(AINPUT_SOURCE_SENSOR),
|
||||||
|
m_controller_number(0), m_name(std::move(name))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~AndroidDevice()
|
||||||
|
{
|
||||||
|
if (m_sensor_event_listener)
|
||||||
|
IDCache::GetEnvForThread()->DeleteGlobalRef(m_sensor_event_listener);
|
||||||
|
}
|
||||||
|
|
||||||
std::string GetName() const override { return m_name; }
|
std::string GetName() const override { return m_name; }
|
||||||
|
|
||||||
std::string GetSource() const override { return "Android"; }
|
std::string GetSource() const override { return "Android"; }
|
||||||
@ -519,6 +554,8 @@ public:
|
|||||||
return -3;
|
return -3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jobject GetSensorEventListener() { return m_sensor_event_listener; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void AddKeys(JNIEnv* env, jobject input_device)
|
void AddKeys(JNIEnv* env, jobject input_device)
|
||||||
{
|
{
|
||||||
@ -571,6 +608,35 @@ private:
|
|||||||
env->DeleteLocalRef(motion_ranges_list);
|
env->DeleteLocalRef(motion_ranges_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jobject AddSensors(JNIEnv* env)
|
||||||
|
{
|
||||||
|
jobject sensor_event_listener =
|
||||||
|
env->NewObject(s_sensor_event_listener_class, s_sensor_event_listener_constructor);
|
||||||
|
|
||||||
|
jobjectArray j_axis_names = reinterpret_cast<jobjectArray>(
|
||||||
|
env->CallObjectMethod(sensor_event_listener, s_sensor_event_listener_get_axis_names));
|
||||||
|
std::vector<std::string> axis_names = JStringArrayToVector(env, j_axis_names);
|
||||||
|
env->DeleteLocalRef(j_axis_names);
|
||||||
|
|
||||||
|
jbooleanArray j_negative_axes = reinterpret_cast<jbooleanArray>(
|
||||||
|
env->CallObjectMethod(sensor_event_listener, s_sensor_event_listener_get_negative_axes));
|
||||||
|
jboolean* negative_axes = env->GetBooleanArrayElements(j_negative_axes, nullptr);
|
||||||
|
|
||||||
|
ASSERT(axis_names.size() == env->GetArrayLength(j_negative_axes));
|
||||||
|
for (size_t i = 0; i < axis_names.size(); ++i)
|
||||||
|
AddInput(new AndroidSensorAxis(axis_names[i], negative_axes[i]));
|
||||||
|
|
||||||
|
env->ReleaseBooleanArrayElements(j_negative_axes, negative_axes, 0);
|
||||||
|
env->DeleteLocalRef(j_negative_axes);
|
||||||
|
|
||||||
|
jobject global_sensor_event_listener = env->NewGlobalRef(sensor_event_listener);
|
||||||
|
|
||||||
|
env->DeleteLocalRef(sensor_event_listener);
|
||||||
|
|
||||||
|
return global_sensor_event_listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobject m_sensor_event_listener;
|
||||||
const int m_source;
|
const int m_source;
|
||||||
const int m_controller_number;
|
const int m_controller_number;
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
@ -648,6 +714,22 @@ void Init()
|
|||||||
s_controller_interface_unregister_input_device_listener =
|
s_controller_interface_unregister_input_device_listener =
|
||||||
env->GetStaticMethodID(s_controller_interface_class, "unregisterInputDeviceListener", "()V");
|
env->GetStaticMethodID(s_controller_interface_class, "unregisterInputDeviceListener", "()V");
|
||||||
|
|
||||||
|
const jclass sensor_event_listener_class =
|
||||||
|
env->FindClass("org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener");
|
||||||
|
s_sensor_event_listener_class =
|
||||||
|
reinterpret_cast<jclass>(env->NewGlobalRef(sensor_event_listener_class));
|
||||||
|
s_sensor_event_listener_constructor =
|
||||||
|
env->GetMethodID(s_sensor_event_listener_class, "<init>", "()V");
|
||||||
|
s_sensor_event_listener_enable_sensor_events =
|
||||||
|
env->GetMethodID(s_sensor_event_listener_class, "enableSensorEvents",
|
||||||
|
"(Lorg/dolphinemu/dolphinemu/features/input/model/SensorEventRequester;)V");
|
||||||
|
s_sensor_event_listener_disable_sensor_events =
|
||||||
|
env->GetMethodID(s_sensor_event_listener_class, "disableSensorEvents", "()V");
|
||||||
|
s_sensor_event_listener_get_axis_names =
|
||||||
|
env->GetMethodID(s_sensor_event_listener_class, "getAxisNames", "()[Ljava/lang/String;");
|
||||||
|
s_sensor_event_listener_get_negative_axes =
|
||||||
|
env->GetMethodID(s_sensor_event_listener_class, "getNegativeAxes", "()[Z");
|
||||||
|
|
||||||
jintArray keycodes_array = CreateKeyCodesArray(env);
|
jintArray keycodes_array = CreateKeyCodesArray(env);
|
||||||
s_keycodes_array = reinterpret_cast<jintArray>(env->NewGlobalRef(keycodes_array));
|
s_keycodes_array = reinterpret_cast<jintArray>(env->NewGlobalRef(keycodes_array));
|
||||||
env->DeleteLocalRef(keycodes_array);
|
env->DeleteLocalRef(keycodes_array);
|
||||||
@ -669,6 +751,7 @@ void Shutdown()
|
|||||||
env->DeleteGlobalRef(s_key_event_class);
|
env->DeleteGlobalRef(s_key_event_class);
|
||||||
env->DeleteGlobalRef(s_motion_event_class);
|
env->DeleteGlobalRef(s_motion_event_class);
|
||||||
env->DeleteGlobalRef(s_controller_interface_class);
|
env->DeleteGlobalRef(s_controller_interface_class);
|
||||||
|
env->DeleteGlobalRef(s_sensor_event_listener_class);
|
||||||
env->DeleteGlobalRef(s_keycodes_array);
|
env->DeleteGlobalRef(s_keycodes_array);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -694,6 +777,25 @@ static void AddDevice(JNIEnv* env, int device_id)
|
|||||||
s_device_id_to_device_qualifier.emplace(device_id, qualifier);
|
s_device_id_to_device_qualifier.emplace(device_id, qualifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void AddSensorDevice(JNIEnv* env)
|
||||||
|
{
|
||||||
|
// Device sensors (accelerometer, etc.) aren't associated with any Android InputDevice.
|
||||||
|
// Create an otherwise empty Dolphin input device so that they have somewhere to live.
|
||||||
|
|
||||||
|
auto device = std::make_shared<AndroidDevice>(env, "Device Sensors");
|
||||||
|
|
||||||
|
if (device->Inputs().empty() && device->Outputs().empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_controller_interface.AddDevice(device);
|
||||||
|
|
||||||
|
Core::DeviceQualifier qualifier;
|
||||||
|
qualifier.FromDevice(device.get());
|
||||||
|
|
||||||
|
INFO_LOG_FMT(CONTROLLERINTERFACE, "Added sensor device as {}", device->GetQualifiedName());
|
||||||
|
s_sensor_device_qualifier = qualifier;
|
||||||
|
}
|
||||||
|
|
||||||
void PopulateDevices()
|
void PopulateDevices()
|
||||||
{
|
{
|
||||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "Android populating devices");
|
INFO_LOG_FMT(CONTROLLERINTERFACE, "Android populating devices");
|
||||||
@ -708,6 +810,8 @@ void PopulateDevices()
|
|||||||
AddDevice(env, device_ids[i]);
|
AddDevice(env, device_ids[i]);
|
||||||
env->ReleaseIntArrayElements(device_ids_array, device_ids, JNI_ABORT);
|
env->ReleaseIntArrayElements(device_ids_array, device_ids, JNI_ABORT);
|
||||||
env->DeleteLocalRef(device_ids_array);
|
env->DeleteLocalRef(device_ids_array);
|
||||||
|
|
||||||
|
AddSensorDevice(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ciface::Android
|
} // namespace ciface::Android
|
||||||
@ -806,6 +910,56 @@ Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatch
|
|||||||
return last_polled >= Clock::now() - ACTIVE_INPUT_TIMEOUT;
|
return last_polled >= Clock::now() - ACTIVE_INPUT_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchSensorEvent(
|
||||||
|
JNIEnv* env, jclass, jstring j_axis_name, jfloat value)
|
||||||
|
{
|
||||||
|
const std::shared_ptr<ciface::Core::Device> device =
|
||||||
|
g_controller_interface.FindDevice(s_sensor_device_qualifier);
|
||||||
|
if (!device)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const std::string axis_name = GetJString(env, j_axis_name);
|
||||||
|
|
||||||
|
for (ciface::Core::Device::Input* input : device->Inputs())
|
||||||
|
{
|
||||||
|
const std::string input_name = input->GetName();
|
||||||
|
if (input_name == axis_name)
|
||||||
|
{
|
||||||
|
auto casted_input = static_cast<ciface::Android::AndroidInput*>(input);
|
||||||
|
casted_input->SetState(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_enableSensorEvents(
|
||||||
|
JNIEnv* env, jclass, jobject j_sensor_event_requester)
|
||||||
|
{
|
||||||
|
const std::shared_ptr<ciface::Core::Device> device =
|
||||||
|
g_controller_interface.FindDevice(s_sensor_device_qualifier);
|
||||||
|
if (!device)
|
||||||
|
return;
|
||||||
|
|
||||||
|
env->CallVoidMethod(
|
||||||
|
static_cast<ciface::Android::AndroidDevice*>(device.get())->GetSensorEventListener(),
|
||||||
|
s_sensor_event_listener_enable_sensor_events, j_sensor_event_requester);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_disableSensorEvents(
|
||||||
|
JNIEnv* env, jclass)
|
||||||
|
{
|
||||||
|
const std::shared_ptr<ciface::Core::Device> device =
|
||||||
|
g_controller_interface.FindDevice(s_sensor_device_qualifier);
|
||||||
|
if (!device)
|
||||||
|
return;
|
||||||
|
|
||||||
|
env->CallVoidMethod(
|
||||||
|
static_cast<ciface::Android::AndroidDevice*>(device.get())->GetSensorEventListener(),
|
||||||
|
s_sensor_event_listener_disable_sensor_events);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_refreshDevices(JNIEnv* env,
|
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_refreshDevices(JNIEnv* env,
|
||||||
jclass)
|
jclass)
|
||||||
|
Reference in New Issue
Block a user