Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f7f4bab7 authored by Behnam Heydarshahi's avatar Behnam Heydarshahi
Browse files

Use main thread when creating internet signal icon

InternetTileDataInteractor would crash loading mobile icon because
SignalDrawable created a Handler() on a context that did not have a
looper assigned to it.

Flag: aconfig com.android.systemui.qs_new_tiles DEVELOPMENT
Fixes: 335868718
Test: atest InternetTileDataInteractorTest
Change-Id: I72439f72113cfd5c1e8d52dbd2d54b1c56951abd
parent f8cf5ad7
Loading
Loading
Loading
Loading
+42 −2
Original line number Diff line number Diff line
@@ -58,13 +58,16 @@ import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupReposi
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@TestableLooper.RunWithLooper
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class InternetTileDataInteractorTest : SysuiTestCase() {
@@ -141,6 +144,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
        underTest =
            InternetTileDataInteractor(
                context,
                testScope.coroutineContext,
                testScope.backgroundScope,
                airplaneModeRepository,
                connectivityRepository,
@@ -433,8 +437,44 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
                .isEqualTo(expectedCd)
        }

    /**
     * We expect a RuntimeException because [underTest] instantiates a SignalDrawable on the
     * provided context, and so the SignalDrawable constructor attempts to instantiate a Handler()
     * on the mentioned context. Since that context does not have a looper assigned to it, the
     * handler instantiation will throw a RuntimeException.
     *
     * TODO(b/338068066): Robolectric behavior differs in that it does not throw the exception
     * So either we should make Robolectric behvase similar to the device test, or change this
     * test to look for a different signal than the exception, when run by Robolectric. For now
     * we just assume the test is not Robolectric.
     */
    @Test(expected = java.lang.RuntimeException::class)
    fun mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException() =
        testScope.runTest {
            assumeFalse(isRobolectricTest());

            collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))

            connectivityRepository.setMobileConnected()
            mobileConnectionsRepository.mobileIsDefault.value = true
            mobileConnectionRepository.apply {
                setAllLevels(3)
                setAllRoaming(false)
                networkName.value = NetworkNameModel.Default("test network")
            }

            runCurrent()
        }

    /**
     * See [mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException] for description of the
     * problem this test solves. The solution here is to assign a looper to the context via
     * RunWithLooper. In the production code, the solution is to use a Main CoroutineContext for
     * creating the SignalDrawable.
     */
    @TestableLooper.RunWithLooper
    @Test
    fun mobileDefault_usesNetworkNameAndIcon() =
    fun mobileDefault_run_withLooper_usesNetworkNameAndIcon() =
        testScope.runTest {
            val latest by
                collectLastValue(
+43 −32
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.systemui.common.shared.model.ContentDescription.Companion.loa
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
@@ -38,7 +39,9 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -48,6 +51,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext

@OptIn(ExperimentalCoroutinesApi::class)
/** Observes internet state changes providing the [InternetTileModel]. */
@@ -55,6 +59,7 @@ class InternetTileDataInteractor
@Inject
constructor(
    private val context: Context,
    @Main private val mainCoroutineContext: CoroutineContext,
    @Application private val scope: CoroutineScope,
    airplaneModeRepository: AirplaneModeRepository,
    private val connectivityRepository: ConnectivityRepository,
@@ -115,6 +120,9 @@ constructor(
                        it.signalLevelIcon,
                        mobileDataContentName,
                    ) { networkNameModel, signalIcon, dataContentDescription ->
                        Triple(networkNameModel, signalIcon, dataContentDescription)
                    }
                    .mapLatestConflated { (networkNameModel, signalIcon, dataContentDescription) ->
                        when (signalIcon) {
                            is SignalIconModel.Cellular -> {
                                val secondary =
@@ -123,21 +131,24 @@ constructor(
                                        dataContentDescription
                                    )

                            val stateLevel = signalIcon.level
                            val drawable = SignalDrawable(context)
                            drawable.setLevel(stateLevel)
                                val drawable =
                                    withContext(mainCoroutineContext) { SignalDrawable(context) }
                                drawable.setLevel(signalIcon.level)
                                val loadedIcon = Icon.Loaded(drawable, null)

                                InternetTileModel.Active(
                                    secondaryTitle = secondary,
                                    icon = loadedIcon,
                                stateDescription = ContentDescription.Loaded(secondary.toString()),
                                    stateDescription =
                                        ContentDescription.Loaded(secondary.toString()),
                                    contentDescription = ContentDescription.Loaded(internetLabel),
                                )
                            }
                            is SignalIconModel.Satellite -> {
                                val secondary =
                                signalIcon.icon.contentDescription.loadContentDescription(context)
                                    signalIcon.icon.contentDescription.loadContentDescription(
                                        context
                                    )
                                InternetTileModel.Active(
                                    secondaryTitle = secondary,
                                    iconId = signalIcon.icon.res,