Keep radio outcome presentation failures inside the controller lifecycle
This commit is contained in:
1 parent
8b982efa05
commit
93b174ecde
2 files changed
+31
-11
No files matched your search
@@ -210,15 +210,17 @@ object RadioCallController {
|
||||
|
||||
private fun showOutcome(reason: Int) {
|
||||
if (reason == 4 || reason == 1) return
|
||||
val ctx = ChatApplication.instance
|
||||
val text = AppLanguage.text(reasonText(reason))
|
||||
if (visible) Toast.makeText(ctx, text, Toast.LENGTH_LONG).show()
|
||||
else try {
|
||||
ctx.getSystemService(NotificationManager::class.java).notify(1002,
|
||||
try {
|
||||
val ctx = ChatApplication.instance
|
||||
val text = AppLanguage.text(reasonText(reason))
|
||||
if (visible) { Toast.makeText(ctx, text, Toast.LENGTH_LONG).show(); return }
|
||||
requireNotNull(ctx.getSystemService(NotificationManager::class.java)) { "notification manager unavailable" }.notify(1002,
|
||||
NotificationCompat.Builder(ctx, IncomingCallManager.CHANNEL_CALL)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info).setContentTitle(AppLanguage.text(R.string.radio_call))
|
||||
.setContentText(text).setAutoCancel(true).build())
|
||||
} catch (e: SecurityException) { LogManager.addLog("WARN", "radio", "Call result notification denied: $e") }
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "radio", "Call result presentation failed reason=$reason visible=$visible: $e")
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkMain() = check(Looper.myLooper() == Looper.getMainLooper()) { "RadioCallController requires main thread" }
|
||||
|
||||
+23
-5
@@ -16,18 +16,27 @@ import com.utun.chat.ChatApplication
|
||||
import com.utun.chat.MainActivity
|
||||
import com.utun.chat.R
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.junit.After
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
/** Настоящие manager/receiver/ringtone: identity OS tokens, отказы служб и жизненный цикл входящего оповещения. */
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class IncomingCallLifecycleTest {
|
||||
private lateinit var context: Context
|
||||
private lateinit var notifications: NotificationManager
|
||||
private lateinit var power: PowerManager
|
||||
private val receiver = IncomingCallActionReceiver()
|
||||
private val current get() = requireNotNull(notifications.active[IncomingCallManager.NOTIF_ID])
|
||||
private val dispatcher = StandardTestDispatcher()
|
||||
private val radioEndings get() = NativeLib.radioCalls.filter { it.action == 4 }.map { it.id }
|
||||
|
||||
@Before fun prepare() {
|
||||
assertSame(Looper.getMainLooper(), Looper.myLooper())
|
||||
@@ -38,7 +47,8 @@ class IncomingCallLifecycleTest {
|
||||
Build.VERSION.SDK_INT = 36
|
||||
AppLanguage.language = "en"; AppLanguage.failContext = false; AppLanguage.failText = false
|
||||
CallController.media = false; CallController.endings.clear()
|
||||
RadioCallController.state.value = null; RadioCallController.endings.clear()
|
||||
Dispatchers.setMain(dispatcher)
|
||||
RadioCallController.reset(); NativeLib.radioCalls.clear(); NativeLib.onRadioCall = { true }
|
||||
NotificationCompat.failBuild = false
|
||||
PendingIntent.reset(); RingtoneManager.reset(); MediaPlayer.reset()
|
||||
// Процесс приложения живёт один раз; JVM-suite моделирует новый Application между сценариями.
|
||||
@@ -52,6 +62,7 @@ class IncomingCallLifecycleTest {
|
||||
power.failures = emptySet(); power.locks.forEach { it.failures = emptySet() }
|
||||
PendingIntent.created.forEach { it.failCancel = false; it.failCancelAfter = false }
|
||||
IncomingCallManager.stopRinging()
|
||||
RadioCallController.reset(); dispatcher.scheduler.advanceUntilIdle(); Dispatchers.resetMain()
|
||||
Looper.getMainLooper().advanceBy(10_000)
|
||||
Build.VERSION.SDK_INT = 36
|
||||
}
|
||||
@@ -64,6 +75,12 @@ class IncomingCallLifecycleTest {
|
||||
private fun isRadio(intent: Intent) = intent.getBooleanExtra(IncomingCallManager.EXTRA_RADIO_CALL, false)
|
||||
private fun deliver(intent: Intent) = receiver.onReceive(context, intent)
|
||||
private fun show(id: Long = 11L, radio: Boolean = false) = IncomingCallManager.show(id, "123", 99L, radio)
|
||||
private fun setRadioCall(id: Long, peerName: String = "Alice", channelName: String = "Team") {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val state = RadioCallController::class.java.getDeclaredField("_state").apply { isAccessible = true }
|
||||
.get(null) as MutableStateFlow<RadioCallState?>
|
||||
state.value = RadioCallState(id, "123", 99, peerName, channelName, true, RadioCallPhase.INCOMING)
|
||||
}
|
||||
private fun rejectOnWorker(action: () -> Unit) {
|
||||
val error = AtomicReference<Throwable?>()
|
||||
Thread({ try { action() } catch (e: Throwable) { error.set(e) } }, "incoming-test-worker").apply { start(); join() }
|
||||
@@ -145,7 +162,7 @@ class IncomingCallLifecycleTest {
|
||||
}
|
||||
|
||||
@Test fun radioAlertPreservesPeerNamesAndDistinctAnswerAction() {
|
||||
RadioCallController.state.value = HostRadioCallState("Alice", "Team")
|
||||
setRadioCall(11)
|
||||
show(radio = true)
|
||||
assertEquals("Alice · Team", current.text)
|
||||
assertEquals("en:${R.string.radio_call_incoming}", current.title)
|
||||
@@ -271,11 +288,12 @@ class IncomingCallLifecycleTest {
|
||||
val intent = decline().send()
|
||||
repeat(3) { deliver(intent) }
|
||||
assertEquals(listOf(11L to 2), CallController.endings)
|
||||
assertTrue(RadioCallController.endings.isEmpty())
|
||||
assertTrue(radioEndings.isEmpty())
|
||||
setRadioCall(22)
|
||||
show(22, radio = true)
|
||||
val radioIntent = decline().send()
|
||||
repeat(3) { deliver(radioIntent) }
|
||||
assertEquals(listOf(22L), RadioCallController.endings)
|
||||
assertEquals(listOf(22L), radioEndings)
|
||||
assertEquals(1, CallController.endings.size)
|
||||
}
|
||||
|
||||
@@ -296,7 +314,7 @@ class IncomingCallLifecycleTest {
|
||||
val wrongKind = decline().send().putExtra(IncomingCallManager.EXTRA_RADIO_CALL, true)
|
||||
deliver(wrongKind)
|
||||
assertTrue(CallController.endings.isEmpty())
|
||||
assertTrue(RadioCallController.endings.isEmpty())
|
||||
assertTrue(radioEndings.isEmpty())
|
||||
assertEquals(22L, callId(decline().send()))
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user