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

Commit e731f7cc authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Fix TetherPreferenceController ANR

getAvailabilityStatus() is called in main thread, so we should avoid
time consuming works in it.

Fix: 377146536
Flag: EXEMPT bug fix
Test: manual - on Network & internet
Test: unit test
Change-Id: Ib5ee19744cf164f91aa90be982f5fc5eead5d4d3
parent a25c82a3
Loading
Loading
Loading
Loading
+30 −5
Original line number Diff line number Diff line
@@ -35,19 +35,35 @@ import com.android.settingslib.TetherUtil
import com.android.settingslib.Utils
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class TetherPreferenceController(context: Context, key: String) :
    BasePreferenceController(context, key) {
class TetherPreferenceController(
    context: Context,
    key: String,
    private val tetheredRepository: TetheredRepository = TetheredRepository(context),
) : BasePreferenceController(context, key) {

    private val tetheredRepository = TetheredRepository(context)
    private val tetheringManager = mContext.getSystemService(TetheringManager::class.java)!!

    private var preference: Preference? = null

    override fun getAvailabilityStatus() =
        if (TetherUtil.isTetherAvailable(mContext)) AVAILABLE else CONDITIONALLY_UNAVAILABLE
    private val isTetherAvailableFlow =
        flow { emit(TetherUtil.isTetherAvailable(mContext)) }
            .distinctUntilChanged()
            .conflate()
            .flowOn(Dispatchers.Default)

    /**
     * Always returns available here to avoid ANR.
     * - Actual UI visibility is handled in [onViewCreated].
     * - Search visibility is handled in [updateNonIndexableKeys].
     */
    override fun getAvailabilityStatus() = AVAILABLE

    override fun displayPreference(screen: PreferenceScreen) {
        super.displayPreference(screen)
@@ -55,6 +71,9 @@ class TetherPreferenceController(context: Context, key: String) :
    }

    override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
        isTetherAvailableFlow.collectLatestWithLifecycle(viewLifecycleOwner) {
            preference?.isVisible = it
        }
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                getTitleResId()?.let { preference?.setTitle(it) }
@@ -84,6 +103,12 @@ class TetherPreferenceController(context: Context, key: String) :
        }
    }

    override fun updateNonIndexableKeys(keys: MutableList<String>) {
        if (!TetherUtil.isTetherAvailable(mContext)) {
            keys += preferenceKey
        }
    }

    companion object {
        @JvmStatic
        fun isTetherConfigDisallowed(context: Context?): Boolean =
+33 −7
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@ package com.android.settings.network

import android.content.Context
import android.net.TetheringManager
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.dx.mockito.inline.extended.ExtendedMockito
@@ -25,11 +28,15 @@ import com.android.settings.R
import com.android.settings.core.BasePreferenceController
import com.android.settingslib.TetherUtil
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoSession
import org.mockito.kotlin.mock
import org.mockito.quality.Strictness

@RunWith(AndroidJUnit4::class)
@@ -38,7 +45,14 @@ class TetherPreferenceControllerTest {

    private val context: Context = ApplicationProvider.getApplicationContext()

    private val controller = TetherPreferenceController(context, TEST_KEY)
    private val mockTetheredRepository =
        mock<TetheredRepository> { on { tetheredTypesFlow() }.thenReturn(flowOf(emptySet())) }

    private val controller = TetherPreferenceController(context, TEST_KEY, mockTetheredRepository)

    private val preference = PreferenceCategory(context).apply { key = TEST_KEY }

    private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)

    @Before
    fun setUp() {
@@ -49,6 +63,9 @@ class TetherPreferenceControllerTest {
            .startMocking()

        ExtendedMockito.doReturn(true).`when` { TetherUtil.isTetherAvailable(context) }

        preferenceScreen.addPreference(preference)
        controller.displayPreference(preferenceScreen)
    }

    @After
@@ -57,21 +74,30 @@ class TetherPreferenceControllerTest {
    }

    @Test
    fun getAvailabilityStatus_whenTetherAvailable() {
        ExtendedMockito.doReturn(true).`when` { TetherUtil.isTetherAvailable(context) }

    fun getAvailabilityStatus_alwaysReturnAvailable() {
        val availabilityStatus = controller.availabilityStatus

        assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE)
    }

    @Test
    fun getAvailabilityStatus_whenTetherNotAvailable() {
    fun onViewCreated_whenTetherAvailable() = runBlocking {
        ExtendedMockito.doReturn(true).`when` { TetherUtil.isTetherAvailable(context) }

        controller.onViewCreated(TestLifecycleOwner())
        delay(100)

        assertThat(preference.isVisible).isTrue()
    }

    @Test
    fun onViewCreated_whenTetherNotAvailable() = runBlocking {
        ExtendedMockito.doReturn(false).`when` { TetherUtil.isTetherAvailable(context) }

        val availabilityStatus = controller.availabilityStatus
        controller.onViewCreated(TestLifecycleOwner())
        delay(100)

        assertThat(availabilityStatus).isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE)
        assertThat(preference.isVisible).isFalse()
    }

    @Test