review + tests
This commit is contained in:
parent
ac7e1d6dc3
commit
2ecd4ecaf5
6 changed files with 88 additions and 1 deletions
|
|
@ -100,6 +100,7 @@ dependencies {
|
||||||
testImplementation(projects.libraries.push.test)
|
testImplementation(projects.libraries.push.test)
|
||||||
testImplementation(projects.services.analytics.test)
|
testImplementation(projects.services.analytics.test)
|
||||||
testImplementation(projects.services.appnavstate.test)
|
testImplementation(projects.services.appnavstate.test)
|
||||||
|
testImplementation(projects.services.toolbox.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
|
|
||||||
|
|
@ -26,5 +26,6 @@ data class CallNotificationData(
|
||||||
val notificationChannelId: String,
|
val notificationChannelId: String,
|
||||||
val timestamp: Long,
|
val timestamp: Long,
|
||||||
val textContent: String?,
|
val textContent: String?,
|
||||||
|
// Expiration timestamp in millis since epoch
|
||||||
val expirationTimestamp: Long,
|
val expirationTimestamp: Long,
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
|
||||||
|
|
@ -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.NotificationIdProvider
|
||||||
import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler
|
import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler
|
||||||
import io.element.android.services.appnavstate.api.AppForegroundStateService
|
import io.element.android.services.appnavstate.api.AppForegroundStateService
|
||||||
|
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
@ -98,6 +99,7 @@ class DefaultActiveCallManager(
|
||||||
private val defaultCurrentCallService: DefaultCurrentCallService,
|
private val defaultCurrentCallService: DefaultCurrentCallService,
|
||||||
private val appForegroundStateService: AppForegroundStateService,
|
private val appForegroundStateService: AppForegroundStateService,
|
||||||
private val imageLoaderHolder: ImageLoaderHolder,
|
private val imageLoaderHolder: ImageLoaderHolder,
|
||||||
|
private val systemClock: SystemClock,
|
||||||
) : ActiveCallManager {
|
) : ActiveCallManager {
|
||||||
private val tag = "DefaultActiveCallManager"
|
private val tag = "DefaultActiveCallManager"
|
||||||
private var timedOutCallJob: Job? = null
|
private var timedOutCallJob: Job? = null
|
||||||
|
|
@ -120,7 +122,7 @@ class DefaultActiveCallManager(
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
val ringDuration =
|
val ringDuration =
|
||||||
min(
|
min(
|
||||||
notificationData.expirationTimestamp - System.currentTimeMillis(),
|
notificationData.expirationTimestamp - systemClock.epochMillis(),
|
||||||
ElementCallConfig.RINGING_CALL_DURATION_SECONDS * 1000L
|
ElementCallConfig.RINGING_CALL_DURATION_SECONDS * 1000L
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ class DefaultElementCallEntryPointTest {
|
||||||
senderName = "senderName",
|
senderName = "senderName",
|
||||||
avatarUrl = "avatarUrl",
|
avatarUrl = "avatarUrl",
|
||||||
timestamp = 0,
|
timestamp = 0,
|
||||||
|
expirationTimestamp = 0,
|
||||||
notificationChannelId = "notificationChannelId",
|
notificationChannelId = "notificationChannelId",
|
||||||
textContent = "textContent",
|
textContent = "textContent",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ class RingingCallNotificationCreatorTest {
|
||||||
roomAvatarUrl = "https://example.com/avatar.jpg",
|
roomAvatarUrl = "https://example.com/avatar.jpg",
|
||||||
notificationChannelId = "channelId",
|
notificationChannelId = "channelId",
|
||||||
timestamp = 0L,
|
timestamp = 0L,
|
||||||
|
expirationTimestamp = 20L,
|
||||||
textContent = "textContent",
|
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.FakeOnMissedCallNotificationHandler
|
||||||
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
|
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
|
||||||
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
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.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.plantTestTimber
|
import io.element.android.tests.testutils.plantTestTimber
|
||||||
|
|
@ -368,6 +370,83 @@ class DefaultActiveCallManagerTest {
|
||||||
assertThat(manager.activeCall.value).isNotNull()
|
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() {
|
private fun setupShadowPowerManager() {
|
||||||
shadowOf(InstrumentationRegistry.getInstrumentation().targetContext.getSystemService<PowerManager>()).apply {
|
shadowOf(InstrumentationRegistry.getInstrumentation().targetContext.getSystemService<PowerManager>()).apply {
|
||||||
setIsWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK, true)
|
setIsWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK, true)
|
||||||
|
|
@ -378,6 +457,7 @@ class DefaultActiveCallManagerTest {
|
||||||
matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
|
matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
|
||||||
onMissedCallNotificationHandler: FakeOnMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(),
|
onMissedCallNotificationHandler: FakeOnMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(),
|
||||||
notificationManagerCompat: NotificationManagerCompat = mockk(relaxed = true),
|
notificationManagerCompat: NotificationManagerCompat = mockk(relaxed = true),
|
||||||
|
systemClock: FakeSystemClock = FakeSystemClock(),
|
||||||
) = DefaultActiveCallManager(
|
) = DefaultActiveCallManager(
|
||||||
context = InstrumentationRegistry.getInstrumentation().targetContext,
|
context = InstrumentationRegistry.getInstrumentation().targetContext,
|
||||||
coroutineScope = backgroundScope,
|
coroutineScope = backgroundScope,
|
||||||
|
|
@ -393,5 +473,6 @@ class DefaultActiveCallManagerTest {
|
||||||
defaultCurrentCallService = DefaultCurrentCallService(),
|
defaultCurrentCallService = DefaultCurrentCallService(),
|
||||||
appForegroundStateService = FakeAppForegroundStateService(),
|
appForegroundStateService = FakeAppForegroundStateService(),
|
||||||
imageLoaderHolder = FakeImageLoaderHolder(),
|
imageLoaderHolder = FakeImageLoaderHolder(),
|
||||||
|
systemClock = systemClock,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue