From 065481d9899e6debc94bdbbb812512757f8eac62 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 17 Sep 2022 12:12:39 +0200 Subject: ControllerInterface/Android: Automatically suspend sensors This is a battery-saving measure. Whether a sensor should be suspended is determined in the same way as whether key events and motion events should be handled by the OS rather than consumed by Dolphin. --- .../dolphinemu/activities/EmulationActivity.java | 7 +- .../features/input/model/ControllerInterface.java | 22 ++--- .../input/model/DolphinSensorEventListener.java | 108 +++++++++++++-------- .../features/input/model/SensorEventRequester.java | 16 --- 4 files changed, 81 insertions(+), 72 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/SensorEventRequester.java (limited to 'Source/Android/app/src/main/java') diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java index adf882d369..4a9816c259 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -11,7 +11,6 @@ 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; @@ -43,7 +42,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.input.model.DolphinSensorEventListener; import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; import org.dolphinemu.dolphinemu.features.settings.model.IntSetting; import org.dolphinemu.dolphinemu.features.settings.model.Settings; @@ -445,14 +444,14 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP updateOrientation(); - ControllerInterface.enableSensorEvents(() -> getWindowManager().getDefaultDisplay()); + DolphinSensorEventListener.setDeviceRotation( + getWindowManager().getDefaultDisplay().getRotation()); } @Override protected void onPause() { super.onPause(); - ControllerInterface.disableSensorEvents(); } @Override diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java index 96480ab69c..2b6e1d62ee 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java @@ -66,24 +66,22 @@ public final class ControllerInterface /** * {@link DolphinSensorEventListener} calls this for each axis of a received SensorEvent. - */ - public static native void dispatchSensorEvent(String deviceQualifier, 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. + * @return true if the emulator core seems to be interested in this event. + * false if the sensor can be suspended to save battery. */ - public static native void enableSensorEvents(SensorEventRequester requester); + public static native boolean dispatchSensorEvent(String deviceQualifier, String axisName, + float value); /** - * Disables delivering sensor events to native code. + * Called when a sensor is suspended or unsuspended. * - * Calling this when sensor events are no longer needed will save battery. + * @param deviceQualifier A string used by native code for uniquely identifying devices. + * @param axisNames The name of all axes for the sensor. + * @param suspended Whether the sensor is now suspended. */ - public static native void disableSensorEvents(); + public static native void notifySensorSuspendedState(String deviceQualifier, String[] axisNames, + boolean suspended); /** * Rescans for input devices. 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 index 21d533a430..cfc4f34dd7 100644 --- 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 @@ -12,12 +12,15 @@ 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 { @@ -43,6 +46,7 @@ public class DolphinSensorEventListener implements SensorEventListener public final int sensorType; public final String[] axisNames; public final AxisSetDetails[] axisSetDetails; + public boolean isSuspended = true; public SensorDetails(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails) { @@ -52,6 +56,8 @@ public class DolphinSensorEventListener implements SensorEventListener } } + private static int sDeviceRotation = Surface.ROTATION_0; + private final SensorManager mSensorManager; private final HashMap mSensorDetails = new HashMap<>(); @@ -60,8 +66,6 @@ public class DolphinSensorEventListener implements SensorEventListener private String mDeviceQualifier = ""; - 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; @@ -218,6 +222,7 @@ public class DolphinSensorEventListener implements SensorEventListener int eventAxisIndex = 0; int detailsAxisIndex = 0; int detailsAxisSetIndex = 0; + boolean keepSensorAlive = false; while (eventAxisIndex < values.length && detailsAxisIndex < axisNames.length) { if (detailsAxisSetIndex < axisSetDetails.length && @@ -227,7 +232,7 @@ public class DolphinSensorEventListener implements SensorEventListener if (mRotateCoordinatesForScreenOrientation && axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES) { - rotation = mRequester.getDisplay().getRotation(); + rotation = sDeviceRotation; } float x, y; @@ -254,17 +259,18 @@ public class DolphinSensorEventListener implements SensorEventListener float z = values[eventAxisIndex + 2]; - ControllerInterface.dispatchSensorEvent(mDeviceQualifier, axisNames[detailsAxisIndex], x); - ControllerInterface.dispatchSensorEvent(mDeviceQualifier, axisNames[detailsAxisIndex + 1], - x); - ControllerInterface.dispatchSensorEvent(mDeviceQualifier, axisNames[detailsAxisIndex + 2], - y); - ControllerInterface.dispatchSensorEvent(mDeviceQualifier, axisNames[detailsAxisIndex + 3], - y); - ControllerInterface.dispatchSensorEvent(mDeviceQualifier, axisNames[detailsAxisIndex + 4], - z); - ControllerInterface.dispatchSensorEvent(mDeviceQualifier, axisNames[detailsAxisIndex + 5], - z); + 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; @@ -272,13 +278,18 @@ public class DolphinSensorEventListener implements SensorEventListener } else { - ControllerInterface.dispatchSensorEvent(mDeviceQualifier, axisNames[detailsAxisIndex], - values[eventAxisIndex]); + keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, + axisNames[detailsAxisIndex], values[eventAxisIndex]); eventAxisIndex++; detailsAxisIndex++; } } + + if (!keepSensorAlive) + { + setSensorSuspended(sensorEvent.sensor, sensorDetails, true); + } } @Override @@ -298,44 +309,48 @@ public class DolphinSensorEventListener implements SensorEventListener } /** - * Enables delivering sensor events to native code. + * If a sensor has been suspended to save battery, this unsuspends it. + * If the sensor isn't currently suspended, nothing happens. * - * @param requester The activity or other component which is requesting sensor events to be - * delivered. + * @param axisName The name of any of the sensor's axes. */ @Keep - public void enableSensorEvents(SensorEventRequester requester) + public void requestUnsuspendSensor(String axisName) { - if (mRequester != null) + for (Map.Entry entry : mSensorDetails.entrySet()) { - throw new IllegalStateException("Attempted to enable sensor events when someone else" + - "had already enabled them"); + if (Arrays.asList(entry.getValue().axisNames).contains(axisName)) + { + setSensorSuspended(entry.getKey(), entry.getValue(), false); + } } + } - mRequester = requester; + private void setSensorSuspended(Sensor sensor, SensorDetails sensorDetails, boolean suspend) + { + boolean changeOccurred = false; - if (mSensorManager != null) + synchronized (sensorDetails) { - for (Sensor sensor : mSensorDetails.keySet()) + if (sensorDetails.isSuspended != suspend) { - mSensorManager.registerListener(this, sensor, SAMPLING_PERIOD_US); + 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; } } - } - /** - * 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; - - if (mSensorManager != null) + if (changeOccurred) { - mSensorManager.unregisterListener(this); + Log.info((suspend ? "Suspended sensor " : "Unsuspended sensor ") + sensor.getName()); } } @@ -403,4 +418,17 @@ public class DolphinSensorEventListener implements SensorEventListener 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/SensorEventRequester.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/SensorEventRequester.java deleted file mode 100644 index 83b58015b2..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/SensorEventRequester.java +++ /dev/null @@ -1,16 +0,0 @@ -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(); -} -- cgit v1.2.3