How to know if mobile is tilted upward or downward in Android?

163 views Asked by At

I am making an game where I need to know if the phone is tilted upward (towards sky) or tilted downwards (towards ground) when in landscape.

I have used this answer Answer Link as my starting point. But when app is in landscape orientation , phone perpendicular to the ground gives value 0 and tilting it upwards or downwards give value 90. Due to this I can't determine if user has tilted phone upwards or downwards.

If anyone wants to know how I am implementing my sensor then he can ask for it but I think this much context would be enough to understand the problem.

2

There are 2 answers

2
Vikalp Ranjan On BEST ANSWER

This is how I reached the correct solution. First I made an abstract class to be used with multiple sensors (learned from Phillip Lanckner Youtube)

 abstract class MeasurableSensor(protected  val sensorType : Int) {
        
            protected var onSensorValuesChanged : ((List<Float>) -> Unit)? = null
        
            abstract val doesSensorExist : Boolean
        
            abstract fun startListening()
            abstract fun stopListening()
        
            fun setOnSensorValuesChangedListener(listener : ((List<Float>) -> Unit)){
                onSensorValuesChanged = listener
            }
        }
  

  abstract class AndroidSensor(
        private val context : Context,
        private val sensorFeature : String,
        sensorType : Int
    ) : MeasurableSensor(sensorType) , SensorEventListener{
    
        override val doesSensorExist: Boolean
            get() = context.packageManager.hasSystemFeature(sensorFeature)
    
        private lateinit var sensorManager: SensorManager
        private var sensor : Sensor? = null
    
        override fun startListening() {
            if(!doesSensorExist) return
    
            if(!::sensorManager.isInitialized && sensor == null){
                sensorManager = context.getSystemService(SensorManager :: class.java) as SensorManager
                sensor = sensorManager.getDefaultSensor(sensorType)
            }
    
            sensor?.let {
                sensorManager.registerListener(this , it , SensorManager.SENSOR_DELAY_NORMAL)
            }
        }
    
        override fun stopListening() {
            if(!doesSensorExist || !::sensorManager.isInitialized) return
            sensorManager.unregisterListener(this)
        }
    
    
        override fun onSensorChanged(event: SensorEvent?) {
            if(!doesSensorExist) return
    
            if (event?.sensor?.type == sensorType){
                onSensorValuesChanged?.invoke(event.values.toList())
            }
        }
    
        override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
    }
    

These are two sensor required to get the exact orientation of phone in 3D plane.

 class AccelerometerSensor(
        context: Context
    ) : AndroidSensor(
        context,
        PackageManager.FEATURE_SENSOR_ACCELEROMETER,
        Sensor.TYPE_ACCELEROMETER
    )
    class MagnetometerSensor(
        context: Context
    ) : AndroidSensor(
        context,
        PackageManager.FEATURE_SENSOR_ACCELEROMETER,
        Sensor.TYPE_MAGNETIC_FIELD
    )

Implementation in the viewModel

@HiltViewModel
class ViewModel @Inject constructor(
    private val accelerometerSensor : MeasurableSensor,
    private val magnetometerSensor : MagnetometerSensor,
) : ViewModel() {

  private val _orientation = MutableStateFlow(OrientationState.PERPENDICULAR)
    val orientation : StateFlow<OrientationState> get() = _orientation


    private val accelerometerReading = FloatArray(3)
    private val magnetometerReading = FloatArray(3)

    private val rotationMatrix = FloatArray(9)
    private val orientationAngles = FloatArray(3)

    fun startSensors(){
        accelerometerSensor.startListening()
        magnetometerSensor.startListening()
        initListeners()
    }

    fun stopSensors(){
        accelerometerSensor.stopListening()
        magnetometerSensor.stopListening()
    }

    private fun initListeners() {
        accelerometerSensor.setOnSensorValuesChangedListener { accelerometerReading ->
            this.accelerometerReading[0] = accelerometerReading[0]
            this.accelerometerReading[1] = accelerometerReading[1]
            this.accelerometerReading[2] = accelerometerReading[2]
            computeOrientation()
        }
        magnetometerSensor.setOnSensorValuesChangedListener { magnetometerReading ->
            this.magnetometerReading[0] = magnetometerReading[0]
            this.magnetometerReading[1] = magnetometerReading[1]
            this.magnetometerReading[2] = magnetometerReading[2]
        }
    }

    private fun computeOrientation() {
        SensorManager.getRotationMatrix(
            rotationMatrix,
            null,
            accelerometerReading,
            magnetometerReading
        )

        // "rotationMatrix" now has up-to-date information.

         SensorManager.getOrientation(rotationMatrix, orientationAngles)

        // "orientationAngles" now has up-to-date information.

        val (_ , _ , y) = orientationAngles

 // Values used here , are the ones needed by me , you may or may not need the same values 
        val orientationState = when (y.radToDegrees()) {
                in 226..314 -> OrientationState.PERPENDICULAR
                in 315..359, in 0..44 -> OrientationState.UPWARDS
                in 136..225 -> OrientationState.DOWNWARDS
                else -> OrientationState.PERPENDICULAR
        }

        viewModelScope.launch {
            _orientation.emit(orientationState)
        }
    }

extension function to convert rad into degrees

fun Float.radToDegrees() : Int {
        return (Math.toDegrees(this.toDouble()).toInt() + 360) % 360
    }

Phillip Lackner Video can be used to learn sensor management in detail and computing the device orientation can be learned further more in android docs

3
Conrad Peyer On

Update:

A simple solution is to use the accelerometer to get the current gravity of the z axis.

override fun onSensorChanged(event: SensorEvent) {
    if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
        // Check the orientation based on accelerometer data
        val x = event.values[0]
        val y = event.values[1]
        val z = event.values[2]

        // Assuming the device is facing up if the Z-axis is close to 9.81 (gravity)
        val isFacingUp = z > 9.0

        // Assuming the device is facing down if the Z-axis is close to -9.81 (gravity)
        val isFacingDown = z < -9.0

        if (isFacingUp) {
            println("I'm facing up!")
        } else if (isFacingDown) {
            println("I'm facing down!")
        } else {
            println("I'm facing another direction!")
        }
    }
}

Old Answer:

If you follow the Google example from https://developer.android.com/guide/topics/sensors/sensors_position#sensors-pos-orient you should get enough information to get the exact orientation of the device.

The relevant data can be found in the rotationMatrix (or as angles in orientationAngles) variable.

If you rotate the device in your hand face down around the y axis, the x and z axes vectors point in the opposite direction.

Display facing up (rounded values)

rotationMatrix: [0, 1, 0, -1, 0, 0, 0, 0, 1]

Display facing down (rounded values)

rotationMatrix: [0, -1, 0, -1, 0, 0, 0, 0, -1]

Here is a nice tool to visualize the orientation: https://ninja-calc.mbedded.ninja/calculators/mathematics/geometry/3d-rotations