Релиз 1.4.3: полноэкранные входящие звонки, аватарки в уведомлениях, фиксы
Some checks failed
Android Kernel Build / build (push) Failing after 4m6s

Звонки:
- IncomingCallActivity — полноэкранный UI входящего звонка поверх lock screen
- fullScreenIntent на нотификации для Android 12+
- ForegroundService синхронизируется при смене фазы и имени
- Запрос fullScreenIntent permission на Android 14+
- dispose() PeerConnection при завершении звонка
- Защита от CREATE_ROOM без ключей (звонок на другом устройстве)
- Дедупликация push + WebSocket сигналов
- setIncomingFromPush — CallManager сразу в INCOMING по push
- Accept ждёт до 5 сек если WebSocket не доставил сигнал
- Decline работает во всех фазах (не только INCOMING)
- Баннер активного звонка внутри диалога

Уведомления:
- Аватарки и имена по publicKey в уведомлениях (message + call)
- Настройка "Avatars in Notifications" в разделе Notifications

UI:
- Ограничение fontScale до 1.3x (вёрстка не ломается на огромном тексте)
- Новые обои: Light 1-3 для светлой темы, убраны старые back_*
- ContentScale.Crop для превью обоев (без растяжения)

CI/CD:
- NDK/CMake в CI, local.properties, ANDROID_NDK_HOME
- Ограничение JVM heap для CI раннера

Диагностика:
- Логирование call notification flow в crash_reports (rosettadev1)
- FCM токен в crash_reports
This commit is contained in:
2026-04-02 01:18:20 +05:00
parent 803fda9abe
commit 876c1ab4df
30 changed files with 789 additions and 170 deletions

View File

@@ -1,6 +1,8 @@
package com.rosetta.messenger
// commit
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
@@ -83,6 +85,10 @@ class MainActivity : FragmentActivity() {
private lateinit var preferencesManager: PreferencesManager
private lateinit var accountManager: AccountManager
// Флаг: Activity открыта для ответа на звонок с lock screen — пропускаем auth
// mutableStateOf чтобы Compose реагировал на изменение (избежать race condition)
private var openedForCall by mutableStateOf(false)
companion object {
private const val TAG = "MainActivity"
// Process-memory session cache: lets app return without password while process is alive.
@@ -120,6 +126,7 @@ class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
handleCallLockScreen(intent)
preferencesManager = PreferencesManager(this)
accountManager = AccountManager(this)
@@ -161,6 +168,21 @@ class MainActivity : FragmentActivity() {
)
}
}
// Android 14+: запрос fullScreenIntent для входящих звонков
if (Build.VERSION.SDK_INT >= 34) {
val nm = getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
if (!nm.canUseFullScreenIntent()) {
try {
startActivity(
android.content.Intent(
android.provider.Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT,
android.net.Uri.parse("package:$packageName")
)
)
} catch (_: Throwable) {}
}
}
}
val scope = rememberCoroutineScope()
@@ -212,6 +234,9 @@ class MainActivity : FragmentActivity() {
showSplash -> "splash"
showOnboarding && hasExistingAccount == false ->
"onboarding"
// При открытии по звонку с lock screen — пропускаем auth
openedForCall && hasExistingAccount == true ->
"main"
isLoggedIn != true && hasExistingAccount == false ->
"auth_new"
isLoggedIn != true && hasExistingAccount == true ->
@@ -433,6 +458,66 @@ class MainActivity : FragmentActivity() {
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleCallLockScreen(intent)
}
private var callLockScreenJob: kotlinx.coroutines.Job? = null
/**
* Показать Activity поверх экрана блокировки при входящем звонке.
* При завершении звонка флаги снимаются чтобы не нарушать обычное поведение.
*/
private fun handleCallLockScreen(intent: Intent?) {
val isCallIntent = intent?.getBooleanExtra(
com.rosetta.messenger.network.CallForegroundService.EXTRA_OPEN_CALL_FROM_NOTIFICATION, false
) == true
if (isCallIntent) {
openedForCall = true
// Включаем экран и показываем поверх lock screen
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
setTurnScreenOn(true)
} else {
@Suppress("DEPRECATION")
window.addFlags(
android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
)
}
// Убираем lock screen полностью
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as? android.app.KeyguardManager
keyguardManager?.requestDismissKeyguard(this, null)
} else {
@Suppress("DEPRECATION")
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD)
}
// Снять флаги когда звонок закончится (отменяем предыдущий коллектор если был)
callLockScreenJob?.cancel()
callLockScreenJob = lifecycleScope.launch {
com.rosetta.messenger.network.CallManager.state.collect { state ->
if (state.phase == com.rosetta.messenger.network.CallPhase.IDLE) {
openedForCall = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(false)
setTurnScreenOn(false)
} else {
@Suppress("DEPRECATION")
window.clearFlags(
android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
)
}
callLockScreenJob?.cancel()
callLockScreenJob = null
}
}
}
}
}
override fun onResume() {
super.onResume()
// 🔥 Приложение стало видимым - отключаем уведомления
@@ -1347,7 +1432,8 @@ fun MainScreen(
chatWallpaperId = chatWallpaperId,
avatarRepository = avatarRepository,
onImageViewerChanged = { isLocked -> isChatSwipeLocked = isLocked },
isCallActive = callUiState.isVisible
isCallActive = callUiState.isVisible,
onOpenCallOverlay = { isCallOverlayExpanded = true }
)
}
}