Android: Convert DolphinSensorEventListener to Kotlin

This commit is contained in:
Charles Lombardo 2023-06-10 05:08:42 -04:00
parent 24c882622f
commit ba9f2373c0
2 changed files with 504 additions and 440 deletions

View File

@ -1,440 +0,0 @@
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.InputDevice;
import android.view.Surface;
import androidx.annotation.Keep;
import org.dolphinemu.dolphinemu.DolphinApplication;
import org.dolphinemu.dolphinemu.utils.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 boolean isSuspended = true;
public SensorDetails(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails)
{
this.sensorType = sensorType;
this.axisNames = axisNames;
this.axisSetDetails = axisSetDetails;
}
}
private static int sDeviceRotation = Surface.ROTATION_0;
private final SensorManager mSensorManager;
private final HashMap<Sensor, SensorDetails> mSensorDetails = new HashMap<>();
private final boolean mRotateCoordinatesForScreenOrientation;
private String mDeviceQualifier = "";
// 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);
mRotateCoordinatesForScreenOrientation = true;
addSensors();
}
@Keep
public DolphinSensorEventListener(InputDevice inputDevice)
{
mRotateCoordinatesForScreenOrientation = false;
if (Build.VERSION.SDK_INT >= 31)
{
mSensorManager = inputDevice.getSensorManager();
// TODO: There is a bug where after suspending sensors, onSensorChanged can get called for
// a sensor that we never registered as a listener for. The way our code is currently written,
// this causes a NullPointerException, but if we checked for null we would instead have the
// problem of being spammed with onSensorChanged calls even though the sensor shouldn't be
// enabled. For now, let's comment out the ability to use InputDevice sensors.
//addSensors();
}
else
{
mSensorManager = null;
}
}
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;
boolean keepSensorAlive = false;
while (eventAxisIndex < values.length && detailsAxisIndex < axisNames.length)
{
if (detailsAxisSetIndex < axisSetDetails.length &&
axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex)
{
int rotation = Surface.ROTATION_0;
if (mRotateCoordinatesForScreenOrientation &&
axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES)
{
rotation = sDeviceRotation;
}
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];
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex], x);
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex + 1], x);
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex + 2], y);
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex + 3], y);
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex + 4], z);
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex + 5], z);
eventAxisIndex += 3;
detailsAxisIndex += 6;
detailsAxisSetIndex++;
}
else
{
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex], values[eventAxisIndex]);
eventAxisIndex++;
detailsAxisIndex++;
}
}
if (!keepSensorAlive)
{
setSensorSuspended(sensorEvent.sensor, sensorDetails, true);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int i)
{
// We don't care about this
}
/**
* The device qualifier set here will be passed on to native code,
* for the purpose of letting native code identify which device this object belongs to.
*/
@Keep
public void setDeviceQualifier(String deviceQualifier)
{
mDeviceQualifier = deviceQualifier;
}
/**
* If a sensor has been suspended to save battery, this unsuspends it.
* If the sensor isn't currently suspended, nothing happens.
*
* @param axisName The name of any of the sensor's axes.
*/
@Keep
public void requestUnsuspendSensor(String axisName)
{
for (Map.Entry<Sensor, SensorDetails> entry : mSensorDetails.entrySet())
{
if (Arrays.asList(entry.getValue().axisNames).contains(axisName))
{
setSensorSuspended(entry.getKey(), entry.getValue(), false);
}
}
}
private void setSensorSuspended(Sensor sensor, SensorDetails sensorDetails, boolean suspend)
{
boolean changeOccurred = false;
synchronized (sensorDetails)
{
if (sensorDetails.isSuspended != suspend)
{
ControllerInterface.notifySensorSuspendedState(mDeviceQualifier, sensorDetails.axisNames,
suspend);
if (suspend)
mSensorManager.unregisterListener(this, sensor);
else
mSensorManager.registerListener(this, sensor, SAMPLING_PERIOD_US);
sensorDetails.isSuspended = suspend;
changeOccurred = true;
}
}
if (changeOccurred)
{
Log.info((suspend ? "Suspended sensor " : "Unsuspended sensor ") + sensor.getName());
}
}
@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;
}
/**
* Should be called when an activity or other component that uses sensor events is resumed.
*
* Sensor events that contain device coordinates will have the coordinates rotated by the value
* passed to this function.
*
* @param deviceRotation The current rotation of the device (i.e. rotation of the default display)
*/
public static void setDeviceRotation(int deviceRotation)
{
sDeviceRotation = deviceRotation;
}
}

View File

@ -0,0 +1,504 @@
// SPDX-License-Identifier: GPL-2.0-or-later
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.InputDevice
import android.view.Surface
import androidx.annotation.Keep
import org.dolphinemu.dolphinemu.DolphinApplication
import org.dolphinemu.dolphinemu.utils.Log
import java.util.Collections
class DolphinSensorEventListener : SensorEventListener {
private class AxisSetDetails(val firstAxisOfSet: Int, val axisSetType: Int)
private class SensorDetails(
val sensorType: Int,
val axisNames: Array<String>,
val axisSetDetails: Array<AxisSetDetails>
) {
var isSuspended = true
}
private val sensorManager: SensorManager?
private val sensorDetails = HashMap<Sensor, SensorDetails>()
private val rotateCoordinatesForScreenOrientation: Boolean
private var deviceQualifier = ""
@Keep
constructor() {
sensorManager = DolphinApplication.getAppContext()
.getSystemService(Context.SENSOR_SERVICE) as SensorManager?
rotateCoordinatesForScreenOrientation = true
addSensors()
}
@Keep
constructor(inputDevice: InputDevice) {
rotateCoordinatesForScreenOrientation = false
sensorManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
inputDevice.sensorManager
// TODO: There is a bug where after suspending sensors, onSensorChanged can get called for
// a sensor that we never registered as a listener for. The way our code is currently written,
// this causes a NullPointerException, but if we checked for null we would instead have the
// problem of being spammed with onSensorChanged calls even though the sensor shouldn't be
// enabled. For now, let's comment out the ability to use InputDevice sensors.
//addSensors();
} else {
null
}
}
private fun addSensors() {
tryAddSensor(
Sensor.TYPE_ACCELEROMETER,
arrayOf(
"Accel Right",
"Accel Left",
"Accel Forward",
"Accel Backward",
"Accel Up",
"Accel Down"
),
arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES))
)
tryAddSensor(
Sensor.TYPE_GYROSCOPE,
arrayOf(
"Gyro Pitch Up",
"Gyro Pitch Down",
"Gyro Roll Right",
"Gyro Roll Left",
"Gyro Yaw Left",
"Gyro Yaw Right"
),
arrayOf(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,
arrayOf(
"Gravity Right",
"Gravity Left",
"Gravity Forward",
"Gravity Backward",
"Gravity Up",
"Gravity Down"
),
arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES))
)
tryAddSensor(
Sensor.TYPE_LINEAR_ACCELERATION,
arrayOf(
"Linear Acceleration Right",
"Linear Acceleration Left",
"Linear Acceleration Forward",
"Linear Acceleration Backward",
"Linear Acceleration Up",
"Linear Acceleration Down"
),
arrayOf(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,
arrayOf(
"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"
),
arrayOf(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,
arrayOf(
"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"
),
arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES))
)
tryAddSensor(
Sensor.TYPE_GYROSCOPE_UNCALIBRATED,
arrayOf(
"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"
),
arrayOf(
AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
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,
arrayOf(
"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"
),
arrayOf(
AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
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,
arrayOf(
"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"
),
arrayOf(
AxisSetDetails(0, AXIS_SET_TYPE_OTHER_COORDINATES),
AxisSetDetails(3, AXIS_SET_TYPE_OTHER_COORDINATES)
)
)
tryAddSensor(Sensor.TYPE_HEADING, arrayOf("Heading", "Heading Accuracy"), arrayOf())
}
}
private fun tryAddSensor(sensorType: Int, axisName: String) {
tryAddSensor(sensorType, arrayOf(axisName), arrayOf())
}
private fun tryAddSensor(
sensorType: Int,
axisNames: Array<String>,
axisSetDetails: Array<AxisSetDetails>
) {
val sensor = sensorManager!!.getDefaultSensor(sensorType)
if (sensor != null) {
sensorDetails[sensor] = SensorDetails(sensorType, axisNames, axisSetDetails)
}
}
override fun onSensorChanged(sensorEvent: SensorEvent) {
val sensorDetails = sensorDetails[sensorEvent.sensor]
val values = sensorEvent.values
val axisNames = sensorDetails!!.axisNames
val axisSetDetails = sensorDetails.axisSetDetails
var eventAxisIndex = 0
var detailsAxisIndex = 0
var detailsAxisSetIndex = 0
var keepSensorAlive = false
while (eventAxisIndex < values.size && detailsAxisIndex < axisNames.size) {
if (detailsAxisSetIndex < axisSetDetails.size &&
axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex
) {
var rotation = Surface.ROTATION_0
if (rotateCoordinatesForScreenOrientation &&
axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES
) {
rotation = deviceRotation
}
var x: Float
var y: Float
when (rotation) {
Surface.ROTATION_0 -> {
x = values[eventAxisIndex]
y = values[eventAxisIndex + 1]
}
Surface.ROTATION_90 -> {
x = -values[eventAxisIndex + 1]
y = values[eventAxisIndex]
}
Surface.ROTATION_180 -> {
x = -values[eventAxisIndex]
y = -values[eventAxisIndex + 1]
}
Surface.ROTATION_270 -> {
x = values[eventAxisIndex + 1]
y = -values[eventAxisIndex]
}
else -> {
x = values[eventAxisIndex]
y = values[eventAxisIndex + 1]
}
}
val z = values[eventAxisIndex + 2]
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
deviceQualifier,
axisNames[detailsAxisIndex],
x
)
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
deviceQualifier,
axisNames[detailsAxisIndex + 1],
x
)
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
deviceQualifier,
axisNames[detailsAxisIndex + 2],
y
)
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
deviceQualifier,
axisNames[detailsAxisIndex + 3],
y
)
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
deviceQualifier,
axisNames[detailsAxisIndex + 4],
z
)
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
deviceQualifier,
axisNames[detailsAxisIndex + 5],
z
)
eventAxisIndex += 3
detailsAxisIndex += 6
detailsAxisSetIndex++
} else {
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
deviceQualifier,
axisNames[detailsAxisIndex], values[eventAxisIndex]
)
eventAxisIndex++
detailsAxisIndex++
}
}
if (!keepSensorAlive) {
setSensorSuspended(sensorEvent.sensor, sensorDetails, true)
}
}
override fun onAccuracyChanged(sensor: Sensor, i: Int) {
// We don't care about this
}
/**
* The device qualifier set here will be passed on to native code,
* for the purpose of letting native code identify which device this object belongs to.
*/
@Keep
fun setDeviceQualifier(deviceQualifier: String) {
this.deviceQualifier = deviceQualifier
}
/**
* If a sensor has been suspended to save battery, this unsuspends it.
* If the sensor isn't currently suspended, nothing happens.
*
* @param axisName The name of any of the sensor's axes.
*/
@Keep
fun requestUnsuspendSensor(axisName: String) {
for ((key, value) in sensorDetails) {
if (listOf(*value.axisNames).contains(axisName)) {
setSensorSuspended(key, value, false)
}
}
}
private fun setSensorSuspended(
sensor: Sensor,
sensorDetails: SensorDetails,
suspend: Boolean
) {
var changeOccurred = false
synchronized(sensorDetails) {
if (sensorDetails.isSuspended != suspend) {
ControllerInterface.notifySensorSuspendedState(
deviceQualifier,
sensorDetails.axisNames,
suspend
)
if (suspend)
sensorManager!!.unregisterListener(this, sensor)
else
sensorManager!!.registerListener(this, sensor, SAMPLING_PERIOD_US)
sensorDetails.isSuspended = suspend
changeOccurred = true
}
}
if (changeOccurred) {
Log.info((if (suspend) "Suspended sensor " else "Unsuspended sensor ") + sensor.name)
}
}
@Keep
fun getAxisNames(): Array<String> {
val axisNames = ArrayList<String>()
for (sensorDetails in sensorDetailsSorted) {
sensorDetails.axisNames.forEach { axisNames.add(it) }
}
return axisNames.toArray(arrayOf())
}
@Keep
fun getNegativeAxes(): BooleanArray {
val negativeAxes = ArrayList<Boolean>()
for (sensorDetails in sensorDetailsSorted) {
var eventAxisIndex = 0
var detailsAxisIndex = 0
var detailsAxisSetIndex = 0
while (detailsAxisIndex < sensorDetails.axisNames.size) {
if (detailsAxisSetIndex < sensorDetails.axisSetDetails.size &&
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++
}
}
}
val result = BooleanArray(negativeAxes.size)
for (i in result.indices) {
result[i] = negativeAxes[i]
}
return result
}
private val sensorDetailsSorted: List<SensorDetails>
get() {
val sensorDetails = ArrayList(sensorDetails.values)
Collections.sort(
sensorDetails,
Comparator.comparingInt { s: SensorDetails -> s.sensorType }
)
return sensorDetails
}
companion object {
// Set of three axes. Creates a negative companion to each axis, and corrects for device rotation.
private const val AXIS_SET_TYPE_DEVICE_COORDINATES = 0
// Set of three axes. Creates a negative companion to each axis.
private const val AXIS_SET_TYPE_OTHER_COORDINATES = 1
private var deviceRotation = Surface.ROTATION_0
// 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 const val SAMPLING_PERIOD_US = 1000000 / 200
/**
* Should be called when an activity or other component that uses sensor events is resumed.
*
* Sensor events that contain device coordinates will have the coordinates rotated by the value
* passed to this function.
*
* @param deviceRotation The current rotation of the device (i.e. rotation of the default display)
*/
fun setDeviceRotation(deviceRotation: Int) {
this.deviceRotation = deviceRotation
}
}
}