Merge branch 'dev' into fix/video-call-rotation-and-avatars

This commit is contained in:
ThomasSession 2024-07-19 17:42:24 +10:00
commit 2d98da10ee
4 changed files with 140 additions and 129 deletions

View File

@ -0,0 +1,46 @@
package org.thoughtcrime.securesms.service
import android.os.Build
import android.telephony.PhoneStateListener
import android.telephony.PhoneStateListener.LISTEN_NONE
import android.telephony.TelephonyManager
import androidx.annotation.RequiresApi
import org.thoughtcrime.securesms.webrtc.HangUpRtcOnPstnCallAnsweredListener
import org.thoughtcrime.securesms.webrtc.HangUpRtcTelephonyCallback
import java.util.concurrent.ExecutorService
internal interface TelephonyHandler {
fun register(telephonyManager: TelephonyManager)
fun unregister(telephonyManager: TelephonyManager)
}
internal fun TelephonyHandler(serviceExecutor: ExecutorService, callback: () -> Unit) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
TelephonyHandlerV31(serviceExecutor, callback)
} else {
TelephonyHandlerV23(callback)
}
@RequiresApi(Build.VERSION_CODES.S)
private class TelephonyHandlerV31(val serviceExecutor: ExecutorService, callback: () -> Unit): TelephonyHandler {
private val callback = HangUpRtcTelephonyCallback(callback)
override fun register(telephonyManager: TelephonyManager) {
telephonyManager.registerTelephonyCallback(serviceExecutor, callback)
}
override fun unregister(telephonyManager: TelephonyManager) {
telephonyManager.unregisterTelephonyCallback(callback)
}
}
private class TelephonyHandlerV23(callback: () -> Unit): TelephonyHandler {
val callback = HangUpRtcOnPstnCallAnsweredListener(callback)
override fun register(telephonyManager: TelephonyManager) {
telephonyManager.listen(callback, PhoneStateListener.LISTEN_CALL_STATE)
}
override fun unregister(telephonyManager: TelephonyManager) {
telephonyManager.listen(callback, LISTEN_NONE)
}
}

View File

@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.service
import android.app.ForegroundServiceStartNotAllowedException
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@ -10,11 +9,11 @@ import android.content.IntentFilter
import android.content.pm.PackageManager
import android.media.AudioManager
import android.os.Build
import android.os.IBinder
import android.os.ResultReceiver
import android.telephony.PhoneStateListener
import android.telephony.PhoneStateListener.LISTEN_NONE
import android.telephony.TelephonyManager
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.lifecycle.LifecycleService
@ -34,14 +33,33 @@ import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_IN
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_PRE_OFFER
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_RINGING
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OUTGOING_RINGING
import org.thoughtcrime.securesms.webrtc.*
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
import org.thoughtcrime.securesms.webrtc.CallManager
import org.thoughtcrime.securesms.webrtc.CallViewModel
import org.thoughtcrime.securesms.webrtc.HangUpRtcOnPstnCallAnsweredListener
import org.thoughtcrime.securesms.webrtc.HangUpRtcTelephonyCallback
import org.thoughtcrime.securesms.webrtc.IncomingPstnCallReceiver
import org.thoughtcrime.securesms.webrtc.NetworkChangeReceiver
import org.thoughtcrime.securesms.webrtc.PeerConnectionException
import org.thoughtcrime.securesms.webrtc.PowerButtonReceiver
import org.thoughtcrime.securesms.webrtc.ProximityLockRelease
import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager
import org.thoughtcrime.securesms.webrtc.WiredHeadsetStateReceiver
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
import org.thoughtcrime.securesms.webrtc.data.Event
import org.thoughtcrime.securesms.webrtc.locks.LockManager
import org.webrtc.*
import org.webrtc.PeerConnection.IceConnectionState.*
import java.util.*
import org.webrtc.DataChannel
import org.webrtc.IceCandidate
import org.webrtc.MediaStream
import org.webrtc.PeerConnection
import org.webrtc.PeerConnection.IceConnectionState.CONNECTED
import org.webrtc.PeerConnection.IceConnectionState.DISCONNECTED
import org.webrtc.PeerConnection.IceConnectionState.FAILED
import org.webrtc.RtpReceiver
import org.webrtc.SessionDescription
import java.util.UUID
import java.util.concurrent.ExecutionException
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
@ -209,16 +227,11 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
private val serviceExecutor = Executors.newSingleThreadExecutor()
private val timeoutExecutor = Executors.newScheduledThreadPool(1)
private val hangupOnCallAnswered by lazy {
HangUpRtcOnPstnCallAnsweredListener {
ContextCompat.startForegroundService(this, hangupIntent(this))
}
}
private val hangupTelephonyCallback by lazy {
HangUpRtcTelephonyCallback {
ContextCompat.startForegroundService(this, hangupIntent(this))
}
private val telephonyHandler = TelephonyHandler(serviceExecutor) {
ContextCompat.startForegroundService(
this@WebRtcCallService,
hangupIntent(this@WebRtcCallService)
)
}
private var networkChangedReceiver: NetworkChangeReceiver? = null
@ -251,17 +264,12 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
return callManager.callId == expectedCallId
}
private fun isPreOffer() = callManager.isPreOffer()
private fun isBusy(intent: Intent) = callManager.isBusy(this, getCallId(intent))
private fun isIdle() = callManager.isIdle()
override fun onBind(intent: Intent): IBinder? {
return super.onBind(intent)
}
override fun onHangup() {
serviceExecutor.execute {
callManager.handleRemoteHangup()
@ -276,38 +284,41 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
if (intent == null || intent.action == null) return START_NOT_STICKY
serviceExecutor.execute {
val action = intent.action
val callId = ((intent.getSerializableExtra(EXTRA_CALL_ID) as? UUID)?.toString() ?: "No callId")
Log.i("Loki", "Handling ${intent.action} for call: ${callId}")
when {
action == ACTION_INCOMING_RING && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleNewOffer(
intent
)
action == ACTION_PRE_OFFER && isIdle() -> handlePreOffer(intent)
action == ACTION_INCOMING_RING && isBusy(intent) -> handleBusyCall(intent)
action == ACTION_INCOMING_RING && isPreOffer() -> handleIncomingRing(intent)
action == ACTION_OUTGOING_CALL && isIdle() -> handleOutgoingCall(intent)
action == ACTION_ANSWER_CALL -> handleAnswerCall(intent)
action == ACTION_DENY_CALL -> handleDenyCall(intent)
action == ACTION_LOCAL_HANGUP -> handleLocalHangup(intent)
action == ACTION_REMOTE_HANGUP -> handleRemoteHangup(intent)
action == ACTION_SET_MUTE_AUDIO -> handleSetMuteAudio(intent)
action == ACTION_SET_MUTE_VIDEO -> handleSetMuteVideo(intent)
action == ACTION_FLIP_CAMERA -> handleSetCameraFlip(intent)
action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(intent)
action == ACTION_SCREEN_OFF -> handleScreenOffChange(intent)
action == ACTION_RESPONSE_MESSAGE && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleResponseMessage(
intent
)
action == ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent)
action == ACTION_ICE_MESSAGE -> handleRemoteIceCandidate(intent)
action == ACTION_ICE_CONNECTED -> handleIceConnected(intent)
action == ACTION_CHECK_TIMEOUT -> handleCheckTimeout(intent)
action == ACTION_CHECK_RECONNECT -> handleCheckReconnect(intent)
action == ACTION_IS_IN_CALL_QUERY -> handleIsInCallQuery(intent)
action == ACTION_UPDATE_AUDIO -> handleUpdateAudio(intent)
when (action) {
ACTION_INCOMING_RING -> if (isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting) {
handleNewOffer(intent)
}
ACTION_PRE_OFFER -> if (isIdle()) handlePreOffer(intent)
ACTION_INCOMING_RING -> when {
isBusy(intent) -> handleBusyCall(intent)
isPreOffer() -> handleIncomingRing(intent)
}
ACTION_OUTGOING_CALL -> if (isIdle()) handleOutgoingCall(intent)
ACTION_ANSWER_CALL -> handleAnswerCall(intent)
ACTION_DENY_CALL -> handleDenyCall(intent)
ACTION_LOCAL_HANGUP -> handleLocalHangup(intent)
ACTION_REMOTE_HANGUP -> handleRemoteHangup(intent)
ACTION_SET_MUTE_AUDIO -> handleSetMuteAudio(intent)
ACTION_SET_MUTE_VIDEO -> handleSetMuteVideo(intent)
ACTION_FLIP_CAMERA -> handleSetCameraFlip(intent)
ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(intent)
ACTION_SCREEN_OFF -> handleScreenOffChange(intent)
ACTION_RESPONSE_MESSAGE -> if (isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting) {
handleResponseMessage(intent)
}
ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent)
ACTION_ICE_MESSAGE -> handleRemoteIceCandidate(intent)
ACTION_ICE_CONNECTED -> handleIceConnected(intent)
ACTION_CHECK_TIMEOUT -> handleCheckTimeout(intent)
ACTION_CHECK_RECONNECT -> handleCheckReconnect(intent)
ACTION_IS_IN_CALL_QUERY -> handleIsInCallQuery(intent)
ACTION_UPDATE_AUDIO -> handleUpdateAudio(intent)
}
}
return START_NOT_STICKY
@ -322,13 +333,7 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
registerWiredHeadsetStateReceiver()
registerWantsToAnswerReceiver()
if (checkSelfPermission(android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
getSystemService(TelephonyManager::class.java)
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
} else {
getSystemService(TelephonyManager::class.java)
.registerTelephonyCallback(serviceExecutor, hangupTelephonyCallback)
}
telephonyHandler.register(getSystemService(TelephonyManager::class.java))
}
registerUncaughtExceptionHandler()
networkChangedReceiver = NetworkChangeReceiver(::networkChange)
@ -735,9 +740,8 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
CallNotificationBuilder.WEBRTC_NOTIFICATION,
CallNotificationBuilder.getCallInProgressNotification(this, type, recipient)
)
}
catch(e: ForegroundServiceStartNotAllowedException) {
Log.e(TAG, "Failed to setCallInProgressNotification as a foreground service for type: ${type}, trying to update instead")
} catch (e: IllegalStateException) {
Log.e(TAG, "Failed to setCallInProgressNotification as a foreground service for type: ${type}, trying to update instead", e)
}
if (!CallNotificationBuilder.areNotificationsEnabled(this) && type == TYPE_INCOMING_PRE_OFFER) {
@ -750,11 +754,7 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
}
private fun getOptionalRemoteRecipient(intent: Intent): Recipient? =
if (intent.hasExtra(EXTRA_RECIPIENT_ADDRESS)) {
getRemoteRecipient(intent)
} else {
null
}
intent.takeIf { it.hasExtra(EXTRA_RECIPIENT_ADDRESS) }?.let(::getRemoteRecipient)
private fun getRemoteRecipient(intent: Intent): Recipient {
val remoteAddress = intent.getParcelableExtra<Address>(EXTRA_RECIPIENT_ADDRESS)
@ -763,10 +763,9 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
return Recipient.from(this, remoteAddress, true)
}
private fun getCallId(intent: Intent): UUID {
return intent.getSerializableExtra(EXTRA_CALL_ID) as? UUID
private fun getCallId(intent: Intent): UUID =
intent.getSerializableExtra(EXTRA_CALL_ID) as? UUID
?: throw AssertionError("No callId in intent!")
}
private fun insertMissedCall(recipient: Recipient, signal: Boolean) {
callManager.insertCallMessage(
@ -788,8 +787,8 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
callReceiver?.let { receiver ->
unregisterReceiver(receiver)
}
wiredHeadsetStateReceiver?.let { unregisterReceiver(it) }
powerButtonReceiver?.let { unregisterReceiver(it) }
wiredHeadsetStateReceiver?.let(::unregisterReceiver)
powerButtonReceiver?.let(::unregisterReceiver)
networkChangedReceiver?.unregister(this)
wantsToAnswerReceiver?.let { receiver ->
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
@ -804,14 +803,7 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
currentTimeouts = 0
isNetworkAvailable = false
if (checkSelfPermission(android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
val telephonyManager = getSystemService(TelephonyManager::class.java)
with(telephonyManager) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
this.listen(hangupOnCallAnswered, LISTEN_NONE)
} else {
this.unregisterTelephonyCallback(hangupTelephonyCallback)
}
}
telephonyHandler.unregister(getSystemService(TelephonyManager::class.java))
}
super.onDestroy()
}
@ -819,13 +811,12 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
private fun networkChange(networkAvailable: Boolean) {
Log.d("Loki", "flipping network available to $networkAvailable")
isNetworkAvailable = networkAvailable
if (networkAvailable && !callManager.isReestablishing && callManager.currentConnectionState == CallState.Connected) {
if (networkAvailable && callManager.currentConnectionState == CallState.Connected) {
Log.d("Loki", "Should reconnected")
}
}
private class CheckReconnectedRunnable(private val callId: UUID, private val context: Context) :
Runnable {
private class CheckReconnectedRunnable(private val callId: UUID, private val context: Context) : Runnable {
override fun run() {
val intent = Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_CHECK_RECONNECT)
@ -834,18 +825,7 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
}
}
private class ReconnectTimeoutRunnable(private val callId: UUID, private val context: Context) :
Runnable {
override fun run() {
val intent = Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_CHECK_RECONNECT_TIMEOUT)
.putExtra(EXTRA_CALL_ID, callId)
context.startService(intent)
}
}
private class TimeoutRunnable(private val callId: UUID, private val context: Context) :
Runnable {
private class TimeoutRunnable(private val callId: UUID, private val context: Context) : Runnable {
override fun run() {
val intent = Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_CHECK_TIMEOUT)

View File

@ -118,7 +118,7 @@ class CallManager(
remoteVideoEnabled = false
)
)
val videoState = _videoState
val videoState = _videoState.asStateFlow()
private val stateProcessor = StateProcessor(CallState.Idle)
@ -137,7 +137,7 @@ class CallManager(
val currentCallState
get() = _callStateEvents.value
var iceState = IceConnectionState.CLOSED
private var iceState = IceConnectionState.CLOSED
private var eglBase: EglBase? = null
@ -151,7 +151,6 @@ class CallManager(
_recipientEvents.value = RecipientUpdate(value)
}
var callStartTime: Long = -1
var isReestablishing: Boolean = false
private var peerConnection: PeerConnectionWrapper? = null
private var dataChannel: DataChannel? = null
@ -628,12 +627,10 @@ class CallManager(
if (_videoState.value.swapped) {
peerConnection?.rotationVideoSink?.setSink(fullscreenRenderer)
floatingRenderer?.let{remoteRotationSink?.setSink(it) }
floatingRenderer?.let { remoteRotationSink?.setSink(it) }
} else {
peerConnection?.rotationVideoSink?.apply {
setSink(floatingRenderer)
}
fullscreenRenderer?.let{ remoteRotationSink?.setSink(it) }
peerConnection?.rotationVideoSink?.setSink(floatingRenderer)
fullscreenRenderer?.let { remoteRotationSink?.setSink(it) }
}
}
@ -645,12 +642,12 @@ class CallManager(
/**
* Returns the renderer currently showing the user's video, not the contact's
*/
private fun getUserRenderer() = if(_videoState.value.swapped) fullscreenRenderer else floatingRenderer
private fun getUserRenderer() = if (_videoState.value.swapped) fullscreenRenderer else floatingRenderer
/**
* Returns the renderer currently showing the contact's video, not the user's
*/
private fun getRemoteRenderer() = if(_videoState.value.swapped) floatingRenderer else fullscreenRenderer
private fun getRemoteRenderer() = if (_videoState.value.swapped) floatingRenderer else fullscreenRenderer
/**
* Makes sure the user's renderer applies mirroring if necessary
@ -659,12 +656,12 @@ class CallManager(
val videoState = _videoState.value
// if we have user video and the camera is front facing, make sure to mirror stream
if(videoState.userVideoEnabled) {
if (videoState.userVideoEnabled) {
getUserRenderer()?.setMirror(isCameraFrontFacing())
}
// the remote video is never mirrored
if(videoState.remoteVideoEnabled){
if (videoState.remoteVideoEnabled){
getRemoteRenderer()?.setMirror(false)
}
}

View File

@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.webrtc
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@ -32,29 +31,25 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
}
val floatingRenderer: SurfaceViewRenderer?
get() = callManager.floatingRenderer
get() = callManager.floatingRenderer
val fullscreenRenderer: SurfaceViewRenderer?
get() = callManager.fullscreenRenderer
get() = callManager.fullscreenRenderer
private var _microphoneEnabled: Boolean = true
var microphoneEnabled: Boolean = true
private set
val microphoneEnabled: Boolean
get() = _microphoneEnabled
private var _isSpeaker: Boolean = false
val isSpeaker: Boolean
get() = _isSpeaker
var isSpeaker: Boolean = false
private set
val audioDeviceState
get() = callManager.audioDeviceEvents
.onEach {
_isSpeaker = it.selectedDevice == SignalAudioManager.AudioDevice.SPEAKER_PHONE
}
get() = callManager.audioDeviceEvents.onEach {
isSpeaker = it.selectedDevice == SignalAudioManager.AudioDevice.SPEAKER_PHONE
}
val localAudioEnabledState
get() = callManager.audioEvents.map { it.isEnabled }
.onEach { _microphoneEnabled = it }
.onEach { microphoneEnabled = it }
val videoState: StateFlow<VideoState>
get() = callManager.videoState
@ -65,17 +60,10 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
callManager.setDeviceOrientation(value)
}
val currentCallState
get() = callManager.currentCallState
val callState
get() = callManager.callStateEvents
val recipient
get() = callManager.recipientEvents
val callStartTime: Long
get() = callManager.callStartTime
val currentCallState get() = callManager.currentCallState
val callState get() = callManager.callStateEvents
val recipient get() = callManager.recipientEvents
val callStartTime: Long get() = callManager.callStartTime
fun swapVideos() {
callManager.swapVideos()