mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Incoming notification + ringing
This commit is contained in:
parent
d8cf44fdc9
commit
843da1d48d
13 changed files with 607 additions and 179 deletions
|
@ -38,6 +38,7 @@ import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
|||
import im.vector.matrix.android.internal.session.room.send.RoomEventSender
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -100,8 +101,8 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||
override fun removeCallListener(listener: CallsListener) {
|
||||
callListeners.remove(listener)
|
||||
}
|
||||
|
||||
override fun getCallWithId(callId: String): MxCall? {
|
||||
Timber.v("## VOIP getCallWithId $callId all calls ${activeCalls.map { it.callId }}")
|
||||
return activeCalls.find { it.callId == callId }
|
||||
}
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@ internal class MxCallImpl(
|
|||
|
||||
override fun offerSdp(sdp: SessionDescription) {
|
||||
if (!isOutgoing) return
|
||||
Timber.v("## VOIP offerSdp $callId")
|
||||
state = CallState.DIALING
|
||||
CallInviteContent(
|
||||
callId = callId,
|
||||
|
@ -113,6 +114,7 @@ internal class MxCallImpl(
|
|||
}
|
||||
|
||||
override fun hangUp() {
|
||||
Timber.v("## VOIP hangup $callId")
|
||||
CallHangupContent(
|
||||
callId = callId
|
||||
)
|
||||
|
@ -122,6 +124,7 @@ internal class MxCallImpl(
|
|||
}
|
||||
|
||||
override fun accept(sdp: SessionDescription) {
|
||||
Timber.v("## VOIP accept $callId")
|
||||
if (isOutgoing) return
|
||||
state = CallState.ANSWERING
|
||||
CallAnswerContent(
|
||||
|
|
|
@ -365,5 +365,6 @@
|
|||
|
||||
<string name="call_notification_answer">Accept</string>
|
||||
<string name="call_notification_reject">Decline</string>
|
||||
<string name="call_notification_hangup">Hang Up</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<!-- READ_PHONE_STATE is needed only if your calling app reads numbers from the `PHONE_STATE`
|
||||
intent action. -->
|
||||
|
||||
<!-- Needed to show incoming calls activity in lock screen-->
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<!-- Needed for incoming calls -->
|
||||
|
|
|
@ -17,9 +17,14 @@
|
|||
package im.vector.riotx.core.extensions
|
||||
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import im.vector.matrix.android.api.extensions.tryThis
|
||||
|
||||
inline fun androidx.fragment.app.FragmentManager.commitTransactionNow(func: FragmentTransaction.() -> FragmentTransaction) {
|
||||
beginTransaction().func().commitNow()
|
||||
// Could throw and make the app crash
|
||||
// e.g sharedActionViewModel.observe()
|
||||
tryThis("Failed to commitTransactionNow") {
|
||||
beginTransaction().func().commitNow()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun androidx.fragment.app.FragmentManager.commitTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
|
||||
|
|
|
@ -165,6 +165,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Timber.i("onCreate Activity ${this.javaClass.simpleName}")
|
||||
val vectorComponent = getVectorComponent()
|
||||
screenComponent = DaggerScreenComponent.factory().create(vectorComponent, this)
|
||||
val timeForInjection = measureTimeMillis {
|
||||
|
@ -252,6 +253,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Timber.i("onDestroy Activity ${this.javaClass.simpleName}")
|
||||
unBinder?.unbind()
|
||||
unBinder = null
|
||||
|
||||
|
@ -279,6 +281,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
Timber.i("onPause Activity ${this.javaClass.simpleName}")
|
||||
|
||||
rageShake.stop()
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.core.services
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioManager
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Build
|
||||
import im.vector.riotx.R
|
||||
import timber.log.Timber
|
||||
|
||||
class CallRingPlayer(
|
||||
context: Context
|
||||
) {
|
||||
|
||||
private val applicationContext = context.applicationContext
|
||||
|
||||
private var player: MediaPlayer? = null
|
||||
|
||||
fun start() {
|
||||
val audioManager: AudioManager = applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
player?.release()
|
||||
player = createPlayer()
|
||||
|
||||
// Check if sound is enabled
|
||||
val ringerMode = audioManager.ringerMode
|
||||
if (player != null && ringerMode == AudioManager.RINGER_MODE_NORMAL) {
|
||||
try {
|
||||
if (player?.isPlaying == false) {
|
||||
player?.start()
|
||||
Timber.v("## VOIP Starting ringing")
|
||||
} else {
|
||||
Timber.v("## VOIP already playing")
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "## VOIP Failed to start ringing")
|
||||
player = null
|
||||
}
|
||||
} else {
|
||||
Timber.v("## VOIP Can't play $player ode $ringerMode")
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
player?.release()
|
||||
player = null
|
||||
}
|
||||
|
||||
private fun createPlayer(): MediaPlayer? {
|
||||
try {
|
||||
val mediaPlayer = MediaPlayer.create(applicationContext, R.raw.ring)
|
||||
|
||||
mediaPlayer.setOnErrorListener(MediaPlayerErrorListener())
|
||||
mediaPlayer.isLooping = true
|
||||
if (Build.VERSION.SDK_INT <= 21) {
|
||||
@Suppress("DEPRECATION")
|
||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING)
|
||||
} else {
|
||||
mediaPlayer.setAudioAttributes(AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||||
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
|
||||
.build())
|
||||
}
|
||||
return mediaPlayer
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "Failed to create Call ring player")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
inner class MediaPlayerErrorListener : MediaPlayer.OnErrorListener {
|
||||
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
|
||||
Timber.w("onError($mp, $what, $extra")
|
||||
player = null
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -20,7 +20,6 @@ package im.vector.riotx.core.services
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.text.TextUtils
|
||||
import androidx.core.content.ContextCompat
|
||||
import im.vector.riotx.core.extensions.vectorComponent
|
||||
import im.vector.riotx.features.call.WebRtcPeerConnectionManager
|
||||
|
@ -38,7 +37,7 @@ class CallService : VectorService() {
|
|||
/**
|
||||
* call in progress (foreground notification)
|
||||
*/
|
||||
private var mCallIdInProgress: String? = null
|
||||
// private var mCallIdInProgress: String? = null
|
||||
|
||||
private lateinit var notificationUtils: NotificationUtils
|
||||
private lateinit var webRtcPeerConnectionManager: WebRtcPeerConnectionManager
|
||||
|
@ -46,15 +45,19 @@ class CallService : VectorService() {
|
|||
/**
|
||||
* incoming (foreground notification)
|
||||
*/
|
||||
private var mIncomingCallId: String? = null
|
||||
// private var mIncomingCallId: String? = null
|
||||
|
||||
private var callRingPlayer: CallRingPlayer? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
notificationUtils = vectorComponent().notificationUtils()
|
||||
webRtcPeerConnectionManager = vectorComponent().webRtcPeerConnectionManager()
|
||||
callRingPlayer = CallRingPlayer(applicationContext)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Timber.v("## VOIP onStartCommand $intent")
|
||||
if (intent == null) {
|
||||
// Service started again by the system.
|
||||
// TODO What do we do here?
|
||||
|
@ -62,12 +65,34 @@ class CallService : VectorService() {
|
|||
}
|
||||
|
||||
when (intent.action) {
|
||||
ACTION_INCOMING_CALL -> displayIncomingCallNotification(intent)
|
||||
ACTION_PENDING_CALL -> displayCallInProgressNotification(intent)
|
||||
ACTION_NO_ACTIVE_CALL -> hideCallNotifications()
|
||||
else ->
|
||||
ACTION_INCOMING_RINGING_CALL -> {
|
||||
callRingPlayer?.start()
|
||||
displayIncomingCallNotification(intent)
|
||||
}
|
||||
ACTION_OUTGOING_RINGING_CALL -> {
|
||||
callRingPlayer?.start()
|
||||
displayOutgoingRingingCallNotification(intent)
|
||||
}
|
||||
ACTION_ONGOING_CALL -> {
|
||||
callRingPlayer?.stop()
|
||||
displayCallInProgressNotification(intent)
|
||||
}
|
||||
ACTION_NO_ACTIVE_CALL -> hideCallNotifications()
|
||||
ACTION_CALL_CONNECTING -> {
|
||||
// lower notification priority
|
||||
displayCallInProgressNotification(intent)
|
||||
// stop ringing
|
||||
callRingPlayer?.stop()
|
||||
}
|
||||
ACTION_ONGOING_CALL_BG -> {
|
||||
// there is an ongoing call but call activity is in background
|
||||
displayCallOnGoingInBackground(intent)
|
||||
}
|
||||
else -> {
|
||||
// Should not happen
|
||||
callRingPlayer?.stop()
|
||||
myStopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
// We want the system to restore the service if killed
|
||||
|
@ -87,29 +112,29 @@ class CallService : VectorService() {
|
|||
* @param callId the callId
|
||||
*/
|
||||
private fun displayIncomingCallNotification(intent: Intent) {
|
||||
Timber.v("displayIncomingCallNotification")
|
||||
Timber.v("## VOIP displayIncomingCallNotification $intent")
|
||||
|
||||
// the incoming call in progress is already displayed
|
||||
if (!TextUtils.isEmpty(mIncomingCallId)) {
|
||||
Timber.v("displayIncomingCallNotification : the incoming call in progress is already displayed")
|
||||
} else if (!TextUtils.isEmpty(mCallIdInProgress)) {
|
||||
Timber.v("displayIncomingCallNotification : a 'call in progress' notification is displayed")
|
||||
} else
|
||||
// if (null == webRtcPeerConnectionManager.currentCall)
|
||||
{
|
||||
val callId = intent.getStringExtra(EXTRA_CALL_ID)
|
||||
// if (!TextUtils.isEmpty(mIncomingCallId)) {
|
||||
// Timber.v("displayIncomingCallNotification : the incoming call in progress is already displayed")
|
||||
// } else if (!TextUtils.isEmpty(mCallIdInProgress)) {
|
||||
// Timber.v("displayIncomingCallNotification : a 'call in progress' notification is displayed")
|
||||
// } else
|
||||
// // if (null == webRtcPeerConnectionManager.currentCall)
|
||||
// {
|
||||
val callId = intent.getStringExtra(EXTRA_CALL_ID)
|
||||
|
||||
Timber.v("displayIncomingCallNotification : display the dedicated notification")
|
||||
val notification = notificationUtils.buildIncomingCallNotification(
|
||||
intent.getBooleanExtra(EXTRA_IS_VIDEO, false),
|
||||
intent.getStringExtra(EXTRA_ROOM_NAME) ?: "",
|
||||
intent.getStringExtra(EXTRA_MATRIX_ID) ?: "",
|
||||
callId ?: "")
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
Timber.v("displayIncomingCallNotification : display the dedicated notification")
|
||||
val notification = notificationUtils.buildIncomingCallNotification(
|
||||
intent.getBooleanExtra(EXTRA_IS_VIDEO, false),
|
||||
intent.getStringExtra(EXTRA_ROOM_NAME) ?: "",
|
||||
intent.getStringExtra(EXTRA_ROOM_ID) ?: "",
|
||||
callId ?: "")
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
|
||||
mIncomingCallId = callId
|
||||
// mIncomingCallId = callId
|
||||
|
||||
// turn the screen on for 3 seconds
|
||||
// turn the screen on for 3 seconds
|
||||
// if (Matrix.getInstance(VectorApp.getInstance())!!.pushManager.isScreenTurnedOn) {
|
||||
// try {
|
||||
// val pm = getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
|
@ -123,16 +148,29 @@ class CallService : VectorService() {
|
|||
// }
|
||||
//
|
||||
// }
|
||||
}
|
||||
// }
|
||||
// else {
|
||||
// Timber.i("displayIncomingCallNotification : do not display the incoming call notification because there is a pending call")
|
||||
// }
|
||||
}
|
||||
|
||||
private fun displayOutgoingRingingCallNotification(intent: Intent) {
|
||||
val callId = intent.getStringExtra(EXTRA_CALL_ID)
|
||||
|
||||
Timber.v("displayOutgoingCallNotification : display the dedicated notification")
|
||||
val notification = notificationUtils.buildOutgoingRingingCallNotification(
|
||||
intent.getBooleanExtra(EXTRA_IS_VIDEO, false),
|
||||
intent.getStringExtra(EXTRA_ROOM_NAME) ?: "",
|
||||
intent.getStringExtra(EXTRA_ROOM_ID) ?: "",
|
||||
callId ?: "")
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a call in progress notification.
|
||||
*/
|
||||
private fun displayCallInProgressNotification(intent: Intent) {
|
||||
Timber.v("## VOIP displayCallInProgressNotification")
|
||||
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
|
||||
|
||||
val notification = notificationUtils.buildPendingCallNotification(
|
||||
|
@ -144,7 +182,27 @@ class CallService : VectorService() {
|
|||
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
|
||||
mCallIdInProgress = callId
|
||||
// mCallIdInProgress = callId
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a call in progress notification.
|
||||
*/
|
||||
private fun displayCallOnGoingInBackground(intent: Intent) {
|
||||
Timber.v("## VOIP displayCallInProgressNotification")
|
||||
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
|
||||
|
||||
val notification = notificationUtils.buildPendingCallNotification(
|
||||
isVideo = intent.getBooleanExtra(EXTRA_IS_VIDEO, false),
|
||||
roomName = intent.getStringExtra(EXTRA_ROOM_NAME) ?: "",
|
||||
roomId = intent.getStringExtra(EXTRA_ROOM_ID) ?: "",
|
||||
matrixId = intent.getStringExtra(EXTRA_MATRIX_ID) ?: "",
|
||||
callId = callId,
|
||||
fromBg = true)
|
||||
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
|
||||
// mCallIdInProgress = callId
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -159,6 +217,11 @@ class CallService : VectorService() {
|
|||
myStopSelf()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
callRingPlayer?.stop()
|
||||
}
|
||||
|
||||
fun addConnection(callConnection: CallConnection) {
|
||||
connections[callConnection.callId] = callConnection
|
||||
}
|
||||
|
@ -166,10 +229,14 @@ class CallService : VectorService() {
|
|||
companion object {
|
||||
private const val NOTIFICATION_ID = 6480
|
||||
|
||||
private const val ACTION_INCOMING_CALL = "im.vector.riotx.core.services.CallService.INCOMING_CALL"
|
||||
private const val ACTION_PENDING_CALL = "im.vector.riotx.core.services.CallService.PENDING_CALL"
|
||||
private const val ACTION_INCOMING_RINGING_CALL = "im.vector.riotx.core.services.CallService.ACTION_INCOMING_RINGING_CALL"
|
||||
private const val ACTION_OUTGOING_RINGING_CALL = "im.vector.riotx.core.services.CallService.ACTION_OUTGOING_RINGING_CALL"
|
||||
private const val ACTION_CALL_CONNECTING = "im.vector.riotx.core.services.CallService.ACTION_CALL_CONNECTING"
|
||||
private const val ACTION_ONGOING_CALL = "im.vector.riotx.core.services.CallService.ACTION_ONGOING_CALL"
|
||||
private const val ACTION_ONGOING_CALL_BG = "im.vector.riotx.core.services.CallService.ACTION_ONGOING_CALL_BG"
|
||||
private const val ACTION_NO_ACTIVE_CALL = "im.vector.riotx.core.services.CallService.NO_ACTIVE_CALL"
|
||||
// private const val ACTION_ON_ACTIVE_CALL = "im.vector.riotx.core.services.CallService.ACTIVE_CALL"
|
||||
// private const val ACTION_ACTIVITY_VISIBLE = "im.vector.riotx.core.services.CallService.ACTION_ACTIVITY_VISIBLE"
|
||||
// private const val ACTION_STOP_RINGING = "im.vector.riotx.core.services.CallService.ACTION_STOP_RINGING"
|
||||
|
||||
private const val EXTRA_IS_VIDEO = "EXTRA_IS_VIDEO"
|
||||
private const val EXTRA_ROOM_NAME = "EXTRA_ROOM_NAME"
|
||||
|
@ -177,15 +244,15 @@ class CallService : VectorService() {
|
|||
private const val EXTRA_MATRIX_ID = "EXTRA_MATRIX_ID"
|
||||
private const val EXTRA_CALL_ID = "EXTRA_CALL_ID"
|
||||
|
||||
fun onIncomingCall(context: Context,
|
||||
isVideo: Boolean,
|
||||
roomName: String,
|
||||
roomId: String,
|
||||
matrixId: String,
|
||||
callId: String) {
|
||||
fun onIncomingCallRinging(context: Context,
|
||||
isVideo: Boolean,
|
||||
roomName: String,
|
||||
roomId: String,
|
||||
matrixId: String,
|
||||
callId: String) {
|
||||
val intent = Intent(context, CallService::class.java)
|
||||
.apply {
|
||||
action = ACTION_INCOMING_CALL
|
||||
action = ACTION_INCOMING_RINGING_CALL
|
||||
putExtra(EXTRA_IS_VIDEO, isVideo)
|
||||
putExtra(EXTRA_ROOM_NAME, roomName)
|
||||
putExtra(EXTRA_ROOM_ID, roomId)
|
||||
|
@ -196,24 +263,43 @@ class CallService : VectorService() {
|
|||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
|
||||
// fun onActiveCall(context: Context,
|
||||
// isVideo: Boolean,
|
||||
// roomName: String,
|
||||
// roomId: String,
|
||||
// matrixId: String,
|
||||
// callId: String) {
|
||||
// val intent = Intent(context, CallService::class.java)
|
||||
// .apply {
|
||||
// action = ACTION_ON_ACTIVE_CALL
|
||||
// putExtra(EXTRA_IS_VIDEO, isVideo)
|
||||
// putExtra(EXTRA_ROOM_NAME, roomName)
|
||||
// putExtra(EXTRA_ROOM_ID, roomId)
|
||||
// putExtra(EXTRA_MATRIX_ID, matrixId)
|
||||
// putExtra(EXTRA_CALL_ID, callId)
|
||||
// }
|
||||
//
|
||||
// ContextCompat.startForegroundService(context, intent)
|
||||
// }
|
||||
fun onOnGoingCallBackground(context: Context,
|
||||
isVideo: Boolean,
|
||||
roomName: String,
|
||||
roomId: String,
|
||||
matrixId: String,
|
||||
callId: String) {
|
||||
val intent = Intent(context, CallService::class.java)
|
||||
.apply {
|
||||
action = ACTION_ONGOING_CALL_BG
|
||||
putExtra(EXTRA_IS_VIDEO, isVideo)
|
||||
putExtra(EXTRA_ROOM_NAME, roomName)
|
||||
putExtra(EXTRA_ROOM_ID, roomId)
|
||||
putExtra(EXTRA_MATRIX_ID, matrixId)
|
||||
putExtra(EXTRA_CALL_ID, callId)
|
||||
}
|
||||
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
|
||||
fun onOutgoingCallRinging(context: Context,
|
||||
isVideo: Boolean,
|
||||
roomName: String,
|
||||
roomId: String,
|
||||
matrixId: String,
|
||||
callId: String) {
|
||||
val intent = Intent(context, CallService::class.java)
|
||||
.apply {
|
||||
action = ACTION_OUTGOING_RINGING_CALL
|
||||
putExtra(EXTRA_IS_VIDEO, isVideo)
|
||||
putExtra(EXTRA_ROOM_NAME, roomName)
|
||||
putExtra(EXTRA_ROOM_ID, roomId)
|
||||
putExtra(EXTRA_MATRIX_ID, matrixId)
|
||||
putExtra(EXTRA_CALL_ID, callId)
|
||||
}
|
||||
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
|
||||
fun onPendingCall(context: Context,
|
||||
isVideo: Boolean,
|
||||
|
@ -223,7 +309,7 @@ class CallService : VectorService() {
|
|||
callId: String) {
|
||||
val intent = Intent(context, CallService::class.java)
|
||||
.apply {
|
||||
action = ACTION_PENDING_CALL
|
||||
action = ACTION_ONGOING_CALL
|
||||
putExtra(EXTRA_IS_VIDEO, isVideo)
|
||||
putExtra(EXTRA_ROOM_NAME, roomName)
|
||||
putExtra(EXTRA_ROOM_ID, roomId)
|
||||
|
|
|
@ -20,6 +20,7 @@ package im.vector.riotx.features.call
|
|||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
|
@ -31,6 +32,7 @@ import androidx.core.view.isInvisible
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import butterknife.BindView
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.jakewharton.rxbinding3.view.clicks
|
||||
|
@ -46,10 +48,11 @@ import im.vector.riotx.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
|
|||
import im.vector.riotx.core.utils.allGranted
|
||||
import im.vector.riotx.core.utils.checkPermissions
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.activity_call.*
|
||||
import kotlinx.android.synthetic.main.fragment_attachments_preview.*
|
||||
import org.webrtc.EglBase
|
||||
import org.webrtc.RendererCommon
|
||||
import org.webrtc.SurfaceViewRenderer
|
||||
|
@ -101,19 +104,6 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
override fun doBeforeSetContentView() {
|
||||
// Set window styles for fullscreen-window size. Needs to be done before adding content.
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setTurnScreenOn(true)
|
||||
setShowWhenLocked(true)
|
||||
getSystemService(KeyguardManager::class.java)?.requestDismissKeyguard(this, null)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
window.addFlags(
|
||||
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
|
||||
or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
|
||||
or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
|
||||
)
|
||||
}
|
||||
|
||||
hideSystemUI()
|
||||
setContentView(R.layout.activity_call)
|
||||
|
@ -179,31 +169,22 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
if (intent.hasExtra(MvRx.KEY_ARG)) {
|
||||
callArgs = intent.getParcelableExtra(MvRx.KEY_ARG)!!
|
||||
} else {
|
||||
Timber.e("## VOIP missing callArgs for VectorCall Activity")
|
||||
CallService.onNoActiveCall(this)
|
||||
finish()
|
||||
}
|
||||
|
||||
Timber.v("## VOIP EXTRA_MODE is ${intent.getStringExtra(EXTRA_MODE)}")
|
||||
if (intent.getStringExtra(EXTRA_MODE) == INCOMING_RINGING) {
|
||||
turnScreenOnAndKeyguardOff()
|
||||
}
|
||||
|
||||
constraintLayout.clicks()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { toggleUiSystemVisibility() }
|
||||
.disposeOnDestroy()
|
||||
|
||||
if (isFirstCreation()) {
|
||||
// Reduce priority of notification as the activity is on screen
|
||||
CallService.onPendingCall(
|
||||
this,
|
||||
callArgs.isVideoCall,
|
||||
callArgs.participantUserId,
|
||||
callArgs.roomId,
|
||||
"",
|
||||
callArgs.callId ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
rootEglBase = EglUtils.rootEglBase ?: return Unit.also {
|
||||
finish()
|
||||
}
|
||||
|
||||
configureCallViews()
|
||||
|
||||
callViewModel.subscribe(this) {
|
||||
|
@ -229,8 +210,68 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
peerConnectionManager.detachRenderers()
|
||||
turnScreenOffAndKeyguardOn()
|
||||
}
|
||||
|
||||
// override fun onResume() {
|
||||
// super.onResume()
|
||||
// }
|
||||
//
|
||||
// override fun onStop() {
|
||||
// super.onStop()
|
||||
// when(callViewModel.call?.state) {
|
||||
// CallState.DIALING -> {
|
||||
// CallService.onIncomingCall(
|
||||
// this,
|
||||
// callArgs.isVideoCall,
|
||||
// callArgs.participantUserId,
|
||||
// callArgs.roomId,
|
||||
// "",
|
||||
// callArgs.callId ?: ""
|
||||
// )
|
||||
// }
|
||||
// CallState.LOCAL_RINGING -> {
|
||||
// CallService.onIncomingCall(
|
||||
// this,
|
||||
// callArgs.isVideoCall,
|
||||
// callArgs.participantUserId,
|
||||
// callArgs.roomId,
|
||||
// "",
|
||||
// callArgs.callId ?: ""
|
||||
// )
|
||||
// }
|
||||
// CallState.ANSWERING,
|
||||
// CallState.CONNECTING,
|
||||
// CallState.CONNECTED -> {
|
||||
// CallService.onPendingCall(
|
||||
// this,
|
||||
// callArgs.isVideoCall,
|
||||
// callArgs.participantUserId,
|
||||
// callArgs.roomId,
|
||||
// "",
|
||||
// callArgs.callId ?: ""
|
||||
// )
|
||||
// }
|
||||
// CallState.TERMINATED ,
|
||||
// CallState.IDLE ,
|
||||
// null -> {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun renderState(state: VectorCallViewState) {
|
||||
Timber.v("## VOIP renderState call $state")
|
||||
if (state.callState is Fail) {
|
||||
// be sure to clear notification
|
||||
CallService.onNoActiveCall(this)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
callControlsView.updateForState(state)
|
||||
when (state.callState.invoke()) {
|
||||
CallState.IDLE,
|
||||
|
@ -271,6 +312,8 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
configureCallInfo(state)
|
||||
callStatusText.text = null
|
||||
}
|
||||
// ensure all attached?
|
||||
peerConnectionManager.attachViewRenderers(pipRenderer, fullscreenRenderer, null)
|
||||
}
|
||||
CallState.TERMINATED -> {
|
||||
finish()
|
||||
|
@ -290,20 +333,6 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
|
||||
private fun configureCallViews() {
|
||||
callControlsView.interactionListener = this
|
||||
// if (callArgs.isVideoCall) {
|
||||
// iv_call_speaker.isVisible = false
|
||||
// iv_call_flip_camera.isVisible = true
|
||||
// iv_call_videocam_off.isVisible = true
|
||||
// } else {
|
||||
// iv_call_speaker.isVisible = true
|
||||
// iv_call_flip_camera.isVisible = false
|
||||
// iv_call_videocam_off.isVisible = false
|
||||
// }
|
||||
//
|
||||
// iv_end_call.setOnClickListener {
|
||||
// callViewModel.handle(VectorCallViewActions.EndCall)
|
||||
// finish()
|
||||
// }
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
|
@ -315,7 +344,12 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
}
|
||||
}
|
||||
|
||||
private fun start(): Boolean {
|
||||
private fun start() {
|
||||
rootEglBase = EglUtils.rootEglBase ?: return Unit.also {
|
||||
Timber.v("## VOIP rootEglBase is null")
|
||||
finish()
|
||||
}
|
||||
|
||||
// Init Picture in Picture renderer
|
||||
pipRenderer.init(rootEglBase!!.eglBaseContext, null)
|
||||
pipRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
|
||||
|
@ -330,16 +364,31 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
|
||||
peerConnectionManager.attachViewRenderers(pipRenderer, fullscreenRenderer,
|
||||
intent.getStringExtra(EXTRA_MODE)?.takeIf { isFirstCreation() })
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
peerConnectionManager.detachRenderers()
|
||||
super.onPause()
|
||||
}
|
||||
// override fun onResume() {
|
||||
// super.onResume()
|
||||
// withState(callViewModel) {
|
||||
// if(it.callState.invoke() == CallState.CONNECTED) {
|
||||
// peerConnectionManager.attachViewRenderers(pipRenderer, fullscreenRenderer)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// override fun onPause() {
|
||||
// peerConnectionManager.detachRenderers()
|
||||
// super.onPause()
|
||||
// }
|
||||
|
||||
private fun handleViewEvents(event: VectorCallViewEvents?) {
|
||||
Timber.v("handleViewEvents $event")
|
||||
Timber.v("## VOIP handleViewEvents $event")
|
||||
when (event) {
|
||||
VectorCallViewEvents.DismissNoCall -> {
|
||||
CallService.onNoActiveCall(this)
|
||||
finish()
|
||||
}
|
||||
null -> {
|
||||
}
|
||||
}
|
||||
// when (event) {
|
||||
// is VectorCallViewEvents.CallAnswered -> {
|
||||
// }
|
||||
|
@ -360,6 +409,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
|
||||
fun newIntent(context: Context, mxCall: MxCallDetail): Intent {
|
||||
return Intent(context, VectorCallActivity::class.java).apply {
|
||||
// what could be the best flags?
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
putExtra(MvRx.KEY_ARG, CallArgs(mxCall.roomId, mxCall.callId, mxCall.otherUserId, !mxCall.isOutgoing, mxCall.isVideoCall, false))
|
||||
putExtra(EXTRA_MODE, OUTGOING_CREATED)
|
||||
|
@ -375,7 +425,8 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
accept: Boolean,
|
||||
mode: String?): Intent {
|
||||
return Intent(context, VectorCallActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
// what could be the best flags?
|
||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra(MvRx.KEY_ARG, CallArgs(roomId, callId, otherUserId, isIncomingCall, isVideoCall, accept))
|
||||
putExtra(EXTRA_MODE, mode)
|
||||
}
|
||||
|
@ -403,11 +454,48 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
}
|
||||
|
||||
override fun returnToChat() {
|
||||
// TODO, what if the room is not in backstack??
|
||||
val args = RoomDetailArgs(callArgs.roomId)
|
||||
val intent = RoomDetailActivity.newIntent(this, args).apply {
|
||||
flags = FLAG_ACTIVITY_CLEAR_TOP
|
||||
}
|
||||
startActivity(intent)
|
||||
// is it needed?
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun didTapMore() {
|
||||
CallControlsBottomSheet().show(supportFragmentManager, "Controls")
|
||||
}
|
||||
|
||||
// Needed to let you answer call when phone is locked
|
||||
private fun turnScreenOnAndKeyguardOff() {
|
||||
Timber.v("## VOIP turnScreenOnAndKeyguardOff")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setShowWhenLocked(true)
|
||||
setTurnScreenOn(true)
|
||||
} else {
|
||||
window.addFlags(
|
||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
|
||||
)
|
||||
}
|
||||
|
||||
with(getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
requestDismissKeyguard(this@VectorCallActivity, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun turnScreenOffAndKeyguardOn() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setShowWhenLocked(false)
|
||||
setTurnScreenOn(false)
|
||||
} else {
|
||||
window.clearFlags(
|
||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.riotx.features.call
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
|
@ -58,6 +59,7 @@ sealed class VectorCallViewActions : VectorViewModelAction {
|
|||
|
||||
sealed class VectorCallViewEvents : VectorViewEvents {
|
||||
|
||||
object DismissNoCall : VectorCallViewEvents()
|
||||
// data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents()
|
||||
// data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents()
|
||||
// object CallAccepted : VectorCallViewEvents()
|
||||
|
@ -117,6 +119,12 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
soundDevice = webRtcPeerConnectionManager.audioManager.getCurrentSoundDevice()
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
setState {
|
||||
copy(
|
||||
callState = Fail(IllegalArgumentException("No call"))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,6 +266,23 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
Timber.v("## VOIP attachViewRenderers localRendeder $localViewRenderer / $remoteViewRenderer")
|
||||
this.localSurfaceRenderer = WeakReference(localViewRenderer)
|
||||
this.remoteSurfaceRenderer = WeakReference(remoteViewRenderer)
|
||||
|
||||
// The call is going to resume from background, we can reduce notif
|
||||
currentCall?.mxCall
|
||||
?.takeIf { it.state == CallState.CONNECTING || it.state == CallState.CONNECTED }
|
||||
?.let { mxCall ->
|
||||
val name = sessionHolder.getSafeActiveSession()?.getUser(mxCall.otherUserId)?.getBestName()
|
||||
?: mxCall.roomId
|
||||
// Start background service with notification
|
||||
CallService.onPendingCall(
|
||||
context = context,
|
||||
isVideo = mxCall.isVideoCall,
|
||||
roomName = name,
|
||||
roomId = mxCall.roomId,
|
||||
matrixId = sessionHolder.getSafeActiveSession()?.myUserId ?: "",
|
||||
callId = mxCall.callId)
|
||||
}
|
||||
|
||||
getTurnServer { turnServer ->
|
||||
val call = currentCall ?: return@getTurnServer
|
||||
when (mode) {
|
||||
|
@ -314,6 +331,19 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
}
|
||||
|
||||
private fun internalAcceptIncomingCall(callContext: CallContext, turnServerResponse: TurnServerResponse?) {
|
||||
val mxCall = callContext.mxCall
|
||||
// Update service state
|
||||
|
||||
val name = sessionHolder.getSafeActiveSession()?.getUser(mxCall.otherUserId)?.getBestName()
|
||||
?: mxCall.roomId
|
||||
CallService.onPendingCall(
|
||||
context = context,
|
||||
isVideo = mxCall.isVideoCall,
|
||||
roomName = name,
|
||||
roomId = mxCall.roomId,
|
||||
matrixId = sessionHolder.getSafeActiveSession()?.myUserId ?: "",
|
||||
callId = mxCall.callId
|
||||
)
|
||||
executor.execute {
|
||||
// 1) create peer connection
|
||||
createPeerConnection(callContext, turnServerResponse)
|
||||
|
@ -435,7 +465,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
|
||||
fun acceptIncomingCall() {
|
||||
Timber.v("## VOIP acceptIncomingCall from state ${currentCall?.mxCall?.state}")
|
||||
if (currentCall?.mxCall?.state == CallState.LOCAL_RINGING) {
|
||||
val mxCall = currentCall?.mxCall
|
||||
if (mxCall?.state == CallState.LOCAL_RINGING) {
|
||||
getTurnServer { turnServer ->
|
||||
internalAcceptIncomingCall(currentCall!!, turnServer)
|
||||
}
|
||||
|
@ -443,6 +474,24 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
}
|
||||
|
||||
fun detachRenderers() {
|
||||
// The call is going to continue in background, so ensure notification is visible
|
||||
currentCall?.mxCall
|
||||
?.takeIf { it.state == CallState.CONNECTING || it.state == CallState.CONNECTED }
|
||||
?.let { mxCall ->
|
||||
// Start background service with notification
|
||||
|
||||
val name = sessionHolder.getSafeActiveSession()?.getUser(mxCall.otherUserId)?.getBestName()
|
||||
?: mxCall.otherUserId
|
||||
CallService.onOnGoingCallBackground(
|
||||
context = context,
|
||||
isVideo = mxCall.isVideoCall,
|
||||
roomName = name,
|
||||
roomId = mxCall.roomId,
|
||||
matrixId = sessionHolder.getSafeActiveSession()?.myUserId ?: "",
|
||||
callId = mxCall.callId
|
||||
)
|
||||
}
|
||||
|
||||
Timber.v("## VOIP detachRenderers")
|
||||
// currentCall?.localMediaStream?.let { currentCall?.peerConnection?.removeStream(it) }
|
||||
localSurfaceRenderer?.get()?.let {
|
||||
|
@ -496,6 +545,16 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
audioManager.startForCall(createdCall)
|
||||
currentCall = callContext
|
||||
|
||||
val name = sessionHolder.getSafeActiveSession()?.getUser(createdCall.otherUserId)?.getBestName()
|
||||
?: createdCall.otherUserId
|
||||
CallService.onOutgoingCallRinging(
|
||||
context = context,
|
||||
isVideo = createdCall.isVideoCall,
|
||||
roomName = name,
|
||||
roomId = createdCall.roomId,
|
||||
matrixId = sessionHolder.getSafeActiveSession()?.myUserId ?: "",
|
||||
callId = createdCall.callId)
|
||||
|
||||
executor.execute {
|
||||
callContext.remoteCandidateSource = ReplaySubject.create()
|
||||
}
|
||||
|
@ -524,9 +583,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
Timber.v("## VOIP onCallInviteReceived callId ${mxCall.callId}")
|
||||
// TODO What if a call is currently active?
|
||||
if (currentCall != null) {
|
||||
Timber.w("## VOIP TODO: Automatically reject incoming call?")
|
||||
mxCall.hangUp()
|
||||
audioManager.stop()
|
||||
Timber.w("## VOIP receiving incoming call while already in call?")
|
||||
// Just ignore, maybe we could answer from other session?
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -537,12 +595,17 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
callContext.remoteCandidateSource = ReplaySubject.create()
|
||||
}
|
||||
|
||||
CallService.onIncomingCall(context,
|
||||
mxCall.isVideoCall,
|
||||
mxCall.otherUserId,
|
||||
mxCall.roomId,
|
||||
sessionHolder.getSafeActiveSession()?.myUserId ?: "",
|
||||
mxCall.callId)
|
||||
// Start background service with notification
|
||||
val name = sessionHolder.getSafeActiveSession()?.getUser(mxCall.otherUserId)?.getBestName()
|
||||
?: mxCall.otherUserId
|
||||
CallService.onIncomingCallRinging(
|
||||
context = context,
|
||||
isVideo = mxCall.isVideoCall,
|
||||
roomName = name,
|
||||
roomId = mxCall.roomId,
|
||||
matrixId = sessionHolder.getSafeActiveSession()?.myUserId ?: "",
|
||||
callId = mxCall.callId
|
||||
)
|
||||
|
||||
callContext.offerSdp = callInviteContent.offer
|
||||
}
|
||||
|
@ -575,12 +638,19 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
}
|
||||
|
||||
fun endCall() {
|
||||
// Update service state
|
||||
CallService.onNoActiveCall(context)
|
||||
// close tracks ASAP
|
||||
currentCall?.localVideoTrack?.setEnabled(false)
|
||||
currentCall?.localVideoTrack?.setEnabled(false)
|
||||
|
||||
currentCall?.cameraAvailabilityCallback?.let { cameraAvailabilityCallback ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback)
|
||||
}
|
||||
}
|
||||
|
||||
currentCall?.mxCall?.hangUp()
|
||||
currentCall = null
|
||||
audioManager.stop()
|
||||
|
@ -592,6 +662,18 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
if (call.mxCall.callId != callAnswerContent.callId) return Unit.also {
|
||||
Timber.w("onCallAnswerReceived for non active call? ${callAnswerContent.callId}")
|
||||
}
|
||||
val mxCall = call.mxCall
|
||||
// Update service state
|
||||
val name = sessionHolder.getSafeActiveSession()?.getUser(mxCall.otherUserId)?.getBestName()
|
||||
?: mxCall.otherUserId
|
||||
CallService.onPendingCall(
|
||||
context = context,
|
||||
isVideo = mxCall.isVideoCall,
|
||||
roomName = name,
|
||||
roomId = mxCall.roomId,
|
||||
matrixId = sessionHolder.getSafeActiveSession()?.myUserId ?: "",
|
||||
callId = mxCall.callId
|
||||
)
|
||||
executor.execute {
|
||||
Timber.v("## VOIP onCallAnswerReceived ${callAnswerContent.callId}")
|
||||
val sdp = SessionDescription(SessionDescription.Type.ANSWER, callAnswerContent.answer.sdp)
|
||||
|
@ -626,7 +708,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
* One or more of the ICE transports on the connection is in the "failed" state.
|
||||
*/
|
||||
PeerConnection.PeerConnectionState.FAILED -> {
|
||||
endCall()
|
||||
// This can be temporary, e.g when other ice not yet received...
|
||||
// callContext.mxCall.state = CallState.ERROR
|
||||
}
|
||||
/**
|
||||
* At least one of the connection's ICE transports (RTCIceTransports or RTCDtlsTransports) are in the "new" state,
|
||||
|
@ -711,7 +794,9 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
* It is, however, possible that the ICE agent did find compatible connections for some components.
|
||||
*/
|
||||
PeerConnection.IceConnectionState.FAILED -> {
|
||||
callContext.mxCall.hangUp()
|
||||
// I should not hangup here..
|
||||
// because new candidates could arrive
|
||||
// callContext.mxCall.hangUp()
|
||||
}
|
||||
/**
|
||||
* The ICE agent has finished gathering candidates, has checked all pairs against one another, and has found a connection for all components.
|
||||
|
@ -742,7 +827,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
remoteVideoTrack.setEnabled(true)
|
||||
callContext.remoteVideoTrack = remoteVideoTrack
|
||||
// sink to renderer if attached
|
||||
remoteSurfaceRenderer?.get().let { remoteVideoTrack.addSink(it) }
|
||||
remoteSurfaceRenderer?.get()?.let { remoteVideoTrack.addSink(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,15 @@ import im.vector.riotx.core.di.HasVectorInjector
|
|||
import im.vector.riotx.features.call.WebRtcPeerConnectionManager
|
||||
import im.vector.riotx.features.notifications.NotificationUtils
|
||||
import im.vector.riotx.features.settings.VectorLocale.context
|
||||
import timber.log.Timber
|
||||
|
||||
class CallHeadsUpActionReceiver : BroadcastReceiver() {
|
||||
|
||||
companion object {
|
||||
const val EXTRA_CALL_ACTION_KEY = "EXTRA_CALL_ACTION_KEY"
|
||||
const val CALL_ACTION_REJECT = 0
|
||||
}
|
||||
|
||||
private lateinit var peerConnectionManager: WebRtcPeerConnectionManager
|
||||
private lateinit var notificationUtils: NotificationUtils
|
||||
|
||||
|
@ -38,10 +44,9 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
|
|||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
// when (intent?.getIntExtra(CallHeadsUpService.EXTRA_CALL_ACTION_KEY, 0)) {
|
||||
// CallHeadsUpService.CALL_ACTION_ANSWER -> onCallAnswerClicked(context)
|
||||
// CallHeadsUpService.CALL_ACTION_REJECT -> onCallRejectClicked()
|
||||
// }
|
||||
when (intent?.getIntExtra(EXTRA_CALL_ACTION_KEY, 0)) {
|
||||
CALL_ACTION_REJECT -> onCallRejectClicked()
|
||||
}
|
||||
|
||||
// Not sure why this should be needed
|
||||
// val it = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
|
||||
|
@ -51,10 +56,10 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
|
|||
// context.stopService(Intent(context, CallHeadsUpService::class.java))
|
||||
}
|
||||
|
||||
// private fun onCallRejectClicked() {
|
||||
// Timber.d("onCallRejectClicked")
|
||||
// peerConnectionManager.endCall()
|
||||
// }
|
||||
private fun onCallRejectClicked() {
|
||||
Timber.d("onCallRejectClicked")
|
||||
peerConnectionManager.endCall()
|
||||
}
|
||||
|
||||
// private fun onCallAnswerClicked(context: Context) {
|
||||
// Timber.d("onCallAnswerClicked")
|
||||
|
|
|
@ -283,31 +283,29 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
.setSmallIcon(R.drawable.incoming_call_notification_transparent)
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
.setLights(accentColor, 500, 500)
|
||||
.setOngoing(true)
|
||||
|
||||
// Compat: Display the incoming call notification on the lock screen
|
||||
builder.priority = NotificationCompat.PRIORITY_HIGH
|
||||
|
||||
// clear the activity stack to home activity
|
||||
// val intent = Intent(context, HomeActivity::class.java)
|
||||
// .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
// TODO .putExtra(VectorHomeActivity.EXTRA_CALL_SESSION_ID, matrixId)
|
||||
// TODO .putExtra(VectorHomeActivity.EXTRA_CALL_ID, callId)
|
||||
|
||||
// Recreate the back stack
|
||||
// val stackBuilder = TaskStackBuilder.create(context)
|
||||
// .addParentStack(HomeActivity::class.java)
|
||||
// .addNextIntent(intent)
|
||||
|
||||
// android 4.3 issue
|
||||
// use a generator for the private requestCode.
|
||||
// When using 0, the intent is not created/launched when the user taps on the notification.
|
||||
//
|
||||
val requestId = Random.nextInt(1000)
|
||||
// val pendingIntent = stackBuilder.getPendingIntent(requestId, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val contentPendingIntent = TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
|
||||
.addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, otherUserId, true, isVideo, false, VectorCallActivity.INCOMING_RINGING))
|
||||
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
val contentIntent = VectorCallActivity.newIntent(
|
||||
context, callId, roomId, otherUserId, true, isVideo,
|
||||
false, VectorCallActivity.INCOMING_RINGING
|
||||
).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
data = Uri.parse("foobar://$callId")
|
||||
}
|
||||
val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
|
||||
|
||||
// val contentPendingIntent = TaskStackBuilder.create(context)
|
||||
// .addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
|
||||
// .addNextIntent(RoomDetailActivity.newIntent(context, RoomDetailArgs(roomId = roomId))
|
||||
// .addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, otherUserId, true, isVideo, false, VectorCallActivity.INCOMING_RINGING))
|
||||
// .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
val answerCallPendingIntent = TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
|
||||
|
@ -321,7 +319,12 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
// putExtra(CallHeadsUpService.EXTRA_CALL_ACTION_KEY, CallHeadsUpService.CALL_ACTION_REJECT)
|
||||
}
|
||||
// val answerCallPendingIntent = PendingIntent.getBroadcast(context, requestId, answerCallActionReceiver, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val rejectCallPendingIntent = PendingIntent.getBroadcast(context, requestId + 1, rejectCallActionReceiver, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val rejectCallPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
requestId + 1,
|
||||
rejectCallActionReceiver,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
builder.addAction(
|
||||
NotificationCompat.Action(
|
||||
|
@ -340,6 +343,54 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
rejectCallPendingIntent)
|
||||
)
|
||||
|
||||
builder.setFullScreenIntent(contentPendingIntent, true)
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
fun buildOutgoingRingingCallNotification(isVideo: Boolean,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
callId: String): Notification {
|
||||
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
|
||||
|
||||
val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
|
||||
.setContentTitle(ensureTitleNotEmpty(otherUserId))
|
||||
.apply {
|
||||
setContentText(stringProvider.getString(R.string.call_ring))
|
||||
}
|
||||
.setSmallIcon(R.drawable.incoming_call_notification_transparent)
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
.setLights(accentColor, 500, 500)
|
||||
.setOngoing(true)
|
||||
|
||||
val requestId = Random.nextInt(1000)
|
||||
|
||||
val contentIntent = VectorCallActivity.newIntent(
|
||||
context, callId, roomId, otherUserId, true, isVideo,
|
||||
false, null).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
data = Uri.parse("foobar://$callId")
|
||||
}
|
||||
val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
|
||||
|
||||
val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
|
||||
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
|
||||
}
|
||||
|
||||
val rejectCallPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
requestId + 1,
|
||||
rejectCallActionReceiver,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
builder.addAction(
|
||||
NotificationCompat.Action(
|
||||
IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
|
||||
context.getString(R.string.call_notification_hangup),
|
||||
rejectCallPendingIntent)
|
||||
)
|
||||
builder.setContentIntent(contentPendingIntent)
|
||||
|
||||
return builder.build()
|
||||
|
@ -360,8 +411,8 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
roomName: String,
|
||||
roomId: String,
|
||||
matrixId: String,
|
||||
callId: String): Notification {
|
||||
val builder = NotificationCompat.Builder(context, CALL_NOTIFICATION_CHANNEL_ID)
|
||||
callId: String, fromBg: Boolean = false): Notification {
|
||||
val builder = NotificationCompat.Builder(context, if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID)
|
||||
.setContentTitle(ensureTitleNotEmpty(roomName))
|
||||
.apply {
|
||||
if (isVideo) {
|
||||
|
@ -373,7 +424,29 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
.setSmallIcon(R.drawable.incoming_call_notification_transparent)
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
|
||||
builder.priority = NotificationCompat.PRIORITY_DEFAULT
|
||||
if (fromBg) {
|
||||
builder.priority = NotificationCompat.PRIORITY_LOW
|
||||
builder.setOngoing(true)
|
||||
}
|
||||
|
||||
val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
|
||||
data = Uri.parse("mxcall://end?$callId")
|
||||
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
|
||||
}
|
||||
|
||||
val rejectCallPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
System.currentTimeMillis().toInt(),
|
||||
rejectCallActionReceiver,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
builder.addAction(
|
||||
NotificationCompat.Action(
|
||||
IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
|
||||
context.getString(R.string.call_notification_hangup),
|
||||
rejectCallPendingIntent)
|
||||
)
|
||||
|
||||
val contentPendingIntent = TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
|
||||
|
@ -381,32 +454,8 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
.addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, false, null))
|
||||
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
// android 4.3 issue
|
||||
// use a generator for the private requestCode.
|
||||
// When using 0, the intent is not created/launched when the user taps on the notification.
|
||||
builder.setContentIntent(contentPendingIntent)
|
||||
|
||||
/* TODO
|
||||
// Build the pending intent for when the notification is clicked
|
||||
val roomIntent = Intent(context, VectorRoomActivity::class.java)
|
||||
.putExtra(VectorRoomActivity.EXTRA_ROOM_ID, roomId)
|
||||
.putExtra(VectorRoomActivity.EXTRA_MATRIX_ID, matrixId)
|
||||
.putExtra(VectorRoomActivity.EXTRA_START_CALL_ID, callId)
|
||||
|
||||
// Recreate the back stack
|
||||
val stackBuilder = TaskStackBuilder.create(context)
|
||||
.addParentStack(VectorRoomActivity::class.java)
|
||||
.addNextIntent(roomIntent)
|
||||
|
||||
// android 4.3 issue
|
||||
// use a generator for the private requestCode.
|
||||
// When using 0, the intent is not created/launched when the user taps on the notification.
|
||||
//
|
||||
val pendingIntent = stackBuilder.getPendingIntent(Random().nextInt(1000), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
builder.setContentIntent(pendingIntent)
|
||||
*/
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue