88package io.element.android.features.call.impl.ui
99
1010import android.annotation.SuppressLint
11- import android.content.Context
12- import android.media.AudioDeviceCallback
13- import android.media.AudioDeviceInfo
14- import android.media.AudioManager
1511import android.util.Log
1612import android.view.ViewGroup
1713import android.webkit.ConsoleMessage
@@ -28,24 +24,23 @@ import androidx.compose.runtime.Composable
2824import androidx.compose.runtime.getValue
2925import androidx.compose.runtime.mutableStateOf
3026import androidx.compose.runtime.remember
27+ import androidx.compose.runtime.rememberCoroutineScope
3128import androidx.compose.runtime.setValue
3229import androidx.compose.ui.Alignment
3330import androidx.compose.ui.Modifier
3431import androidx.compose.ui.platform.LocalInspectionMode
3532import androidx.compose.ui.res.stringResource
3633import androidx.compose.ui.tooling.preview.PreviewParameter
3734import androidx.compose.ui.viewinterop.AndroidView
38- import androidx.core.content.getSystemService
3935import io.element.android.compound.tokens.generated.CompoundIcons
4036import io.element.android.features.call.impl.R
4137import io.element.android.features.call.impl.pip.PictureInPictureEvents
4238import io.element.android.features.call.impl.pip.PictureInPictureState
4339import io.element.android.features.call.impl.pip.PictureInPictureStateProvider
4440import io.element.android.features.call.impl.pip.aPictureInPictureState
41+ import io.element.android.features.call.impl.utils.WebViewAudioManager
4542import io.element.android.features.call.impl.utils.WebViewPipController
4643import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor
47- import io.element.android.libraries.androidutils.compat.disableExternalAudioDevice
48- import io.element.android.libraries.androidutils.compat.enableExternalAudioDevice
4944import io.element.android.libraries.architecture.AsyncData
5045import io.element.android.libraries.designsystem.components.ProgressDialog
5146import io.element.android.libraries.designsystem.components.button.BackButton
@@ -108,6 +103,8 @@ internal fun CallScreenView(
108103 onSubmit = { state.eventSink(CallScreenEvents .Hangup ) },
109104 )
110105 } else {
106+ var webViewAudioManager by remember { mutableStateOf<WebViewAudioManager ?>(null ) }
107+ val coroutineScope = rememberCoroutineScope()
111108 CallWebView (
112109 modifier = Modifier
113110 .padding(padding)
@@ -120,14 +117,27 @@ internal fun CallScreenView(
120117 val callback: RequestPermissionCallback = { request.grant(it) }
121118 requestPermissions(androidPermissions.toTypedArray(), callback)
122119 },
123- onWebViewCreate = { webView ->
120+ onCreateWebView = { webView ->
124121 val interceptor = WebViewWidgetMessageInterceptor (
125122 webView = webView,
123+ onUrlLoaded = { url ->
124+ if (webViewAudioManager?.isInCallMode?.get() == false ) {
125+ Timber .d(" URL $url is loaded, starting in-call audio mode" )
126+ webViewAudioManager?.onCallStarted()
127+ } else {
128+ Timber .d(" Can't start in-call audio mode since the app is already in it." )
129+ }
130+ },
126131 onError = { state.eventSink(CallScreenEvents .OnWebViewError (it)) },
127132 )
133+ webViewAudioManager = WebViewAudioManager (webView, coroutineScope)
128134 state.eventSink(CallScreenEvents .SetupMessageChannels (interceptor))
129135 val pipController = WebViewPipController (webView)
130136 pipState.eventSink(PictureInPictureEvents .SetPipController (pipController))
137+ },
138+ onDestroyWebView = {
139+ // Reset audio mode
140+ webViewAudioManager?.onCallStopped()
131141 }
132142 )
133143 when (state.urlState) {
@@ -152,21 +162,20 @@ private fun CallWebView(
152162 url : AsyncData <String >,
153163 userAgent : String ,
154164 onPermissionsRequest : (PermissionRequest ) -> Unit ,
155- onWebViewCreate : (WebView ) -> Unit ,
165+ onCreateWebView : (WebView ) -> Unit ,
166+ onDestroyWebView : (WebView ) -> Unit ,
156167 modifier : Modifier = Modifier ,
157168) {
158169 if (LocalInspectionMode .current) {
159170 Box (modifier = modifier, contentAlignment = Alignment .Center ) {
160171 Text (" WebView - can't be previewed" )
161172 }
162173 } else {
163- var audioDeviceCallback: AudioDeviceCallback ? by remember { mutableStateOf(null ) }
164174 AndroidView (
165175 modifier = modifier,
166176 factory = { context ->
167- audioDeviceCallback = context.setupAudioConfiguration()
168177 WebView (context).apply {
169- onWebViewCreate (this )
178+ onCreateWebView (this )
170179 setup(userAgent, onPermissionsRequest)
171180 }
172181 },
@@ -176,41 +185,13 @@ private fun CallWebView(
176185 }
177186 },
178187 onRelease = { webView ->
179- // Reset audio mode
180- webView.context.releaseAudioConfiguration(audioDeviceCallback)
188+ onDestroyWebView(webView)
181189 webView.destroy()
182190 }
183191 )
184192 }
185193}
186194
187- private fun Context.setupAudioConfiguration (): AudioDeviceCallback ? {
188- val audioManager = getSystemService<AudioManager >() ? : return null
189- // Set 'voice call' mode so volume keys actually control the call volume
190- audioManager.mode = AudioManager .MODE_IN_COMMUNICATION
191- audioManager.enableExternalAudioDevice()
192- return object : AudioDeviceCallback () {
193- override fun onAudioDevicesAdded (addedDevices : Array <out AudioDeviceInfo >? ) {
194- Timber .d(" Audio devices added" )
195- audioManager.enableExternalAudioDevice()
196- }
197-
198- override fun onAudioDevicesRemoved (removedDevices : Array <out AudioDeviceInfo >? ) {
199- Timber .d(" Audio devices removed" )
200- audioManager.enableExternalAudioDevice()
201- }
202- }.also {
203- audioManager.registerAudioDeviceCallback(it, null )
204- }
205- }
206-
207- private fun Context.releaseAudioConfiguration (audioDeviceCallback : AudioDeviceCallback ? ) {
208- val audioManager = getSystemService<AudioManager >() ? : return
209- audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
210- audioManager.disableExternalAudioDevice()
211- audioManager.mode = AudioManager .MODE_NORMAL
212- }
213-
214195@SuppressLint(" SetJavaScriptEnabled" )
215196private fun WebView.setup (
216197 userAgent : String ,
0 commit comments