Добавлен кастомный WebRTC AAR для CI + фиксы звонков
All checks were successful
Android Kernel Build / build (push) Successful in 18m55s
All checks were successful
Android Kernel Build / build (push) Successful in 18m55s
- libwebrtc-custom.aar закоммичен (был в .gitignore, CI использовал Maven без relative vtables → SIGSEGV) - Фикс ForegroundServiceDidNotStartInTimeException (safeStopForeground) - Фикс бесконечного "Exchanging keys" (ретрай KEY_EXCHANGE, auto-bind account) - Фикс "Unknown" при сбросе звонка (stop ForegroundService до сброса state) - Decline работает во всех фазах звонка
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
# Built application files
|
# Built application files
|
||||||
*.apk
|
*.apk
|
||||||
*.aar
|
# *.aar — кастомный WebRTC разрешён в app/libs/
|
||||||
|
!app/libs/*.aar
|
||||||
*.ap_
|
*.ap_
|
||||||
*.aab
|
*.aab
|
||||||
|
|
||||||
|
|||||||
BIN
app/libs/libwebrtc-custom.aar
Normal file
BIN
app/libs/libwebrtc-custom.aar
Normal file
Binary file not shown.
@@ -53,15 +53,13 @@ class CallForegroundService : Service() {
|
|||||||
when (action) {
|
when (action) {
|
||||||
ACTION_STOP -> {
|
ACTION_STOP -> {
|
||||||
notifLog("ACTION_STOP → stopSelf")
|
notifLog("ACTION_STOP → stopSelf")
|
||||||
stopForegroundCompat()
|
safeStopForeground()
|
||||||
stopSelf()
|
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
ACTION_END -> {
|
ACTION_END -> {
|
||||||
notifLog("ACTION_END → endCall")
|
notifLog("ACTION_END → endCall")
|
||||||
CallManager.endCall()
|
CallManager.endCall()
|
||||||
stopForegroundCompat()
|
safeStopForeground()
|
||||||
stopSelf()
|
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
ACTION_DECLINE -> {
|
ACTION_DECLINE -> {
|
||||||
@@ -70,11 +68,9 @@ class CallForegroundService : Service() {
|
|||||||
if (phase == CallPhase.INCOMING) {
|
if (phase == CallPhase.INCOMING) {
|
||||||
CallManager.declineIncomingCall()
|
CallManager.declineIncomingCall()
|
||||||
} else {
|
} else {
|
||||||
// Если звонок уже не в INCOMING (CONNECTING/ACTIVE) — endCall
|
|
||||||
CallManager.endCall()
|
CallManager.endCall()
|
||||||
}
|
}
|
||||||
stopForegroundCompat()
|
safeStopForeground()
|
||||||
stopSelf()
|
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
ACTION_ACCEPT -> {
|
ACTION_ACCEPT -> {
|
||||||
@@ -378,6 +374,26 @@ class CallForegroundService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Безопасная остановка: startForeground → stopForeground → stopSelf.
|
||||||
|
* Предотвращает ForegroundServiceDidNotStartInTimeException. */
|
||||||
|
private fun safeStopForeground() {
|
||||||
|
ensureNotificationChannel()
|
||||||
|
try {
|
||||||
|
startForeground(NOTIFICATION_ID, buildPlaceholderNotification())
|
||||||
|
} catch (_: Throwable) {}
|
||||||
|
stopForegroundCompat()
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildPlaceholderNotification(): Notification {
|
||||||
|
return NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
|
.setContentTitle("Rosetta")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
.setSilent(true)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "CallForegroundService"
|
private const val TAG = "CallForegroundService"
|
||||||
private const val CHANNEL_ID = "rosetta_calls"
|
private const val CHANNEL_ID = "rosetta_calls"
|
||||||
@@ -404,7 +420,10 @@ class CallForegroundService : Service() {
|
|||||||
fun syncWithCallState(context: Context, state: CallUiState) {
|
fun syncWithCallState(context: Context, state: CallUiState) {
|
||||||
val appContext = context.applicationContext
|
val appContext = context.applicationContext
|
||||||
if (state.phase == CallPhase.IDLE) {
|
if (state.phase == CallPhase.IDLE) {
|
||||||
appContext.stopService(Intent(appContext, CallForegroundService::class.java))
|
// Используем ACTION_STOP вместо stopService — он вызовет safeStopForeground
|
||||||
|
val stopIntent = Intent(appContext, CallForegroundService::class.java).setAction(ACTION_STOP)
|
||||||
|
runCatching { appContext.startService(stopIntent) }
|
||||||
|
.onFailure { appContext.stopService(Intent(appContext, CallForegroundService::class.java)) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,28 +263,57 @@ object CallManager {
|
|||||||
val snapshot = _state.value
|
val snapshot = _state.value
|
||||||
if (snapshot.phase != CallPhase.INCOMING) return CallActionResult.NOT_INCOMING
|
if (snapshot.phase != CallPhase.INCOMING) return CallActionResult.NOT_INCOMING
|
||||||
if (snapshot.peerPublicKey.isBlank()) return CallActionResult.INVALID_TARGET
|
if (snapshot.peerPublicKey.isBlank()) return CallActionResult.INVALID_TARGET
|
||||||
if (ownPublicKey.isBlank()) return CallActionResult.ACCOUNT_NOT_BOUND
|
|
||||||
|
// Если ownPublicKey пустой (push разбудил но аккаунт не привязан) — попробуем привязать
|
||||||
|
if (ownPublicKey.isBlank()) {
|
||||||
|
val lastPk = appContext?.let { com.rosetta.messenger.data.AccountManager(it).getLastLoggedPublicKey() }.orEmpty()
|
||||||
|
if (lastPk.isNotBlank()) {
|
||||||
|
bindAccount(lastPk)
|
||||||
|
breadcrumb("acceptIncomingCall: auto-bind account pk=${lastPk.take(8)}…")
|
||||||
|
} else {
|
||||||
|
return CallActionResult.ACCOUNT_NOT_BOUND
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
role = CallRole.CALLEE
|
role = CallRole.CALLEE
|
||||||
generateSessionKeys()
|
generateSessionKeys()
|
||||||
val localPublic = localPublicKey ?: return CallActionResult.INVALID_TARGET
|
val localPublic = localPublicKey ?: return CallActionResult.INVALID_TARGET
|
||||||
|
|
||||||
ProtocolManager.sendCallSignal(
|
|
||||||
signalType = SignalType.KEY_EXCHANGE,
|
|
||||||
src = ownPublicKey,
|
|
||||||
dst = snapshot.peerPublicKey,
|
|
||||||
sharedPublic = localPublic.toHex()
|
|
||||||
)
|
|
||||||
keyExchangeSent = true
|
|
||||||
incomingRingTimeoutJob?.cancel()
|
incomingRingTimeoutJob?.cancel()
|
||||||
incomingRingTimeoutJob = null
|
incomingRingTimeoutJob = null
|
||||||
|
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
phase = CallPhase.CONNECTING,
|
phase = CallPhase.CONNECTING,
|
||||||
statusText = "Exchanging keys..."
|
statusText = "Connecting..."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Отправляем KEY_EXCHANGE — если WebSocket не подключен, ждём и ретраим
|
||||||
|
scope.launch {
|
||||||
|
var sent = false
|
||||||
|
for (attempt in 1..30) { // 30 * 200ms = 6 sec
|
||||||
|
if (ProtocolManager.isAuthenticated()) {
|
||||||
|
ProtocolManager.sendCallSignal(
|
||||||
|
signalType = SignalType.KEY_EXCHANGE,
|
||||||
|
src = ownPublicKey,
|
||||||
|
dst = snapshot.peerPublicKey,
|
||||||
|
sharedPublic = localPublic.toHex()
|
||||||
|
)
|
||||||
|
keyExchangeSent = true
|
||||||
|
breadcrumb("acceptIncomingCall: KEY_EXCHANGE sent (attempt #$attempt)")
|
||||||
|
sent = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
breadcrumb("acceptIncomingCall: waiting for auth (attempt #$attempt)")
|
||||||
|
kotlinx.coroutines.delay(200)
|
||||||
|
}
|
||||||
|
if (!sent) {
|
||||||
|
breadcrumb("acceptIncomingCall: FAILED to send KEY_EXCHANGE after 6s — resetting")
|
||||||
|
resetSession(reason = "Failed to connect", notifyPeer = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
breadcrumbState("acceptIncomingCall")
|
breadcrumbState("acceptIncomingCall")
|
||||||
return CallActionResult.STARTED
|
return CallActionResult.STARTED
|
||||||
}
|
}
|
||||||
@@ -940,9 +969,9 @@ object CallManager {
|
|||||||
incomingRingTimeoutJob?.cancel()
|
incomingRingTimeoutJob?.cancel()
|
||||||
incomingRingTimeoutJob = null
|
incomingRingTimeoutJob = null
|
||||||
setSpeakerphone(false)
|
setSpeakerphone(false)
|
||||||
_state.value = CallUiState()
|
// Останавливаем ForegroundService ДО сброса state — иначе "Unknown" мелькает
|
||||||
// Останавливаем ForegroundService
|
|
||||||
appContext?.let { CallForegroundService.stop(it) }
|
appContext?.let { CallForegroundService.stop(it) }
|
||||||
|
_state.value = CallUiState()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetRtcObjects() {
|
private fun resetRtcObjects() {
|
||||||
|
|||||||
Reference in New Issue
Block a user