Add WakeLock to dismiss ringing call screen when call is cancelled (#4478)
* Add `WakeLock` to dismiss ringing call screen when call is cancelled We had already some checks in place to automatically cancel a ringing call notification/screen when the call was no longer active, but the `RoomInfo` updates weren't being processed because the app was 'paused'. The partial wakelock should ensure these room info updates are handled. * Add mutual exclusion to `ActiveCallManager` methods to improve thread safety
This commit is contained in:
parent
f52f24b464
commit
e45151d9d1
12 changed files with 152 additions and 48 deletions
|
|
@ -32,7 +32,7 @@ interface ElementCallEntryPoint {
|
||||||
* @param notificationChannelId The id of the notification channel to use for the call notification.
|
* @param notificationChannelId The id of the notification channel to use for the call notification.
|
||||||
* @param textContent The text content of the notification. If null the default content from the system will be used.
|
* @param textContent The text content of the notification. If null the default content from the system will be used.
|
||||||
*/
|
*/
|
||||||
fun handleIncomingCall(
|
suspend fun handleIncomingCall(
|
||||||
callType: CallType.RoomCall,
|
callType: CallType.RoomCall,
|
||||||
eventId: EventId,
|
eventId: EventId,
|
||||||
senderId: UserId,
|
senderId: UserId,
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class DefaultElementCallEntryPoint @Inject constructor(
|
||||||
context.startActivity(IntentProvider.createIntent(context, callType))
|
context.startActivity(IntentProvider.createIntent(context, callType))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleIncomingCall(
|
override suspend fun handleIncomingCall(
|
||||||
callType: CallType.RoomCall,
|
callType: CallType.RoomCall,
|
||||||
eventId: EventId,
|
eventId: EventId,
|
||||||
senderId: UserId,
|
senderId: UserId,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ import io.element.android.features.call.impl.di.CallBindings
|
||||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||||
import io.element.android.features.call.impl.utils.ActiveCallManager
|
import io.element.android.features.call.impl.utils.ActiveCallManager
|
||||||
import io.element.android.libraries.architecture.bindings
|
import io.element.android.libraries.architecture.bindings
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -27,10 +29,16 @@ class DeclineCallBroadcastReceiver : BroadcastReceiver() {
|
||||||
}
|
}
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var activeCallManager: ActiveCallManager
|
lateinit var activeCallManager: ActiveCallManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var appCoroutineScope: CoroutineScope
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
val notificationData = intent?.let { IntentCompat.getParcelableExtra(it, EXTRA_NOTIFICATION_DATA, CallNotificationData::class.java) }
|
val notificationData = intent?.let { IntentCompat.getParcelableExtra(it, EXTRA_NOTIFICATION_DATA, CallNotificationData::class.java) }
|
||||||
?: return
|
?: return
|
||||||
context.bindings<CallBindings>().inject(this)
|
context.bindings<CallBindings>().inject(this)
|
||||||
activeCallManager.hungUpCall(callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
|
appCoroutineScope.launch {
|
||||||
|
activeCallManager.hungUpCall(callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
private val activeCallManager: ActiveCallManager,
|
private val activeCallManager: ActiveCallManager,
|
||||||
private val languageTagProvider: LanguageTagProvider,
|
private val languageTagProvider: LanguageTagProvider,
|
||||||
private val appForegroundStateService: AppForegroundStateService,
|
private val appForegroundStateService: AppForegroundStateService,
|
||||||
|
private val appCoroutineScope: CoroutineScope,
|
||||||
) : Presenter<CallScreenState> {
|
) : Presenter<CallScreenState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
@ -87,7 +88,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
// Sets the call as joined
|
// Sets the call as joined
|
||||||
activeCallManager.joinedCall(callType)
|
activeCallManager.joinedCall(callType)
|
||||||
loadUrl(
|
fetchRoomCallUrl(
|
||||||
inputs = callType,
|
inputs = callType,
|
||||||
urlState = urlState,
|
urlState = urlState,
|
||||||
callWidgetDriver = callWidgetDriver,
|
callWidgetDriver = callWidgetDriver,
|
||||||
|
|
@ -96,7 +97,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onDispose {
|
onDispose {
|
||||||
activeCallManager.hungUpCall(callType)
|
appCoroutineScope.launch { activeCallManager.hungUpCall(callType) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,7 +188,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadUrl(
|
private suspend fun fetchRoomCallUrl(
|
||||||
inputs: CallType,
|
inputs: CallType,
|
||||||
urlState: MutableState<AsyncData<String>>,
|
urlState: MutableState<AsyncData<String>>,
|
||||||
callWidgetDriver: MutableState<MatrixWidgetDriver?>,
|
callWidgetDriver: MutableState<MatrixWidgetDriver?>,
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,11 @@ import io.element.android.libraries.architecture.bindings
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.designsystem.theme.ElementThemeApp
|
import io.element.android.libraries.designsystem.theme.ElementThemeApp
|
||||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -55,6 +57,9 @@ class IncomingCallActivity : AppCompatActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var buildMeta: BuildMeta
|
lateinit var buildMeta: BuildMeta
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var appCoroutineScope: CoroutineScope
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
|
@ -102,6 +107,8 @@ class IncomingCallActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private fun onCancel() {
|
private fun onCancel() {
|
||||||
val activeCall = activeCallManager.activeCall.value ?: return
|
val activeCall = activeCallManager.activeCall.value ?: return
|
||||||
activeCallManager.hungUpCall(callType = activeCall.callType)
|
appCoroutineScope.launch {
|
||||||
|
activeCallManager.hungUpCall(callType = activeCall.callType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,11 @@
|
||||||
package io.element.android.features.call.impl.utils
|
package io.element.android.features.call.impl.utils
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.PowerManager
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.appconfig.ElementCallConfig
|
import io.element.android.appconfig.ElementCallConfig
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallType
|
||||||
|
|
@ -17,6 +20,7 @@ import io.element.android.features.call.api.CurrentCall
|
||||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||||
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
import io.element.android.libraries.di.SingleIn
|
import io.element.android.libraries.di.SingleIn
|
||||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
import io.element.android.libraries.push.api.notifications.ForegroundServiceType
|
import io.element.android.libraries.push.api.notifications.ForegroundServiceType
|
||||||
|
|
@ -38,6 +42,8 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
@ -55,25 +61,26 @@ interface ActiveCallManager {
|
||||||
* Registers an incoming call if there isn't an existing active call and posts a [CallState.Ringing] notification.
|
* Registers an incoming call if there isn't an existing active call and posts a [CallState.Ringing] notification.
|
||||||
* @param notificationData The data for the incoming call notification.
|
* @param notificationData The data for the incoming call notification.
|
||||||
*/
|
*/
|
||||||
fun registerIncomingCall(notificationData: CallNotificationData)
|
suspend fun registerIncomingCall(notificationData: CallNotificationData)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the active call has been hung up. It will remove any existing UI and the active call.
|
* Called when the active call has been hung up. It will remove any existing UI and the active call.
|
||||||
* @param callType The type of call that the user hung up, either an external url one or a room one.
|
* @param callType The type of call that the user hung up, either an external url one or a room one.
|
||||||
*/
|
*/
|
||||||
fun hungUpCall(callType: CallType)
|
suspend fun hungUpCall(callType: CallType)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after the user joined a call. It will remove any existing UI and set the call state as [CallState.InCall].
|
* Called after the user joined a call. It will remove any existing UI and set the call state as [CallState.InCall].
|
||||||
*
|
*
|
||||||
* @param callType The type of call that the user joined, either an external url one or a room one.
|
* @param callType The type of call that the user joined, either an external url one or a room one.
|
||||||
*/
|
*/
|
||||||
fun joinedCall(callType: CallType)
|
suspend fun joinedCall(callType: CallType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SingleIn(AppScope::class)
|
@SingleIn(AppScope::class)
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultActiveCallManager @Inject constructor(
|
class DefaultActiveCallManager @Inject constructor(
|
||||||
|
@ApplicationContext context: Context,
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
private val onMissedCallNotificationHandler: OnMissedCallNotificationHandler,
|
private val onMissedCallNotificationHandler: OnMissedCallNotificationHandler,
|
||||||
private val ringingCallNotificationCreator: RingingCallNotificationCreator,
|
private val ringingCallNotificationCreator: RingingCallNotificationCreator,
|
||||||
|
|
@ -83,33 +90,47 @@ class DefaultActiveCallManager @Inject constructor(
|
||||||
) : ActiveCallManager {
|
) : ActiveCallManager {
|
||||||
private var timedOutCallJob: Job? = null
|
private var timedOutCallJob: Job? = null
|
||||||
|
|
||||||
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||||
|
internal val activeWakeLock: PowerManager.WakeLock? = context.getSystemService<PowerManager>()
|
||||||
|
?.takeIf { it.isWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK) }
|
||||||
|
?.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "${context.packageName}:IncomingCallWakeLock")
|
||||||
|
|
||||||
override val activeCall = MutableStateFlow<ActiveCall?>(null)
|
override val activeCall = MutableStateFlow<ActiveCall?>(null)
|
||||||
|
|
||||||
|
private val mutex = Mutex()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
observeRingingCall()
|
observeRingingCall()
|
||||||
observeCurrentCall()
|
observeCurrentCall()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun registerIncomingCall(notificationData: CallNotificationData) {
|
override suspend fun registerIncomingCall(notificationData: CallNotificationData) {
|
||||||
if (activeCall.value != null) {
|
mutex.withLock {
|
||||||
displayMissedCallNotification(notificationData)
|
if (activeCall.value != null) {
|
||||||
Timber.w("Already have an active call, ignoring incoming call: $notificationData")
|
displayMissedCallNotification(notificationData)
|
||||||
return
|
Timber.w("Already have an active call, ignoring incoming call: $notificationData")
|
||||||
}
|
return
|
||||||
activeCall.value = ActiveCall(
|
}
|
||||||
callType = CallType.RoomCall(
|
activeCall.value = ActiveCall(
|
||||||
sessionId = notificationData.sessionId,
|
callType = CallType.RoomCall(
|
||||||
roomId = notificationData.roomId,
|
sessionId = notificationData.sessionId,
|
||||||
),
|
roomId = notificationData.roomId,
|
||||||
callState = CallState.Ringing(notificationData),
|
),
|
||||||
)
|
callState = CallState.Ringing(notificationData),
|
||||||
|
)
|
||||||
|
|
||||||
timedOutCallJob = coroutineScope.launch {
|
timedOutCallJob = coroutineScope.launch {
|
||||||
showIncomingCallNotification(notificationData)
|
showIncomingCallNotification(notificationData)
|
||||||
|
|
||||||
// Wait for the ringing call to time out
|
// Wait for the ringing call to time out
|
||||||
delay(ElementCallConfig.RINGING_CALL_DURATION_SECONDS.seconds)
|
delay(ElementCallConfig.RINGING_CALL_DURATION_SECONDS.seconds)
|
||||||
incomingCallTimedOut(displayMissedCallNotification = true)
|
incomingCallTimedOut(displayMissedCallNotification = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire a wake lock to keep the device awake during the incoming call, so we can process the room info data
|
||||||
|
if (activeWakeLock?.isHeld == false) {
|
||||||
|
activeWakeLock.acquire(ElementCallConfig.RINGING_CALL_DURATION_SECONDS * 1000L)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,10 +138,13 @@ class DefaultActiveCallManager @Inject constructor(
|
||||||
* Called when the incoming call timed out. It will remove the active call and remove any associated UI, adding a 'missed call' notification.
|
* Called when the incoming call timed out. It will remove the active call and remove any associated UI, adding a 'missed call' notification.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||||
fun incomingCallTimedOut(displayMissedCallNotification: Boolean) {
|
suspend fun incomingCallTimedOut(displayMissedCallNotification: Boolean) = mutex.withLock {
|
||||||
val previousActiveCall = activeCall.value ?: return
|
val previousActiveCall = activeCall.value ?: return
|
||||||
val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return
|
val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return
|
||||||
activeCall.value = null
|
activeCall.value = null
|
||||||
|
if (activeWakeLock?.isHeld == true) {
|
||||||
|
activeWakeLock.release()
|
||||||
|
}
|
||||||
|
|
||||||
cancelIncomingCallNotification()
|
cancelIncomingCallNotification()
|
||||||
|
|
||||||
|
|
@ -129,18 +153,24 @@ class DefaultActiveCallManager @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hungUpCall(callType: CallType) {
|
override suspend fun hungUpCall(callType: CallType) = mutex.withLock {
|
||||||
if (activeCall.value?.callType != callType) {
|
if (activeCall.value?.callType != callType) {
|
||||||
Timber.w("Call type $callType does not match the active call type, ignoring")
|
Timber.w("Call type $callType does not match the active call type, ignoring")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cancelIncomingCallNotification()
|
cancelIncomingCallNotification()
|
||||||
|
if (activeWakeLock?.isHeld == true) {
|
||||||
|
activeWakeLock.release()
|
||||||
|
}
|
||||||
timedOutCallJob?.cancel()
|
timedOutCallJob?.cancel()
|
||||||
activeCall.value = null
|
activeCall.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun joinedCall(callType: CallType) {
|
override suspend fun joinedCall(callType: CallType) = mutex.withLock {
|
||||||
cancelIncomingCallNotification()
|
cancelIncomingCallNotification()
|
||||||
|
if (activeWakeLock?.isHeld == true) {
|
||||||
|
activeWakeLock.release()
|
||||||
|
}
|
||||||
timedOutCallJob?.cancel()
|
timedOutCallJob?.cancel()
|
||||||
|
|
||||||
activeCall.value = ActiveCall(
|
activeCall.value = ActiveCall(
|
||||||
|
|
@ -201,6 +231,7 @@ class DefaultActiveCallManager @Inject constructor(
|
||||||
?.getRoom(callType.roomId)
|
?.getRoom(callType.roomId)
|
||||||
?.roomInfoFlow
|
?.roomInfoFlow
|
||||||
?.map {
|
?.map {
|
||||||
|
Timber.d("Has room call status changed for ringing call: ${it.hasRoomCall}")
|
||||||
it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
|
it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
|
||||||
}
|
}
|
||||||
?: flowOf()
|
?: flowOf()
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,21 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.advanceTimeBy
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
import org.robolectric.RuntimeEnvironment
|
import org.robolectric.RuntimeEnvironment
|
||||||
import org.robolectric.Shadows.shadowOf
|
import org.robolectric.Shadows.shadowOf
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
class DefaultElementCallEntryPointTest {
|
class DefaultElementCallEntryPointTest {
|
||||||
@Test
|
@Test
|
||||||
fun `startCall - starts ElementCallActivity setup with the needed extras`() {
|
fun `startCall - starts ElementCallActivity setup with the needed extras`() = runTest {
|
||||||
val entryPoint = createEntryPoint()
|
val entryPoint = createEntryPoint()
|
||||||
entryPoint.startCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID))
|
entryPoint.startCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID))
|
||||||
|
|
||||||
|
|
@ -39,8 +44,9 @@ class DefaultElementCallEntryPointTest {
|
||||||
assertThat(intent.extras?.containsKey("EXTRA_CALL_TYPE")).isTrue()
|
assertThat(intent.extras?.containsKey("EXTRA_CALL_TYPE")).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun `handleIncomingCall - registers the incoming call using ActiveCallManager`() {
|
fun `handleIncomingCall - registers the incoming call using ActiveCallManager`() = runTest {
|
||||||
val registerIncomingCallLambda = lambdaRecorder<CallNotificationData, Unit> {}
|
val registerIncomingCallLambda = lambdaRecorder<CallNotificationData, Unit> {}
|
||||||
val activeCallManager = FakeActiveCallManager(registerIncomingCallResult = registerIncomingCallLambda)
|
val activeCallManager = FakeActiveCallManager(registerIncomingCallResult = registerIncomingCallLambda)
|
||||||
val entryPoint = createEntryPoint(activeCallManager = activeCallManager)
|
val entryPoint = createEntryPoint(activeCallManager = activeCallManager)
|
||||||
|
|
@ -57,10 +63,12 @@ class DefaultElementCallEntryPointTest {
|
||||||
textContent = "textContent",
|
textContent = "textContent",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
advanceTimeBy(1.seconds)
|
||||||
|
|
||||||
registerIncomingCallLambda.assertions().isCalledOnce()
|
registerIncomingCallLambda.assertions().isCalledOnce()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEntryPoint(
|
private fun TestScope.createEntryPoint(
|
||||||
activeCallManager: FakeActiveCallManager = FakeActiveCallManager(),
|
activeCallManager: FakeActiveCallManager = FakeActiveCallManager(),
|
||||||
) = DefaultElementCallEntryPoint(
|
) = DefaultElementCallEntryPoint(
|
||||||
context = InstrumentationRegistry.getInstrumentation().targetContext,
|
context = InstrumentationRegistry.getInstrumentation().targetContext,
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,14 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
|
import kotlinx.coroutines.test.advanceTimeBy
|
||||||
import kotlinx.coroutines.test.runCurrent
|
import kotlinx.coroutines.test.runCurrent
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class CallScreenPresenterTest {
|
@OptIn(ExperimentalCoroutinesApi::class) class CallScreenPresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
|
|
@ -66,7 +68,8 @@ class CallScreenPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
// Wait until the URL is loaded
|
// Wait until the URL is loaded
|
||||||
skipItems(1)
|
advanceTimeBy(1.seconds)
|
||||||
|
skipItems(2)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
|
assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
|
||||||
assertThat(initialState.webViewError).isNull()
|
assertThat(initialState.webViewError).isNull()
|
||||||
|
|
@ -101,16 +104,23 @@ class CallScreenPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
// Wait until the URL is loaded
|
// Wait until the URL is loaded
|
||||||
|
advanceTimeBy(1.seconds)
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
||||||
joinedCallLambda.assertions().isCalledOnce()
|
joinedCallLambda.assertions().isCalledOnce()
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.urlState).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(initialState.urlState).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
assertThat(initialState.isCallActive).isFalse()
|
assertThat(initialState.isCallActive).isFalse()
|
||||||
assertThat(initialState.isInWidgetMode).isTrue()
|
assertThat(initialState.isInWidgetMode).isTrue()
|
||||||
assertThat(widgetProvider.getWidgetCalled).isTrue()
|
assertThat(widgetProvider.getWidgetCalled).isTrue()
|
||||||
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
|
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
|
||||||
analyticsLambda.assertions().isCalledOnce().with(value(MobileScreen.ScreenName.RoomCall))
|
analyticsLambda.assertions().isCalledOnce().with(value(MobileScreen.ScreenName.RoomCall))
|
||||||
sendCallNotificationIfNeededLambda.assertions().isCalledOnce()
|
sendCallNotificationIfNeededLambda.assertions().isCalledOnce()
|
||||||
|
|
||||||
|
// Wait until the WidgetDriver is loaded
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
|
assertThat(awaitItem().urlState).isInstanceOf(AsyncData.Success::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,6 +136,9 @@ class CallScreenPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
// Give it time to load the URL and WidgetDriver
|
||||||
|
advanceTimeBy(1.seconds)
|
||||||
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
||||||
|
|
||||||
|
|
@ -141,7 +154,6 @@ class CallScreenPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - hang up event closes the screen and stops the widget driver`() = runTest(UnconfinedTestDispatcher()) {
|
fun `present - hang up event closes the screen and stops the widget driver`() = runTest(UnconfinedTestDispatcher()) {
|
||||||
val navigator = FakeCallScreenNavigator()
|
val navigator = FakeCallScreenNavigator()
|
||||||
|
|
@ -158,11 +170,15 @@ class CallScreenPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
|
|
||||||
|
// Give it time to load the URL and WidgetDriver
|
||||||
|
advanceTimeBy(1.seconds)
|
||||||
|
|
||||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
||||||
|
|
||||||
initialState.eventSink(CallScreenEvents.Hangup)
|
initialState.eventSink(CallScreenEvents.Hangup)
|
||||||
|
|
||||||
// Let background coroutines run
|
// Let background coroutines run and the widget drive be received
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
assertThat(navigator.closeCalled).isTrue()
|
assertThat(navigator.closeCalled).isTrue()
|
||||||
|
|
@ -172,7 +188,6 @@ class CallScreenPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - a received close message closes the screen and stops the widget driver`() = runTest(UnconfinedTestDispatcher()) {
|
fun `present - a received close message closes the screen and stops the widget driver`() = runTest(UnconfinedTestDispatcher()) {
|
||||||
val navigator = FakeCallScreenNavigator()
|
val navigator = FakeCallScreenNavigator()
|
||||||
|
|
@ -189,11 +204,16 @@ class CallScreenPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
|
|
||||||
|
// Give it time to load the URL and WidgetDriver
|
||||||
|
advanceTimeBy(1.seconds)
|
||||||
|
|
||||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
||||||
|
|
||||||
messageInterceptor.givenInterceptedMessage("""{"action":"io.element.close","api":"fromWidget","widgetId":"1","requestId":"1"}""")
|
messageInterceptor.givenInterceptedMessage("""{"action":"io.element.close","api":"fromWidget","widgetId":"1","requestId":"1"}""")
|
||||||
|
|
||||||
// Let background coroutines run
|
// Let background coroutines run
|
||||||
|
advanceTimeBy(1.seconds)
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
assertThat(navigator.closeCalled).isTrue()
|
assertThat(navigator.closeCalled).isTrue()
|
||||||
|
|
@ -218,7 +238,9 @@ class CallScreenPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
// Give it time to load the URL and WidgetDriver
|
||||||
|
advanceTimeBy(1.seconds)
|
||||||
|
skipItems(2)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.isCallActive).isFalse()
|
assertThat(initialState.isCallActive).isFalse()
|
||||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
||||||
|
|
@ -235,7 +257,7 @@ class CallScreenPresenterTest {
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
skipItems(1)
|
skipItems(2)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.isCallActive).isTrue()
|
assertThat(finalState.isCallActive).isTrue()
|
||||||
}
|
}
|
||||||
|
|
@ -300,7 +322,8 @@ class CallScreenPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
// Wait until the URL is loaded
|
// Wait until the URL is loaded
|
||||||
skipItems(1)
|
advanceTimeBy(1.seconds)
|
||||||
|
skipItems(2)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error"))
|
initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error"))
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
|
|
@ -329,6 +352,8 @@ class CallScreenPresenterTest {
|
||||||
initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error"))
|
initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error"))
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.webViewError).isNull()
|
assertThat(finalState.webViewError).isNull()
|
||||||
|
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -361,6 +386,7 @@ class CallScreenPresenterTest {
|
||||||
screenTracker = screenTracker,
|
screenTracker = screenTracker,
|
||||||
languageTagProvider = FakeLanguageTagProvider("en-US"),
|
languageTagProvider = FakeLanguageTagProvider("en-US"),
|
||||||
appForegroundStateService = appForegroundStateService,
|
appForegroundStateService = appForegroundStateService,
|
||||||
|
appCoroutineScope = backgroundScope,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@
|
||||||
|
|
||||||
package io.element.android.features.call.utils
|
package io.element.android.features.call.utils
|
||||||
|
|
||||||
|
import android.os.PowerManager
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallType
|
||||||
|
|
@ -49,6 +51,7 @@ import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.Shadows.shadowOf
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
class DefaultActiveCallManagerTest {
|
class DefaultActiveCallManagerTest {
|
||||||
|
|
@ -57,10 +60,12 @@ class DefaultActiveCallManagerTest {
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun `registerIncomingCall - sets the incoming call as active`() = runTest {
|
fun `registerIncomingCall - sets the incoming call as active`() = runTest {
|
||||||
|
setupShadowPowerManager()
|
||||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||||
inCancellableScope {
|
inCancellableScope {
|
||||||
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
|
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
|
||||||
|
|
||||||
|
assertThat(manager.activeWakeLock?.isHeld).isFalse()
|
||||||
assertThat(manager.activeCall.value).isNull()
|
assertThat(manager.activeCall.value).isNull()
|
||||||
|
|
||||||
val callNotificationData = aCallNotificationData()
|
val callNotificationData = aCallNotificationData()
|
||||||
|
|
@ -78,6 +83,7 @@ class DefaultActiveCallManagerTest {
|
||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||||
verify { notificationManagerCompat.notify(notificationId, any()) }
|
verify { notificationManagerCompat.notify(notificationId, any()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -128,6 +134,7 @@ class DefaultActiveCallManagerTest {
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun `incomingCallTimedOut - when there is an active call removes it and adds a missed call notification`() = runTest {
|
fun `incomingCallTimedOut - when there is an active call removes it and adds a missed call notification`() = runTest {
|
||||||
|
setupShadowPowerManager()
|
||||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||||
val addMissedCallNotificationLambda = lambdaRecorder<SessionId, RoomId, EventId, Unit> { _, _, _ -> }
|
val addMissedCallNotificationLambda = lambdaRecorder<SessionId, RoomId, EventId, Unit> { _, _, _ -> }
|
||||||
inCancellableScope {
|
inCancellableScope {
|
||||||
|
|
@ -138,11 +145,13 @@ class DefaultActiveCallManagerTest {
|
||||||
|
|
||||||
manager.registerIncomingCall(aCallNotificationData())
|
manager.registerIncomingCall(aCallNotificationData())
|
||||||
assertThat(manager.activeCall.value).isNotNull()
|
assertThat(manager.activeCall.value).isNotNull()
|
||||||
|
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||||
|
|
||||||
manager.incomingCallTimedOut(displayMissedCallNotification = true)
|
manager.incomingCallTimedOut(displayMissedCallNotification = true)
|
||||||
advanceTimeBy(1)
|
advanceTimeBy(1)
|
||||||
|
|
||||||
assertThat(manager.activeCall.value).isNull()
|
assertThat(manager.activeCall.value).isNull()
|
||||||
|
assertThat(manager.activeWakeLock?.isHeld).isFalse()
|
||||||
addMissedCallNotificationLambda.assertions().isCalledOnce()
|
addMissedCallNotificationLambda.assertions().isCalledOnce()
|
||||||
verify { notificationManagerCompat.cancel(notificationId) }
|
verify { notificationManagerCompat.cancel(notificationId) }
|
||||||
}
|
}
|
||||||
|
|
@ -150,6 +159,7 @@ class DefaultActiveCallManagerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `hungUpCall - removes existing call if the CallType matches`() = runTest {
|
fun `hungUpCall - removes existing call if the CallType matches`() = runTest {
|
||||||
|
setupShadowPowerManager()
|
||||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||||
// Create a cancellable coroutine scope to cancel the test when needed
|
// Create a cancellable coroutine scope to cancel the test when needed
|
||||||
inCancellableScope {
|
inCancellableScope {
|
||||||
|
|
@ -158,9 +168,11 @@ class DefaultActiveCallManagerTest {
|
||||||
val notificationData = aCallNotificationData()
|
val notificationData = aCallNotificationData()
|
||||||
manager.registerIncomingCall(notificationData)
|
manager.registerIncomingCall(notificationData)
|
||||||
assertThat(manager.activeCall.value).isNotNull()
|
assertThat(manager.activeCall.value).isNotNull()
|
||||||
|
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||||
|
|
||||||
manager.hungUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
|
manager.hungUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
|
||||||
assertThat(manager.activeCall.value).isNull()
|
assertThat(manager.activeCall.value).isNull()
|
||||||
|
assertThat(manager.activeWakeLock?.isHeld).isFalse()
|
||||||
|
|
||||||
verify { notificationManagerCompat.cancel(notificationId) }
|
verify { notificationManagerCompat.cancel(notificationId) }
|
||||||
}
|
}
|
||||||
|
|
@ -168,6 +180,7 @@ class DefaultActiveCallManagerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `hungUpCall - does nothing if the CallType doesn't match`() = runTest {
|
fun `hungUpCall - does nothing if the CallType doesn't match`() = runTest {
|
||||||
|
setupShadowPowerManager()
|
||||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||||
// Create a cancellable coroutine scope to cancel the test when needed
|
// Create a cancellable coroutine scope to cancel the test when needed
|
||||||
inCancellableScope {
|
inCancellableScope {
|
||||||
|
|
@ -175,9 +188,11 @@ class DefaultActiveCallManagerTest {
|
||||||
|
|
||||||
manager.registerIncomingCall(aCallNotificationData())
|
manager.registerIncomingCall(aCallNotificationData())
|
||||||
assertThat(manager.activeCall.value).isNotNull()
|
assertThat(manager.activeCall.value).isNotNull()
|
||||||
|
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||||
|
|
||||||
manager.hungUpCall(CallType.ExternalUrl("https://example.com"))
|
manager.hungUpCall(CallType.ExternalUrl("https://example.com"))
|
||||||
assertThat(manager.activeCall.value).isNotNull()
|
assertThat(manager.activeCall.value).isNotNull()
|
||||||
|
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||||
|
|
||||||
verify(exactly = 0) { notificationManagerCompat.cancel(notificationId) }
|
verify(exactly = 0) { notificationManagerCompat.cancel(notificationId) }
|
||||||
}
|
}
|
||||||
|
|
@ -284,12 +299,19 @@ class DefaultActiveCallManagerTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupShadowPowerManager() {
|
||||||
|
shadowOf(InstrumentationRegistry.getInstrumentation().targetContext.getSystemService<PowerManager>()).apply {
|
||||||
|
setIsWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.createActiveCallManager(
|
private fun CoroutineScope.createActiveCallManager(
|
||||||
matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
|
matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
|
||||||
onMissedCallNotificationHandler: FakeOnMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(),
|
onMissedCallNotificationHandler: FakeOnMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(),
|
||||||
notificationManagerCompat: NotificationManagerCompat = mockk(relaxed = true),
|
notificationManagerCompat: NotificationManagerCompat = mockk(relaxed = true),
|
||||||
coroutineScope: CoroutineScope = this,
|
coroutineScope: CoroutineScope = this,
|
||||||
) = DefaultActiveCallManager(
|
) = DefaultActiveCallManager(
|
||||||
|
context = InstrumentationRegistry.getInstrumentation().targetContext,
|
||||||
coroutineScope = coroutineScope,
|
coroutineScope = coroutineScope,
|
||||||
onMissedCallNotificationHandler = onMissedCallNotificationHandler,
|
onMissedCallNotificationHandler = onMissedCallNotificationHandler,
|
||||||
ringingCallNotificationCreator = RingingCallNotificationCreator(
|
ringingCallNotificationCreator = RingingCallNotificationCreator(
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import io.element.android.features.call.api.CallType
|
||||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||||
import io.element.android.features.call.impl.utils.ActiveCall
|
import io.element.android.features.call.impl.utils.ActiveCall
|
||||||
import io.element.android.features.call.impl.utils.ActiveCallManager
|
import io.element.android.features.call.impl.utils.ActiveCallManager
|
||||||
|
import io.element.android.tests.testutils.simulateLongTask
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
class FakeActiveCallManager(
|
class FakeActiveCallManager(
|
||||||
|
|
@ -20,15 +21,15 @@ class FakeActiveCallManager(
|
||||||
) : ActiveCallManager {
|
) : ActiveCallManager {
|
||||||
override val activeCall = MutableStateFlow<ActiveCall?>(null)
|
override val activeCall = MutableStateFlow<ActiveCall?>(null)
|
||||||
|
|
||||||
override fun registerIncomingCall(notificationData: CallNotificationData) {
|
override suspend fun registerIncomingCall(notificationData: CallNotificationData) = simulateLongTask {
|
||||||
registerIncomingCallResult(notificationData)
|
registerIncomingCallResult(notificationData)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hungUpCall(callType: CallType) {
|
override suspend fun hungUpCall(callType: CallType) = simulateLongTask {
|
||||||
hungUpCallResult(callType)
|
hungUpCallResult(callType)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun joinedCall(callType: CallType) {
|
override suspend fun joinedCall(callType: CallType) = simulateLongTask {
|
||||||
joinedCallResult(callType)
|
joinedCallResult(callType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class FakeElementCallEntryPoint(
|
||||||
startCallResult(callType)
|
startCallResult(callType)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleIncomingCall(
|
override suspend fun handleIncomingCall(
|
||||||
callType: CallType.RoomCall,
|
callType: CallType.RoomCall,
|
||||||
eventId: EventId,
|
eventId: EventId,
|
||||||
senderId: UserId,
|
senderId: UserId,
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ class DefaultPushHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRingingCallEvent(notifiableEvent: NotifiableRingingCallEvent) {
|
private suspend fun handleRingingCallEvent(notifiableEvent: NotifiableRingingCallEvent) {
|
||||||
Timber.i("## handleInternal() : Incoming call.")
|
Timber.i("## handleInternal() : Incoming call.")
|
||||||
elementCallEntryPoint.handleIncomingCall(
|
elementCallEntryPoint.handleIncomingCall(
|
||||||
callType = CallType.RoomCall(notifiableEvent.sessionId, notifiableEvent.roomId),
|
callType = CallType.RoomCall(notifiableEvent.sessionId, notifiableEvent.roomId),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue