Добавлен кастомный 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
|
||||
*.apk
|
||||
*.aar
|
||||
# *.aar — кастомный WebRTC разрешён в app/libs/
|
||||
!app/libs/*.aar
|
||||
*.ap_
|
||||
*.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) {
|
||||
ACTION_STOP -> {
|
||||
notifLog("ACTION_STOP → stopSelf")
|
||||
stopForegroundCompat()
|
||||
stopSelf()
|
||||
safeStopForeground()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
ACTION_END -> {
|
||||
notifLog("ACTION_END → endCall")
|
||||
CallManager.endCall()
|
||||
stopForegroundCompat()
|
||||
stopSelf()
|
||||
safeStopForeground()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
ACTION_DECLINE -> {
|
||||
@@ -70,11 +68,9 @@ class CallForegroundService : Service() {
|
||||
if (phase == CallPhase.INCOMING) {
|
||||
CallManager.declineIncomingCall()
|
||||
} else {
|
||||
// Если звонок уже не в INCOMING (CONNECTING/ACTIVE) — endCall
|
||||
CallManager.endCall()
|
||||
}
|
||||
stopForegroundCompat()
|
||||
stopSelf()
|
||||
safeStopForeground()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
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 {
|
||||
private const val TAG = "CallForegroundService"
|
||||
private const val CHANNEL_ID = "rosetta_calls"
|
||||
@@ -404,7 +420,10 @@ class CallForegroundService : Service() {
|
||||
fun syncWithCallState(context: Context, state: CallUiState) {
|
||||
val appContext = context.applicationContext
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -263,12 +263,37 @@ object CallManager {
|
||||
val snapshot = _state.value
|
||||
if (snapshot.phase != CallPhase.INCOMING) return CallActionResult.NOT_INCOMING
|
||||
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
|
||||
generateSessionKeys()
|
||||
val localPublic = localPublicKey ?: return CallActionResult.INVALID_TARGET
|
||||
|
||||
incomingRingTimeoutJob?.cancel()
|
||||
incomingRingTimeoutJob = null
|
||||
|
||||
updateState {
|
||||
it.copy(
|
||||
phase = CallPhase.CONNECTING,
|
||||
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,
|
||||
@@ -276,15 +301,19 @@ object CallManager {
|
||||
sharedPublic = localPublic.toHex()
|
||||
)
|
||||
keyExchangeSent = true
|
||||
incomingRingTimeoutJob?.cancel()
|
||||
incomingRingTimeoutJob = null
|
||||
|
||||
updateState {
|
||||
it.copy(
|
||||
phase = CallPhase.CONNECTING,
|
||||
statusText = "Exchanging keys..."
|
||||
)
|
||||
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")
|
||||
return CallActionResult.STARTED
|
||||
}
|
||||
@@ -940,9 +969,9 @@ object CallManager {
|
||||
incomingRingTimeoutJob?.cancel()
|
||||
incomingRingTimeoutJob = null
|
||||
setSpeakerphone(false)
|
||||
_state.value = CallUiState()
|
||||
// Останавливаем ForegroundService
|
||||
// Останавливаем ForegroundService ДО сброса state — иначе "Unknown" мелькает
|
||||
appContext?.let { CallForegroundService.stop(it) }
|
||||
_state.value = CallUiState()
|
||||
}
|
||||
|
||||
private fun resetRtcObjects() {
|
||||
|
||||
Reference in New Issue
Block a user