diff --git a/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkNativeCallLauncher.kt b/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkNativeCallLauncher.kt index d73d0eb..b01e839 100644 --- a/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkNativeCallLauncher.kt +++ b/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkNativeCallLauncher.kt @@ -5,6 +5,10 @@ import android.content.Context import android.content.Intent import android.util.Log import android.widget.Toast +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import ru.f7cloud.talk.activities.CallActivity import ru.f7cloud.talk.services.CallForegroundService import ru.f7cloud.talk.utils.bundle.BundleKeys @@ -22,6 +26,9 @@ object TalkNativeCallLauncher { private const val TAG = "TalkNativeCallLauncher" private const val WEBVIEW_CALL_ACTIVITY = "ru.forbion.f7cloud.feature.talk.TalkCallActivity" + // Main-scope для запуска звонка после suspend-подготовки vendor-юзера (IO) + private val launchScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + fun launchIncomingCall( context: Context, session: AuthSession, @@ -83,26 +90,30 @@ object TalkNativeCallLauncher { } return } - if (!TalkVendorBootstrap.ensureSessionReady(context, session)) { - showError(context, "Не удалось подготовить Talk для звонка") - return - } val token = callContext.roomToken.trim() if (token.isBlank()) { showError(context, "Некорректная комната звонка") return } - runCatching { - startCallActivity( - context, - buildCallIntent(context, session, callContext, incomingFromNotification, suppressIncomingRingtone), - ) - }.onFailure { error -> - Log.e(TAG, "Native CallActivity launch failed", error) - if (TalkNativeConfig.allowWebViewCallFallback) { - launchWebViewFallback(context, session, token, incomingFromNotification) - } else { - showError(context, "Не удалось начать звонок") + // Подготовка vendor-юзера — suspend (IO): раньше runBlocking держал main + // на пути звонка (ANR-риск на входящем звонке, аудит C4). Запуск activity — на main. + launchScope.launch { + if (!TalkVendorBootstrap.ensureSessionReady(context, session)) { + showError(context, "Не удалось подготовить Talk для звонка") + return@launch + } + runCatching { + startCallActivity( + context, + buildCallIntent(context, session, callContext, incomingFromNotification, suppressIncomingRingtone), + ) + }.onFailure { error -> + Log.e(TAG, "Native CallActivity launch failed", error) + if (TalkNativeConfig.allowWebViewCallFallback) { + launchWebViewFallback(context, session, token, incomingFromNotification) + } else { + showError(context, "Не удалось начать звонок") + } } } } diff --git a/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkVendorBootstrap.kt b/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkVendorBootstrap.kt index 66dd921..30c6bcb 100644 --- a/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkVendorBootstrap.kt +++ b/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkVendorBootstrap.kt @@ -2,6 +2,10 @@ package ru.forbion.f7cloud.feature.talknative import android.content.Context import android.util.Log +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import ru.forbion.f7cloud.core.auth.AuthSession import ru.forbion.f7cloud.core.auth.AuthStore @@ -10,15 +14,20 @@ import ru.forbion.f7cloud.core.auth.AuthStore */ object TalkVendorBootstrap { private const val TAG = "TalkVendorBootstrap" + private val warmupScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + /** Прогрев vendor-юзера на старте — в фоне (раньше блокировал main в Application.onCreate). */ fun onApplicationCreate(context: Context) { if (!TalkNativeConfig.bootstrapVendorRuntime) return - val session = AuthStore(context).load() ?: return - runCatching { TalkVendorUserSync.ensureUser(context, session) } - .onFailure { Log.w(TAG, "Talk user sync failed: ${it.message}") } + val appContext = context.applicationContext + warmupScope.launch { + val session = AuthStore(appContext).load() ?: return@launch + runCatching { TalkVendorUserSync.ensureUser(appContext, session) } + .onFailure { Log.w(TAG, "Talk user sync failed: ${it.message}") } + } } - fun ensureSessionReady(context: Context, session: AuthSession): Boolean { + suspend fun ensureSessionReady(context: Context, session: AuthSession): Boolean { if (!TalkNativeConfig.bootstrapVendorRuntime) return true return runCatching { TalkVendorUserSync.ensureUser(context, session) } .onFailure { Log.w(TAG, "Talk user sync failed: ${it.message}") } diff --git a/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkVendorUserSync.kt b/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkVendorUserSync.kt index a80ae61..d7ee4c7 100644 --- a/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkVendorUserSync.kt +++ b/feature/talk-native/src/main/java/ru/forbion/f7cloud/feature/talknative/TalkVendorUserSync.kt @@ -5,22 +5,25 @@ import android.util.Log import ru.f7cloud.talk.application.F7cloudTalkApplication import ru.f7cloud.talk.f7cloud.F7TalkUserSync import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import ru.forbion.f7cloud.core.auth.AuthSession /** * Ensures talk-android [ru.f7cloud.talk.data.user.model.User] exists for F7cloud sessions. * Required before native [ru.f7cloud.talk.activities.CallActivity] can run. + * + * suspend (IO) — раньше был runBlocking на main (старт приложения и путь звонка), + * что давало ANR-риск из аудита (C4/п.7). */ object TalkVendorUserSync { private const val TAG = "TalkVendorUserSync" - fun ensureUser(context: Context, session: AuthSession): Boolean { + suspend fun ensureUser(context: Context, session: AuthSession): Boolean { if (context.applicationContext !is F7cloudTalkApplication) { Log.w(TAG, "Application is not F7cloudTalkApplication, skip sync") return false } - return runBlocking(Dispatchers.IO) { + return withContext(Dispatchers.IO) { runCatching { F7TalkUserSync.get().ensureUser( serverUrl = session.serverUrl,