mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-11 18:54:21 +00:00
OrientationManager
The new OrientationManager encapsulate the orientation logic and sends out a mutable state flow
This commit is contained in:
parent
503f361f63
commit
a1c8974e7b
@ -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) {}
|
||||||
|
}
|
@ -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() {
|
||||||
|
Loading…
Reference in New Issue
Block a user