diff --git a/src/com/android/settings/network/TetherPreferenceController.kt b/src/com/android/settings/network/TetherPreferenceController.kt index c36a2382c62..524eb7836fc 100644 --- a/src/com/android/settings/network/TetherPreferenceController.kt +++ b/src/com/android/settings/network/TetherPreferenceController.kt @@ -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) { + if (!TetherUtil.isTetherAvailable(mContext)) { + keys += preferenceKey + } + } + companion object { @JvmStatic fun isTetherConfigDisallowed(context: Context?): Boolean = diff --git a/tests/spa_unit/src/com/android/settings/network/TetherPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/TetherPreferenceControllerTest.kt index 51d2c87dff3..205cfa06277 100644 --- a/tests/spa_unit/src/com/android/settings/network/TetherPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/TetherPreferenceControllerTest.kt @@ -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 { 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