diff --git a/src/com/android/settings/network/AirplaneModePreference.kt b/src/com/android/settings/network/AirplaneModePreference.kt index d9d1bd8061e..0899addd0ef 100644 --- a/src/com/android/settings/network/AirplaneModePreference.kt +++ b/src/com/android/settings/network/AirplaneModePreference.kt @@ -28,8 +28,7 @@ import com.android.settingslib.metadata.SwitchPreference // LINT.IfChange class AirplaneModePreference : - SwitchPreference(KEY, R.string.airplane_mode), - PreferenceAvailabilityProvider { + SwitchPreference(KEY, R.string.airplane_mode), PreferenceAvailabilityProvider { override val icon: Int @DrawableRes get() = R.drawable.ic_airplanemode_active @@ -40,11 +39,13 @@ class AirplaneModePreference : get() = SensitivityLevel.HIGH_SENSITIVITY override fun isAvailable(context: Context) = - (context.resources.getBoolean(R.bool.config_show_toggle_airplane) - && !context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) + (context.resources.getBoolean(R.bool.config_show_toggle_airplane) && + !context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) companion object { const val KEY = Settings.Global.AIRPLANE_MODE_ON + + fun Context.isAirplaneModeOn() = SettingsGlobalStore.get(this).getBoolean(KEY) == true } } // LINT.ThenChange(AirplaneModePreferenceController.java) diff --git a/src/com/android/settings/network/MobileDataPreference.kt b/src/com/android/settings/network/MobileDataPreference.kt new file mode 100644 index 00000000000..d285a8ca26e --- /dev/null +++ b/src/com/android/settings/network/MobileDataPreference.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 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.telephony.SubscriptionManager +import com.android.settings.R +import com.android.settings.network.telephony.MobileDataRepository +import com.android.settings.network.telephony.SubscriptionRepository +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.datastore.NoOpKeyedObservable +import com.android.settingslib.metadata.PreferenceAvailabilityProvider +import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel +import com.android.settingslib.metadata.SwitchPreference +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking + +class MobileDataPreference : + SwitchPreference( + KEY, + R.string.mobile_data_settings_title, + R.string.mobile_data_settings_summary, + ), + PreferenceAvailabilityProvider { + + override fun isAvailable(context: Context) = + SubscriptionRepository(context).getSelectableSubscriptionInfoList().any { + it.simSlotIndex > -1 + } + + override fun storage(context: Context): KeyValueStore = MobileDataStorage(context) + + override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override val sensitivityLevel + get() = SensitivityLevel.LOW_SENSITIVITY + + @Suppress("UNCHECKED_CAST") + private class MobileDataStorage(private val context: Context) : + NoOpKeyedObservable(), KeyValueStore { + + override fun contains(key: String) = key == KEY + + override fun getValue(key: String, valueType: Class): T { + val subId = SubscriptionManager.getDefaultDataSubscriptionId() + val flow = MobileDataRepository(context).isMobileDataEnabledFlow(subId) + return runBlocking { flow.first() } as T + } + + override fun setValue(key: String, valueType: Class, value: T?) { + val subId = SubscriptionManager.getDefaultDataSubscriptionId() + MobileDataRepository(context).setMobileDataEnabled(subId, value as Boolean) + } + } + + companion object { + const val KEY = "mobile_data" + } +} diff --git a/src/com/android/settings/network/MobileNetworkListScreen.kt b/src/com/android/settings/network/MobileNetworkListScreen.kt index 2e05e3aabb4..d7231ccb711 100644 --- a/src/com/android/settings/network/MobileNetworkListScreen.kt +++ b/src/com/android/settings/network/MobileNetworkListScreen.kt @@ -17,15 +17,49 @@ package com.android.settings.network import android.content.Context import android.os.UserManager +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener +import androidx.preference.Preference +import androidx.preference.Preference.OnPreferenceClickListener import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R import com.android.settings.flags.Flags +import com.android.settings.network.AirplaneModePreference.Companion.isAirplaneModeOn +import com.android.settings.network.SubscriptionUtil.getUniqueSubscriptionDisplayName +import com.android.settings.network.telephony.SimRepository +import com.android.settings.network.telephony.SubscriptionRepository +import com.android.settings.network.telephony.euicc.EuiccRepository +import com.android.settings.spa.network.getAddSimIntent +import com.android.settings.spa.network.startAddSimFlow +import com.android.settingslib.RestrictedPreference +import com.android.settingslib.datastore.HandlerExecutor +import com.android.settingslib.datastore.KeyedObserver +import com.android.settingslib.datastore.SettingsGlobalStore +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.PreferenceScreenBinding import com.android.settingslib.preference.PreferenceScreenCreator @ProvidePreferenceScreen -class MobileNetworkListScreen : PreferenceScreenCreator, PreferenceRestrictionMixin { +class MobileNetworkListScreen : + PreferenceScreenCreator, + PreferenceScreenBinding, + PreferenceAvailabilityProvider, + PreferenceSummaryProvider, + PreferenceLifecycleProvider, + PreferenceRestrictionMixin, + OnPreferenceClickListener { + + private var airplaneModeObserver: KeyedObserver? = null + private var subscriptionInfoList: List? = null + private var onSubscriptionsChangedListener: OnSubscriptionsChangedListener? = null + override val key: String get() = KEY @@ -38,18 +72,95 @@ class MobileNetworkListScreen : PreferenceScreenCreator, PreferenceRestrictionMi override val keywords: Int get() = R.string.keywords_more_mobile_networks - override fun isEnabled(context: Context) = super.isEnabled(context) + override fun intent(context: Context) = getAddSimIntent() + + override fun getSummary(context: Context): CharSequence? { + val list = getSelectableSubscriptionInfoList(context) + return when { + list.isNotEmpty() -> + list + .map { getUniqueSubscriptionDisplayName(it, context).toString() } + .distinct() + .joinToString(", ") + EuiccRepository(context).showEuiccSettings() -> + context.getString(R.string.mobile_network_summary_add_a_network) + else -> null + } + } + + override fun isAvailable(context: Context) = + SimRepository(context).showMobileNetworkPageEntrance() + + override fun isEnabled(context: Context) = + super.isEnabled(context) && + !context.isAirplaneModeOn() && + (getSelectableSubscriptionInfoList(context).isNotEmpty() || + EuiccRepository(context).showEuiccSettings()) + + private fun getSelectableSubscriptionInfoList(context: Context): List = + subscriptionInfoList + ?: SubscriptionRepository(context).getSelectableSubscriptionInfoList().also { + subscriptionInfoList = it + } override val restrictionKeys get() = arrayOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS) + override val useAdminDisabledSummary + get() = true + + override fun createWidget(context: Context) = RestrictedPreference(context) + + override fun bind(preference: Preference, metadata: PreferenceMetadata) { + super.bind(preference, metadata) + preference.onPreferenceClickListener = this + } + + override fun onPreferenceClick(preference: Preference): Boolean { + val summary = preference.summary ?: return true // no-op + val context = preference.context + if (summary == context.getString(R.string.mobile_network_summary_add_a_network)) { + startAddSimFlow(context) // start intent + return true + } + return false // start fragment + } + + override fun onCreate(context: PreferenceLifecycleContext) { + val executor = HandlerExecutor.main + val observer = KeyedObserver { _, _ -> context.notifyPreferenceChange(KEY) } + airplaneModeObserver = observer + SettingsGlobalStore.get(context).addObserver(AirplaneModePreference.KEY, observer, executor) + context.getSystemService(SubscriptionManager::class.java)?.let { + val listener = + object : OnSubscriptionsChangedListener() { + override fun onSubscriptionsChanged() { + subscriptionInfoList = null // invalid cache + context.notifyPreferenceChange(KEY) + } + } + it.addOnSubscriptionsChangedListener(executor, listener) + onSubscriptionsChangedListener = listener + } + } + + override fun onDestroy(context: PreferenceLifecycleContext) { + airplaneModeObserver?.let { + SettingsGlobalStore.get(context).removeObserver(AirplaneModePreference.KEY, it) + } + context.getSystemService(SubscriptionManager::class.java)?.apply { + onSubscriptionsChangedListener?.let { removeOnSubscriptionsChangedListener(it) } + } + } + override fun isFlagEnabled(context: Context) = Flags.catalystMobileNetworkList() override fun hasCompleteHierarchy() = false override fun fragmentClass() = MobileNetworkListFragment::class.java - override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {} + override fun getPreferenceHierarchy(context: Context) = + preferenceHierarchy(this) { +MobileDataPreference() } companion object { const val KEY = "mobile_network_list" diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.kt b/src/com/android/settings/network/MobileNetworkSummaryController.kt index 8cf9bec283e..62c57661809 100644 --- a/src/com/android/settings/network/MobileNetworkSummaryController.kt +++ b/src/com/android/settings/network/MobileNetworkSummaryController.kt @@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.Flow * - Has subscriptions: click action takes you to a page listing the subscriptions, and the summary * text gives the count of SIMs */ +// LINT.IfChange class MobileNetworkSummaryController @JvmOverloads constructor( @@ -119,3 +120,4 @@ constructor( ) } } +// LINT.ThenChange(MobileNetworkListScreen.kt) diff --git a/src/com/android/settings/network/NetworkDashboardScreen.kt b/src/com/android/settings/network/NetworkDashboardScreen.kt index 3fb2cbefdbe..5dadcaf941c 100644 --- a/src/com/android/settings/network/NetworkDashboardScreen.kt +++ b/src/com/android/settings/network/NetworkDashboardScreen.kt @@ -46,6 +46,7 @@ class NetworkDashboardScreen : PreferenceScreenCreator, PreferenceIconProvider { override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) { + +MobileNetworkListScreen.KEY order -15 +DataSaverScreen.KEY order 10 } diff --git a/src/com/android/settings/spa/network/SimsSection.kt b/src/com/android/settings/spa/network/SimsSection.kt index 7d88748785d..fa7fa448950 100644 --- a/src/com/android/settings/spa/network/SimsSection.kt +++ b/src/com/android/settings/spa/network/SimsSection.kt @@ -137,9 +137,9 @@ private fun AddSim() { } } -fun startAddSimFlow(context: Context) { - val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION) - intent.setPackage(Utils.PHONE_PACKAGE_NAME) - intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true) - context.startActivity(intent) +fun startAddSimFlow(context: Context) = context.startActivity(getAddSimIntent()) + +fun getAddSimIntent() = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION).apply { + setPackage(Utils.PHONE_PACKAGE_NAME) + putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true) }