Skip to content

Commit e6c6ad9

Browse files
committed
Start the outgoing call service in joinAndRing and cover its notification
The caller had no outgoing call notification in the join-and-ring flow: the notification is posted by the foreground service started with TRIGGER_OUTGOING_CALL, which only registerOutgoingRing() starts, and only the create-with-ring path called it. joinAndRing only called markRinging(), so no service and no notification (setActiveCall logs 'Outgoing call service should already be running'). On develop this was sometimes masked when the ringing state flapped to Idle at setActiveCall time and the ongoing service started instead; with the deterministic Outgoing state it never rendered. - joinAndRing now calls registerOutgoingRing() on ring success, which registers the ringing call exactly like markRinging() and also starts the outgoing call service, mirroring the create-with-ring path. - The outgoing ringing E2E test asserts the notification both ways: shown while the outgoing screen is up, gone after the decline. The check reads NotificationManager.activeNotifications in the app process and matches the notification title, because the outgoing screen shows the same 'Calling...' text in the shade and the notification is posted on the ongoing calls channel. - CallJoinCoordinatorTest verifies registerOutgoingRing on ring success. Verified locally on an API 35 emulator through the real fastlane flow: the test fails at the notification assert with the old markRinging() code and passes with the fix.
1 parent 0c3db2c commit e6c6ad9

4 files changed

Lines changed: 42 additions & 1 deletion

File tree

demo-app/src/androidTestE2etestingDebug/kotlin/io/getstream/video/android/robots/UserRobotCallAsserts.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616

1717
package io.getstream.video.android.robots
1818

19+
import android.app.Notification
20+
import android.app.NotificationManager
1921
import androidx.test.uiautomator.BySelector
2022
import io.getstream.video.android.pages.CallPage
2123
import io.getstream.video.android.pages.CallPage.SettingsMenu
2224
import io.getstream.video.android.pages.RingPage
2325
import io.getstream.video.android.robots.UserControls.DISABLE
2426
import io.getstream.video.android.robots.UserControls.ENABLE
27+
import io.getstream.video.android.uiautomator.appContext
2528
import io.getstream.video.android.uiautomator.defaultTimeout
2629
import io.getstream.video.android.uiautomator.device
2730
import io.getstream.video.android.uiautomator.findObject
@@ -305,6 +308,30 @@ fun UserRobot.assertOutgoingCall(audioOnly: Boolean = true, isDisplayed: Boolean
305308
return this
306309
}
307310

311+
/**
312+
* Asserts the presence of the outgoing call notification, which the outgoing call foreground
313+
* service posts with the "Calling..." title (on the ongoing calls channel, see
314+
* getSimpleOngoingCallNotification). The instrumentation runs inside the app process, so the
315+
* check reads NotificationManager.activeNotifications directly instead of matching text in
316+
* the notification shade, where the outgoing screen shows the same "Calling..." text.
317+
* The service start and stop are asynchronous, so both directions poll.
318+
*/
319+
fun UserRobot.assertOutgoingCallNotification(isDisplayed: Boolean): UserRobot {
320+
val title = appContext.getString(
321+
io.getstream.video.android.core.R.string.stream_video_outgoing_call_notification_title,
322+
)
323+
val notificationManager = appContext.getSystemService(NotificationManager::class.java)
324+
fun displayed() = notificationManager.activeNotifications.any {
325+
it.notification.extras.getCharSequence(Notification.EXTRA_TITLE)?.toString() == title
326+
}
327+
val endTime = System.currentTimeMillis() + defaultTimeout
328+
while (displayed() != isDisplayed && System.currentTimeMillis() < endTime) {
329+
Thread.sleep(250)
330+
}
331+
assertEquals("Outgoing call notification displayed", isDisplayed, displayed())
332+
return this
333+
}
334+
308335
fun UserRobot.assertConnectingView(): UserRobot {
309336
assertEquals("Connecting...", RingPage.callProgressBar.waitToAppear().text)
310337
// Connecting covers the same call join round-trip as waitForCallToStart, which can

demo-app/src/androidTestE2etestingDebug/kotlin/io/getstream/video/android/tests/RingingTests.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import io.getstream.video.android.robots.assertAudioCallControls
2121
import io.getstream.video.android.robots.assertConnectingView
2222
import io.getstream.video.android.robots.assertIncomingCall
2323
import io.getstream.video.android.robots.assertOutgoingCall
24+
import io.getstream.video.android.robots.assertOutgoingCallNotification
2425
import io.getstream.video.android.robots.assertThatCallIsEnded
2526
import io.getstream.video.android.robots.assertVideoCallControls
2627
import io.qameta.allure.kotlin.Allure.step
@@ -80,12 +81,18 @@ class RingingTests : StreamTestCase() {
8081
step("THEN the outgoing call starts") {
8182
userRobot.assertOutgoingCall(audioOnly = true, isDisplayed = true)
8283
}
84+
step("AND the outgoing call notification is displayed") {
85+
userRobot.assertOutgoingCallNotification(isDisplayed = true)
86+
}
8387
step("WHEN user rejects the outgoing call") {
8488
userRobot.declineOutgoingCall()
8589
}
8690
step("THEN the outgoing call ends") {
8791
userRobot.assertOutgoingCall(isDisplayed = false)
8892
}
93+
step("AND the outgoing call notification is dismissed") {
94+
userRobot.assertOutgoingCallNotification(isDisplayed = false)
95+
}
8996
}
9097

9198
@AllureId("7776")

stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/components/CallJoinCoordinator.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,11 @@ internal class CallJoinCoordinator(
327327
logger.d { "[joinAndRing] Joined #ringing; #track; ring: $members" }
328328
apiClient.ring(RingCallRequest(isVideoEnabled(), members)).map {
329329
logger.d { "[joinAndRing] Ringed #ringing; #track; ring: $members" }
330-
callRegistry.markRinging()
330+
// registerOutgoingRing registers the ringing call AND starts the outgoing call
331+
// foreground service, like the create-with-ring path does. markRinging alone
332+
// never started the service here, so the caller had no outgoing notification
333+
// (setActiveCall logs "Outgoing call service should already be running").
334+
callRegistry.registerOutgoingRing()
331335
// An event that arrived before the ring completed (e.g. call.session_started)
332336
// computed the ringing state without the ringing call registered. Recompute so
333337
// the state cannot stay Idle when no further coordinator event arrives.

stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/call/components/CallJoinCoordinatorTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,9 @@ class CallJoinCoordinatorTest {
677677

678678
assertThat(result).isInstanceOf(Success::class.java)
679679
coVerify { apiClient.ring(any<RingCallRequest>()) }
680+
// registerOutgoingRing (not markRinging) so the outgoing call foreground service
681+
// starts and the caller gets the outgoing call notification, like create-with-ring.
682+
verify { callRegistry.registerOutgoingRing() }
680683
}
681684

682685
@Test

0 commit comments

Comments
 (0)