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.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.media.AudioManager
import android.os.Build
@ -16,10 +15,8 @@ import android.os.Bundle
import android.provider.Settings
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.view.WindowManager
import android.widget.FrameLayout
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
@ -57,7 +54,7 @@ import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.SP
import kotlin.math.asin
@AndroidEntryPoint
class WebRtcCallActivity : PassphraseRequiredActionBarActivity(), SensorEventListener {
class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
companion object {
const val ACTION_PRE_OFFER = "pre-offer"
@ -81,9 +78,13 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity(), SensorEventLis
}
private var hangupReceiver: BroadcastReceiver? = null
private var sensorManager: SensorManager? = null
private var rotationVectorSensor: Sensor? = null
private var lastOrientation = Orientation.UNKNOWN
/**
* 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.
*/
private var orientationManager = OrientationManager(this)
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
@ -105,15 +106,6 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity(), SensorEventLis
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
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)
setContentView(binding.root)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
@ -200,6 +192,13 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity(), SensorEventLis
onBackPressed()
}
lifecycleScope.launch {
orientationManager.orientation.collect { orientation ->
viewModel.deviceOrientation = orientation
updateControlsRotation()
}
}
clipFloatingInsets()
}
@ -222,68 +221,24 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity(), SensorEventLis
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() {
super.onResume()
rotationVectorSensor?.also { sensor ->
sensorManager?.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI)
}
orientationManager.startOrientationListener()
}
override fun 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() {
super.onDestroy()
hangupReceiver?.let { receiver ->
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
}
rotationVectorSensor = null
orientationManager.destroy()
}
private fun answerCall() {