OrientationManager

The new OrientationManager encapsulate the orientation logic and sends out a mutable state flow
This commit is contained in:
ThomasSession 2024-07-10 15:06:00 +10:00
parent 503f361f63
commit a1c8974e7b
2 changed files with 106 additions and 64 deletions

View File

@ -0,0 +1,87 @@
package org.thoughtcrime.securesms.calls
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.provider.Settings
import androidx.core.content.ContextCompat.getSystemService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity.SENSOR_SERVICE
import org.thoughtcrime.securesms.webrtc.Orientation
import kotlin.math.asin
class OrientationManager(private val context: Context): SensorEventListener {
private var sensorManager: SensorManager? = null
private var rotationVectorSensor: Sensor? = null
private val _orientation = MutableStateFlow(Orientation.UNKNOWN)
val orientation: StateFlow<Orientation> = _orientation
fun startOrientationListener(){
// create the sensor manager if it's still null
if(sensorManager == null) {
sensorManager = context.getSystemService(SENSOR_SERVICE) as SensorManager
}
if(rotationVectorSensor == null) {
rotationVectorSensor = sensorManager!!.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
}
sensorManager?.registerListener(this, rotationVectorSensor, SensorManager.SENSOR_DELAY_UI)
}
fun stopOrientationListener(){
sensorManager?.unregisterListener(this)
}
fun destroy(){
stopOrientationListener()
sensorManager = null
rotationVectorSensor = null
_orientation.value = Orientation.UNKNOWN
}
override fun onSensorChanged(event: SensorEvent) {
if (event.sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
// if auto-rotate is off, bail and send UNKNOWN
if (!isAutoRotateOn()) {
_orientation.value = Orientation.UNKNOWN
return
}
// Get the quaternion from the rotation vector sensor
val quaternion = FloatArray(4)
SensorManager.getQuaternionFromVector(quaternion, event.values)
// Calculate Euler angles from the quaternion
val pitch = asin(2.0 * (quaternion[0] * quaternion[2] - quaternion[3] * quaternion[1]))
// Convert radians to degrees
val pitchDegrees = Math.toDegrees(pitch).toFloat()
// Determine the device's orientation based on the pitch and roll values
val currentOrientation = when {
pitchDegrees > 45 -> Orientation.LANDSCAPE
pitchDegrees < -45 -> Orientation.REVERSED_LANDSCAPE
else -> Orientation.PORTRAIT
}
if (currentOrientation != _orientation.value) {
_orientation.value = currentOrientation
}
}
}
//Function to check if Android System Auto-rotate is on or off
private fun isAutoRotateOn(): Boolean {
return Settings.System.getInt(
context.contentResolver,
Settings.System.ACCELEROMETER_ROTATION, 0
) == 1
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
}

View File

@ -8,7 +8,6 @@ import android.content.IntentFilter
import android.graphics.Outline import android.graphics.Outline
import android.hardware.Sensor import android.hardware.Sensor
import android.hardware.SensorEvent import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager import android.hardware.SensorManager
import android.media.AudioManager import android.media.AudioManager
import android.os.Build import android.os.Build
@ -16,10 +15,8 @@ import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
import android.view.WindowManager import android.view.WindowManager
import android.widget.FrameLayout
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -57,7 +54,7 @@ import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.SP
import kotlin.math.asin import kotlin.math.asin
@AndroidEntryPoint @AndroidEntryPoint
class WebRtcCallActivity : PassphraseRequiredActionBarActivity(), SensorEventListener { class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
companion object { companion object {
const val ACTION_PRE_OFFER = "pre-offer" const val ACTION_PRE_OFFER = "pre-offer"
@ -81,9 +78,13 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity(), SensorEventLis
} }
private var hangupReceiver: BroadcastReceiver? = null private var hangupReceiver: BroadcastReceiver? = null
private var sensorManager: SensorManager? = null /**
private var rotationVectorSensor: Sensor? = null * We need to track the device's orientation so we can calculate whether or not to rotate the video streams
private var lastOrientation = Orientation.UNKNOWN * This works a lot better than using `OrientationEventListener > onOrientationChanged'
* which gives us a rotation angle that doesn't take into account pitch vs roll, so tipping the device from front to back would
* trigger the video rotation logic, while we really only want it when the device is in portrait or landscape.
*/
private var orientationManager = OrientationManager(this)
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) { if (item.itemId == android.R.id.home) {
@ -105,15 +106,6 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity(), SensorEventLis
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready) super.onCreate(savedInstanceState, ready)
// Only enable auto-rotate if system auto-rotate is enabled
if (isAutoRotateOn()) {
// Initialize the SensorManager
sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
// Initialize the sensors
rotationVectorSensor = sensorManager!!.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
}
binding = ActivityWebrtcBinding.inflate(layoutInflater) binding = ActivityWebrtcBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
@ -200,6 +192,13 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity(), SensorEventLis
onBackPressed() onBackPressed()
} }
lifecycleScope.launch {
orientationManager.orientation.collect { orientation ->
viewModel.deviceOrientation = orientation
updateControlsRotation()
}
}
clipFloatingInsets() clipFloatingInsets()
} }
@ -222,68 +221,24 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity(), SensorEventLis
binding.floatingRendererContainer.clipToOutline = true binding.floatingRendererContainer.clipToOutline = true
} }
//Function to check if Android System Auto-rotate is on or off
private fun isAutoRotateOn(): Boolean {
return Settings.System.getInt(
contentResolver,
Settings.System.ACCELEROMETER_ROTATION, 0
) == 1
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
rotationVectorSensor?.also { sensor -> orientationManager.startOrientationListener()
sensorManager?.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI)
}
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
sensorManager?.unregisterListener(this) orientationManager.stopOrientationListener()
} }
/**
* We need to track the device's orientation so we can calculate whether or not to rotate the video streams
* This works a lot better than using `OrientationEventListener > onOrientationChanged'
* which gives us a rotation angle that doesn't take into account pitch vs roll, so tipping the device from front to back would
* trigger the video rotation logic, while we really only want it when the device is in portrait or landscape.
*/
override fun onSensorChanged(event: SensorEvent) {
if (event.sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
// Get the quaternion from the rotation vector sensor
val quaternion = FloatArray(4)
SensorManager.getQuaternionFromVector(quaternion, event.values)
// Calculate Euler angles from the quaternion
val pitch = asin(2.0 * (quaternion[0] * quaternion[2] - quaternion[3] * quaternion[1]))
// Convert radians to degrees
val pitchDegrees = Math.toDegrees(pitch).toFloat()
// Determine the device's orientation based on the pitch and roll values
val currentOrientation = when {
pitchDegrees > 45 -> Orientation.LANDSCAPE
pitchDegrees < -45 -> Orientation.REVERSED_LANDSCAPE
else -> Orientation.PORTRAIT
}
if (currentOrientation != lastOrientation) {
lastOrientation = currentOrientation
viewModel.deviceOrientation = currentOrientation
updateControlsRotation()
}
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
hangupReceiver?.let { receiver -> hangupReceiver?.let { receiver ->
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
} }
rotationVectorSensor = null orientationManager.destroy()
} }
private fun answerCall() { private fun answerCall() {