From a1c8974e7bc381d237155f6db7910771137afbf3 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Wed, 10 Jul 2024 15:06:00 +1000 Subject: [PATCH] OrientationManager The new OrientationManager encapsulate the orientation logic and sends out a mutable state flow --- .../securesms/calls/OrientationManager.kt | 87 +++++++++++++++++++ .../securesms/calls/WebRtcCallActivity.kt | 83 ++++-------------- 2 files changed, 106 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/calls/OrientationManager.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/OrientationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/OrientationManager.kt new file mode 100644 index 0000000000..baae40bcb2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/OrientationManager.kt @@ -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 + + 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) {} +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt index b4c05e4b7a..92f1e27736 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt @@ -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() {