diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallAudioManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallAudioManager.kt index d703c22d8..171f53243 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallAudioManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallAudioManager.kt @@ -24,6 +24,10 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager import android.media.AudioAttributes import android.media.AudioDeviceInfo import android.media.AudioManager @@ -62,6 +66,25 @@ class CallAudioManager( private val _isBluetoothAvailable = MutableStateFlow(false) val isBluetoothAvailable: StateFlow = _isBluetoothAvailable.asStateFlow() + // Proximity sensor — detects when the phone is held near the ear + private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager + private val proximitySensor: Sensor? = sensorManager?.getDefaultSensor(Sensor.TYPE_PROXIMITY) + private val _isNearEar = MutableStateFlow(false) + val isNearEar: StateFlow = _isNearEar.asStateFlow() + + private val proximityListener = + object : SensorEventListener { + override fun onSensorChanged(event: SensorEvent) { + val maxRange = event.sensor.maximumRange + _isNearEar.value = event.values[0] < maxRange + } + + override fun onAccuracyChanged( + sensor: Sensor?, + accuracy: Int, + ) {} + } + fun startRinging() { startRingtone() startVibration() @@ -137,11 +160,24 @@ class CallAudioManager( "amethyst:call_proximity", ) proximityWakeLock?.acquire(60 * 60 * 1000L) + registerProximitySensor() } fun releaseProximityWakeLock() { proximityWakeLock?.let { if (it.isHeld) it.release() } proximityWakeLock = null + unregisterProximitySensor() + } + + private fun registerProximitySensor() { + proximitySensor?.let { + sensorManager?.registerListener(proximityListener, it, SensorManager.SENSOR_DELAY_NORMAL) + } + } + + private fun unregisterProximitySensor() { + sensorManager?.unregisterListener(proximityListener) + _isNearEar.value = false } fun release() { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt index cbdd6f3d9..b33875ac1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt @@ -93,6 +93,9 @@ class CallController( val audioRoute: StateFlow = audioManager.audioRoute val isBluetoothAvailable: StateFlow = audioManager.isBluetoothAvailable + // Tracks whether video is paused because the phone is near the ear + private var videoPausedByProximity = false + init { callManager.onRenegotiationOfferReceived = { event -> onRenegotiationOfferReceived(event) } @@ -126,6 +129,22 @@ class CallController( } } } + + // Pause outgoing video when the phone is held near the ear + scope.launch { + audioManager.isNearEar.collect { nearEar -> + val session = webRtcSession ?: return@collect + if (nearEar && _isVideoEnabled.value && !videoPausedByProximity) { + videoPausedByProximity = true + session.setVideoEnabled(false) + session.stopCamera() + } else if (!nearEar && videoPausedByProximity) { + videoPausedByProximity = false + session.setVideoEnabled(true) + session.startCamera() + } + } + } } fun initiateCall( @@ -323,6 +342,7 @@ class CallController( _isAudioMuted.value = false _isVideoEnabled.value = false _isRemoteVideoActive.value = false + videoPausedByProximity = false webRtcSession?.dispose() webRtcSession = null remoteDescriptionSet.set(false)