From 957a878b44fb1fc3bad9a07edc83497ee75a90c8 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Mon, 30 Jan 2023 21:47:48 +0000 Subject: [PATCH] [SB Refactor] Add logs for the carrier merged connection. Bug: 264684296 Fixes: 267501739 Bug: 238425913 Test: Manual: Dumped MobileConnectionLog[3] and verified logs Test: atest FullMobileConnectionRepositoryTest CarrierMergedConnectionRepositoryTest Change-Id: Ife7d154e0bd07f5ede9cfd5404407e1863c78df6 --- .../prod/CarrierMergedConnectionRepository.kt | 3 - .../prod/FullMobileConnectionRepository.kt | 18 +- .../prod/MobileConnectionRepositoryImpl.kt | 23 +- .../FullMobileConnectionRepositoryTest.kt | 284 +++++++++++++++++- .../prod/MobileConnectionRepositoryTest.kt | 12 +- .../repository/prod/MobileTelephonyHelpers.kt | 39 +++ 6 files changed, 339 insertions(+), 40 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt index c783b12e0c0b0..f5041d89c1d18 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt @@ -91,9 +91,6 @@ class CarrierMergedConnectionRepository( .map { it.toMobileConnectionModel() } .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel()) - // TODO(b/238425913): Add logging to this class. - // TODO(b/238425913): Make sure SignalStrength.getEmptyState is used when appropriate. - // Carrier merged is never roaming. override val cdmaRoaming: StateFlow = MutableStateFlow(false).asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt index dd2cc928f7ad1..f17791b655029 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt @@ -117,11 +117,22 @@ class FullMobileConnectionRepository( override val connectionInfo = activeRepo .flatMapLatest { it.connectionInfo } + .logDiffsForTable( + tableLogBuffer, + columnPrefix = "", + initialValue = activeRepo.value.connectionInfo.value, + ) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value) override val dataEnabled = activeRepo .flatMapLatest { it.dataEnabled } + .logDiffsForTable( + tableLogBuffer, + columnPrefix = "", + columnName = "dataEnabled", + initialValue = activeRepo.value.dataEnabled.value, + ) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value) override val numberOfLevels = @@ -132,6 +143,11 @@ class FullMobileConnectionRepository( override val networkName = activeRepo .flatMapLatest { it.networkName } + .logDiffsForTable( + tableLogBuffer, + columnPrefix = "", + initialValue = activeRepo.value.networkName.value, + ) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value) class Factory @@ -168,7 +184,7 @@ class FullMobileConnectionRepository( const val MOBILE_CONNECTION_BUFFER_SIZE = 100 /** Returns a log buffer name for a mobile connection with the given [subId]. */ - fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]" + fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]" } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index 76fef358726d2..cfc4cc4ba9471 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -36,7 +36,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.table.TableLogBuffer -import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType @@ -62,7 +61,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapNotNull @@ -87,7 +85,7 @@ class MobileConnectionRepositoryImpl( private val mobileMappingsProxy: MobileMappingsProxy, bgDispatcher: CoroutineDispatcher, logger: ConnectivityPipelineLogger, - mobileLogger: TableLogBuffer, + override val tableLogBuffer: TableLogBuffer, scope: CoroutineScope, ) : MobileConnectionRepository { init { @@ -101,8 +99,6 @@ class MobileConnectionRepositoryImpl( private val telephonyCallbackEvent = MutableSharedFlow(extraBufferCapacity = 1) - override val tableLogBuffer: TableLogBuffer = mobileLogger - /** * This flow defines the single shared connection to system_server via TelephonyCallback. Any * new callback should be added to this listener and funneled through callbackEvents via a data @@ -243,11 +239,6 @@ class MobileConnectionRepositoryImpl( val initial = MobileConnectionModel() callbackEvents .scan(initial, ::updateConnectionState) - .logDiffsForTable( - mobileLogger, - columnPrefix = "MobileConnection ($subId)", - initialValue = initial, - ) .stateIn(scope, SharingStarted.WhileSubscribed(), initial) } @@ -285,24 +276,12 @@ class MobileConnectionRepositoryImpl( intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName } } - .distinctUntilChanged() - .logDiffsForTable( - mobileLogger, - columnPrefix = "", - initialValue = defaultNetworkName, - ) .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName) override val dataEnabled = run { val initial = telephonyManager.isDataConnectionAllowed callbackEvents .mapNotNull { (it as? CallbackEvent.OnDataEnabledChanged)?.enabled } - .logDiffsForTable( - mobileLogger, - columnPrefix = "", - columnName = "dataEnabled", - initialValue = initial - ) .stateIn(scope, SharingStarted.WhileSubscribed(), initial) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index 24b9f7d55da50..da208a7f08c9a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -16,20 +16,32 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod +import android.telephony.ServiceState +import android.telephony.SignalStrength +import android.telephony.TelephonyCallback +import android.telephony.TelephonyManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -52,9 +64,10 @@ import org.mockito.Mockito.verify class FullMobileConnectionRepositoryTest : SysuiTestCase() { private lateinit var underTest: FullMobileConnectionRepository + private val systemClock = FakeSystemClock() private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) - private val tableLogBuffer = mock() + private val tableLogBuffer = TableLogBuffer(maxSize = 100, name = "TestName", systemClock) private val mobileFactory = mock() private val carrierMergedFactory = mock() @@ -347,8 +360,214 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { .isSameInstanceAs(connection1Repeat.tableLogBuffer) } - // TODO(b/238425913): Verify that the logging switches correctly (once the carrier merged repo - // implements logging). + @Test + fun connectionInfo_logging_notCarrierMerged_getsUpdates() = + testScope.runTest { + // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.) + val telephonyManager = mock() + createRealMobileRepo(telephonyManager) + createRealCarrierMergedRepo(FakeWifiRepository()) + + initializeRepo(startingIsCarrierMerged = false) + + val job = underTest.connectionInfo.launchIn(this) + + // WHEN we set up some mobile connection info + val serviceState = ServiceState() + serviceState.setOperatorName("longName", "OpTypical", "1") + serviceState.isEmergencyOnly = false + getTelephonyCallbackForType(telephonyManager) + .onServiceStateChanged(serviceState) + + // THEN it's logged to the buffer + assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpTypical") + assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}false") + + // WHEN we update mobile connection info + val serviceState2 = ServiceState() + serviceState2.setOperatorName("longName", "OpDiff", "1") + serviceState2.isEmergencyOnly = true + getTelephonyCallbackForType(telephonyManager) + .onServiceStateChanged(serviceState2) + + // THEN the updates are logged + assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpDiff") + assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}true") + + job.cancel() + } + + @Test + fun connectionInfo_logging_carrierMerged_getsUpdates() = + testScope.runTest { + // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.) + createRealMobileRepo(mock()) + val wifiRepository = FakeWifiRepository() + createRealCarrierMergedRepo(wifiRepository) + + initializeRepo(startingIsCarrierMerged = true) + + val job = underTest.connectionInfo.launchIn(this) + + // WHEN we set up carrier merged info + val networkId = 2 + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged( + networkId, + SUB_ID, + level = 3, + ) + ) + + // THEN the carrier merged info is logged + assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3") + + // WHEN we update the info + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged( + networkId, + SUB_ID, + level = 1, + ) + ) + + // THEN the updates are logged + assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1") + + job.cancel() + } + + @Test + fun connectionInfo_logging_updatesWhenCarrierMergedUpdates() = + testScope.runTest { + // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.) + val telephonyManager = mock() + createRealMobileRepo(telephonyManager) + + val wifiRepository = FakeWifiRepository() + createRealCarrierMergedRepo(wifiRepository) + + initializeRepo(startingIsCarrierMerged = false) + + val job = underTest.connectionInfo.launchIn(this) + + // WHEN we set up some mobile connection info + val signalStrength = mock() + whenever(signalStrength.level).thenReturn(1) + + getTelephonyCallbackForType(telephonyManager) + .onSignalStrengthsChanged(signalStrength) + + // THEN it's logged to the buffer + assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1") + + // WHEN isCarrierMerged is set to true + val networkId = 2 + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged( + networkId, + SUB_ID, + level = 3, + ) + ) + underTest.setIsCarrierMerged(true) + + // THEN the carrier merged info is logged + assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3") + + // WHEN the carrier merge network is updated + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged( + networkId, + SUB_ID, + level = 4, + ) + ) + + // THEN the new level is logged + assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4") + + // WHEN isCarrierMerged is set to false + underTest.setIsCarrierMerged(false) + + // THEN the typical info is logged + // Note: Since our first logs also had the typical info, we need to search the log + // contents for after our carrier merged level log. + val fullBuffer = dumpBuffer() + val carrierMergedContentIndex = fullBuffer.indexOf("${BUFFER_SEPARATOR}4") + val bufferAfterCarrierMerged = fullBuffer.substring(carrierMergedContentIndex) + assertThat(bufferAfterCarrierMerged).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1") + + // WHEN the normal network is updated + val newMobileInfo = + MobileConnectionModel( + operatorAlphaShort = "Mobile Operator 2", + primaryLevel = 0, + ) + mobileRepo.setConnectionInfo(newMobileInfo) + + // THEN the new level is logged + assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}0") + + job.cancel() + } + + @Test + fun connectionInfo_logging_doesNotLogUpdatesForNotActiveRepo() = + testScope.runTest { + // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.) + val telephonyManager = mock() + createRealMobileRepo(telephonyManager) + + val wifiRepository = FakeWifiRepository() + createRealCarrierMergedRepo(wifiRepository) + + // WHEN isCarrierMerged = false + initializeRepo(startingIsCarrierMerged = false) + + val job = underTest.connectionInfo.launchIn(this) + + val signalStrength = mock() + whenever(signalStrength.level).thenReturn(1) + getTelephonyCallbackForType(telephonyManager) + .onSignalStrengthsChanged(signalStrength) + + // THEN updates to the carrier merged level aren't logged + val networkId = 2 + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged( + networkId, + SUB_ID, + level = 4, + ) + ) + assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4") + + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged( + networkId, + SUB_ID, + level = 3, + ) + ) + assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3") + + // WHEN isCarrierMerged is set to true + underTest.setIsCarrierMerged(true) + + // THEN updates to the normal level aren't logged + whenever(signalStrength.level).thenReturn(5) + getTelephonyCallbackForType(telephonyManager) + .onSignalStrengthsChanged(signalStrength) + assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}5") + + whenever(signalStrength.level).thenReturn(6) + getTelephonyCallbackForType(telephonyManager) + .onSignalStrengthsChanged(signalStrength) + assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}6") + + job.cancel() + } private fun initializeRepo(startingIsCarrierMerged: Boolean) { underTest = @@ -364,9 +583,68 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { ) } + private fun createRealMobileRepo( + telephonyManager: TelephonyManager, + ): MobileConnectionRepositoryImpl { + whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID) + + val realRepo = + MobileConnectionRepositoryImpl( + context, + SUB_ID, + defaultNetworkName = NetworkNameModel.Default("default"), + networkNameSeparator = SEP, + telephonyManager, + systemUiCarrierConfig = mock(), + fakeBroadcastDispatcher, + mobileMappingsProxy = mock(), + testDispatcher, + logger = mock(), + tableLogBuffer, + testScope.backgroundScope, + ) + whenever( + mobileFactory.build( + eq(SUB_ID), + any(), + eq(DEFAULT_NAME), + eq(SEP), + ) + ) + .thenReturn(realRepo) + + return realRepo + } + + private fun createRealCarrierMergedRepo( + wifiRepository: FakeWifiRepository, + ): CarrierMergedConnectionRepository { + wifiRepository.setIsWifiEnabled(true) + wifiRepository.setIsWifiDefault(true) + val realRepo = + CarrierMergedConnectionRepository( + SUB_ID, + tableLogBuffer, + defaultNetworkName = NetworkNameModel.Default("default"), + testScope.backgroundScope, + wifiRepository, + ) + whenever(carrierMergedFactory.build(eq(SUB_ID), any(), eq(DEFAULT_NAME))) + .thenReturn(realRepo) + + return realRepo + } + + private fun dumpBuffer(): String { + val outputWriter = StringWriter() + tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf()) + return outputWriter.toString() + } + private companion object { const val SUB_ID = 42 private val DEFAULT_NAME = NetworkNameModel.Default("default name") private const val SEP = "-" + private const val BUFFER_SEPARATOR = "|" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 3f36bc18af08e..1a5cc9abd9b64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -71,7 +71,6 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -86,7 +85,6 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.mockito.Mock -import org.mockito.Mockito import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @@ -670,16 +668,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { job.cancel() } - private fun getTelephonyCallbacks(): List { - val callbackCaptor = argumentCaptor() - Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture()) - return callbackCaptor.allValues - } - private inline fun getTelephonyCallbackForType(): T { - val cbs = getTelephonyCallbacks().filterIsInstance() - assertThat(cbs.size).isEqualTo(1) - return cbs[0] + return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager) } /** Convenience constructor for SignalStrength */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt new file mode 100644 index 0000000000000..621f79307e49f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod + +import android.telephony.TelephonyCallback +import android.telephony.TelephonyManager +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.google.common.truth.Truth.assertThat +import org.mockito.Mockito.verify + +/** Helper methods for telephony-related callbacks for mobile tests. */ +object MobileTelephonyHelpers { + fun getTelephonyCallbacks(mockTelephonyManager: TelephonyManager): List { + val callbackCaptor = argumentCaptor() + verify(mockTelephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture()) + return callbackCaptor.allValues + } + + inline fun getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T { + val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance() + assertThat(cbs.size).isEqualTo(1) + return cbs[0] + } +}