androidkotlinaccelerometerandroid-sensorsgyroscope

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


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.


Solution

  • 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