Merge branch 'develop' into feature/bma/testEntryPoint
This commit is contained in:
commit
b194153b66
50 changed files with 316 additions and 124 deletions
|
|
@ -96,4 +96,5 @@ dependencies {
|
|||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.services.appnavstate.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class DefaultElementCallEntryPoint(
|
|||
senderName: String?,
|
||||
avatarUrl: String?,
|
||||
timestamp: Long,
|
||||
expirationTimestamp: Long,
|
||||
notificationChannelId: String,
|
||||
textContent: String?,
|
||||
) {
|
||||
|
|
@ -55,6 +56,7 @@ class DefaultElementCallEntryPoint(
|
|||
senderName = senderName,
|
||||
avatarUrl = avatarUrl,
|
||||
timestamp = timestamp,
|
||||
expirationTimestamp = expirationTimestamp,
|
||||
notificationChannelId = notificationChannelId,
|
||||
textContent = textContent,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,4 +26,6 @@ data class CallNotificationData(
|
|||
val notificationChannelId: String,
|
||||
val timestamp: Long,
|
||||
val textContent: String?,
|
||||
// Expiration timestamp in millis since epoch
|
||||
val expirationTimestamp: Long,
|
||||
) : Parcelable
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ class RingingCallNotificationCreator(
|
|||
roomAvatarUrl: String?,
|
||||
notificationChannelId: String,
|
||||
timestamp: Long,
|
||||
expirationTimestamp: Long,
|
||||
textContent: String?,
|
||||
): Notification? {
|
||||
val matrixClient = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null
|
||||
|
|
@ -88,6 +89,7 @@ class RingingCallNotificationCreator(
|
|||
notificationChannelId = notificationChannelId,
|
||||
timestamp = timestamp,
|
||||
textContent = textContent,
|
||||
expirationTimestamp = expirationTimestamp,
|
||||
)
|
||||
|
||||
val declineIntent = PendingIntentCompat.getBroadcast(
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ internal fun IncomingCallScreenPreview() = ElementPreview {
|
|||
notificationChannelId = "incoming_call",
|
||||
timestamp = 0L,
|
||||
textContent = null,
|
||||
expirationTimestamp = 1000L,
|
||||
),
|
||||
onAnswer = {},
|
||||
onCancel = {},
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import io.element.android.libraries.push.api.notifications.ForegroundServiceType
|
|||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler
|
||||
import io.element.android.services.appnavstate.api.AppForegroundStateService
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -53,7 +54,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Manages the active call state.
|
||||
|
|
@ -98,6 +99,7 @@ class DefaultActiveCallManager(
|
|||
private val defaultCurrentCallService: DefaultCurrentCallService,
|
||||
private val appForegroundStateService: AppForegroundStateService,
|
||||
private val imageLoaderHolder: ImageLoaderHolder,
|
||||
private val systemClock: SystemClock,
|
||||
) : ActiveCallManager {
|
||||
private val tag = "DefaultActiveCallManager"
|
||||
private var timedOutCallJob: Job? = null
|
||||
|
|
@ -118,8 +120,20 @@ class DefaultActiveCallManager(
|
|||
|
||||
override suspend fun registerIncomingCall(notificationData: CallNotificationData) {
|
||||
mutex.withLock {
|
||||
val ringDuration =
|
||||
min(
|
||||
notificationData.expirationTimestamp - systemClock.epochMillis(),
|
||||
ElementCallConfig.RINGING_CALL_DURATION_SECONDS * 1000L
|
||||
)
|
||||
|
||||
if (ringDuration < 0) {
|
||||
// Should already have stopped ringing, ignore.
|
||||
Timber.tag(tag).d("Received timed-out incoming ringing call for room id: ${notificationData.roomId}, cancel ringing")
|
||||
return
|
||||
}
|
||||
|
||||
appForegroundStateService.updateHasRingingCall(true)
|
||||
Timber.tag(tag).d("Received incoming call for room id: ${notificationData.roomId}")
|
||||
Timber.tag(tag).d("Received incoming call for room id: ${notificationData.roomId}, ringDuration(ms): $ringDuration")
|
||||
if (activeCall.value != null) {
|
||||
displayMissedCallNotification(notificationData)
|
||||
Timber.tag(tag).w("Already have an active call, ignoring incoming call: $notificationData")
|
||||
|
|
@ -138,14 +152,14 @@ class DefaultActiveCallManager(
|
|||
showIncomingCallNotification(notificationData)
|
||||
|
||||
// Wait for the ringing call to time out
|
||||
delay(ElementCallConfig.RINGING_CALL_DURATION_SECONDS.seconds)
|
||||
delay(timeMillis = ringDuration)
|
||||
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) {
|
||||
Timber.tag(tag).d("Acquiring partial wakelock")
|
||||
activeWakeLock.acquire(ElementCallConfig.RINGING_CALL_DURATION_SECONDS * 1000L)
|
||||
activeWakeLock.acquire(ringDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -236,6 +250,7 @@ class DefaultActiveCallManager(
|
|||
notificationChannelId = notificationData.notificationChannelId,
|
||||
timestamp = notificationData.timestamp,
|
||||
textContent = notificationData.textContent,
|
||||
expirationTimestamp = notificationData.expirationTimestamp,
|
||||
) ?: return
|
||||
runCatchingExceptions {
|
||||
notificationManagerCompat.notify(
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class DefaultElementCallEntryPointTest {
|
|||
senderName = "senderName",
|
||||
avatarUrl = "avatarUrl",
|
||||
timestamp = 0,
|
||||
expirationTimestamp = 0,
|
||||
notificationChannelId = "notificationChannelId",
|
||||
textContent = "textContent",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ class RingingCallNotificationCreatorTest {
|
|||
roomAvatarUrl = "https://example.com/avatar.jpg",
|
||||
notificationChannelId = "channelId",
|
||||
timestamp = 0L,
|
||||
expirationTimestamp = 20L,
|
||||
textContent = "textContent",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolde
|
|||
import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler
|
||||
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
|
||||
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
||||
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
|
||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.plantTestTimber
|
||||
|
|
@ -368,6 +370,83 @@ class DefaultActiveCallManagerTest {
|
|||
assertThat(manager.activeCall.value).isNotNull()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `IncomingCall - rings no longer than expiration time`() = runTest {
|
||||
setupShadowPowerManager()
|
||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||
val clock = FakeSystemClock()
|
||||
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat, systemClock = clock)
|
||||
|
||||
assertThat(manager.activeWakeLock?.isHeld).isFalse()
|
||||
assertThat(manager.activeCall.value).isNull()
|
||||
|
||||
val eventTimestamp = A_FAKE_TIMESTAMP
|
||||
// The call should not ring more than 30 seconds after the initial event was sent
|
||||
val expirationTimestamp = eventTimestamp + 30_000
|
||||
|
||||
val callNotificationData = aCallNotificationData(
|
||||
timestamp = eventTimestamp,
|
||||
expirationTimestamp = expirationTimestamp,
|
||||
)
|
||||
|
||||
// suppose it took 10s to be notified
|
||||
clock.epochMillisResult = eventTimestamp + 10_000
|
||||
manager.registerIncomingCall(callNotificationData)
|
||||
|
||||
assertThat(manager.activeCall.value).isEqualTo(
|
||||
ActiveCall(
|
||||
callType = CallType.RoomCall(
|
||||
sessionId = callNotificationData.sessionId,
|
||||
roomId = callNotificationData.roomId,
|
||||
),
|
||||
callState = CallState.Ringing(callNotificationData)
|
||||
)
|
||||
)
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||
verify { notificationManagerCompat.notify(notificationId, any()) }
|
||||
|
||||
// advance by 21s it should have stopped ringing
|
||||
advanceTimeBy(21_000)
|
||||
runCurrent()
|
||||
|
||||
verify { notificationManagerCompat.cancel(any()) }
|
||||
}
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `IncomingCall - ignore expired ring lifetime`() = runTest {
|
||||
setupShadowPowerManager()
|
||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||
val clock = FakeSystemClock()
|
||||
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat, systemClock = clock)
|
||||
|
||||
assertThat(manager.activeWakeLock?.isHeld).isFalse()
|
||||
assertThat(manager.activeCall.value).isNull()
|
||||
|
||||
val eventTimestamp = A_FAKE_TIMESTAMP
|
||||
// The call should not ring more than 30 seconds after the initial event was sent
|
||||
val expirationTimestamp = eventTimestamp + 30_000
|
||||
|
||||
val callNotificationData = aCallNotificationData(
|
||||
timestamp = eventTimestamp,
|
||||
expirationTimestamp = expirationTimestamp,
|
||||
)
|
||||
|
||||
// suppose it took 35s to be notified
|
||||
clock.epochMillisResult = eventTimestamp + 35_000
|
||||
manager.registerIncomingCall(callNotificationData)
|
||||
|
||||
assertThat(manager.activeCall.value).isNull()
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(manager.activeWakeLock?.isHeld).isFalse()
|
||||
verify(exactly = 0) { notificationManagerCompat.notify(notificationId, any()) }
|
||||
}
|
||||
|
||||
private fun setupShadowPowerManager() {
|
||||
shadowOf(InstrumentationRegistry.getInstrumentation().targetContext.getSystemService<PowerManager>()).apply {
|
||||
setIsWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK, true)
|
||||
|
|
@ -378,6 +457,7 @@ class DefaultActiveCallManagerTest {
|
|||
matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
|
||||
onMissedCallNotificationHandler: FakeOnMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(),
|
||||
notificationManagerCompat: NotificationManagerCompat = mockk(relaxed = true),
|
||||
systemClock: FakeSystemClock = FakeSystemClock(),
|
||||
) = DefaultActiveCallManager(
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext,
|
||||
coroutineScope = backgroundScope,
|
||||
|
|
@ -393,5 +473,6 @@ class DefaultActiveCallManagerTest {
|
|||
defaultCurrentCallService = DefaultCurrentCallService(),
|
||||
appForegroundStateService = FakeAppForegroundStateService(),
|
||||
imageLoaderHolder = FakeImageLoaderHolder(),
|
||||
systemClock = systemClock,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue