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
diff --git a/src/com/android/settings/network/TetherPreferenceController.kt b/src/com/android/settings/network/TetherPreferenceController.kt
index c36a2382..524eb78 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.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 @@
     }
 
     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 @@
         }
     }
 
+    override fun updateNonIndexableKeys(keys: MutableList<String>) {
+        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 51d2c87..205cfa0 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 @@
 
 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.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 @@
 
     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 @@
             .startMocking()
 
         ExtendedMockito.doReturn(true).`when` { TetherUtil.isTetherAvailable(context) }
+
+        preferenceScreen.addPreference(preference)
+        controller.displayPreference(preferenceScreen)
     }
 
     @After
@@ -57,21 +74,30 @@
     }
 
     @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