diff --git a/vector/src/main/java/im/vector/app/core/services/CallService.kt b/vector/src/main/java/im/vector/app/core/services/CallService.kt index 59eee14d37..7c7b51acc9 100644 --- a/vector/src/main/java/im/vector/app/core/services/CallService.kt +++ b/vector/src/main/java/im/vector/app/core/services/CallService.kt @@ -46,7 +46,8 @@ import timber.log.Timber class CallService : VectorService() { private val connections = mutableMapOf() - private val knownCalls = mutableSetOf() + private val knownCalls = mutableSetOf() + private val ongoingCallIds = mutableSetOf() private lateinit var notificationManager: NotificationManagerCompat private lateinit var notificationUtils: NotificationUtils @@ -115,19 +116,19 @@ class CallService : VectorService() { callRingPlayerOutgoing?.start() displayOutgoingRingingCallNotification(intent) } - ACTION_ONGOING_CALL -> { + ACTION_ONGOING_CALL -> { callRingPlayerIncoming?.stop() callRingPlayerOutgoing?.stop() displayCallInProgressNotification(intent) } - ACTION_CALL_CONNECTING -> { + ACTION_CALL_CONNECTING -> { // lower notification priority displayCallInProgressNotification(intent) // stop ringing callRingPlayerIncoming?.stop() callRingPlayerOutgoing?.stop() } - ACTION_CALL_TERMINATED -> { + ACTION_CALL_TERMINATED -> { handleCallTerminated(intent) } else -> { @@ -153,9 +154,9 @@ class CallService : VectorService() { val call = callManager.getCallById(callId) ?: return Unit.also { handleUnexpectedState(callId) } + val callInformation = call.toCallInformation() val isVideoCall = call.mxCall.isVideoCall val fromBg = intent.getBooleanExtra(EXTRA_IS_IN_BG, false) - val opponentMatrixItem = getOpponentMatrixItem(call) Timber.v("displayIncomingCallNotification : display the dedicated notification") val incomingCallAlert = IncomingCallAlert(callId, shouldBeDisplayedIn = { activity -> @@ -165,7 +166,7 @@ class CallService : VectorService() { } ).apply { viewBinder = IncomingCallAlert.ViewBinder( - matrixItem = opponentMatrixItem, + matrixItem = callInformation.matrixItem, avatarRenderer = avatarRenderer, isVideoCall = isVideoCall, onAccept = { showCallScreen(call, VectorCallActivity.INCOMING_ACCEPT) }, @@ -177,7 +178,7 @@ class CallService : VectorService() { alertManager.postVectorAlert(incomingCallAlert) val notification = notificationUtils.buildIncomingCallNotification( call = call, - title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId, + title = callInformation.matrixItem?.getBestName() ?: callInformation.opponentUserId, fromBg = fromBg ) if (knownCalls.isEmpty()) { @@ -185,19 +186,33 @@ class CallService : VectorService() { } else { notificationManager.notify(callId.hashCode(), notification) } - knownCalls.add(callId) + knownCalls.add(callInformation) } private fun handleCallTerminated(intent: Intent) { val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" + val isRejected = intent.getBooleanExtra(EXTRA_IS_REJECTED, false) alertManager.cancelAlert(callId) - if (!knownCalls.remove(callId)) { + val terminatedCall = knownCalls.firstOrNull { it.callId == callId } + if (terminatedCall == null) { Timber.v("Call terminated for unknown call $callId$") handleUnexpectedState(callId) return } - val notification = notificationUtils.buildCallEndedNotification() - notificationManager.notify(callId.hashCode(), notification) + knownCalls.remove(terminatedCall) + val wasOngoing = ongoingCallIds.remove(callId) + if (wasOngoing || isRejected) { + val notification = notificationUtils.buildCallEndedNotification() + notificationManager.notify(callId.hashCode(), notification) + } else { + val notification = notificationUtils.buildCallMissedNotification( + roomId = terminatedCall.nativeRoomId, + caller = terminatedCall.matrixItem?.getBestName() ?: terminatedCall.opponentUserId + ) + notificationManager.cancel(callId.hashCode()) + notificationManager.notify(MISSED_CALL_TAG, callId.hashCode(), notification) + } + if (knownCalls.isEmpty()) { mediaSession?.isActive = false myStopSelf() @@ -218,18 +233,18 @@ class CallService : VectorService() { val call = callManager.getCallById(callId) ?: return Unit.also { handleUnexpectedState(callId) } - val opponentMatrixItem = getOpponentMatrixItem(call) + val callInformation = call.toCallInformation() Timber.v("displayOutgoingCallNotification : display the dedicated notification") val notification = notificationUtils.buildOutgoingRingingCallNotification( call = call, - title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId + title = callInformation.matrixItem?.getBestName() ?: callInformation.opponentUserId ) if (knownCalls.isEmpty()) { startForeground(callId.hashCode(), notification) } else { notificationManager.notify(callId.hashCode(), notification) } - knownCalls.add(callId) + knownCalls.add(callInformation) } /** @@ -238,21 +253,22 @@ class CallService : VectorService() { private fun displayCallInProgressNotification(intent: Intent) { Timber.v("## VOIP displayCallInProgressNotification") val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" + ongoingCallIds.add(callId) val call = callManager.getCallById(callId) ?: return Unit.also { handleUnexpectedState(callId) } - val opponentMatrixItem = getOpponentMatrixItem(call) alertManager.cancelAlert(callId) + val callInformation = call.toCallInformation() val notification = notificationUtils.buildPendingCallNotification( call = call, - title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId + title = callInformation.matrixItem?.getBestName() ?: callInformation.opponentUserId ) if (knownCalls.isEmpty()) { startForeground(callId.hashCode(), notification) } else { notificationManager.notify(callId.hashCode(), notification) } - knownCalls.add(callId) + knownCalls.add(callInformation) } private fun handleUnexpectedState(callId: String?) { @@ -274,14 +290,27 @@ class CallService : VectorService() { connections[callConnection.callId] = callConnection } - private fun getOpponentMatrixItem(call: WebRtcCall): MatrixItem? { - return vectorComponent().activeSessionHolder().getSafeActiveSession()?.let { - call.getOpponentAsMatrixItem(it) - } + private fun WebRtcCall.toCallInformation(): CallInformation{ + return CallInformation( + callId = this.callId, + nativeRoomId = this.nativeRoomId, + opponentUserId = this.mxCall.opponentUserId, + matrixItem = vectorComponent().activeSessionHolder().getSafeActiveSession()?.let { + this.getOpponentAsMatrixItem(it) + } + ) } + private data class CallInformation( + val callId: String, + val nativeRoomId: String, + val opponentUserId: String, + val matrixItem: MatrixItem? + ) + companion object { private const val DEFAULT_NOTIFICATION_ID = 6480 + private const val MISSED_CALL_TAG = "MISSED_CALL_TAG" private const val ACTION_INCOMING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_INCOMING_RINGING_CALL" private const val ACTION_OUTGOING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_OUTGOING_RINGING_CALL" @@ -294,6 +323,7 @@ class CallService : VectorService() { private const val EXTRA_CALL_ID = "EXTRA_CALL_ID" private const val EXTRA_IS_IN_BG = "EXTRA_IS_IN_BG" + private const val EXTRA_IS_REJECTED = "EXTRA_IS_REJECTED" fun onIncomingCallRinging(context: Context, callId: String, @@ -329,11 +359,12 @@ class CallService : VectorService() { ContextCompat.startForegroundService(context, intent) } - fun onCallTerminated(context: Context, callId: String) { + fun onCallTerminated(context: Context, callId: String, isRejected: Boolean) { val intent = Intent(context, CallService::class.java) .apply { action = ACTION_CALL_TERMINATED putExtra(EXTRA_CALL_ID, callId) + putExtra(EXTRA_IS_REJECTED, isRejected) } ContextCompat.startForegroundService(context, intent) } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index 3259b0915f..086a3be9d3 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -99,7 +99,7 @@ class WebRtcCall( private val sessionProvider: Provider, private val peerConnectionFactoryProvider: Provider, private val onCallBecomeActive: (WebRtcCall) -> Unit, - private val onCallEnded: (String) -> Unit + private val onCallEnded: (String, Boolean) -> Unit ) : MxCall.StateListener { interface Listener : MxCall.StateListener { @@ -810,7 +810,8 @@ class WebRtcCall( val wasRinging = mxCall.state is CallState.LocalRinging mxCall.state = CallState.Terminated release() - onCallEnded(callId) + val isRejected = wasRinging && sendEndSignaling + onCallEnded(callId, isRejected) if (sendEndSignaling) { if (wasRinging) { mxCall.reject() diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt index 25463428e9..d7628cef90 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt @@ -232,12 +232,12 @@ class WebRtcCallManager @Inject constructor( this.currentCall.setAndNotify(call) } - private fun onCallEnded(callId: String) { + private fun onCallEnded(callId: String, isRejected: Boolean) { Timber.v("## VOIP WebRtcPeerConnectionManager onCall ended: $callId") val webRtcCall = callsByCallId.remove(callId) ?: return Unit.also { Timber.v("On call ended for unknown call $callId") } - CallService.onCallTerminated(context, callId) + CallService.onCallTerminated(context, callId, isRejected) callsByRoomId[webRtcCall.signalingRoomId]?.remove(webRtcCall) callsByRoomId[webRtcCall.nativeRoomId]?.remove(webRtcCall) transferees.remove(callId) @@ -420,7 +420,7 @@ class WebRtcCallManager @Inject constructor( override fun onCallManagedByOtherSession(callId: String) { Timber.v("## VOIP onCallManagedByOtherSession: $callId") - onCallEnded(callId) + onCallEnded(callId, false) } override fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 439705c9d6..d6358fe710 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -459,6 +459,26 @@ class NotificationUtils @Inject constructor(private val context: Context, .build() } + /** + * Build notification for the CallService, when a call is missed + */ + fun buildCallMissedNotification(roomId: String, caller: String): Notification { + val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) + .setContentTitle(caller) + .setContentText(stringProvider.getString(R.string.call_missed, caller)) + .setSmallIcon(R.drawable.ic_material_call_end_grey) + .setAutoCancel(true) + .setCategory(NotificationCompat.CATEGORY_CALL) + + val contentPendingIntent = TaskStackBuilder.create(context) + .addNextIntentWithParentStack(HomeActivity.newIntent(context)) + .addNextIntent(RoomDetailActivity.newIntent(context, RoomDetailArgs(roomId))) + .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) + + builder.setContentIntent(contentPendingIntent) + return builder.build() + } + fun buildDownloadFileNotification(uri: Uri, fileName: String, mimeType: String): Notification { return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) .setGroup(stringProvider.getString(R.string.app_name)) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index ef25329eed..8ef366656e 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -727,6 +727,7 @@ Call connected Call connecting… Call ended + You missed a call from %s Calling… Incoming Call Incoming Video Call