New MobileDataEnabledFlow
To easily collect the mobile data enabled setting changes.
Bug: 308903704
Test: manual - on DataUsageList
Test: unit tests
Change-Id: I31327f59ac32c1a621e2853e64bd30d7d17e079c
diff --git a/src/com/android/settings/datausage/DataUsageList.kt b/src/com/android/settings/datausage/DataUsageList.kt
index 7240150..30e8db3 100644
--- a/src/com/android/settings/datausage/DataUsageList.kt
+++ b/src/com/android/settings/datausage/DataUsageList.kt
@@ -31,9 +31,10 @@
import com.android.settings.R
import com.android.settings.datausage.lib.BillingCycleRepository
import com.android.settings.datausage.lib.NetworkUsageData
-import com.android.settings.network.MobileDataEnabledListener
import com.android.settings.network.MobileNetworkRepository
+import com.android.settings.network.mobileDataEnabledFlow
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spaprivileged.framework.common.userManager
import com.android.settingslib.utils.ThreadUtils
import kotlin.jvm.optionals.getOrNull
@@ -43,10 +44,7 @@
* to inspect based on usage cycle and control through [NetworkPolicy].
*/
@OpenForTesting
-open class DataUsageList : DataUsageBaseFragment(), MobileDataEnabledListener.Client {
- @VisibleForTesting
- lateinit var dataStateListener: MobileDataEnabledListener
-
+open class DataUsageList : DataUsageBaseFragment() {
@JvmField
@VisibleForTesting
var template: NetworkTemplate? = null
@@ -89,7 +87,6 @@
return
}
updateSubscriptionInfoEntity()
- dataStateListener = MobileDataEnabledListener(activity, this)
dataUsageListAppsController = use(DataUsageListAppsController::class.java).apply {
init(template)
}
@@ -103,6 +100,9 @@
override fun onViewCreated(v: View, savedInstanceState: Bundle?) {
super.onViewCreated(v, savedInstanceState)
+ requireContext().mobileDataEnabledFlow(subId)
+ .collectLatestWithLifecycle(viewLifecycleOwner) { updatePolicy() }
+
val template = template ?: return
dataUsageListHeaderController = DataUsageListHeaderController(
setPinnedHeaderView(R.layout.apps_filter_spinner),
@@ -114,17 +114,6 @@
)
}
- override fun onResume() {
- super.onResume()
- dataStateListener.start(subId)
- updatePolicy()
- }
-
- override fun onPause() {
- super.onPause()
- dataStateListener.stop()
- }
-
override fun getPreferenceScreenResId() = R.xml.data_usage_list
override fun getLogTag() = TAG
@@ -154,13 +143,6 @@
}
}
- /**
- * Implementation of `MobileDataEnabledListener.Client`
- */
- override fun onMobileDataEnabledChange() {
- updatePolicy()
- }
-
/** Update chart sweeps and cycle list to reflect [NetworkPolicy] for current [template]. */
@VisibleForTesting
fun updatePolicy() {
diff --git a/src/com/android/settings/network/MobileDataEnabledFlow.kt b/src/com/android/settings/network/MobileDataEnabledFlow.kt
new file mode 100644
index 0000000..2342377
--- /dev/null
+++ b/src/com/android/settings/network/MobileDataEnabledFlow.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network
+
+import android.content.Context
+import android.provider.Settings
+import android.telephony.SubscriptionManager
+import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalChangeFlow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.merge
+
+/**
+ * Flow for mobile data enabled changed event.
+ *
+ * Note: This flow can only notify enabled status changes, cannot provide the latest status.
+ */
+fun Context.mobileDataEnabledFlow(subId: Int): Flow<Unit> {
+ val flow = settingsGlobalChangeFlow(Settings.Global.MOBILE_DATA)
+ return when (subId) {
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID -> flow
+ else -> {
+ val subIdFlow = settingsGlobalChangeFlow(
+ name = Settings.Global.MOBILE_DATA + subId,
+ sendInitialValue = false,
+ )
+ merge(flow, subIdFlow)
+ }
+ }
+}
diff --git a/src/com/android/settings/network/MobileDataEnabledListener.java b/src/com/android/settings/network/MobileDataEnabledListener.java
index b030823..f2d55ab 100644
--- a/src/com/android/settings/network/MobileDataEnabledListener.java
+++ b/src/com/android/settings/network/MobileDataEnabledListener.java
@@ -20,7 +20,12 @@
import android.provider.Settings;
import android.telephony.SubscriptionManager;
-/** Helper class to listen for changes in the enabled state of mobile data. */
+/**
+ * Helper class to listen for changes in the enabled state of mobile data.
+ *
+ * @deprecated use {@link MobileDataEnabledFlowKt} instead
+ */
+@Deprecated
public class MobileDataEnabledListener {
private Context mContext;
private Client mClient;
diff --git a/src/com/android/settings/network/MobileNetworkListFragment.kt b/src/com/android/settings/network/MobileNetworkListFragment.kt
index 5000afd..09b1150 100644
--- a/src/com/android/settings/network/MobileNetworkListFragment.kt
+++ b/src/com/android/settings/network/MobileNetworkListFragment.kt
@@ -28,15 +28,16 @@
import com.android.settings.dashboard.DashboardFragment
import com.android.settings.network.telephony.MobileNetworkUtils
import com.android.settings.search.BaseSearchIndexProvider
-import com.android.settings.utils.observeSettingsGlobalBoolean
import com.android.settingslib.search.SearchIndexable
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
@SearchIndexable(forTarget = SearchIndexable.ALL and SearchIndexable.ARC.inv())
class MobileNetworkListFragment : DashboardFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- observeAirplaneModeAndFinishIfOn()
+ collectAirplaneModeAndFinishIfOn()
}
override fun onResume() {
@@ -59,15 +60,13 @@
private const val KEY_ADD_SIM = "add_sim"
@JvmStatic
- fun SettingsPreferenceFragment.observeAirplaneModeAndFinishIfOn() {
- requireContext().observeSettingsGlobalBoolean(
- name = Settings.Global.AIRPLANE_MODE_ON,
- lifecycle = viewLifecycleOwner.lifecycle,
- ) { isAirplaneModeOn: Boolean ->
- if (isAirplaneModeOn) {
- finish()
+ fun SettingsPreferenceFragment.collectAirplaneModeAndFinishIfOn() {
+ requireContext().settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON)
+ .collectLatestWithLifecycle(viewLifecycleOwner) { isAirplaneModeOn ->
+ if (isAirplaneModeOn) {
+ finish()
+ }
}
- }
}
@JvmField
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index 7e290b8..a514414 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -16,7 +16,7 @@
package com.android.settings.network.telephony;
-import static com.android.settings.network.MobileNetworkListFragment.observeAirplaneModeAndFinishIfOn;
+import static com.android.settings.network.MobileNetworkListFragment.collectAirplaneModeAndFinishIfOn;
import android.app.Activity;
import android.app.settings.SettingsEnums;
@@ -334,7 +334,7 @@
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- observeAirplaneModeAndFinishIfOn(this);
+ collectAirplaneModeAndFinishIfOn(this);
}
@Override
diff --git a/src/com/android/settings/utils/SettingsGlobalBooleanDelegate.kt b/src/com/android/settings/utils/SettingsGlobalBooleanDelegate.kt
deleted file mode 100644
index fdfbdb4..0000000
--- a/src/com/android/settings/utils/SettingsGlobalBooleanDelegate.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.utils
-
-import android.content.ContentResolver
-import android.content.Context
-import android.database.ContentObserver
-import android.os.Handler
-import android.provider.Settings
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import kotlin.properties.ReadWriteProperty
-import kotlin.reflect.KProperty
-
-fun Context.observeSettingsGlobalBoolean(
- name: String,
- lifecycle: Lifecycle,
- onChange: (newValue: Boolean) -> Unit,
-) {
- val field by settingsGlobalBoolean(name)
- val contentObserver = object : ContentObserver(Handler.getMain()) {
- override fun onChange(selfChange: Boolean) {
- onChange(field)
- }
- }
- val uri = Settings.Global.getUriFor(name)
- lifecycle.addObserver(object : DefaultLifecycleObserver {
- override fun onStart(owner: LifecycleOwner) {
- contentResolver.registerContentObserver(uri, false, contentObserver)
- onChange(field)
- }
-
- override fun onStop(owner: LifecycleOwner) {
- contentResolver.unregisterContentObserver(contentObserver)
- }
- })
-}
-
-fun Context.settingsGlobalBoolean(name: String): ReadWriteProperty<Any?, Boolean> =
- SettingsGlobalBooleanDelegate(this, name)
-
-private class SettingsGlobalBooleanDelegate(context: Context, private val name: String) :
- ReadWriteProperty<Any?, Boolean> {
-
- private val contentResolver: ContentResolver = context.contentResolver
-
- override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean =
- Settings.Global.getInt(contentResolver, name, 0) != 0
-
- override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
- Settings.Global.putInt(contentResolver, name, if (value) 1 else 0)
- }
-}
diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.kt b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.kt
index 90bb048..39b8446 100644
--- a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.kt
+++ b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.kt
@@ -23,22 +23,18 @@
import android.os.UserManager
import android.provider.Settings
import androidx.preference.Preference
-import androidx.preference.PreferenceManager
import androidx.test.core.app.ApplicationProvider
import com.android.settings.datausage.DataUsageListTest.ShadowDataUsageBaseFragment
import com.android.settings.datausage.TemplatePreference.NetworkServices
import com.android.settings.datausage.lib.BillingCycleRepository
-import com.android.settings.network.MobileDataEnabledListener
import com.android.settings.testutils.FakeFeatureFactory
import com.android.settingslib.NetworkPolicyEditor
import com.android.settingslib.core.AbstractPreferenceController
-import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
@@ -62,9 +58,6 @@
val mockito: MockitoRule = MockitoJUnit.rule()
@Mock
- private lateinit var mobileDataEnabledListener: MobileDataEnabledListener
-
- @Mock
private lateinit var networkServices: NetworkServices
@Mock
@@ -86,7 +79,6 @@
fun setUp() {
FakeFeatureFactory.setupForTest()
networkServices.mPolicyEditor = mock(NetworkPolicyEditor::class.java)
- dataUsageList.dataStateListener = mobileDataEnabledListener
doReturn(context).`when`(dataUsageList).context
doReturn(userManager).`when`(context).getSystemService(UserManager::class.java)
doReturn(false).`when`(userManager).isGuestUser
@@ -113,46 +105,6 @@
}
@Test
- fun resume_shouldListenDataStateChange() {
- dataUsageList.template = mock(NetworkTemplate::class.java)
- dataUsageList.onCreate(null)
- dataUsageList.dataStateListener = mobileDataEnabledListener
- ReflectionHelpers.setField(
- dataUsageList,
- "mVisibilityLoggerMixin",
- mock(VisibilityLoggerMixin::class.java),
- )
- ReflectionHelpers.setField(
- dataUsageList,
- "mPreferenceManager",
- mock(PreferenceManager::class.java),
- )
- dataUsageList.onResume()
- verify(mobileDataEnabledListener).start(ArgumentMatchers.anyInt())
- dataUsageList.onPause()
- }
-
- @Test
- fun pause_shouldUnlistenDataStateChange() {
- dataUsageList.template = mock(NetworkTemplate::class.java)
- dataUsageList.onCreate(null)
- dataUsageList.dataStateListener = mobileDataEnabledListener
- ReflectionHelpers.setField(
- dataUsageList, "mVisibilityLoggerMixin", mock(
- VisibilityLoggerMixin::class.java
- )
- )
- ReflectionHelpers.setField(
- dataUsageList, "mPreferenceManager", mock(
- PreferenceManager::class.java
- )
- )
- dataUsageList.onResume()
- dataUsageList.onPause()
- verify(mobileDataEnabledListener).stop()
- }
-
- @Test
fun processArgument_shouldGetTemplateFromArgument() {
val args = Bundle()
args.putParcelable(
diff --git a/tests/spa_unit/src/com/android/settings/network/MobileDataEnabledFlowTest.kt b/tests/spa_unit/src/com/android/settings/network/MobileDataEnabledFlowTest.kt
new file mode 100644
index 0000000..4862309
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/MobileDataEnabledFlowTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network
+
+import android.content.Context
+import android.provider.Settings
+import android.telephony.SubscriptionManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spa.testutils.toListWithTimeout
+import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBoolean
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MobileDataEnabledFlowTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun mobileDataEnabledFlow_notified(): Unit = runBlocking {
+ val flow = context.mobileDataEnabledFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+
+ assertThat(flow.firstWithTimeoutOrNull()).isNotNull()
+ }
+
+ @Test
+ fun mobileDataEnabledFlow_changed_notified(): Unit = runBlocking {
+ var mobileDataEnabled by context.settingsGlobalBoolean(Settings.Global.MOBILE_DATA)
+ mobileDataEnabled = false
+
+ val flow = context.mobileDataEnabledFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ mobileDataEnabled = true
+
+ assertThat(flow.firstWithTimeoutOrNull()).isNotNull()
+ }
+
+ @Test
+ fun mobileDataEnabledFlow_forSubIdNotChanged(): Unit = runBlocking {
+ var mobileDataEnabled by context.settingsGlobalBoolean(Settings.Global.MOBILE_DATA)
+ mobileDataEnabled = false
+ var mobileDataEnabledForSubId
+ by context.settingsGlobalBoolean(Settings.Global.MOBILE_DATA + SUB_ID)
+ mobileDataEnabledForSubId = false
+
+ val listDeferred = async {
+ context.mobileDataEnabledFlow(SUB_ID).toListWithTimeout()
+ }
+
+ assertThat(listDeferred.await()).hasSize(1)
+ }
+
+ @Test
+ fun mobileDataEnabledFlow_forSubIdChanged(): Unit = runBlocking {
+ var mobileDataEnabled by context.settingsGlobalBoolean(Settings.Global.MOBILE_DATA)
+ mobileDataEnabled = false
+ var mobileDataEnabledForSubId
+ by context.settingsGlobalBoolean(Settings.Global.MOBILE_DATA + SUB_ID)
+ mobileDataEnabledForSubId = false
+
+ val listDeferred = async {
+ context.mobileDataEnabledFlow(SUB_ID).toListWithTimeout()
+ }
+ delay(100)
+ mobileDataEnabledForSubId = true
+
+ assertThat(listDeferred.await()).hasSize(2)
+ }
+
+ private companion object {
+ const val SUB_ID = 123
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/utils/SettingsGlobalBooleanDelegateTest.kt b/tests/spa_unit/src/com/android/settings/utils/SettingsGlobalBooleanDelegateTest.kt
deleted file mode 100644
index 75c3685..0000000
--- a/tests/spa_unit/src/com/android/settings/utils/SettingsGlobalBooleanDelegateTest.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.utils
-
-import android.content.Context
-import android.provider.Settings
-import androidx.lifecycle.testing.TestLifecycleOwner
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class SettingsGlobalBooleanDelegateTest {
-
- private val context: Context = ApplicationProvider.getApplicationContext()
-
- @Test
- fun getValue_setTrue_returnTrue() {
- Settings.Global.putInt(context.contentResolver, TEST_NAME, 1)
-
- val value by context.settingsGlobalBoolean(TEST_NAME)
-
- assertThat(value).isTrue()
- }
-
- @Test
- fun getValue_setFalse_returnFalse() {
- Settings.Global.putInt(context.contentResolver, TEST_NAME, 0)
-
- val value by context.settingsGlobalBoolean(TEST_NAME)
-
- assertThat(value).isFalse()
- }
-
- @Test
- fun setValue_setTrue_returnTrue() {
- var value by context.settingsGlobalBoolean(TEST_NAME)
-
- value = true
-
- assertThat(Settings.Global.getInt(context.contentResolver, TEST_NAME, 0)).isEqualTo(1)
- }
-
- @Test
- fun setValue_setFalse_returnFalse() {
- var value by context.settingsGlobalBoolean(TEST_NAME)
-
- value = false
-
- assertThat(Settings.Global.getInt(context.contentResolver, TEST_NAME, 1)).isEqualTo(0)
- }
-
- @Test
- fun observeSettingsGlobalBoolean_valueNotChanged() {
- var value by context.settingsGlobalBoolean(TEST_NAME)
- value = false
- var newValue: Boolean? = null
-
- context.observeSettingsGlobalBoolean(TEST_NAME, TestLifecycleOwner().lifecycle) {
- newValue = it
- }
-
- assertThat(newValue).isFalse()
- }
-
- @Test
- fun observeSettingsGlobalBoolean_valueChanged() {
- var value by context.settingsGlobalBoolean(TEST_NAME)
- value = false
- var newValue: Boolean? = null
-
- context.observeSettingsGlobalBoolean(TEST_NAME, TestLifecycleOwner().lifecycle) {
- newValue = it
- }
- value = true
-
- assertThat(newValue).isFalse()
- }
-
- private companion object {
- const val TEST_NAME = "test_boolean_delegate"
- }
-}