Cover endpoint callback order and malformed Telecom service requests

This commit is contained in:
evgeny committed 2026-10-29 13:34:25 +01:00
1 parent 90e33f568b
commit e7a03f33fb
2 files changed
+50 -3

No files matched your search

@@ -66,14 +66,15 @@ object Build { object VERSION { var SDK_INT = 36 }; object VERSION_CODES { const
object Process { const val THREAD_PRIORITY_AUDIO = -16; fun setThreadPriority(value: Int) {} }
class Bundle {
val values = mutableMapOf<String, Any>()
var failReads = false
fun putInt(key: String, value: Int) { values[key] = value }
fun putLong(key: String, value: Long) { values[key] = value }
fun putString(key: String, value: String) { values[key] = value }
fun putBundle(key: String, value: Bundle) { values[key] = value }
fun putParcelable(key: String, value: Any) { values[key] = value }
fun getLong(key: String, fallback: Long): Long = values[key] as? Long ?: fallback
fun getString(key: String): String? = values[key] as? String
fun keySet() = values.keys
fun getLong(key: String, fallback: Long): Long { check(!failReads) { "Bundle decode failed" }; return values[key] as? Long ?: fallback }
fun getString(key: String): String? { check(!failReads) { "Bundle decode failed" }; return values[key] as? String }
fun keySet(): Set<String> { check(!failReads) { "Bundle decode failed" }; return values.keys }
}
data class ParcelUuid(val uuid: java.util.UUID)
interface OutcomeReceiver<R, E : Throwable> { fun onResult(result: R?); fun onError(error: E) }
@@ -141,4 +141,50 @@ class TelecomLifecycleTest {
conn.callAudioState = CallAudioState(CallAudioState.ROUTE_WIRED_HEADSET, 12); conn.onCallAudioStateChanged(conn.callAudioState)
assertEquals(AudioRoute.HEADSET, conn.confirmedRoute)
}
@Test fun failedEndpointRequestDoesNotSendAnUnneededRestoreToTheUnchangedRoute() {
val conn = routed(); conn.routeTo(AudioRoute.SPEAKER); conn.routeTo(AudioRoute.EARPIECE)
conn.endpointRequests.single().fail(); main.advanceBy(0)
assertEquals(1, conn.endpointRequests.size); assertEquals(AudioRoute.EARPIECE, conn.confirmedRoute)
}
@Test fun endpointConfirmationBeforeItsOutcomePreservesTheLatestDesiredRoute() {
val conn = routed(); conn.routeTo(AudioRoute.SPEAKER); conn.routeTo(AudioRoute.EARPIECE)
conn.onCallEndpointChanged(speaker); conn.endpointRequests.first().succeed(); main.advanceBy(0)
assertEquals(2, conn.endpointRequests.size); assertEquals(earpiece, conn.endpointRequests.last().endpoint)
}
@Test fun outcomeFromAThrowingSdkRequestCannotClearANewerEndpointRequest() {
val conn = routed(); conn.failures = setOf("endpoint after"); conn.routeTo(AudioRoute.SPEAKER)
conn.failures = emptySet(); conn.routeTo(AudioRoute.EARPIECE); assertEquals(2, conn.endpointRequests.size)
conn.endpointRequests.first().succeed(); main.advanceBy(0)
val pending = UtunConnection::class.java.getDeclaredField("requestedEndpoint").apply { isAccessible = true }.get(conn)
assertEquals(earpiece.identifier, pending)
}
@Test fun routeWaitsForAMissingEndpointAndUsesItWhenTheListChanges() {
val conn = registered(); conn.onAvailableCallEndpointsChanged(mutableListOf(earpiece)); conn.routeTo(AudioRoute.SPEAKER)
assertTrue(conn.endpointRequests.isEmpty())
conn.onAvailableCallEndpointsChanged(mutableListOf(earpiece, speaker)); assertEquals(1, conn.endpointRequests.size)
}
@Test fun malformedServiceIdentityCancelsTheConnectionAndReportsItsKnownCallId() {
current(); val extras = Bundle().apply { putLong(TelecomCallManager.EXTRA_CALL_ID, 17) }
val result = UtunConnectionService().onCreateIncomingConnection(null, ConnectionRequest(extras))
main.advanceBy(0); assertTrue(result!!.cancelled); assertTrue("failed:17" in events)
}
@Test fun undecodableExtrasCannotEscapeFromConnectionServiceCallbacks() {
val request = ConnectionRequest(Bundle().apply { failReads = true }); val service = UtunConnectionService()
val result = service.onCreateOutgoingConnection(null, request)
service.onCreateIncomingConnectionFailed(null, request); service.onCreateOutgoingConnectionFailed(null, request)
main.advanceBy(0); assertTrue(result!!.cancelled); assertEquals(listOf("failed:0", "failed:0", "failed:0"), events)
}
@Test fun constructorFailureReturnsAFailedSdkConnectionAndNotifiesTheController() {
current(); Connection.nextFailures = setOf("properties")
val extras = Bundle().apply { putLong(TelecomCallManager.EXTRA_CALL_ID, 17); putLong(TelecomCallManager.EXTRA_PEER_NODE_ID, 99) }
val result = UtunConnectionService().onCreateOutgoingConnection(null, ConnectionRequest(extras))
main.advanceBy(0); assertEquals(android.telecom.DisconnectCause.ERROR, result!!.disconnectCause!!.code)
assertTrue("failed:17" in events)
}
@Test fun registryMutationsOutsideMainFailBeforeTouchingItsOwner() {
val conn = registered(); val error = java.util.concurrent.atomic.AtomicReference<Throwable>()
Thread { try { TelecomCallManager.removeConnection(17) } catch (e: Throwable) { error.set(e) } }.apply { start(); join() }
assertTrue(error.get() is IllegalStateException); assertSame(conn, TelecomCallManager.connectionFor(17)); assertFalse(conn.destroyed)
}
}