diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/TelephonyHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/service/TelephonyHandler.kt new file mode 100644 index 0000000000..3e78c223fa --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/service/TelephonyHandler.kt @@ -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) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt index 36106123a8..ffcf311d21 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -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
(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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt index e2f8a9f21c..90e1ce6085 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -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) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt index f49e2d3333..da5eb07549 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt @@ -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 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()