diff --git a/tools/chatgui-android/app/src/main/AndroidManifest.xml b/tools/chatgui-android/app/src/main/AndroidManifest.xml
index c04eadf9..def0d7da 100644
--- a/tools/chatgui-android/app/src/main/AndroidManifest.xml
+++ b/tools/chatgui-android/app/src/main/AndroidManifest.xml
@@ -82,7 +82,7 @@
+ android:foregroundServiceType="phoneCall|microphone|camera" />
diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt
index 8d2cec19..121dfec6 100644
--- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt
+++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt
@@ -24,6 +24,14 @@ object CallController : TelecomCallManager.Listener {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
private val _state = MutableStateFlow(null)
val state: StateFlow = _state
+ private val _video = MutableStateFlow(null)
+ val video: StateFlow = _video
+ fun setVideo(settings: CallVideoSettings) {
+ checkMain()
+ val owner = service ?: return
+ if (_state.value?.phase != CallPhase.ACTIVE) return
+ if (owner.setVideoCapture(settings.enabled)) owner.video?.select(settings)
+ }
private val _muted = MutableStateFlow(false)
val muted: StateFlow = _muted
private val _route = MutableStateFlow(AudioRoute.SPEAKER)
@@ -202,6 +210,7 @@ object CallController : TelecomCallManager.Listener {
acceptSent = false
answerWhenVisible = 0L
owner?.shutdown()
+ _video.value = null
proximity.stop()
TelecomCallManager.setDisconnected(id, reason)
TelecomCallManager.removeConnection(id)
@@ -327,6 +336,7 @@ object CallController : TelecomCallManager.Listener {
return
}
accepting = false
+ _video.value = owner.video
TelecomCallManager.setActive(id)
_state.value = st.copy(phase = CallPhase.ACTIVE, durationSeconds = 0, startedAtElapsedRealtime = SystemClock.elapsedRealtime())
owner.audio.router.onChanged = { route, headset -> routeChanged(route, headset) }
diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallService.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallService.kt
index fb31adee..d7cf377b 100644
--- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallService.kt
+++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallService.kt
@@ -43,6 +43,9 @@ class CallService : Service() {
}
val audio = CallAudioEngine()
+ var video: CallVideoEngine? = null
+ private set
+ private var cameraForeground = false
private var callId = 0L
private var wakeLock: PowerManager.WakeLock? = null
private var powerEventsRegistered = false
@@ -137,7 +140,8 @@ class CallService : Service() {
val micType = if (microphone && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE else 0
val types = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL or micType
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL or micType or
+ (if (cameraForeground && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA else 0)
} else 0
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification(), types)
LogManager.addLog("INFO", "CallService", "foreground notification id=$callId phase=${CallController.state.value?.phase}")
@@ -149,7 +153,14 @@ class CallService : Service() {
return false
}
audio.router.setPreferredRoute(ChatApplication.instance.configProvider.getCallAudioRoute())
- return audio.start(id)
+ if (!audio.start(id)) return false
+ video = CallVideoEngine(this, id)
+ return true
+ }
+
+ fun setVideoCapture(enabled: Boolean): Boolean {
+ return try { cameraForeground = enabled; promote(true); true }
+ catch (e: Exception) { cameraForeground = false; LogManager.addLog("ERROR", "call", "camera foreground failed: ${e.message}"); false }
}
fun updateNotification() {
@@ -202,6 +213,7 @@ class CallService : Service() {
private fun releaseResources() {
val id = callId
+ video?.stop(); video = null; cameraForeground = false
if (powerEventsRegistered) {
try { unregisterReceiver(powerEvents) }
catch (e: Exception) { LogManager.addLog("WARN", "CallService", "power receiver cleanup failed id=$id: ${e.message}") }