From 387753c0050cca382cac2ae698bb849f0c406cfd Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Sun, 8 Dec 2024 21:08:38 +0800 Subject: [PATCH 1/2] [Catalyst] Refine DataSaverMainSwitchPreference Export datastore for preference state monitoring. Bug: 368359883 Flag: com.android.settings.flags.catalyst Test: devtool Change-Id: Ie6b001cdd6a8d27f9cb16d563852ad88b7366916 --- .../DataSaverMainSwitchPreference.kt | 40 ++++++++----------- .../settings/datausage/DataSaverScreen.kt | 32 +++++++++------ .../settings/datausage/DataSaverScreenTest.kt | 5 ++- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt b/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt index e4e38d42530..23cfadce3d0 100644 --- a/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt +++ b/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt @@ -19,18 +19,14 @@ package com.android.settings.datausage import android.content.Context import com.android.settings.R import com.android.settings.widget.MainSwitchBarMetadata +import com.android.settingslib.datastore.AbstractKeyedDataObservable +import com.android.settingslib.datastore.DataChangeReason import com.android.settingslib.datastore.KeyValueStore -import com.android.settingslib.datastore.NoOpKeyedObservable -import com.android.settingslib.metadata.PreferenceLifecycleContext import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.metadata.SensitivityLevel -class DataSaverMainSwitchPreference(context: Context) : - MainSwitchBarMetadata, PreferenceLifecycleProvider { - - private val dataSaverBackend = DataSaverBackend(context) - private var dataSaverBackendListener: DataSaverBackend.Listener? = null +class DataSaverMainSwitchPreference : MainSwitchBarMetadata, PreferenceLifecycleProvider { override val key get() = KEY @@ -38,7 +34,7 @@ class DataSaverMainSwitchPreference(context: Context) : override val title get() = R.string.data_saver_switch_title - override fun storage(context: Context): KeyValueStore = DataSaverStore(dataSaverBackend) + override fun storage(context: Context) = createDataStore(context) override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW @@ -49,24 +45,11 @@ class DataSaverMainSwitchPreference(context: Context) : override val sensitivityLevel get() = SensitivityLevel.NO_SENSITIVITY - override fun onStart(context: PreferenceLifecycleContext) { - val listener = DataSaverBackend.Listener { context.notifyPreferenceChange(KEY) } - dataSaverBackendListener = listener - dataSaverBackend.addListener(listener) - } - - override fun onStop(context: PreferenceLifecycleContext) { - dataSaverBackendListener?.let { - dataSaverBackend.remListener(it) - dataSaverBackendListener = null - } - } - @Suppress("UNCHECKED_CAST") private class DataSaverStore(private val dataSaverBackend: DataSaverBackend) : - NoOpKeyedObservable(), KeyValueStore { + AbstractKeyedDataObservable(), KeyValueStore, DataSaverBackend.Listener { - override fun contains(key: String) = true // just assume the datastore contains the value + override fun contains(key: String) = key == KEY override fun getValue(key: String, valueType: Class): T? = dataSaverBackend.isDataSaverEnabled as T? @@ -74,9 +57,20 @@ class DataSaverMainSwitchPreference(context: Context) : override fun setValue(key: String, valueType: Class, value: T?) { dataSaverBackend.isDataSaverEnabled = value as Boolean } + + override fun onFirstObserverAdded() = dataSaverBackend.addListener(this) + + override fun onLastObserverRemoved() = dataSaverBackend.remListener(this) + + override fun onDataSaverChanged(isDataSaving: Boolean) = + notifyChange(KEY, DataChangeReason.UPDATE) } companion object { const val KEY = "use_data_saver" + + /** Creates [KeyValueStore] for data saver preference. */ + fun createDataStore(context: Context): KeyValueStore = + DataSaverStore(DataSaverBackend(context)) } } diff --git a/src/com/android/settings/datausage/DataSaverScreen.kt b/src/com/android/settings/datausage/DataSaverScreen.kt index 2e806432833..fd4441fbbe2 100644 --- a/src/com/android/settings/datausage/DataSaverScreen.kt +++ b/src/com/android/settings/datausage/DataSaverScreen.kt @@ -19,24 +19,30 @@ package com.android.settings.datausage import android.content.Context import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import com.android.settings.R +import com.android.settings.Settings.DataSaverSummaryActivity import com.android.settings.flags.Flags +import com.android.settings.utils.makeLaunchIntent +import com.android.settingslib.datastore.HandlerExecutor +import com.android.settingslib.datastore.KeyedObserver import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceLifecycleContext import com.android.settingslib.metadata.PreferenceLifecycleProvider +import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.metadata.ProvidePreferenceScreen import com.android.settingslib.metadata.preferenceHierarchy import com.android.settingslib.preference.PreferenceScreenCreator +import com.android.settings.datausage.DataSaverMainSwitchPreference.Companion.KEY as DATA_SAVER_KEY @ProvidePreferenceScreen -class DataSaverScreen : +class DataSaverScreen(context: Context) : PreferenceScreenCreator, PreferenceAvailabilityProvider, PreferenceSummaryProvider, PreferenceLifecycleProvider { - private var dataSaverBackend: DataSaverBackend? = null - private var dataSaverBackendListener: DataSaverBackend.Listener? = null + private val dataSaverStore = DataSaverMainSwitchPreference.createDataStore(context) + private lateinit var keyedObserver: KeyedObserver override val key get() = KEY @@ -53,7 +59,7 @@ class DataSaverScreen : override fun getSummary(context: Context): CharSequence? = when { - DataSaverBackend(context).isDataSaverEnabled -> + dataSaverStore.getBoolean(DATA_SAVER_KEY) == true -> context.getString(R.string.data_saver_on) else -> context.getString(R.string.data_saver_off) } @@ -65,21 +71,21 @@ class DataSaverScreen : override fun fragmentClass() = DataSaverSummary::class.java + override fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?) = + makeLaunchIntent(context, DataSaverSummaryActivity::class.java, metadata?.key) + override fun getPreferenceHierarchy(context: Context) = - preferenceHierarchy(this) { +DataSaverMainSwitchPreference(context) } + preferenceHierarchy(this) { +DataSaverMainSwitchPreference() } override fun hasCompleteHierarchy() = false - override fun onStart(context: PreferenceLifecycleContext) { - val listener = DataSaverBackend.Listener { context.notifyPreferenceChange(KEY) } - dataSaverBackendListener = listener - dataSaverBackend = DataSaverBackend(context).apply { addListener(listener) } + override fun onCreate(context: PreferenceLifecycleContext) { + keyedObserver = KeyedObserver { _, _ -> context.notifyPreferenceChange(KEY) } + dataSaverStore.addObserver(DATA_SAVER_KEY, keyedObserver, HandlerExecutor.main) } - override fun onStop(context: PreferenceLifecycleContext) { - dataSaverBackend?.remListener(dataSaverBackendListener) - dataSaverBackend = null - dataSaverBackendListener = null + override fun onDestroy(context: PreferenceLifecycleContext) { + dataSaverStore.removeObserver(DATA_SAVER_KEY, keyedObserver) } companion object { diff --git a/tests/robotests/src/com/android/settings/datausage/DataSaverScreenTest.kt b/tests/robotests/src/com/android/settings/datausage/DataSaverScreenTest.kt index 08af4c0e862..24e8213e566 100644 --- a/tests/robotests/src/com/android/settings/datausage/DataSaverScreenTest.kt +++ b/tests/robotests/src/com/android/settings/datausage/DataSaverScreenTest.kt @@ -16,13 +16,16 @@ package com.android.settings.datausage +import androidx.test.core.app.ApplicationProvider import com.android.settings.flags.Flags import com.android.settingslib.preference.CatalystScreenTestCase import com.google.common.truth.Truth.assertThat import org.junit.Test class DataSaverScreenTest : CatalystScreenTestCase() { - override val preferenceScreenCreator = DataSaverScreen() + override val preferenceScreenCreator = + DataSaverScreen(ApplicationProvider.getApplicationContext()) + override val flagName get() = Flags.FLAG_CATALYST_RESTRICT_BACKGROUND_PARENT_ENTRY From d989dc5c36c1be15e4298628f33aefda6d46875c Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Sun, 8 Dec 2024 21:19:47 +0800 Subject: [PATCH 2/2] [Catalyst] Refine WifiHotspotSwitchPreference NO_IFTTT=Catalyst only Bug: 368359963 Flag: com.android.settings.flags.catalyst_tether_settings Test: devtool Change-Id: Ica09fe05cc4d30e1f55b4bd91996425ef951abf7 --- .../settings/network/tether/TetherScreen.kt | 23 ++- .../tether/WifiHotspotSwitchPreference.kt | 153 ++++++++---------- 2 files changed, 81 insertions(+), 95 deletions(-) diff --git a/src/com/android/settings/network/tether/TetherScreen.kt b/src/com/android/settings/network/tether/TetherScreen.kt index c666683a902..5865c861f58 100644 --- a/src/com/android/settings/network/tether/TetherScreen.kt +++ b/src/com/android/settings/network/tether/TetherScreen.kt @@ -20,19 +20,27 @@ import android.net.TetheringManager import android.os.UserManager import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R +import com.android.settings.Settings.TetherSettingsActivity +import com.android.settings.datausage.DataSaverMainSwitchPreference import com.android.settings.flags.Flags import com.android.settings.network.TetherPreferenceController +import com.android.settings.utils.makeLaunchIntent import com.android.settings.wifi.tether.WifiHotspotSwitchPreference import com.android.settingslib.TetherUtil import com.android.settingslib.Utils import com.android.settingslib.metadata.PreferenceAvailabilityProvider +import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.PreferenceTitleProvider import com.android.settingslib.metadata.ProvidePreferenceScreen import com.android.settingslib.metadata.preferenceHierarchy import com.android.settingslib.preference.PreferenceScreenCreator @ProvidePreferenceScreen class TetherScreen : - PreferenceScreenCreator, PreferenceAvailabilityProvider, PreferenceRestrictionMixin { + PreferenceScreenCreator, + PreferenceTitleProvider, + PreferenceAvailabilityProvider, + PreferenceRestrictionMixin { override val key: String get() = KEY @@ -43,7 +51,7 @@ class TetherScreen : override val keywords: Int get() = R.string.keywords_hotspot_tethering - override fun getPreferenceTitle(context: Context): CharSequence? = + override fun getTitle(context: Context): CharSequence? = if (TetherPreferenceController.isTetherConfigDisallowed(context)) { context.getText(R.string.tether_settings_title_all) } else { @@ -64,9 +72,14 @@ class TetherScreen : override fun fragmentClass() = TetherSettings::class.java - override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) { - +WifiHotspotSwitchPreference(context) - } + override fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?) = + makeLaunchIntent(context, TetherSettingsActivity::class.java, metadata?.key) + + override fun getPreferenceHierarchy(context: Context) = + preferenceHierarchy(this) { + val dataSaverStore = DataSaverMainSwitchPreference.createDataStore(context) + +WifiHotspotSwitchPreference(context, dataSaverStore) + } companion object { const val KEY = "tether_settings" diff --git a/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt b/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt index 4866d19ab17..c185e02c949 100644 --- a/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt +++ b/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt @@ -20,6 +20,7 @@ import android.app.settings.SettingsEnums import android.content.Context import android.content.Intent import android.net.TetheringManager +import android.net.TetheringManager.TETHERING_WIFI import android.net.wifi.WifiClient import android.net.wifi.WifiManager import android.os.UserManager @@ -30,7 +31,7 @@ import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R import com.android.settings.Utils import com.android.settings.core.SubSettingLauncher -import com.android.settings.datausage.DataSaverBackend +import com.android.settings.datausage.DataSaverMainSwitchPreference.Companion.KEY as DATA_SAVER_KEY import com.android.settings.wifi.WifiUtils.canShowWifiHotspot import com.android.settingslib.PrimarySwitchPreference import com.android.settingslib.TetherUtil @@ -38,9 +39,8 @@ import com.android.settingslib.datastore.AbstractKeyedDataObservable import com.android.settingslib.datastore.DataChangeReason import com.android.settingslib.datastore.HandlerExecutor import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.datastore.KeyedObserver import com.android.settingslib.metadata.PreferenceAvailabilityProvider -import com.android.settingslib.metadata.PreferenceLifecycleContext -import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.metadata.ReadWritePermit @@ -50,20 +50,15 @@ import com.android.settingslib.preference.PreferenceBinding import com.android.settingslib.wifi.WifiUtils.Companion.getWifiTetherSummaryForConnectedDevices // LINT.IfChange -@Deprecated("Deprecated in Java") @Suppress("MissingPermission", "NewApi", "UNCHECKED_CAST") -class WifiHotspotSwitchPreference(context: Context) : +class WifiHotspotSwitchPreference(context: Context, dataSaverStore: KeyValueStore) : SwitchPreference(KEY, R.string.wifi_hotspot_checkbox_text), PreferenceBinding, PreferenceAvailabilityProvider, PreferenceSummaryProvider, - PreferenceLifecycleProvider, PreferenceRestrictionMixin { - private val wifiHotspotStore = WifiHotspotStore(context) - - private val dataSaverBackend = DataSaverBackend(context) - private var dataSaverBackendListener: DataSaverBackend.Listener? = null + private val wifiHotspotStore = WifiHotspotStore(context, dataSaverStore) override fun isAvailable(context: Context) = canShowWifiHotspot(context) && @@ -71,22 +66,19 @@ class WifiHotspotSwitchPreference(context: Context) : !Utils.isMonkeyRunning() override fun getSummary(context: Context): CharSequence? = - when (wifiHotspotStore.sapState) { + when (context.wifiManager?.wifiApState) { WifiManager.WIFI_AP_STATE_ENABLING -> context.getString(R.string.wifi_tether_starting) - WifiManager.WIFI_AP_STATE_ENABLED -> - when (wifiHotspotStore.sapClientsSize) { - null -> - context.getString( - R.string.wifi_tether_enabled_subtext, - BidiFormatter.getInstance() - .unicodeWrap(context.getSoftApConfiguration()?.ssid), - ) - else -> - getWifiTetherSummaryForConnectedDevices( - context, - wifiHotspotStore.sapClientsSize!!, - ) + WifiManager.WIFI_AP_STATE_ENABLED -> { + val sapClientsSize = wifiHotspotStore.sapClientsSize + if (sapClientsSize == null) { + context.getString( + R.string.wifi_tether_enabled_subtext, + BidiFormatter.getInstance().unicodeWrap(context.wifiSsid), + ) + } else { + getWifiTetherSummaryForConnectedDevices(context, sapClientsSize) } + } WifiManager.WIFI_AP_STATE_DISABLING -> context.getString(R.string.wifi_tether_stopping) WifiManager.WIFI_AP_STATE_DISABLED -> context.getString(R.string.wifi_hotspot_off_subtext) @@ -108,7 +100,8 @@ class WifiHotspotSwitchPreference(context: Context) : .toIntent() override fun isEnabled(context: Context) = - !dataSaverBackend.isDataSaverEnabled && super.isEnabled(context) + wifiHotspotStore.dataSaverStore.getBoolean(DATA_SAVER_KEY) == true && + super.isEnabled(context) override val restrictionKeys get() = arrayOf(UserManager.DISALLOW_WIFI_TETHERING) @@ -117,10 +110,7 @@ class WifiHotspotSwitchPreference(context: Context) : ReadWritePermit.ALLOW override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = - when { - dataSaverBackend.isDataSaverEnabled -> ReadWritePermit.DISALLOW - else -> ReadWritePermit.ALLOW - } + ReadWritePermit.ALLOW override val sensitivityLevel get() = SensitivityLevel.HIGH_SENSITIVITY @@ -129,11 +119,17 @@ class WifiHotspotSwitchPreference(context: Context) : override fun storage(context: Context): KeyValueStore = wifiHotspotStore - private class WifiHotspotStore(private val context: Context) : - AbstractKeyedDataObservable(), KeyValueStore { + private class WifiHotspotStore( + private val context: Context, + val dataSaverStore: KeyValueStore, + ) : + AbstractKeyedDataObservable(), + KeyValueStore, + WifiTetherSoftApManager.WifiTetherSoftApCallback, + TetheringManager.StartTetheringCallback, + KeyedObserver { private var wifiTetherSoftApManager: WifiTetherSoftApManager? = null - var sapState: Int = WifiManager.WIFI_AP_STATE_DISABLED var sapFailureReason: Int? = null var sapClientsSize: Int? = null @@ -150,54 +146,47 @@ class WifiHotspotSwitchPreference(context: Context) : override fun setValue(key: String, valueType: Class, value: T?) { if (value !is Boolean) return - context.tetheringManager?.let { - if (value) { - val startTetheringCallback = - object : TetheringManager.StartTetheringCallback { - override fun onTetheringStarted() { - Log.d(TAG, "onTetheringStarted()") - } - - override fun onTetheringFailed(error: Int) { - Log.e(TAG, "onTetheringFailed(),error=$error") - } - } - it.startTethering( - TetheringManager.TETHERING_WIFI, - HandlerExecutor.main, - startTetheringCallback, - ) - } else { - it.stopTethering(TetheringManager.TETHERING_WIFI) - } + val tetheringManager = context.tetheringManager ?: return + if (value) { + tetheringManager.startTethering(TETHERING_WIFI, HandlerExecutor.main, this) + } else { + tetheringManager.stopTethering(TETHERING_WIFI) } } override fun onFirstObserverAdded() { - val wifiSoftApCallback = - object : WifiTetherSoftApManager.WifiTetherSoftApCallback { - override fun onStateChanged(state: Int, failureReason: Int) { - Log.d(TAG, "onStateChanged(),state=$state,failureReason=$failureReason") - sapState = state - sapFailureReason = failureReason - if (state == WifiManager.WIFI_AP_STATE_DISABLED) sapClientsSize = null - notifyChange(KEY, DataChangeReason.UPDATE) - } - - override fun onConnectedClientsChanged(clients: List?) { - sapClientsSize = clients?.size ?: 0 - Log.d(TAG, "onConnectedClientsChanged(),sapClientsSize=$sapClientsSize") - notifyChange(KEY, DataChangeReason.UPDATE) - } - } - wifiTetherSoftApManager = - WifiTetherSoftApManager(context.wifiManager, wifiSoftApCallback) - wifiTetherSoftApManager?.registerSoftApCallback() + val apManager = WifiTetherSoftApManager(context.wifiManager, this) + wifiTetherSoftApManager = apManager + apManager.registerSoftApCallback() + dataSaverStore.addObserver(DATA_SAVER_KEY, this, HandlerExecutor.main) } override fun onLastObserverRemoved() { + dataSaverStore.removeObserver(DATA_SAVER_KEY, this) wifiTetherSoftApManager?.unRegisterSoftApCallback() } + + override fun onStateChanged(state: Int, failureReason: Int) { + Log.d(TAG, "onStateChanged(),state=$state,failureReason=$failureReason") + sapFailureReason = failureReason + if (state == WifiManager.WIFI_AP_STATE_DISABLED) sapClientsSize = null + notifyChange(KEY, DataChangeReason.UPDATE) + } + + override fun onConnectedClientsChanged(clients: List?) { + sapClientsSize = clients?.size ?: 0 + Log.d(TAG, "onConnectedClientsChanged(),sapClientsSize=$sapClientsSize") + notifyChange(KEY, DataChangeReason.UPDATE) + } + + override fun onTetheringStarted() {} + + override fun onTetheringFailed(error: Int) { + Log.e(TAG, "onTetheringFailed(),error=$error") + } + + override fun onKeyChanged(key: String, reason: Int) = + notifyChange(KEY, DataChangeReason.UPDATE) } override fun bind(preference: Preference, metadata: PreferenceMetadata) { @@ -207,24 +196,6 @@ class WifiHotspotSwitchPreference(context: Context) : } } - override fun onStart(context: PreferenceLifecycleContext) { - val listener = - DataSaverBackend.Listener { isDataSaving: Boolean -> - context.findPreference(KEY)?.isSwitchEnabled = - !isDataSaving - context.notifyPreferenceChange(KEY) - } - dataSaverBackendListener = listener - dataSaverBackend.addListener(listener) - } - - override fun onStop(context: PreferenceLifecycleContext) { - dataSaverBackendListener?.let { - dataSaverBackend.remListener(it) - dataSaverBackendListener = null - } - } - companion object { const val TAG = "WifiHotspotSwitchPreference" const val KEY = "wifi_tether" @@ -232,7 +203,9 @@ class WifiHotspotSwitchPreference(context: Context) : private val Context.wifiManager: WifiManager? get() = applicationContext.getSystemService(WifiManager::class.java) - private fun Context.getSoftApConfiguration() = wifiManager?.softApConfiguration + @Suppress("DEPRECATION") + private val Context.wifiSsid + get() = wifiManager?.softApConfiguration?.ssid private val Context.tetheringManager: TetheringManager? get() = applicationContext.getSystemService(TetheringManager::class.java)