diff --git a/features/call/impl/src/main/AndroidManifest.xml b/features/call/impl/src/main/AndroidManifest.xml
index 8337a45b69..8049f66294 100644
--- a/features/call/impl/src/main/AndroidManifest.xml
+++ b/features/call/impl/src/main/AndroidManifest.xml
@@ -21,8 +21,8 @@
-
-
+
+
@@ -80,7 +80,7 @@
android:name=".services.CallForegroundService"
android:enabled="true"
android:exported="false"
- android:foregroundServiceType="phoneCall" />
+ android:foregroundServiceType="microphone" />
= Build.VERSION_CODES.Q) {
- ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
+ val serviceType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
} else {
0
}
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt
index 3a4a31f190..918acc1200 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt
@@ -180,6 +180,7 @@ class CallScreenPresenter @AssistedInject constructor(
urlState = urlState.value,
webViewError = webViewError,
userAgent = userAgent,
+ isCallActive = isJoinedCall,
isInWidgetMode = isInWidgetMode,
eventSink = { handleEvents(it) },
)
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt
index 48a4672e1a..7da23f24ff 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt
@@ -13,6 +13,7 @@ data class CallScreenState(
val urlState: AsyncData,
val webViewError: String?,
val userAgent: String,
+ val isCallActive: Boolean,
val isInWidgetMode: Boolean,
val eventSink: (CallScreenEvents) -> Unit,
)
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt
index bb4794749d..f891c4c526 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt
@@ -24,6 +24,7 @@ internal fun aCallScreenState(
urlState: AsyncData = AsyncData.Success("https://call.element.io/some-actual-call?with=parameters"),
webViewError: String? = null,
userAgent: String = "",
+ isCallActive: Boolean = true,
isInWidgetMode: Boolean = false,
eventSink: (CallScreenEvents) -> Unit = {},
): CallScreenState {
@@ -31,6 +32,7 @@ internal fun aCallScreenState(
urlState = urlState,
webViewError = webViewError,
userAgent = userAgent,
+ isCallActive = isCallActive,
isInWidgetMode = isInWidgetMode,
eventSink = eventSink,
)
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt
index b55c39f41b..a685ef7b9a 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt
@@ -26,6 +26,7 @@ import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberUpdatedState
@@ -95,7 +96,6 @@ class ElementCallActivity :
pictureInPicturePresenter.setPipView(this)
audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
- requestAudioFocus()
setContent {
val pipState = pictureInPicturePresenter.present()
@@ -103,6 +103,12 @@ class ElementCallActivity :
ElementThemeApp(appPreferencesStore) {
val state = presenter.present()
eventSink = state.eventSink
+ LaunchedEffect(state.isCallActive, state.isInWidgetMode) {
+ // Note when not in WidgetMode, isCallActive will never be true, so consider the call is active
+ if (state.isCallActive || !state.isInWidgetMode) {
+ setCallIsActive()
+ }
+ }
CallScreenView(
state = state,
pipState = pipState,
@@ -115,6 +121,11 @@ class ElementCallActivity :
}
}
+ private fun setCallIsActive() {
+ requestAudioFocus()
+ CallForegroundService.start(this)
+ }
+
@Composable
private fun ListenToAndroidEvents(pipState: PictureInPictureState) {
val pipEventSink by rememberUpdatedState(pipState.eventSink)
@@ -156,18 +167,6 @@ class ElementCallActivity :
setCallType(intent)
}
- override fun onStart() {
- super.onStart()
- CallForegroundService.stop(this)
- }
-
- override fun onStop() {
- super.onStop()
- if (!isFinishing && !isChangingConfigurations) {
- CallForegroundService.start(this)
- }
- }
-
override fun onDestroy() {
super.onDestroy()
releaseAudioFocus()
@@ -231,10 +230,10 @@ class ElementCallActivity :
@Suppress("DEPRECATION")
private fun requestAudioFocus() {
- val audioAttributes = AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
- .build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val audioAttributes = AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+ .build()
val request = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(audioAttributes)
.build()
@@ -247,7 +246,6 @@ class ElementCallActivity :
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
)
-
audioFocusChangeListener = listener
}
}
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt
index f0e5708db3..d33f16b29e 100644
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt
+++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt
@@ -73,6 +73,7 @@ class CallScreenPresenterTest {
assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
assertThat(initialState.webViewError).isNull()
assertThat(initialState.isInWidgetMode).isFalse()
+ assertThat(initialState.isCallActive).isFalse()
analyticsLambda.assertions().isNeverCalled()
joinedCallLambda.assertions().isCalledOnce()
}
@@ -106,6 +107,7 @@ class CallScreenPresenterTest {
joinedCallLambda.assertions().isCalledOnce()
val initialState = awaitItem()
assertThat(initialState.urlState).isInstanceOf(AsyncData.Success::class.java)
+ assertThat(initialState.isCallActive).isFalse()
assertThat(initialState.isInWidgetMode).isTrue()
assertThat(widgetProvider.getWidgetCalled).isTrue()
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
@@ -203,6 +205,44 @@ class CallScreenPresenterTest {
}
}
+ @Test
+ fun `present - a received room member message makes the call to be active`() = runTest {
+ val navigator = FakeCallScreenNavigator()
+ val widgetDriver = FakeMatrixWidgetDriver()
+ val presenter = createCallScreenPresenter(
+ callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
+ widgetDriver = widgetDriver,
+ navigator = navigator,
+ dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
+ screenTracker = FakeScreenTracker {},
+ )
+ val messageInterceptor = FakeWidgetMessageInterceptor()
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ skipItems(1)
+ val initialState = awaitItem()
+ assertThat(initialState.isCallActive).isFalse()
+ initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
+ messageInterceptor.givenInterceptedMessage(
+ """
+ {
+ "action":"send_event",
+ "api":"fromWidget",
+ "widgetId":"1",
+ "requestId":"1",
+ "data":{
+ "type":"org.matrix.msc3401.call.member"
+ }
+ }
+ """.trimIndent()
+ )
+ skipItems(1)
+ val finalState = awaitItem()
+ assertThat(finalState.isCallActive).isTrue()
+ }
+ }
+
@Test
fun `present - automatically starts the Matrix client sync when on RoomCall`() = runTest {
val navigator = FakeCallScreenNavigator()