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

Commit 7bee1511 authored by Chaohui Wang's avatar Chaohui Wang Committed by Android (Google) Code Review
Browse files

Merge "Fix TetherPreferenceController ANR" into main

parents d0e7d9fc e731f7cc
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