mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-27 22:38:53 +00:00
fix: various bug fixes for calls
This commit is contained in:
@@ -314,7 +314,8 @@
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
|
||||
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name="org.thoughtcrime.securesms.service.KeyCachingService"
|
||||
android:enabled="true"
|
||||
|
||||
@@ -8,16 +8,14 @@ import android.content.IntentFilter
|
||||
import android.media.AudioManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity
|
||||
import android.view.MenuItem
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.view.*
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.contains
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.android.synthetic.main.activity_conversation_v2.*
|
||||
@@ -59,6 +57,11 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
|
||||
private val glide by lazy { GlideApp.with(this) }
|
||||
private var uiJob: Job? = null
|
||||
private var wantsToAnswer = false
|
||||
set(value) {
|
||||
field = value
|
||||
WebRtcCallService.broadcastWantsToAnswer(this, value)
|
||||
}
|
||||
private var hangupReceiver: BroadcastReceiver? = null
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
@@ -112,7 +115,10 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
|
||||
}
|
||||
|
||||
acceptCallButton.setOnClickListener {
|
||||
wantsToAnswer = true
|
||||
if (viewModel.currentCallState == CALL_PRE_INIT) {
|
||||
wantsToAnswer = true
|
||||
updateControls()
|
||||
}
|
||||
val answerIntent = WebRtcCallService.acceptCallIntent(this)
|
||||
ContextCompat.startForegroundService(this,answerIntent)
|
||||
}
|
||||
@@ -122,11 +128,13 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
|
||||
startService(declineIntent)
|
||||
}
|
||||
|
||||
registerReceiver(object: BroadcastReceiver() {
|
||||
hangupReceiver = object: BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
finish()
|
||||
}
|
||||
},IntentFilter(ACTION_END))
|
||||
}
|
||||
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(hangupReceiver!!,IntentFilter(ACTION_END))
|
||||
|
||||
enableCameraButton.setOnClickListener {
|
||||
Permissions.with(this)
|
||||
@@ -148,15 +156,36 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
hangupReceiver?.let { receiver ->
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
||||
}
|
||||
}
|
||||
|
||||
private fun answerCall() {
|
||||
val answerIntent = WebRtcCallService.acceptCallIntent(this)
|
||||
ContextCompat.startForegroundService(this,answerIntent)
|
||||
}
|
||||
|
||||
private fun updateControls(state: CallViewModel.State? = null) {
|
||||
if (state == null) {
|
||||
if (wantsToAnswer) {
|
||||
controlGroup.isVisible = true
|
||||
remote_loading_view.isVisible = true
|
||||
incomingControlGroup.isVisible = false
|
||||
}
|
||||
} else {
|
||||
controlGroup.isVisible = state in listOf(CALL_CONNECTED, CALL_OUTGOING, CALL_INCOMING) || (state == CALL_PRE_INIT && wantsToAnswer)
|
||||
remote_loading_view.isVisible = state !in listOf(CALL_CONNECTED, CALL_RINGING, CALL_PRE_INIT) || wantsToAnswer
|
||||
incomingControlGroup.isVisible = state in listOf(CALL_RINGING, CALL_PRE_INIT) && !wantsToAnswer
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
uiJob = lifecycleScope.launchWhenResumed {
|
||||
uiJob = lifecycleScope.launch {
|
||||
|
||||
launch {
|
||||
viewModel.audioDeviceState.collect { state ->
|
||||
@@ -177,11 +206,10 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
|
||||
CALL_OUTGOING -> {
|
||||
}
|
||||
CALL_CONNECTED -> {
|
||||
wantsToAnswer = false
|
||||
}
|
||||
}
|
||||
controlGroup.isVisible = state in listOf(CALL_CONNECTED, CALL_OUTGOING, CALL_INCOMING) || (state == CALL_PRE_INIT && wantsToAnswer)
|
||||
remote_loading_view.isVisible = state !in listOf(CALL_CONNECTED, CALL_RINGING, CALL_PRE_INIT)
|
||||
incomingControlGroup.isVisible = state in listOf(CALL_RINGING, CALL_PRE_INIT) && !wantsToAnswer
|
||||
updateControls(state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,11 +221,14 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
|
||||
supportActionBar?.title = displayName
|
||||
val signalProfilePicture = latestRecipient.recipient.contactPhoto
|
||||
val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject
|
||||
val sizeInPX = resources.getDimensionPixelSize(R.dimen.extra_large_profile_picture_size)
|
||||
if (signalProfilePicture != null && avatar != "0" && avatar != "") {
|
||||
glide.clear(remote_recipient)
|
||||
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(remote_recipient)
|
||||
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.circleCrop()
|
||||
.error(AvatarPlaceholderGenerator.generate(this@WebRtcCallActivity, sizeInPX, publicKey, displayName))
|
||||
.into(remote_recipient)
|
||||
} else {
|
||||
val sizeInPX = resources.getDimensionPixelSize(R.dimen.extra_large_profile_picture_size)
|
||||
glide.clear(remote_recipient)
|
||||
glide.load(AvatarPlaceholderGenerator.generate(this@WebRtcCallActivity, sizeInPX, publicKey, displayName))
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(remote_recipient)
|
||||
|
||||
@@ -105,12 +105,16 @@ class ProfilePictureView : RelativeLayout {
|
||||
if (profilePicturesCache.containsKey(publicKey) && profilePicturesCache[publicKey] == recipient.profileAvatar) return
|
||||
val signalProfilePicture = recipient.contactPhoto
|
||||
val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject
|
||||
val sizeInPX = resources.getDimensionPixelSize(sizeResId)
|
||||
if (signalProfilePicture != null && avatar != "0" && avatar != "") {
|
||||
glide.clear(imageView)
|
||||
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(imageView)
|
||||
glide.load(signalProfilePicture)
|
||||
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.circleCrop()
|
||||
.error(AvatarPlaceholderGenerator.generate(context,sizeInPX, publicKey, displayName))
|
||||
.into(imageView)
|
||||
profilePicturesCache[publicKey] = recipient.profileAvatar
|
||||
} else {
|
||||
val sizeInPX = resources.getDimensionPixelSize(sizeResId)
|
||||
glide.clear(imageView)
|
||||
glide.load(AvatarPlaceholderGenerator.generate(context, sizeInPX, publicKey, displayName))
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.thoughtcrime.securesms.service
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
@@ -57,6 +57,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
const val ACTION_SCREEN_OFF = "SCREEN_OFF"
|
||||
const val ACTION_CHECK_TIMEOUT = "CHECK_TIMEOUT"
|
||||
const val ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL"
|
||||
const val ACTION_WANTS_TO_ANSWER = "WANTS_TO_ANSWER"
|
||||
|
||||
const val ACTION_PRE_OFFER = "PRE_OFFER"
|
||||
const val ACTION_RESPONSE_MESSAGE = "RESPONSE_MESSAGE"
|
||||
@@ -79,6 +80,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
const val EXTRA_ICE_SDP_MID = "ice_sdp_mid"
|
||||
const val EXTRA_ICE_SDP_LINE_INDEX = "ice_sdp_line_index"
|
||||
const val EXTRA_RESULT_RECEIVER = "result_receiver"
|
||||
const val EXTRA_WANTS_TO_ANSWER = "wants_to_answer"
|
||||
|
||||
const val INVALID_NOTIFICATION_ID = -1
|
||||
private const val TIMEOUT_SECONDS = 30L
|
||||
@@ -116,12 +118,12 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
.putExtra(EXTRA_CALL_ID, callId)
|
||||
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
|
||||
|
||||
fun preOffer(context: Context, address: Address, callId: UUID, sentTimestamp: Long) =
|
||||
fun preOffer(context: Context, address: Address, callId: UUID, callTime: Long) =
|
||||
Intent(context, WebRtcCallService::class.java)
|
||||
.setAction(ACTION_PRE_OFFER)
|
||||
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
|
||||
.putExtra(EXTRA_CALL_ID, callId)
|
||||
.putExtra(EXTRA_TIMESTAMP, sentTimestamp)
|
||||
.putExtra(EXTRA_TIMESTAMP, callTime)
|
||||
|
||||
fun iceCandidates(context: Context, address: Address, iceCandidates: List<IceCandidate>, callId: UUID) =
|
||||
Intent(context, WebRtcCallService::class.java)
|
||||
@@ -147,6 +149,13 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
context.startService(intent)
|
||||
}
|
||||
|
||||
fun broadcastWantsToAnswer(context: Context, wantsToAnswer: Boolean) {
|
||||
val intent = Intent(ACTION_WANTS_TO_ANSWER)
|
||||
.putExtra(EXTRA_WANTS_TO_ANSWER, wantsToAnswer)
|
||||
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isCallActive(context: Context, resultReceiver: ResultReceiver) {
|
||||
val intent = Intent(context, WebRtcCallService::class.java)
|
||||
@@ -158,8 +167,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
|
||||
@Inject lateinit var callManager: CallManager
|
||||
|
||||
private var lastNotificationId: Int = INVALID_NOTIFICATION_ID
|
||||
private var lastNotification: Notification? = null
|
||||
private var wantsToAnswer = false
|
||||
|
||||
private val lockManager by lazy { LockManager(this) }
|
||||
private val serviceExecutor = Executors.newSingleThreadExecutor()
|
||||
@@ -170,13 +178,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
|
||||
private var networkChangedReceiver: NetworkChangeReceiver? = null
|
||||
private var callReceiver: IncomingPstnCallReceiver? = null
|
||||
private var wantsToAnswerReceiver: BroadcastReceiver? = null
|
||||
private var wiredHeadsetStateReceiver: WiredHeadsetStateReceiver? = null
|
||||
private var uncaughtExceptionHandlerManager: UncaughtExceptionHandlerManager? = null
|
||||
private var powerButtonReceiver: PowerButtonReceiver? = null
|
||||
|
||||
@Synchronized
|
||||
private fun terminate() {
|
||||
sendBroadcast(Intent(WebRtcCallActivity.ACTION_END))
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(WebRtcCallActivity.ACTION_END))
|
||||
lockManager.updatePhoneState(LockManager.PhoneState.IDLE)
|
||||
callManager.stop()
|
||||
stopForeground(true)
|
||||
@@ -244,8 +253,10 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
callManager.registerListener(this)
|
||||
wantsToAnswer = false
|
||||
registerIncomingPstnCallReceiver()
|
||||
registerWiredHeadsetStateReceiver()
|
||||
registerWantsToAnswerReceiver()
|
||||
getSystemService(TelephonyManager::class.java)
|
||||
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
|
||||
registerUncaughtExceptionHandler()
|
||||
@@ -266,6 +277,16 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
registerReceiver(callReceiver, IntentFilter("android.intent.action.PHONE_STATE"))
|
||||
}
|
||||
|
||||
private fun registerWantsToAnswerReceiver() {
|
||||
val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
wantsToAnswer = intent?.getBooleanExtra(EXTRA_WANTS_TO_ANSWER, false) ?: false
|
||||
}
|
||||
}
|
||||
wantsToAnswerReceiver = receiver
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, IntentFilter(ACTION_WANTS_TO_ANSWER))
|
||||
}
|
||||
|
||||
private fun registerWiredHeadsetStateReceiver() {
|
||||
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver()
|
||||
registerReceiver(wiredHeadsetStateReceiver, IntentFilter(AudioManager.ACTION_HEADSET_PLUG))
|
||||
@@ -352,7 +373,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
|
||||
val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return
|
||||
val timestamp = intent.getLongExtra(EXTRA_TIMESTAMP, -1)
|
||||
setCallInProgressNotification(TYPE_INCOMING_RINGING, recipient)
|
||||
if (wantsToAnswer) {
|
||||
setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient)
|
||||
} else {
|
||||
setCallInProgressNotification(TYPE_INCOMING_RINGING, recipient)
|
||||
}
|
||||
callManager.clearPendingIceUpdates()
|
||||
callManager.onIncomingRing(offer, callId, recipient, timestamp)
|
||||
callManager.postConnectionEvent(STATE_LOCAL_RINGING)
|
||||
@@ -398,16 +423,20 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
}
|
||||
|
||||
private fun handleAnswerCall(intent: Intent) {
|
||||
val recipient = callManager.recipient ?: return
|
||||
|
||||
if (callManager.currentConnectionState != STATE_LOCAL_RINGING) {
|
||||
Log.e(TAG,"Can only answer from ringing!")
|
||||
if (callManager.currentConnectionState == STATE_PRE_OFFER) {
|
||||
// show answer state from pre-offer
|
||||
setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient)
|
||||
}
|
||||
Log.e(TAG, "Can only answer from ringing!")
|
||||
return
|
||||
}
|
||||
|
||||
val pending = callManager.pendingOffer ?: return
|
||||
val callId = callManager.callId ?: return
|
||||
val recipient = callManager.recipient ?: return
|
||||
val timestamp = callManager.pendingOfferTime
|
||||
|
||||
setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient)
|
||||
|
||||
intent.putExtra(EXTRA_CALL_ID, callId)
|
||||
@@ -469,7 +498,6 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
private fun handleRemoteHangup(intent: Intent) {
|
||||
if (callManager.callId != getCallId(intent)) {
|
||||
Log.e(TAG, "Hangup for non-active call...")
|
||||
terminate()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -562,7 +590,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
val callId = callManager.callId ?: return
|
||||
val callState = callManager.currentConnectionState
|
||||
|
||||
if (callId == getCallId(intent) && callState !in arrayOf(STATE_CONNECTED)) {
|
||||
if (callId == getCallId(intent) && callState !in arrayOf(STATE_CONNECTED)) { // TODO: add check for ice state connecting
|
||||
Log.w(TAG, "Timing out call: $callId")
|
||||
handleLocalHangup(intent)
|
||||
}
|
||||
@@ -614,13 +642,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||
networkChangedReceiver?.let { receiver ->
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
||||
}
|
||||
wantsToAnswerReceiver?.let { receiver ->
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
||||
}
|
||||
networkChangedReceiver = null
|
||||
callReceiver = null
|
||||
uncaughtExceptionHandlerManager?.unregister()
|
||||
wantsToAnswer = false
|
||||
super.onDestroy()
|
||||
// shutdown audiomanager
|
||||
// unregister network receiver
|
||||
// unregister power button
|
||||
}
|
||||
|
||||
fun networkChange(networkAvailable: Boolean) {
|
||||
|
||||
@@ -105,6 +105,9 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
||||
val currentConnectionState
|
||||
get() = (_connectionEvents.value as CallStateUpdate).state
|
||||
|
||||
val currentCallState
|
||||
get() = _callStateEvents.value
|
||||
|
||||
private var eglBase: EglBase? = null
|
||||
|
||||
var pendingOffer: String? = null
|
||||
@@ -418,6 +421,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
||||
connection.setRemoteDescription(SessionDescription(SessionDescription.Type.OFFER, offer))
|
||||
val answer = connection.createAnswer(MediaConstraints())
|
||||
connection.setLocalDescription(answer)
|
||||
setAudioEnabled(true)
|
||||
val answerMessage = CallMessage.answer(answer.description, callId)
|
||||
val userAddress = storage.getUserPublicKey() ?: return Promise.ofFail(NullPointerException("No user public key"))
|
||||
MessageSender.sendNonDurably(answerMessage, Address.fromSerialized(userAddress))
|
||||
|
||||
@@ -104,7 +104,7 @@ class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle, p
|
||||
context = context,
|
||||
address = Address.fromSerialized(recipientAddress),
|
||||
callId = callId,
|
||||
sentTimestamp = callMessage.sentTimestamp!!
|
||||
callTime = callMessage.sentTimestamp!!
|
||||
)
|
||||
ContextCompat.startForegroundService(context, incomingIntent)
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
|
||||
val remoteVideoEnabledState
|
||||
get() = callManager.remoteVideoEvents.map { it.isEnabled }
|
||||
|
||||
val currentCallState
|
||||
get() = callManager.currentCallState
|
||||
|
||||
val callState
|
||||
get() = callManager.callStateEvents
|
||||
|
||||
|
||||
@@ -47,13 +47,13 @@ class PeerConnectionWrapper(context: Context,
|
||||
}
|
||||
|
||||
peerConnection = factory.createPeerConnection(configuration, constraints, observer)!!
|
||||
peerConnection.setAudioPlayout(false)
|
||||
peerConnection.setAudioRecording(false)
|
||||
peerConnection.setAudioPlayout(true)
|
||||
peerConnection.setAudioRecording(true)
|
||||
|
||||
val mediaStream = factory.createLocalMediaStream("ARDAMS")
|
||||
audioSource = factory.createAudioSource(audioConstraints)
|
||||
audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource)
|
||||
audioTrack.setEnabled(false)
|
||||
audioTrack.setEnabled(true)
|
||||
mediaStream.addTrack(audioTrack)
|
||||
|
||||
camera = Camera(context, cameraEventListener)
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
app:layout_constraintEnd_toEndOf="@id/remote_parent"
|
||||
app:layout_constraintTop_toTopOf="@id/remote_parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/remote_parent"
|
||||
app:layout_constraintVertical_bias="0.4"
|
||||
android:layout_width="@dimen/extra_large_profile_picture_size"
|
||||
android:layout_height="@dimen/extra_large_profile_picture_size"/>
|
||||
|
||||
@@ -58,6 +59,7 @@
|
||||
<TextView
|
||||
android:id="@+id/callTime"
|
||||
android:textSize="@dimen/medium_font_size"
|
||||
android:theme="@style/Theme.Session.CallActivity"
|
||||
android:textColor="@color/text"
|
||||
tools:text="@tools:sample/date/hhmmss"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
Reference in New Issue
Block a user