feat: adding more lifecycle vm and callmanager / call service functionality

This commit is contained in:
jubb
2021-11-04 12:07:06 +11:00
parent 2e3f46ff9f
commit 5cff5ffb45
11 changed files with 295 additions and 34 deletions

View File

@@ -297,7 +297,7 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true" android:exported="true"
android:theme="@style/Theme.Session.DayNight.NoActionBar" /> android:theme="@style/Theme.Session.DayNight.NoActionBar" />
<activity android:name="org.thoughtcrime.securesms.calls.WebRtcTestsActivity" <activity android:name="org.thoughtcrime.securesms.calls.WebRtcCallActivity"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:launchMode="singleTop" android:launchMode="singleTop"
android:parentActivityName="org.thoughtcrime.securesms.home.HomeActivity" android:parentActivityName="org.thoughtcrime.securesms.home.HomeActivity"

View File

@@ -11,7 +11,6 @@ import android.view.MenuItem
import android.view.Window import android.view.Window
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
@@ -25,7 +24,6 @@ import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.WebRtcUtils import org.session.libsession.messaging.utilities.WebRtcUtils
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Debouncer
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
@@ -35,7 +33,7 @@ import org.webrtc.*
import java.util.* import java.util.*
@AndroidEntryPoint @AndroidEntryPoint
class WebRtcTestsActivity: PassphraseRequiredActionBarActivity() { class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
companion object { companion object {
const val CALL_ID = "call_id_session" const val CALL_ID = "call_id_session"
@@ -48,6 +46,8 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity() {
const val EXTRA_SDP = "WebRtcTestsActivity_EXTRA_SDP" const val EXTRA_SDP = "WebRtcTestsActivity_EXTRA_SDP"
const val EXTRA_ADDRESS = "WebRtcTestsActivity_EXTRA_ADDRESS" const val EXTRA_ADDRESS = "WebRtcTestsActivity_EXTRA_ADDRESS"
const val EXTRA_CALL_ID = "WebRtcTestsActivity_EXTRA_CALL_ID" const val EXTRA_CALL_ID = "WebRtcTestsActivity_EXTRA_CALL_ID"
const val BUSY_SIGNAL_DELAY_FINISH = 5500L
} }
private val viewModel by viewModels<CallViewModel>() private val viewModel by viewModels<CallViewModel>()
@@ -135,7 +135,7 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity() {
} }
if (answer != null) { if (answer != null) {
peerConnection.setRemoteDescription( peerConnection.setRemoteDescription(
this@WebRtcTestsActivity, this@WebRtcCallActivity,
SessionDescription(SessionDescription.Type.ANSWER, answer.sdps[0]) SessionDescription(SessionDescription.Type.ANSWER, answer.sdps[0])
) )
break break
@@ -153,7 +153,7 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity() {
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
while (this.isActive) { while (this.isActive) {
delay(2_000L) delay(2_000L)
peerConnection.getStats(this@WebRtcTestsActivity) peerConnection.getStats(this@WebRtcCallActivity)
synchronized(WebRtcUtils.callCache) { synchronized(WebRtcUtils.callCache) {
val set = WebRtcUtils.callCache[callId] ?: mutableSetOf() val set = WebRtcUtils.callCache[callId] ?: mutableSetOf()
set.filter { it.hashCode() !in acceptedCallMessageHashes set.filter { it.hashCode() !in acceptedCallMessageHashes

View File

@@ -36,7 +36,7 @@ import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.utilities.guava.Optional
import org.session.libsignal.utilities.toHexString import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.* import org.thoughtcrime.securesms.*
import org.thoughtcrime.securesms.calls.WebRtcTestsActivity import org.thoughtcrime.securesms.calls.WebRtcCallActivity
import org.thoughtcrime.securesms.contacts.SelectContactsActivity import org.thoughtcrime.securesms.contacts.SelectContactsActivity
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils
@@ -187,15 +187,15 @@ object ConversationMenuHelper {
.setTitle("Call") .setTitle("Call")
.setMessage("Use relay?") .setMessage("Use relay?")
.setPositiveButton("Use Relay") { d, w -> .setPositiveButton("Use Relay") { d, w ->
val intent = Intent(context, WebRtcTestsActivity::class.java) val intent = Intent(context, WebRtcCallActivity::class.java)
intent.putExtra(WebRtcTestsActivity.EXTRA_CALL_ID, UUID.randomUUID().toString()) intent.putExtra(WebRtcCallActivity.EXTRA_CALL_ID, UUID.randomUUID().toString())
intent.putExtra(WebRtcTestsActivity.EXTRA_ADDRESS, thread.address) intent.putExtra(WebRtcCallActivity.EXTRA_ADDRESS, thread.address)
val activity = context as AppCompatActivity val activity = context as AppCompatActivity
activity.startActivity(intent) activity.startActivity(intent)
} }
.setNeutralButton("P2P only") { d, w -> .setNeutralButton("P2P only") { d, w ->
val intent = Intent(context, WebRtcTestsActivity::class.java) val intent = Intent(context, WebRtcCallActivity::class.java)
intent.putExtra(WebRtcTestsActivity.EXTRA_ADDRESS, thread.address) intent.putExtra(WebRtcCallActivity.EXTRA_ADDRESS, thread.address)
val activity = context as AppCompatActivity val activity = context as AppCompatActivity
activity.startActivity(intent) activity.startActivity(intent)
} }

View File

@@ -45,7 +45,7 @@ import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.MuteDialog import org.thoughtcrime.securesms.MuteDialog
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.calls.WebRtcTestsActivity import org.thoughtcrime.securesms.calls.WebRtcCallActivity
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
@@ -185,7 +185,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
synchronized(WebRtcUtils.callCache) { synchronized(WebRtcUtils.callCache) {
WebRtcUtils.callCache[callId] = mutableSetOf() WebRtcUtils.callCache[callId] = mutableSetOf()
} }
sendBroadcast(Intent(WebRtcTestsActivity.ACTION_END)) sendBroadcast(Intent(WebRtcCallActivity.ACTION_END))
} }
else -> { /* do nothing */ } else -> { /* do nothing */ }
} }

View File

@@ -6,18 +6,32 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.media.AudioManager import android.media.AudioManager
import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.ResultReceiver import android.os.ResultReceiver
import android.telephony.PhoneStateListener import android.telephony.PhoneStateListener
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.FutureTaskListener import org.session.libsession.utilities.FutureTaskListener
import org.session.libsession.utilities.Util
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.calls.WebRtcCallActivity
import org.thoughtcrime.securesms.util.CallNotificationBuilder
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_ESTABLISHED
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_CONNECTING
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.*
import org.thoughtcrime.securesms.webrtc.CallManager.CallState.*
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
import java.lang.AssertionError
import java.util.* import java.util.*
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@@ -26,6 +40,9 @@ class WebRtcCallService: Service() {
@Inject lateinit var callManager: CallManager @Inject lateinit var callManager: CallManager
companion object { companion object {
private val TAG = Log.tag(WebRtcCallService::class.java)
const val ACTION_INCOMING_CALL = "CALL_INCOMING" const val ACTION_INCOMING_CALL = "CALL_INCOMING"
const val ACTION_OUTGOING_CALL = "CALL_OUTGOING" const val ACTION_OUTGOING_CALL = "CALL_OUTGOING"
const val ACTION_ANSWER_CALL = "ANSWER_CALL" const val ACTION_ANSWER_CALL = "ANSWER_CALL"
@@ -168,6 +185,100 @@ class WebRtcCallService: Service() {
registerReceiver(wiredHeadsetStateReceiver, IntentFilter(AudioManager.ACTION_HEADSET_PLUG)) registerReceiver(wiredHeadsetStateReceiver, IntentFilter(AudioManager.ACTION_HEADSET_PLUG))
} }
private fun handleBusyCall(intent: Intent) {
val recipient = getRemoteRecipient(intent)
val callId = getCallId(intent)
val callState = callManager.currentConnectionState
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
when (callState) {
STATE_DIALING,
STATE_REMOTE_RINGING -> setCallInProgressNotification(TYPE_OUTGOING_RINGING, callManager.recipient)
STATE_IDLE -> setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient)
STATE_ANSWERING -> setCallInProgressNotification(TYPE_INCOMING_CONNECTING, callManager.recipient)
STATE_LOCAL_RINGING -> setCallInProgressNotification(TYPE_INCOMING_RINGING, callManager.recipient)
STATE_CONNECTED -> setCallInProgressNotification(TYPE_ESTABLISHED, callManager.recipient)
else -> throw AssertionError()
}
}
if (callState == STATE_IDLE) {
stopForeground(true)
}
// TODO: send hangup via messageSender
insertMissedCall(getRemoteRecipient(intent), false)
}
private fun handleBusyMessage(intent: Intent) {
val recipient = getRemoteRecipient(intent)
val callId = getCallId(intent)
if (callManager.currentConnectionState != STATE_DIALING || callManager.callId != callManager.callId || callManager.recipient != callManager.recipient) {
Log.w(TAG,"Got busy message for inactive session...")
return
}
callManager.postViewModelState(CallViewModel.State.CALL_BUSY)
callManager.startOutgoingRinger(OutgoingRinger.Type.BUSY)
Util.runOnMainDelayed({
startService(
Intent(this, WebRtcCallService::class.java)
.setAction(ACTION_LOCAL_HANGUP)
)
}, WebRtcCallActivity.BUSY_SIGNAL_DELAY_FINISH)
}
private fun handleIncomingCall(intent: Intent) {
if (callManager.currentConnectionState != STATE_IDLE) throw IllegalStateException("Incoming on non-idle")
val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION)
callManager.postConnectionEvent(STATE_ANSWERING)
callManager.callId = getCallId(intent)
callManager.clearPendingIceUpdates()
val recipient = getRemoteRecipient(intent)
callManager.recipient = recipient
if (isIncomingMessageExpired(intent)) {
insertMissedCall(recipient, true)
}
}
private fun handleCheckTimeout(intent: Intent) {
val callId = callManager.callId ?: return
val callState = callManager.currentConnectionState
if (callId == getCallId(intent) && callState != STATE_CONNECTED) {
Log.w(TAG, "Timing out call: $callId")
callManager.postViewModelState(CallViewModel.State.CALL_DISCONNECTED)
}
}
private fun setCallInProgressNotification(type: Int, recipient: Recipient?) {
startForeground(
CallNotificationBuilder.WEBRTC_NOTIFICATION,
CallNotificationBuilder.getCallInProgressNotification(this, type, recipient)
)
}
private fun getRemoteRecipient(intent: Intent): Recipient {
val remoteAddress = intent.getParcelableExtra<Address>(EXTRA_RECIPIENT_ADDRESS)
?: throw AssertionError("No recipient in intent!")
return Recipient.from(this, remoteAddress, true)
}
private fun getCallId(intent: Intent) : UUID {
return intent.getSerializableExtra(EXTRA_CALL_ID) as? UUID
?: throw AssertionError("No callId in intent!")
}
private fun insertMissedCall(recipient: Recipient, signal: Boolean) {
// TODO
// val messageAndThreadId = DatabaseComponent.get(this).smsDatabase().insertReceivedCall(recipient.address)
// MessageNotifier.updateNotification(this, messageAndThreadId.second, signal)
}
private fun isIncomingMessageExpired(intent: Intent) =
System.currentTimeMillis() - intent.getLongExtra(EXTRA_TIMESTAMP, -1) > TimeUnit.MINUTES.toMillis(2)
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
callReceiver?.let { receiver -> callReceiver?.let { receiver ->

View File

@@ -0,0 +1,107 @@
package org.thoughtcrime.securesms.util
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.app.NotificationCompat
import network.loki.messenger.R
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.calls.WebRtcCallActivity
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.service.WebRtcCallService
class CallNotificationBuilder {
companion object {
const val WEBRTC_NOTIFICATION = 313388
const val TYPE_INCOMING_RINGING = 1
const val TYPE_OUTGOING_RINGING = 2
const val TYPE_ESTABLISHED = 3
const val TYPE_INCOMING_CONNECTING = 4
@JvmStatic
fun getCallInProgressNotification(context: Context, type: Int, recipient: Recipient?): Notification {
val contentIntent = Intent(context, WebRtcCallActivity::class.java)
.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
val pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, 0)
val builder = NotificationCompat.Builder(context, NotificationChannels.CALLS)
.setSmallIcon(R.drawable.ic_baseline_call_24)
.setContentIntent(pendingIntent)
.setOngoing(true)
recipient?.name?.let { name ->
builder.setContentTitle(name)
}
when (type) {
TYPE_INCOMING_CONNECTING -> {
builder.setContentText(context.getString(R.string.CallNotificationBuilder_connecting))
builder.priority = NotificationCompat.PRIORITY_MIN
}
TYPE_INCOMING_RINGING -> {
builder.setContentText(context.getString(R.string.NotificationBarManager__incoming_signal_call))
builder.addAction(getServiceNotificationAction(
context,
WebRtcCallService.ACTION_DENY_CALL,
R.drawable.ic_close_grey600_32dp,
R.string.NotificationBarManager__deny_call
))
builder.addAction(getActivityNotificationAction(
context,
WebRtcCallActivity.ACTION_ANSWER,
R.drawable.ic_phone_grey600_32dp,
R.string.NotificationBarManager__answer_call
))
}
TYPE_OUTGOING_RINGING -> {
builder.setContentText(context.getString(R.string.NotificationBarManager__establishing_signal_call))
builder.addAction(getServiceNotificationAction(
context,
WebRtcCallService.ACTION_LOCAL_HANGUP,
R.drawable.ic_call_end_grey600_32dp,
R.string.NotificationBarManager__cancel_call
))
}
else -> {
builder.setContentText(context.getString(R.string.NotificationBarManager_call_in_progress))
builder.addAction(getServiceNotificationAction(
context,
WebRtcCallService.ACTION_LOCAL_HANGUP,
R.drawable.ic_call_end_grey600_32dp,
R.string.NotificationBarManager__end_call
))
}
}
return builder.build()
}
@JvmStatic
private fun getServiceNotificationAction(context: Context, action: String, iconResId: Int, titleResId: Int): NotificationCompat.Action {
val intent = Intent(context, WebRtcCallService::class.java)
.setAction(action)
val pendingIntent = PendingIntent.getService(context, 0, intent, 0)
return NotificationCompat.Action(iconResId, context.getString(titleResId), pendingIntent)
}
@JvmStatic
private fun getActivityNotificationAction(context: Context, action: String,
@DrawableRes iconResId: Int, @StringRes titleResId: Int): NotificationCompat.Action {
val intent = Intent(context, WebRtcCallActivity::class.java)
.setAction(action)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
return NotificationCompat.Action(iconResId, context.getString(titleResId), pendingIntent)
}
}
}

View File

@@ -15,7 +15,7 @@ import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.calls.WebRtcTestsActivity import org.thoughtcrime.securesms.calls.WebRtcCallActivity
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import java.util.* import java.util.*
@@ -54,13 +54,13 @@ class CallBottomSheet: BottomSheetDialogFragment() {
nameTextView.text = recipient.name ?: address.serialize() nameTextView.text = recipient.name ?: address.serialize()
acceptButton.setOnClickListener { acceptButton.setOnClickListener {
val intent = Intent(requireContext(), WebRtcTestsActivity::class.java) val intent = Intent(requireContext(), WebRtcCallActivity::class.java)
val bundle = bundleOf( val bundle = bundleOf(
WebRtcTestsActivity.EXTRA_ADDRESS to address, WebRtcCallActivity.EXTRA_ADDRESS to address,
WebRtcTestsActivity.EXTRA_CALL_ID to callId WebRtcCallActivity.EXTRA_CALL_ID to callId
) )
intent.action = WebRtcTestsActivity.ACTION_ANSWER intent.action = WebRtcCallActivity.ACTION_ANSWER
bundle.putStringArray(WebRtcTestsActivity.EXTRA_SDP, sdp) bundle.putStringArray(WebRtcCallActivity.EXTRA_SDP, sdp)
intent.putExtras(bundle) intent.putExtras(bundle)
startActivity(intent) startActivity(intent)

View File

@@ -11,6 +11,7 @@ import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.service.WebRtcCallService import org.thoughtcrime.securesms.service.WebRtcCallService
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
import org.thoughtcrime.securesms.webrtc.video.CameraState import org.thoughtcrime.securesms.webrtc.video.CameraState
import org.webrtc.* import org.webrtc.*
@@ -59,19 +60,21 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
val remoteVideoEvents = _remoteVideoEvents.asSharedFlow() val remoteVideoEvents = _remoteVideoEvents.asSharedFlow()
private val _connectionEvents = MutableStateFlow<StateEvent>(StateEvent.CallStateUpdate(CallState.STATE_IDLE)) private val _connectionEvents = MutableStateFlow<StateEvent>(StateEvent.CallStateUpdate(CallState.STATE_IDLE))
val connectionEvents = _connectionEvents.asSharedFlow() val connectionEvents = _connectionEvents.asSharedFlow()
private val _callStateEvents = MutableStateFlow(CallViewModel.State.CALL_PENDING)
val callStateEvents = _callStateEvents.asSharedFlow()
private var localCameraState: CameraState = CameraState.UNKNOWN private var localCameraState: CameraState = CameraState.UNKNOWN
private var microphoneEnabled = true private var microphoneEnabled = true
private var remoteVideoEnabled = false private var remoteVideoEnabled = false
private var bluetoothAvailable = false private var bluetoothAvailable = false
private val currentCallState = (_connectionEvents.value as StateEvent.CallStateUpdate).state val currentConnectionState = (_connectionEvents.value as StateEvent.CallStateUpdate).state
private val networkExecutor = Executors.newSingleThreadExecutor() private val networkExecutor = Executors.newSingleThreadExecutor()
private var eglBase: EglBase? = null private var eglBase: EglBase? = null
private var callId: UUID? = null var callId: UUID? = null
private var recipient: Recipient? = null var recipient: Recipient? = null
private var peerConnectionWrapper: PeerConnectionWrapper? = null private var peerConnectionWrapper: PeerConnectionWrapper? = null
private var dataChannel: DataChannel? = null private var dataChannel: DataChannel? = null
@@ -82,6 +85,23 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
private var remoteRenderer: SurfaceViewRenderer? = null private var remoteRenderer: SurfaceViewRenderer? = null
private var peerConnectionFactory: PeerConnectionFactory? = null private var peerConnectionFactory: PeerConnectionFactory? = null
fun clearPendingIceUpdates() {
pendingOutgoingIceUpdates.clear()
pendingIncomingIceUpdates.clear()
}
fun startOutgoingRinger(ringerType: OutgoingRinger.Type) {
signalAudioManager.startOutgoingRinger(ringerType)
}
fun postConnectionEvent(newState: CallState) {
_connectionEvents.value = StateEvent.CallStateUpdate(newState)
}
fun postViewModelState(newState: CallViewModel.State) {
_callStateEvents.value = newState
}
private fun createCameraCapturer(enumerator: CameraEnumerator): CameraVideoCapturer? { private fun createCameraCapturer(enumerator: CameraEnumerator): CameraVideoCapturer? {
val deviceNames = enumerator.deviceNames val deviceNames = enumerator.deviceNames
@@ -128,7 +148,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
} }
fun isBusy(context: Context) = currentCallState != CallState.STATE_IDLE fun isBusy(context: Context) = currentConnectionState != CallState.STATE_IDLE
|| context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE || context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE
fun initializeVideo(context: Context) { fun initializeVideo(context: Context) {
@@ -162,14 +182,14 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
} }
fun setAudioEnabled(isEnabled: Boolean) { fun setAudioEnabled(isEnabled: Boolean) {
currentCallState.withState(*(CONNECTED_STATES + PENDING_CONNECTION_STATES)) { currentConnectionState.withState(*(CONNECTED_STATES + PENDING_CONNECTION_STATES)) {
peerConnectionWrapper?.setAudioEnabled(isEnabled) peerConnectionWrapper?.setAudioEnabled(isEnabled)
_audioEvents.value = StateEvent.AudioEnabled(true) _audioEvents.value = StateEvent.AudioEnabled(true)
} }
} }
fun setVideoEnabled(isEnabled: Boolean) { fun setVideoEnabled(isEnabled: Boolean) {
currentCallState.withState(*(CONNECTED_STATES + PENDING_CONNECTION_STATES)) { currentConnectionState.withState(*(CONNECTED_STATES + PENDING_CONNECTION_STATES)) {
peerConnectionWrapper?.setVideoEnabled(isEnabled) peerConnectionWrapper?.setVideoEnabled(isEnabled)
_audioEvents.value = StateEvent.AudioEnabled(true) _audioEvents.value = StateEvent.AudioEnabled(true)
} }
@@ -236,7 +256,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
} }
fun stop() { fun stop() {
signalAudioManager.stop(currentCallState in OUTGOING_STATES) signalAudioManager.stop(currentConnectionState in OUTGOING_STATES)
peerConnectionWrapper?.dispose() peerConnectionWrapper?.dispose()
peerConnectionWrapper = null peerConnectionWrapper = null

View File

@@ -14,15 +14,30 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class CallViewModel @Inject constructor(private val callManager: CallManager): ViewModel() { class CallViewModel @Inject constructor(private val callManager: CallManager): ViewModel() {
enum class State {
CALL_PENDING,
CALL_INCOMING,
CALL_OUTGOING,
CALL_CONNECTED,
CALL_RINGING,
CALL_BUSY,
CALL_DISCONNECTED,
NETWORK_FAILURE,
RECIPIENT_UNAVAILABLE,
NO_SUCH_USER,
UNTRUSTED_IDENTITY,
}
val localAudioEnabledState = callManager.audioEvents.map { it.isEnabled } val localAudioEnabledState = callManager.audioEvents.map { it.isEnabled }
val localVideoEnabledState = callManager.videoEvents.map { it.isEnabled } val localVideoEnabledState = callManager.videoEvents.map { it.isEnabled }
val remoteVideoEnabledState = callManager.remoteVideoEvents.map { it.isEnabled } val remoteVideoEnabledState = callManager.remoteVideoEvents.map { it.isEnabled }
val callState = callManager.callStateEvents
// set up listeners for establishing connection toggling video / audio // set up listeners for establishing connection toggling video / audio
init { init {
callManager.audioEvents.onEach { (enabled) -> callManager.setAudioEnabled(enabled) }
.launchIn(viewModelScope)
callManager.videoEvents.onEach { (enabled) -> callManager.setVideoEnabled(enabled) }
.launchIn(viewModelScope)
} }
} }

View File

@@ -58,8 +58,8 @@ class SignalAudioManager(private val context: Context,
private val connectedSoundId = soundPool.load(context, R.raw.webrtc_completed, 1) private val connectedSoundId = soundPool.load(context, R.raw.webrtc_completed, 1)
private val disconnectedSoundId = soundPool.load(context, R.raw.webrtc_disconnected, 1) private val disconnectedSoundId = soundPool.load(context, R.raw.webrtc_disconnected, 1)
private val incomingRinger = IncomingRinger(context) val incomingRinger = IncomingRinger(context)
private val outgoingRinger = OutgoingRinger(context) val outgoingRinger = OutgoingRinger(context)
private var wiredHeadsetReceiver: WiredHeadsetReceiver? = null private var wiredHeadsetReceiver: WiredHeadsetReceiver? = null
@@ -340,7 +340,7 @@ class SignalAudioManager(private val context: Context,
incomingRinger.stop() incomingRinger.stop()
} }
private fun startOutgoingRinger() { fun startOutgoingRinger(type: OutgoingRinger.Type) {
Log.i(TAG, "startOutgoingRinger(): currentDevice: $selectedAudioDevice") Log.i(TAG, "startOutgoingRinger(): currentDevice: $selectedAudioDevice")
androidAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION androidAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION

View File

@@ -903,5 +903,13 @@
<string name="activity_settings_support">Debug Log</string> <string name="activity_settings_support">Debug Log</string>
<string name="dialog_share_logs_title">Share Logs</string> <string name="dialog_share_logs_title">Share Logs</string>
<string name="dialog_share_logs_explanation">Would you like to export your application logs to be able to share for troubleshooting?</string> <string name="dialog_share_logs_explanation">Would you like to export your application logs to be able to share for troubleshooting?</string>
<string name="CallNotificationBuilder_connecting">Connecting…</string>
<string name="NotificationBarManager__incoming_signal_call">Incoming call</string>
<string name="NotificationBarManager__deny_call">Deny call</string>
<string name="NotificationBarManager__answer_call">Answer call</string>
<string name="NotificationBarManager_call_in_progress">Call in progress</string>
<string name="NotificationBarManager__cancel_call">Cancel call</string>
<string name="NotificationBarManager__establishing_signal_call">Establishing call</string>
<string name="NotificationBarManager__end_call">End call</string>
</resources> </resources>