ControllerInterface/Android: Implement sensor input

This commit is contained in:
JosJuice 2022-09-16 22:03:03 +02:00
parent 104ea09892
commit 5e51b56d72
5 changed files with 559 additions and 3 deletions

View File

@ -11,6 +11,7 @@ import android.os.Build;
import android.os.Bundle;
import android.util.Pair;
import android.util.SparseIntArray;
import android.view.Display;
import android.view.KeyEvent;
import android.view.Menu;
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.DialogSkylandersManagerBinding;
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.IntSetting;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
@ -442,12 +444,15 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
}
updateOrientation();
ControllerInterface.enableSensorEvents(() -> getWindowManager().getDefaultDisplay());
}
@Override
protected void onPause()
{
super.onPause();
ControllerInterface.disableSensorEvents();
}
@Override

View File

@ -64,6 +64,26 @@ public final class ControllerInterface
*/
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.
*/

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -17,6 +17,7 @@
#include <android/keycodes.h>
#include <jni.h>
#include "Common/Assert.h"
#include "Common/Logging/Log.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_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;
using Clock = std::chrono::steady_clock;
constexpr Clock::duration ACTIVE_INPUT_TIMEOUT = std::chrono::milliseconds(1000);
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
@ -460,7 +469,7 @@ public:
explicit AndroidKey(int keycode) : AndroidInput(ConstructKeyName(keycode)) {}
};
class AndroidAxis final : public AndroidInput
class AndroidAxis : public AndroidInput
{
public:
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
{
return m_negative ? -AndroidInput::GetState() : AndroidInput::GetState();
@ -477,12 +490,21 @@ private:
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
{
public:
AndroidDevice(JNIEnv* env, jobject input_device)
: m_source(env->CallIntMethod(input_device, s_input_device_get_sources)),
m_controller_number(env->CallIntMethod(input_device, s_input_device_get_controller_number)
: m_sensor_event_listener(nullptr),
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 =
reinterpret_cast<jstring>(env->CallObjectMethod(input_device, s_input_device_get_name));
@ -495,6 +517,19 @@ public:
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 GetSource() const override { return "Android"; }
@ -519,6 +554,8 @@ public:
return -3;
}
jobject GetSensorEventListener() { return m_sensor_event_listener; }
private:
void AddKeys(JNIEnv* env, jobject input_device)
{
@ -571,6 +608,35 @@ private:
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_controller_number;
std::string m_name;
@ -648,6 +714,22 @@ void Init()
s_controller_interface_unregister_input_device_listener =
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);
s_keycodes_array = reinterpret_cast<jintArray>(env->NewGlobalRef(keycodes_array));
env->DeleteLocalRef(keycodes_array);
@ -669,6 +751,7 @@ void Shutdown()
env->DeleteGlobalRef(s_key_event_class);
env->DeleteGlobalRef(s_motion_event_class);
env->DeleteGlobalRef(s_controller_interface_class);
env->DeleteGlobalRef(s_sensor_event_listener_class);
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);
}
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()
{
INFO_LOG_FMT(CONTROLLERINTERFACE, "Android populating devices");
@ -708,6 +810,8 @@ void PopulateDevices()
AddDevice(env, device_ids[i]);
env->ReleaseIntArrayElements(device_ids_array, device_ids, JNI_ABORT);
env->DeleteLocalRef(device_ids_array);
AddSensorDevice(env);
}
} // namespace ciface::Android
@ -806,6 +910,56 @@ Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatch
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
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_refreshDevices(JNIEnv* env,
jclass)