mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-25 17:37:57 +00:00
feat: adding more lifecycle vm and callmanager / call service functionality
This commit is contained in:
@@ -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"
|
||||||
|
@@ -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
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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 */ }
|
||||||
}
|
}
|
||||||
|
@@ -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 ->
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -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)
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -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
|
||||||
|
@@ -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>
|
||||||
|
Reference in New Issue
Block a user