summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java/org
diff options
context:
space:
mode:
authorCharles Lombardo <clombardo169@gmail.com>2023-06-10 05:08:42 -0400
committerCharles Lombardo <clombardo169@gmail.com>2023-08-25 14:20:31 -0400
commitba9f2373c0d017ff094dd99a068c6b8a6afd80dc (patch)
treeef3c894a151a32501170d7e48cfcc9bf70796d6b /Source/Android/app/src/main/java/org
parent24c882622fbcfc7dafee14824ba1f070b000b3aa (diff)
Android: Convert DolphinSensorEventListener to Kotlin
Diffstat (limited to 'Source/Android/app/src/main/java/org')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java440
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.kt504
2 files changed, 504 insertions, 440 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java
deleted file mode 100644
index 78d0e4785d..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java
+++ /dev/null
@@ -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;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.kt
new file mode 100644
index 0000000000..e89143aa31
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.kt
@@ -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
+ }
+ }
+}