fix: various bug fixes for calls

This commit is contained in:
jubb
2021-12-08 17:29:10 +11:00
parent 44b2a9d412
commit 38a8738674
9 changed files with 109 additions and 35 deletions

View File

@@ -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"

View File

@@ -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)

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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))

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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"