From 1eff13ef764f411a67b90abb440785357c4cf999 Mon Sep 17 00:00:00 2001 From: gladiuscode Date: Wed, 18 Mar 2026 20:05:04 +0100 Subject: [PATCH 1/7] feat(android): fully migrate to reliable sensor + face up / down detection --- .../OrientationDirectorModuleImpl.kt | 28 +--- .../OrientationSensorsEventListener.kt | 154 +++++++++--------- .../implementation/Utils.kt | 54 ------ 3 files changed, 84 insertions(+), 152 deletions(-) diff --git a/android/src/main/java/com/orientationdirector/implementation/OrientationDirectorModuleImpl.kt b/android/src/main/java/com/orientationdirector/implementation/OrientationDirectorModuleImpl.kt index cdf36bf..27cd29b 100644 --- a/android/src/main/java/com/orientationdirector/implementation/OrientationDirectorModuleImpl.kt +++ b/android/src/main/java/com/orientationdirector/implementation/OrientationDirectorModuleImpl.kt @@ -26,8 +26,8 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re private var didComputeInitialDeviceOrientation = false; init { - mOrientationSensorsEventListener.setOnOrientationAnglesChangedCallback { orientation -> - onOrientationAnglesChanged(orientation) + mOrientationSensorsEventListener.setOnDeviceOrientationChangedCallback { orientation -> + onDeviceOrientationChanged(orientation) } mAutoRotationObserver.enable() @@ -151,15 +151,10 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re ).contains(activity.requestedOrientation) } - private fun onOrientationAnglesChanged(orientationAngles: FloatArray) { - val deviceOrientation = mUtils.convertToDeviceOrientationFrom(orientationAngles) - if (deviceOrientation == Orientation.UNKNOWN) { - return - } + private fun onDeviceOrientationChanged(deviceOrientation: Orientation) { + if (deviceOrientation == Orientation.UNKNOWN) return - if (lastDeviceOrientation == deviceOrientation) { - return - } + if (lastDeviceOrientation == deviceOrientation) return mEventManager.sendDeviceOrientationDidChange(deviceOrientation.ordinal) lastDeviceOrientation = deviceOrientation @@ -168,9 +163,8 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re // NOTE(2.init): This is needed to disable sensors if they were needed just for the initial // device computation. - if (didComputeInitialDeviceOrientation) { - return - } + if (didComputeInitialDeviceOrientation) return + didComputeInitialDeviceOrientation = true if (!areOrientationSensorsEnabled) { @@ -179,13 +173,9 @@ class OrientationDirectorModuleImpl internal constructor(private val context: Re } private fun checkInterfaceOrientation(skipIfAutoRotationIsDisabled: Boolean = true) { - if (skipIfAutoRotationIsDisabled && !mAutoRotationObserver.getLastAutoRotationStatus()) { - return - } + if (skipIfAutoRotationIsDisabled && !mAutoRotationObserver.getLastAutoRotationStatus()) return - if (isLocked) { - return - } + if (isLocked) return if (lastDeviceOrientation != Orientation.LANDSCAPE_RIGHT && lastDeviceOrientation != Orientation.LANDSCAPE_LEFT) { val rotation = mUtils.getInterfaceRotation() diff --git a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt index 4790e3f..f49ea0a 100644 --- a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt +++ b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt @@ -5,52 +5,35 @@ import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager +import android.util.Log +import android.view.OrientationEventListener import com.facebook.react.bridge.ReactApplicationContext +import kotlin.math.abs class OrientationSensorsEventListener( context: ReactApplicationContext, -) : SensorEventListener { +) : SensorEventListener, OrientationEventListener(context, SensorManager.SENSOR_DELAY_UI) { + private val rotationMatrix = FloatArray(9) + private val orientationAngles = FloatArray(3) + private var mSensorManager: SensorManager = context.getSystemService(SENSOR_SERVICE) as SensorManager - private var mRotationSensor: Sensor? = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) - private var mAccelerometerSensor: Sensor? = - mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) - private var mMagneticFieldSensor: Sensor? = - mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) - private var hasRotationSensor: Boolean = - mRotationSensor != null - private var hasAccelerometerAndMagneticFieldSensors: Boolean = - mAccelerometerSensor != null && mMagneticFieldSensor != null + mRotationSensor != null - private val accelerometerReading = FloatArray(3) - private val magnetometerReading = FloatArray(3) - - private var lastComputedOrientationAngles = FloatArray(3) - private var onOrientationAnglesChangedCallback: ((orientationAngles: FloatArray) -> Unit)? = null - - fun setOnOrientationAnglesChangedCallback(callback: (orientation: FloatArray) -> Unit) { - onOrientationAnglesChangedCallback = callback - } - - override fun onSensorChanged(event: SensorEvent?) { - if (event == null) { - return - } - - if (event.sensor.type == Sensor.TYPE_ROTATION_VECTOR) { - computeOrientationFromRotationSensor(event.values); - return - } + private var lastComputedDeviceOrientation = Orientation.UNKNOWN + private var lastComputedFaceOrientation = Orientation.UNKNOWN - computeOrientationFromOtherSensors(event) + private var onDeviceOrientationChangedCallback: ((deviceOrientation: Orientation) -> Unit)? = null + fun setOnDeviceOrientationChangedCallback(callback: (deviceOrientation: Orientation) -> Unit) { + onDeviceOrientationChangedCallback = callback } - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} + override fun enable() { + super.enable() - fun enable() { if (hasRotationSensor) { mSensorManager.registerListener( this, @@ -60,70 +43,83 @@ class OrientationSensorsEventListener( ) return } + } - if (hasAccelerometerAndMagneticFieldSensors) { - mSensorManager.registerListener( - this, - mAccelerometerSensor, - SensorManager.SENSOR_DELAY_NORMAL, - SensorManager.SENSOR_DELAY_UI - ) - mSensorManager.registerListener( - this, - mMagneticFieldSensor, - SensorManager.SENSOR_DELAY_NORMAL, - SensorManager.SENSOR_DELAY_UI - ) - return + override fun disable() { + super.disable() + + if (hasRotationSensor) { + mSensorManager.unregisterListener(this) } } - fun disable() { - mSensorManager.unregisterListener(this) - } + override fun onOrientationChanged(angleDegrees: Int) { + if (angleDegrees == ORIENTATION_UNKNOWN) { + lastComputedDeviceOrientation = Orientation.UNKNOWN + return + } - private fun computeOrientationFromRotationSensor(values: FloatArray) { - val rotationMatrix = FloatArray(9) - SensorManager.getRotationMatrixFromVector(rotationMatrix, values) + val currentDeviceOrientation = when (angleDegrees) { + in LANDSCAPE_RIGHT_START .. LANDSCAPE_RIGHT_END -> Orientation.LANDSCAPE_RIGHT + in PORTRAIT_UPSIDE_DOWN_START..PORTRAIT_UPSIDE_DOWN_END -> Orientation.PORTRAIT_UPSIDE_DOWN + in LANDSCAPE_LEFT_START..LANDSCAPE_LEFT_END -> Orientation.LANDSCAPE_LEFT + else -> Orientation.PORTRAIT + } - val orientationAngles = FloatArray(3) - SensorManager.getOrientation(rotationMatrix, orientationAngles) + if (currentDeviceOrientation == lastComputedDeviceOrientation) return - notifyOrientationAnglesChanged(orientationAngles) + notifyDeviceOrientationChanged(currentDeviceOrientation) } - private fun computeOrientationFromOtherSensors(event: SensorEvent) { - if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) { - System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size) - } - - if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) { - System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size) - } + override fun onSensorChanged(event: SensorEvent) { + if (event.sensor.type != Sensor.TYPE_ROTATION_VECTOR) return - val rotationMatrix = FloatArray(9) - val didComputeMatrix = SensorManager.getRotationMatrix( - rotationMatrix, - null, - accelerometerReading, - magnetometerReading - ) - if (!didComputeMatrix) { + if (lastComputedDeviceOrientation != Orientation.UNKNOWN) { + lastComputedFaceOrientation = Orientation.UNKNOWN return } - val orientationAngles = FloatArray(3) + SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values) SensorManager.getOrientation(rotationMatrix, orientationAngles) - notifyOrientationAnglesChanged(orientationAngles) - } + val pitch = Math.toDegrees(orientationAngles[1].toDouble()).toFloat() + val roll = Math.toDegrees(orientationAngles[2].toDouble()).toFloat() + val absPitch = abs(pitch) + val absRoll = abs(roll) - private fun notifyOrientationAnglesChanged(orientationAngles: FloatArray) { - if (lastComputedOrientationAngles.contentEquals(orientationAngles)) { - return + val currentFaceOrientation = when { + absPitch < FACE_UP_LIMIT && absRoll < FACE_UP_LIMIT -> Orientation.FACE_UP + absPitch > FACE_DOWN_LIMIT || absRoll > FACE_DOWN_LIMIT -> Orientation.FACE_DOWN + else -> null } - onOrientationAnglesChangedCallback?.invoke(orientationAngles) - lastComputedOrientationAngles = orientationAngles + if (currentFaceOrientation == null) return + + if (currentFaceOrientation == lastComputedFaceOrientation) return + + notifyFaceOrientationChanged(currentFaceOrientation) + } + + private fun notifyDeviceOrientationChanged(deviceOrientation: Orientation) { + onDeviceOrientationChangedCallback?.invoke(deviceOrientation) + lastComputedDeviceOrientation = deviceOrientation + } + + private fun notifyFaceOrientationChanged(faceOrientation: Orientation) { + onDeviceOrientationChangedCallback?.invoke(faceOrientation) + lastComputedFaceOrientation = faceOrientation + } + + override fun onAccuracyChanged(p0: Sensor?, p1: Int) {} + + companion object { + private const val LANDSCAPE_RIGHT_START = 45 + private const val LANDSCAPE_RIGHT_END = 134 + private const val PORTRAIT_UPSIDE_DOWN_START = 135 + private const val PORTRAIT_UPSIDE_DOWN_END = 224 + private const val LANDSCAPE_LEFT_START = 225 + private const val LANDSCAPE_LEFT_END = 314 + private const val FACE_UP_LIMIT = 15f + private const val FACE_DOWN_LIMIT = 155f } } diff --git a/android/src/main/java/com/orientationdirector/implementation/Utils.kt b/android/src/main/java/com/orientationdirector/implementation/Utils.kt index 0553307..915084a 100644 --- a/android/src/main/java/com/orientationdirector/implementation/Utils.kt +++ b/android/src/main/java/com/orientationdirector/implementation/Utils.kt @@ -19,51 +19,6 @@ class Utils(private val context: ReactContext) { } } - fun convertToDeviceOrientationFrom(orientationAngles: FloatArray): Orientation { - if (orientationAngles.size < 3) { - return Orientation.PORTRAIT - } - - val (_, pitchRadians, rollRadians) = orientationAngles - - val pitch = Math.toDegrees(pitchRadians.toDouble()).toFloat() - val roll = Math.toDegrees(rollRadians.toDouble()).toFloat() - - val faceUpDownPitchTolerance = 30f - - fun isValueCloseTo(value: Float, target: Float, tolerance: Float): Boolean { - return value in (target - tolerance)..(target + tolerance) - } - - return when { - // Face up: device is lying flat with screen up - isValueCloseTo(pitch, 0f, faceUpDownPitchTolerance) && - isValueCloseTo(roll, 0f, faceUpDownPitchTolerance) -> Orientation.FACE_UP - - // Face down: device is lying flat with screen down - isValueCloseTo(pitch, 0f, faceUpDownPitchTolerance) && - (isValueCloseTo(roll, 180f, faceUpDownPitchTolerance) || isValueCloseTo( - roll, - -180f, - faceUpDownPitchTolerance - )) -> Orientation.FACE_DOWN - - // Portrait - isValueCloseTo(pitch, -90f, 45f) -> Orientation.PORTRAIT - - // Portrait upside down - isValueCloseTo(pitch, 90f, 45f) -> Orientation.PORTRAIT_UPSIDE_DOWN - - // Landscape left - isValueCloseTo(roll, -90f, 45f) -> Orientation.LANDSCAPE_LEFT - - // Landscape right - isValueCloseTo(roll, 90f, 45f) -> Orientation.LANDSCAPE_RIGHT - - else -> Orientation.UNKNOWN - } - } - fun convertToActivityOrientationFrom(orientation: Orientation): Int { return when (orientation) { Orientation.LANDSCAPE_RIGHT -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE @@ -93,13 +48,4 @@ class Utils(private val context: ReactContext) { } } - fun convertToInterfaceOrientationFrom(deviceOrientation: Orientation): Orientation { - return when (deviceOrientation) { - Orientation.PORTRAIT -> Orientation.PORTRAIT - Orientation.LANDSCAPE_RIGHT -> Orientation.LANDSCAPE_LEFT - Orientation.PORTRAIT_UPSIDE_DOWN -> Orientation.PORTRAIT_UPSIDE_DOWN - Orientation.LANDSCAPE_LEFT -> Orientation.LANDSCAPE_RIGHT - else -> Orientation.UNKNOWN - } - } } From b574664ecbcc0e869fd8f7fff7782ff015a8a9f5 Mon Sep 17 00:00:00 2001 From: gladiuscode Date: Wed, 18 Mar 2026 20:12:55 +0100 Subject: [PATCH 2/7] fix(android): onAccuracyChanged body --- .../implementation/OrientationSensorsEventListener.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt index f49ea0a..2f9d351 100644 --- a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt +++ b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt @@ -110,7 +110,7 @@ class OrientationSensorsEventListener( lastComputedFaceOrientation = faceOrientation } - override fun onAccuracyChanged(p0: Sensor?, p1: Int) {} + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit companion object { private const val LANDSCAPE_RIGHT_START = 45 From fa2a2793511568e4ad95c56c92cec0f29fb611b9 Mon Sep 17 00:00:00 2001 From: gladiuscode Date: Wed, 18 Mar 2026 21:14:48 +0100 Subject: [PATCH 3/7] fix(android): missing last computation reset ono enable / disable --- .../implementation/OrientationSensorsEventListener.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt index 2f9d351..6399776 100644 --- a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt +++ b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt @@ -41,8 +41,10 @@ class OrientationSensorsEventListener( SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI ) - return } + + lastComputedDeviceOrientation = Orientation.UNKNOWN + lastComputedFaceOrientation = Orientation.UNKNOWN } override fun disable() { @@ -51,6 +53,9 @@ class OrientationSensorsEventListener( if (hasRotationSensor) { mSensorManager.unregisterListener(this) } + + lastComputedDeviceOrientation = Orientation.UNKNOWN + lastComputedFaceOrientation = Orientation.UNKNOWN } override fun onOrientationChanged(angleDegrees: Int) { From 44e708b6d53abb53115a852507147b419f8856fd Mon Sep 17 00:00:00 2001 From: gladiuscode Date: Thu, 19 Mar 2026 20:40:26 +0100 Subject: [PATCH 4/7] fix(android): face orientation computation --- .../OrientationSensorsEventListener.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt index 6399776..a094575 100644 --- a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt +++ b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt @@ -87,14 +87,10 @@ class OrientationSensorsEventListener( SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values) SensorManager.getOrientation(rotationMatrix, orientationAngles) - val pitch = Math.toDegrees(orientationAngles[1].toDouble()).toFloat() - val roll = Math.toDegrees(orientationAngles[2].toDouble()).toFloat() - val absPitch = abs(pitch) - val absRoll = abs(roll) - + val zUp = rotationMatrix[8] val currentFaceOrientation = when { - absPitch < FACE_UP_LIMIT && absRoll < FACE_UP_LIMIT -> Orientation.FACE_UP - absPitch > FACE_DOWN_LIMIT || absRoll > FACE_DOWN_LIMIT -> Orientation.FACE_DOWN + zUp > FACE_UP_Z_THRESHOLD -> Orientation.FACE_UP + zUp < -FACE_DOWN_Z_THRESHOLD -> Orientation.FACE_DOWN else -> null } @@ -124,7 +120,7 @@ class OrientationSensorsEventListener( private const val PORTRAIT_UPSIDE_DOWN_END = 224 private const val LANDSCAPE_LEFT_START = 225 private const val LANDSCAPE_LEFT_END = 314 - private const val FACE_UP_LIMIT = 15f - private const val FACE_DOWN_LIMIT = 155f + private const val FACE_UP_Z_THRESHOLD = 0.906f + private const val FACE_DOWN_Z_THRESHOLD = 0.906f } } From 7fb6b359f0542ff026440f01fdd9e922e6358f71 Mon Sep 17 00:00:00 2001 From: gladiuscode Date: Thu, 19 Mar 2026 20:43:57 +0100 Subject: [PATCH 5/7] chore: clean up code --- .../implementation/OrientationSensorsEventListener.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt index a094575..85271b4 100644 --- a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt +++ b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt @@ -5,10 +5,8 @@ import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager -import android.util.Log import android.view.OrientationEventListener import com.facebook.react.bridge.ReactApplicationContext -import kotlin.math.abs class OrientationSensorsEventListener( context: ReactApplicationContext, From af729fdf206bc42fe783c9cb1b8d5717acbe3b3d Mon Sep 17 00:00:00 2001 From: gladiuscode Date: Thu, 19 Mar 2026 21:21:13 +0100 Subject: [PATCH 6/7] fix(android): coderabbit issue --- .../implementation/OrientationSensorsEventListener.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt index 85271b4..ec1c4e9 100644 --- a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt +++ b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt @@ -100,13 +100,13 @@ class OrientationSensorsEventListener( } private fun notifyDeviceOrientationChanged(deviceOrientation: Orientation) { - onDeviceOrientationChangedCallback?.invoke(deviceOrientation) lastComputedDeviceOrientation = deviceOrientation + onDeviceOrientationChangedCallback?.invoke(deviceOrientation) } private fun notifyFaceOrientationChanged(faceOrientation: Orientation) { - onDeviceOrientationChangedCallback?.invoke(faceOrientation) lastComputedFaceOrientation = faceOrientation + onDeviceOrientationChangedCallback?.invoke(faceOrientation) } override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit From c4c8a09350a1270f12014b4ba0d72f744175aa7a Mon Sep 17 00:00:00 2001 From: gladiuscode Date: Thu, 19 Mar 2026 21:23:43 +0100 Subject: [PATCH 7/7] fix(android): remove useless getOrientation computation --- .../implementation/OrientationSensorsEventListener.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt index ec1c4e9..0f388e3 100644 --- a/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt +++ b/android/src/main/java/com/orientationdirector/implementation/OrientationSensorsEventListener.kt @@ -12,7 +12,6 @@ class OrientationSensorsEventListener( context: ReactApplicationContext, ) : SensorEventListener, OrientationEventListener(context, SensorManager.SENSOR_DELAY_UI) { private val rotationMatrix = FloatArray(9) - private val orientationAngles = FloatArray(3) private var mSensorManager: SensorManager = context.getSystemService(SENSOR_SERVICE) as SensorManager @@ -83,7 +82,6 @@ class OrientationSensorsEventListener( } SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values) - SensorManager.getOrientation(rotationMatrix, orientationAngles) val zUp = rotationMatrix[8] val currentFaceOrientation = when {