mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-24 10:55:33 +00:00
android, desktop: landscape calls on Android and better local camera ratio management (#5124)
* android, desktop: landscape calls on Android and better local camera ratio management The main thing is that now when exiting from CallActivity while in call audio devices are not reset to default. It allows to have landscape mode enabled * styles * fix changing calls
This commit is contained in:
committed by
GitHub
parent
7d6c7c58d7
commit
307211a47f
+6
-2
@@ -71,8 +71,12 @@ class PostSCallAudioDeviceManager: CallAudioDeviceManagerInterface {
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
am.unregisterAudioDeviceCallback(audioCallback)
|
||||
am.removeOnCommunicationDeviceChangedListener(listener)
|
||||
try {
|
||||
am.unregisterAudioDeviceCallback(audioCallback)
|
||||
am.removeOnCommunicationDeviceChangedListener(listener)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun selectLastExternalDeviceOrDefault(speaker: Boolean, keepAnyExternal: Boolean) {
|
||||
|
||||
+62
-51
@@ -6,12 +6,12 @@ import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.*
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.media.*
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.os.PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK
|
||||
import android.os.PowerManager.WakeLock
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.*
|
||||
@@ -23,7 +23,6 @@ import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -47,7 +46,6 @@ import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.onboarding.OnboardingStage
|
||||
import chat.simplex.res.MR
|
||||
import com.google.accompanist.permissions.*
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
@@ -58,6 +56,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.serialization.encodeToString
|
||||
import java.io.Closeable
|
||||
|
||||
// Should be destroy()'ed and set as null when call is ended. Otherwise, it will be a leak
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@@ -72,49 +71,62 @@ fun activeCallDestroyWebView() = withApi {
|
||||
Log.d(TAG, "CallView: webview was destroyed")
|
||||
}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Composable
|
||||
actual fun ActiveCallView() {
|
||||
val call = remember { chatModel.activeCall }.value
|
||||
val scope = rememberCoroutineScope()
|
||||
val proximityLock = remember {
|
||||
class ActiveCallState: Closeable {
|
||||
val proximityLock: WakeLock? = screenOffWakeLock()
|
||||
var wasConnected = false
|
||||
val callAudioDeviceManager = CallAudioDeviceManagerInterface.new()
|
||||
private var closed = false
|
||||
|
||||
init {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
callAudioDeviceManager.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (closed) return
|
||||
closed = true
|
||||
CallSoundsPlayer.stop()
|
||||
if (wasConnected) {
|
||||
CallSoundsPlayer.vibrate()
|
||||
}
|
||||
callAudioDeviceManager.stop()
|
||||
dropAudioManagerOverrides()
|
||||
if (proximityLock?.isHeld == true) {
|
||||
proximityLock.release()
|
||||
}
|
||||
}
|
||||
|
||||
private fun screenOffWakeLock(): WakeLock? {
|
||||
val pm = (androidAppContext.getSystemService(Context.POWER_SERVICE) as PowerManager)
|
||||
if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
|
||||
return if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
|
||||
pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, androidAppContext.packageName + ":proximityLock")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
val wasConnected = rememberSaveable { mutableStateOf(false) }
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Composable
|
||||
actual fun ActiveCallView() {
|
||||
val call = remember { chatModel.activeCall }.value
|
||||
val callState = call?.androidCallState as ActiveCallState?
|
||||
val scope = rememberCoroutineScope()
|
||||
LaunchedEffect(call) {
|
||||
if (call?.callState == CallState.Connected && !wasConnected.value) {
|
||||
if (call?.callState == CallState.Connected && callState != null && !callState.wasConnected) {
|
||||
CallSoundsPlayer.vibrate(2)
|
||||
wasConnected.value = true
|
||||
callState.wasConnected = true
|
||||
}
|
||||
}
|
||||
val callAudioDeviceManager = remember { CallAudioDeviceManagerInterface.new() }
|
||||
DisposableEffect(Unit) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
callAudioDeviceManager.start()
|
||||
}
|
||||
onDispose {
|
||||
CallSoundsPlayer.stop()
|
||||
if (wasConnected.value) {
|
||||
CallSoundsPlayer.vibrate()
|
||||
}
|
||||
callAudioDeviceManager.stop()
|
||||
dropAudioManagerOverrides()
|
||||
if (proximityLock?.isHeld == true) {
|
||||
proximityLock.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(chatModel.activeCallViewIsCollapsed.value) {
|
||||
LaunchedEffect(callState, chatModel.activeCallViewIsCollapsed.value) {
|
||||
callState ?: return@LaunchedEffect
|
||||
if (chatModel.activeCallViewIsCollapsed.value) {
|
||||
if (proximityLock?.isHeld == true) proximityLock.release()
|
||||
if (callState.proximityLock?.isHeld == true) callState.proximityLock.release()
|
||||
} else {
|
||||
delay(1000)
|
||||
if (proximityLock?.isHeld == false) proximityLock.acquire()
|
||||
if (callState.proximityLock?.isHeld == false) callState.proximityLock.acquire()
|
||||
}
|
||||
}
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
@@ -122,6 +134,7 @@ actual fun ActiveCallView() {
|
||||
Log.d(TAG, "received from WebRTCView: $apiMsg")
|
||||
val call = chatModel.activeCall.value
|
||||
if (call != null) {
|
||||
val callState = call.androidCallState as ActiveCallState
|
||||
Log.d(TAG, "has active call $call")
|
||||
val callRh = call.remoteHostId
|
||||
when (val r = apiMsg.resp) {
|
||||
@@ -131,9 +144,9 @@ actual fun ActiveCallView() {
|
||||
updateActiveCall(call) { it.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities) }
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
// Starting is delayed to make Android <= 11 working good with Bluetooth
|
||||
callAudioDeviceManager.start()
|
||||
callState.callAudioDeviceManager.start()
|
||||
} else {
|
||||
callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true)
|
||||
callState.callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true)
|
||||
}
|
||||
CallSoundsPlayer.startConnectingCallSound(scope)
|
||||
activeCallWaitDeliveryReceipt(scope)
|
||||
@@ -143,9 +156,9 @@ actual fun ActiveCallView() {
|
||||
updateActiveCall(call) { it.copy(callState = CallState.OfferSent, localCapabilities = r.capabilities) }
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
// Starting is delayed to make Android <= 11 working good with Bluetooth
|
||||
callAudioDeviceManager.start()
|
||||
callState.callAudioDeviceManager.start()
|
||||
} else {
|
||||
callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true)
|
||||
callState.callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true)
|
||||
}
|
||||
}
|
||||
is WCallResponse.Answer -> withBGApi {
|
||||
@@ -228,14 +241,14 @@ actual fun ActiveCallView() {
|
||||
!chatModel.activeCallViewIsCollapsed.value -> true
|
||||
else -> false
|
||||
}
|
||||
if (call != null && showOverlay) {
|
||||
ActiveCallOverlay(call, chatModel, callAudioDeviceManager)
|
||||
if (call != null && showOverlay && callState != null) {
|
||||
ActiveCallOverlay(call, chatModel, callState.callAudioDeviceManager)
|
||||
}
|
||||
}
|
||||
KeyChangeEffect(call?.localMediaSources?.hasVideo) {
|
||||
if (call != null && call.hasVideo && callAudioDeviceManager.currentDevice.value?.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
|
||||
KeyChangeEffect(callState, call?.localMediaSources?.hasVideo) {
|
||||
if (call != null && call.hasVideo && callState != null && callState.callAudioDeviceManager.currentDevice.value?.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
|
||||
// enabling speaker on user action (peer action ignored) and not disabling it again
|
||||
callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true)
|
||||
callState.callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true)
|
||||
}
|
||||
}
|
||||
val context = LocalContext.current
|
||||
@@ -243,16 +256,12 @@ actual fun ActiveCallView() {
|
||||
val activity = context as? Activity ?: return@DisposableEffect onDispose {}
|
||||
val prevVolumeControlStream = activity.volumeControlStream
|
||||
activity.volumeControlStream = AudioManager.STREAM_VOICE_CALL
|
||||
// Lock orientation to portrait in order to have good experience with calls
|
||||
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
chatModel.activeCallViewIsVisible.value = true
|
||||
// After the first call, End command gets added to the list which prevents making another calls
|
||||
chatModel.callCommand.removeAll { it is WCallCommand.End }
|
||||
keepScreenOn(true)
|
||||
onDispose {
|
||||
activity.volumeControlStream = prevVolumeControlStream
|
||||
// Unlock orientation
|
||||
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
chatModel.activeCallViewIsVisible.value = false
|
||||
chatModel.callCommand.clear()
|
||||
keepScreenOn(false)
|
||||
@@ -264,8 +273,8 @@ actual fun ActiveCallView() {
|
||||
private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, callAudioDeviceManager: CallAudioDeviceManagerInterface) {
|
||||
ActiveCallOverlayLayout(
|
||||
call = call,
|
||||
devices = remember { callAudioDeviceManager.devices }.value,
|
||||
currentDevice = remember { callAudioDeviceManager.currentDevice },
|
||||
devices = remember(callAudioDeviceManager) { callAudioDeviceManager.devices }.value,
|
||||
currentDevice = remember(callAudioDeviceManager) { callAudioDeviceManager.currentDevice },
|
||||
dismiss = { withBGApi { chatModel.callManager.endCall(call) } },
|
||||
toggleAudio = { chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Mic, enable = !call.localMediaSources.mic)) },
|
||||
selectDevice = { callAudioDeviceManager.selectDevice(it.id) },
|
||||
@@ -832,7 +841,8 @@ fun PreviewActiveCallOverlayVideo() {
|
||||
connectionInfo = ConnectionInfo(
|
||||
RTCIceCandidate(RTCIceCandidateType.Host, "tcp"),
|
||||
RTCIceCandidate(RTCIceCandidateType.Host, "tcp")
|
||||
)
|
||||
),
|
||||
androidCallState = {}
|
||||
),
|
||||
devices = emptyList(),
|
||||
currentDevice = remember { mutableStateOf(null) },
|
||||
@@ -841,7 +851,7 @@ fun PreviewActiveCallOverlayVideo() {
|
||||
selectDevice = {},
|
||||
toggleVideo = {},
|
||||
toggleSound = {},
|
||||
flipCamera = {}
|
||||
flipCamera = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -862,7 +872,8 @@ fun PreviewActiveCallOverlayAudio() {
|
||||
connectionInfo = ConnectionInfo(
|
||||
RTCIceCandidate(RTCIceCandidateType.Host, "udp"),
|
||||
RTCIceCandidate(RTCIceCandidateType.Host, "udp")
|
||||
)
|
||||
),
|
||||
androidCallState = {}
|
||||
),
|
||||
devices = emptyList(),
|
||||
currentDevice = remember { mutableStateOf(null) },
|
||||
|
||||
Reference in New Issue
Block a user