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
This commit is contained in:
@@ -35,19 +35,35 @@ import com.android.settingslib.TetherUtil
|
|||||||
import com.android.settingslib.Utils
|
import com.android.settingslib.Utils
|
||||||
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
|
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
|
||||||
import kotlinx.coroutines.Dispatchers
|
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.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class TetherPreferenceController(context: Context, key: String) :
|
class TetherPreferenceController(
|
||||||
BasePreferenceController(context, key) {
|
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 val tetheringManager = mContext.getSystemService(TetheringManager::class.java)!!
|
||||||
|
|
||||||
private var preference: Preference? = null
|
private var preference: Preference? = null
|
||||||
|
|
||||||
override fun getAvailabilityStatus() =
|
private val isTetherAvailableFlow =
|
||||||
if (TetherUtil.isTetherAvailable(mContext)) AVAILABLE else CONDITIONALLY_UNAVAILABLE
|
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) {
|
override fun displayPreference(screen: PreferenceScreen) {
|
||||||
super.displayPreference(screen)
|
super.displayPreference(screen)
|
||||||
@@ -55,6 +71,9 @@ class TetherPreferenceController(context: Context, key: String) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
|
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
|
||||||
|
isTetherAvailableFlow.collectLatestWithLifecycle(viewLifecycleOwner) {
|
||||||
|
preference?.isVisible = it
|
||||||
|
}
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
getTitleResId()?.let { preference?.setTitle(it) }
|
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 {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isTetherConfigDisallowed(context: Context?): Boolean =
|
fun isTetherConfigDisallowed(context: Context?): Boolean =
|
||||||
|
@@ -18,6 +18,9 @@ package com.android.settings.network
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.TetheringManager
|
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.core.app.ApplicationProvider
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.android.dx.mockito.inline.extended.ExtendedMockito
|
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.settings.core.BasePreferenceController
|
||||||
import com.android.settingslib.TetherUtil
|
import com.android.settingslib.TetherUtil
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.MockitoSession
|
import org.mockito.MockitoSession
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
import org.mockito.quality.Strictness
|
import org.mockito.quality.Strictness
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@@ -38,7 +45,14 @@ class TetherPreferenceControllerTest {
|
|||||||
|
|
||||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
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
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
@@ -49,6 +63,9 @@ class TetherPreferenceControllerTest {
|
|||||||
.startMocking()
|
.startMocking()
|
||||||
|
|
||||||
ExtendedMockito.doReturn(true).`when` { TetherUtil.isTetherAvailable(context) }
|
ExtendedMockito.doReturn(true).`when` { TetherUtil.isTetherAvailable(context) }
|
||||||
|
|
||||||
|
preferenceScreen.addPreference(preference)
|
||||||
|
controller.displayPreference(preferenceScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -57,21 +74,30 @@ class TetherPreferenceControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getAvailabilityStatus_whenTetherAvailable() {
|
fun getAvailabilityStatus_alwaysReturnAvailable() {
|
||||||
ExtendedMockito.doReturn(true).`when` { TetherUtil.isTetherAvailable(context) }
|
|
||||||
|
|
||||||
val availabilityStatus = controller.availabilityStatus
|
val availabilityStatus = controller.availabilityStatus
|
||||||
|
|
||||||
assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE)
|
assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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) }
|
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
|
@Test
|
||||||
|
Reference in New Issue
Block a user