166 lines
7.0 KiB
Kotlin
166 lines
7.0 KiB
Kotlin
package com.rosetta.messenger
|
||
|
||
import android.app.KeyguardManager
|
||
import android.content.Context
|
||
import android.content.Intent
|
||
import android.os.Build
|
||
import android.os.Bundle
|
||
import android.util.Log
|
||
import android.view.WindowManager
|
||
import androidx.activity.ComponentActivity
|
||
import androidx.activity.compose.setContent
|
||
import androidx.compose.runtime.*
|
||
import com.rosetta.messenger.network.CallActionResult
|
||
import com.rosetta.messenger.network.CallForegroundService
|
||
import com.rosetta.messenger.network.CallManager
|
||
import com.rosetta.messenger.network.CallPhase
|
||
import com.rosetta.messenger.ui.chats.calls.CallOverlay
|
||
import com.rosetta.messenger.ui.theme.RosettaAndroidTheme
|
||
|
||
/**
|
||
* Лёгкая Activity для показа входящего звонка на lock screen.
|
||
* Показывается поверх экрана блокировки, без auth/splash.
|
||
* При Accept → переходит в MainActivity. При Decline → закрывается.
|
||
*/
|
||
class IncomingCallActivity : ComponentActivity() {
|
||
|
||
companion object {
|
||
private const val TAG = "IncomingCallActivity"
|
||
}
|
||
|
||
override fun onCreate(savedInstanceState: Bundle?) {
|
||
try {
|
||
super.onCreate(savedInstanceState)
|
||
} catch (e: Throwable) {
|
||
Log.e(TAG, "super.onCreate CRASHED", e)
|
||
callLog("super.onCreate CRASHED: ${e.message}")
|
||
finish()
|
||
return
|
||
}
|
||
callLog("onCreate START")
|
||
|
||
// Показываем поверх lock screen и включаем экран
|
||
callLog("setting lock screen flags, SDK=${Build.VERSION.SDK_INT}")
|
||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||
setShowWhenLocked(true)
|
||
setTurnScreenOn(true)
|
||
} else {
|
||
@Suppress("DEPRECATION")
|
||
window.addFlags(
|
||
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
||
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
|
||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||
)
|
||
}
|
||
|
||
// Dismiss keyguard
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||
val km = getSystemService(Context.KEYGUARD_SERVICE) as? KeyguardManager
|
||
km?.requestDismissKeyguard(this, null)
|
||
} else {
|
||
@Suppress("DEPRECATION")
|
||
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD)
|
||
}
|
||
|
||
try {
|
||
CallManager.initialize(applicationContext)
|
||
callLog("CallManager initialized, phase=${CallManager.state.value.phase}")
|
||
} catch (e: Throwable) {
|
||
callLog("CallManager.initialize CRASHED: ${e.message}")
|
||
Log.e(TAG, "CallManager init failed", e)
|
||
}
|
||
|
||
callLog("calling setContent")
|
||
setContent {
|
||
val callState by CallManager.state.collectAsState()
|
||
|
||
// Ждём до 10 сек пока WebSocket доставит сигнал (CallManager перейдёт из IDLE)
|
||
var wasIncoming by remember { mutableStateOf(false) }
|
||
|
||
LaunchedEffect(callState.phase) {
|
||
callLog("phase changed: ${callState.phase}")
|
||
if (callState.phase == CallPhase.INCOMING) wasIncoming = true
|
||
// Закрываем только когда звонок завершился
|
||
if (callState.phase == CallPhase.IDLE && wasIncoming) {
|
||
callLog("IDLE after INCOMING → finish()")
|
||
finish()
|
||
}
|
||
// НЕ закрываемся при CONNECTING/ACTIVE — остаёмся на экране звонка
|
||
// IncomingCallActivity показывает полный CallOverlay, не нужно переходить в MainActivity
|
||
}
|
||
|
||
// Показываем INCOMING в IDLE только до первого реального входящего состояния.
|
||
// Иначе после Decline/END на мгновение мелькает "Unknown".
|
||
val shouldShowProvisionalIncoming =
|
||
callState.phase == CallPhase.IDLE &&
|
||
!wasIncoming &&
|
||
(callState.peerPublicKey.isNotBlank() ||
|
||
callState.peerTitle.isNotBlank() ||
|
||
callState.peerUsername.isNotBlank())
|
||
|
||
val displayState = if (shouldShowProvisionalIncoming) {
|
||
callState.copy(phase = CallPhase.INCOMING, statusText = "Incoming call...")
|
||
} else {
|
||
callState
|
||
}
|
||
|
||
RosettaAndroidTheme(darkTheme = true) {
|
||
CallOverlay(
|
||
state = displayState,
|
||
isDarkTheme = true,
|
||
isExpanded = true,
|
||
onAccept = {
|
||
callLog("onAccept tapped, phase=${callState.phase}")
|
||
if (callState.phase == CallPhase.INCOMING) {
|
||
val result = CallManager.acceptIncomingCall()
|
||
callLog("acceptIncomingCall result=$result")
|
||
// Остаёмся на IncomingCallActivity — она покажет CONNECTING → ACTIVE
|
||
} else {
|
||
callLog("onAccept: phase=${callState.phase}, trying accept anyway")
|
||
CallManager.acceptIncomingCall()
|
||
}
|
||
},
|
||
onDecline = {
|
||
callLog("onDecline tapped")
|
||
CallManager.declineIncomingCall()
|
||
finish()
|
||
},
|
||
onEnd = {
|
||
callLog("onEnd tapped")
|
||
CallManager.endCall()
|
||
finish()
|
||
},
|
||
onToggleMute = { CallManager.toggleMute() },
|
||
onToggleSpeaker = { CallManager.toggleSpeaker() }
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
private fun openMainActivity() {
|
||
callLog("openMainActivity")
|
||
val intent = Intent(this, MainActivity::class.java).apply {
|
||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
|
||
Intent.FLAG_ACTIVITY_SINGLE_TOP or
|
||
Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||
putExtra(CallForegroundService.EXTRA_OPEN_CALL_FROM_NOTIFICATION, true)
|
||
}
|
||
startActivity(intent)
|
||
}
|
||
|
||
private fun callLog(msg: String) {
|
||
Log.d(TAG, msg)
|
||
try {
|
||
val ctx = applicationContext ?: return
|
||
val dir = java.io.File(ctx.filesDir, "crash_reports")
|
||
if (!dir.exists()) dir.mkdirs()
|
||
val f = java.io.File(dir, "call_notification_log.txt")
|
||
val ts = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date())
|
||
f.appendText("$ts [IncomingCallActivity] $msg\n")
|
||
} catch (e: Throwable) {
|
||
Log.e(TAG, "callLog write failed: ${e.message}")
|
||
}
|
||
}
|
||
}
|