Merge "Fix restriction to configure Calls & SMS" into main
diff --git a/res/drawable/ic_calls_sms.xml b/res/drawable/ic_calls_sms.xml
deleted file mode 100644
index 2033e8f..0000000
--- a/res/drawable/ic_calls_sms.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<!--
- Copyright (C) 2020 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?android:attr/colorControlNormal"
- >
-
- <path
- android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" />
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.17,14.85l-3.26-0.65c-0.33-0.07-0.67,0.04-0.9,0.27l-2.62,2.62c-2.75-1.49-5.01-3.75-6.5-6.5l2.62-2.62 c0.24-0.24,0.34-0.58,0.27-0.9L9.13,3.82c-0.09-0.47-0.5-0.8-0.98-0.8H4c-0.56,0-1.03,0.47-1,1.03c0.17,2.91,1.04,5.63,2.43,8.01 c1.57,2.69,3.81,4.93,6.5,6.5c2.38,1.39,5.1,2.26,8.01,2.43c0.56,0.03,1.03-0.44,1.03-1v-4.15C20.97,15.36,20.64,14.95,20.17,14.85 L20.17,14.85z M12,3v10l3-3h6V3H12z M19,8h-5V5h5V8z" />
-</vector>
diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml
index ef6ed16..b055ea3 100644
--- a/res/xml/network_provider_internet.xml
+++ b/res/xml/network_provider_internet.xml
@@ -31,16 +31,11 @@
settings:keywords="@string/keywords_internet"
settings:useAdminDisabledSummary="true" />
- <com.android.settingslib.RestrictedPreference
+ <com.android.settings.spa.preference.ComposePreference
android:key="calls_and_sms"
android:title="@string/calls_and_sms"
- android:icon="@drawable/ic_calls_sms"
android:order="-20"
- android:summary="@string/summary_placeholder"
- settings:isPreferenceVisible="@bool/config_show_sim_info"
- settings:allowDividerBelow="true"
- settings:keywords="@string/calls_and_sms"
- settings:useAdminDisabledSummary="true" />
+ settings:controller="com.android.settings.network.NetworkProviderCallsSmsController" />
<com.android.settingslib.RestrictedPreference
android:key="mobile_network_list"
diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java
index d558aa8..4790c65 100644
--- a/src/com/android/settings/network/NetworkDashboardFragment.java
+++ b/src/com/android/settings/network/NetworkDashboardFragment.java
@@ -22,7 +22,6 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.provider.SearchIndexableResource;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
@@ -31,19 +30,16 @@
import com.android.settings.R;
import com.android.settings.SettingsDumpService;
-import com.android.settings.Utils;
import com.android.settings.core.OnActivityResultListener;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.network.MobilePlanPreferenceController.MobilePlanPreferenceHost;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settings.wifi.WifiPrimarySwitchPreferenceController;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
@SearchIndexable
@@ -122,7 +118,6 @@
controllers.add(internetPreferenceController);
}
controllers.add(privateDnsPreferenceController);
- controllers.add(new NetworkProviderCallsSmsController(context, lifecycle, lifecycleOwner));
// Start SettingsDumpService after the MobileNetworkRepository is created.
Intent intent = new Intent(context, SettingsDumpService.class);
diff --git a/src/com/android/settings/network/NetworkProviderCallsSmsController.java b/src/com/android/settings/network/NetworkProviderCallsSmsController.java
deleted file mode 100644
index 5eec3d9..0000000
--- a/src/com/android/settings/network/NetworkProviderCallsSmsController.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2020 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 static androidx.lifecycle.Lifecycle.Event;
-
-import android.content.Context;
-import android.os.UserManager;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.OnLifecycleEvent;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settingslib.RestrictedPreference;
-import com.android.settingslib.Utils;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
-
-import java.util.List;
-
-public class NetworkProviderCallsSmsController extends AbstractPreferenceController implements
- LifecycleObserver, MobileNetworkRepository.MobileNetworkCallback,
- DefaultSubscriptionReceiver.DefaultSubscriptionListener {
-
- private static final String TAG = "NetworkProviderCallsSmsController";
- private static final String KEY = "calls_and_sms";
- private static final String RTL_MARK = "\u200F";
-
- private UserManager mUserManager;
- private TelephonyManager mTelephonyManager;
- private RestrictedPreference mPreference;
- private boolean mIsRtlMode;
- private LifecycleOwner mLifecycleOwner;
- private MobileNetworkRepository mMobileNetworkRepository;
- private List<SubscriptionInfoEntity> mSubInfoEntityList;
- private int mDefaultVoiceSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private int mDefaultSmsSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private DefaultSubscriptionReceiver mDataSubscriptionChangedReceiver;
-
- /**
- * The summary text and click behavior of the "Calls & SMS" item on the
- * Network & internet page.
- */
- public NetworkProviderCallsSmsController(Context context, Lifecycle lifecycle,
- LifecycleOwner lifecycleOwner) {
- super(context);
-
- mUserManager = context.getSystemService(UserManager.class);
- mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
- mIsRtlMode = context.getResources().getConfiguration().getLayoutDirection()
- == View.LAYOUT_DIRECTION_RTL;
- mLifecycleOwner = lifecycleOwner;
- mMobileNetworkRepository = MobileNetworkRepository.getInstance(context);
- mDataSubscriptionChangedReceiver = new DefaultSubscriptionReceiver(context, this);
- if (lifecycle != null) {
- lifecycle.addObserver(this);
- }
- }
-
- @OnLifecycleEvent(Event.ON_RESUME)
- public void onResume() {
- mMobileNetworkRepository.addRegister(mLifecycleOwner, this,
- SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- mMobileNetworkRepository.updateEntity();
- mDataSubscriptionChangedReceiver.registerReceiver();
- mDefaultVoiceSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
- mDefaultSmsSubId = SubscriptionManager.getDefaultSmsSubscriptionId();
- }
-
- @OnLifecycleEvent(Event.ON_PAUSE)
- public void onPause() {
- mMobileNetworkRepository.removeRegister(this);
- mDataSubscriptionChangedReceiver.unRegisterReceiver();
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
- }
-
- @Override
- public CharSequence getSummary() {
- List<SubscriptionInfoEntity> list = getSubscriptionInfoList();
- if (list == null || list.isEmpty()) {
- return setSummaryResId(R.string.calls_sms_no_sim);
- } else {
- final StringBuilder summary = new StringBuilder();
- SubscriptionInfoEntity[] entityArray = list.toArray(
- new SubscriptionInfoEntity[0]);
- for (SubscriptionInfoEntity subInfo : entityArray) {
- int subsSize = list.size();
- int subId = Integer.parseInt(subInfo.subId);
- final CharSequence displayName = subInfo.uniqueName;
-
- // Set displayName as summary if there is only one valid SIM.
- if (subsSize == 1
- && list.get(0).isValidSubscription
- && isInService(subId)) {
- return displayName;
- }
-
- CharSequence status = getPreferredStatus(subInfo, subsSize, subId);
- if (status.toString().isEmpty()) {
- // If there are 2 or more SIMs and one of these has no preferred status,
- // set only its displayName as summary.
- summary.append(displayName);
- } else {
- summary.append(displayName)
- .append(" (")
- .append(status)
- .append(")");
- }
- // Do not add ", " for the last subscription.
- if (list.size() > 0 && !subInfo.equals(list.get(list.size() - 1))) {
- summary.append(", ");
- }
-
- if (mIsRtlMode) {
- summary.insert(0, RTL_MARK).insert(summary.length(), RTL_MARK);
- }
- }
- return summary;
- }
- }
-
- @VisibleForTesting
- protected CharSequence getPreferredStatus(SubscriptionInfoEntity subInfo, int subsSize,
- int subId) {
- String status = "";
- boolean isCallPreferred = subInfo.getSubId() == getDefaultVoiceSubscriptionId();
- boolean isSmsPreferred = subInfo.getSubId() == getDefaultSmsSubscriptionId();
-
- if (!subInfo.isValidSubscription || !isInService(subId)) {
- status = setSummaryResId(subsSize > 1 ? R.string.calls_sms_unavailable :
- R.string.calls_sms_temp_unavailable);
- } else {
- if (isCallPreferred && isSmsPreferred) {
- status = setSummaryResId(R.string.calls_sms_preferred);
- } else if (isCallPreferred) {
- status = setSummaryResId(R.string.calls_sms_calls_preferred);
- } else if (isSmsPreferred) {
- status = setSummaryResId(R.string.calls_sms_sms_preferred);
- }
- }
- return status;
- }
-
- private String setSummaryResId(int resId) {
- return mContext.getResources().getString(resId);
- }
-
- @VisibleForTesting
- protected List<SubscriptionInfoEntity> getSubscriptionInfoList() {
- return mSubInfoEntityList;
- }
-
- private void update() {
- if (mPreference == null || mPreference.isDisabledByAdmin()) {
- return;
- }
- refreshSummary(mPreference);
- mPreference.setOnPreferenceClickListener(null);
- mPreference.setFragment(null);
-
- if (mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) {
- mPreference.setEnabled(false);
- } else {
- mPreference.setEnabled(true);
- mPreference.setFragment(NetworkProviderCallsSmsFragment.class.getCanonicalName());
- }
- }
-
- @Override
- public boolean isAvailable() {
- return SubscriptionUtil.isSimHardwareVisible(mContext) &&
- mUserManager.isAdminUser();
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY;
- }
-
- @Override
- public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
- update();
- }
-
- @Override
- public void updateState(Preference preference) {
- super.updateState(preference);
- if (preference == null) {
- return;
- }
- refreshSummary(mPreference);
- update();
- }
-
- @VisibleForTesting
- protected boolean isInService(int subId) {
- ServiceState serviceState =
- mTelephonyManager.createForSubscriptionId(subId).getServiceState();
- return Utils.isInService(serviceState);
- }
-
- @Override
- public void onActiveSubInfoChanged(List<SubscriptionInfoEntity> activeSubInfoList) {
- mSubInfoEntityList = activeSubInfoList;
- update();
- }
-
- @VisibleForTesting
- protected int getDefaultVoiceSubscriptionId() {
- return mDefaultVoiceSubId;
- }
-
- @VisibleForTesting
- protected int getDefaultSmsSubscriptionId() {
- return mDefaultSmsSubId;
- }
-
- @Override
- public void onDefaultVoiceChanged(int defaultVoiceSubId) {
- mDefaultVoiceSubId = defaultVoiceSubId;
- update();
- }
-
- @Override
- public void onDefaultSmsChanged(int defaultSmsSubId) {
- mDefaultSmsSubId = defaultSmsSubId;
- update();
- }
-}
diff --git a/src/com/android/settings/network/NetworkProviderCallsSmsController.kt b/src/com/android/settings/network/NetworkProviderCallsSmsController.kt
new file mode 100644
index 0000000..a265041
--- /dev/null
+++ b/src/com/android/settings/network/NetworkProviderCallsSmsController.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.app.settings.SettingsEnums
+import android.content.Context
+import android.content.IntentFilter
+import android.os.UserManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.PermPhoneMsg
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.settings.R
+import com.android.settings.core.SubSettingLauncher
+import com.android.settings.spa.preference.ComposePreferenceController
+import com.android.settingslib.Utils
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.android.settingslib.spaprivileged.framework.compose.placeholder
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/**
+ * The summary text and click behavior of the "Calls & SMS" item on the Network & internet page.
+ */
+open class NetworkProviderCallsSmsController @JvmOverloads constructor(
+ context: Context,
+ preferenceKey: String,
+ private val getDisplayName: (SubscriptionInfo) -> CharSequence = { subInfo ->
+ SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, context)
+ },
+ private val isInService: (Int) -> Boolean = IsInServiceImpl(context)::isInService,
+) : ComposePreferenceController(context, preferenceKey) {
+
+ override fun getAvailabilityStatus() = when {
+ !SubscriptionUtil.isSimHardwareVisible(mContext) -> UNSUPPORTED_ON_DEVICE
+ !mContext.userManager.isAdminUser -> DISABLED_FOR_USER
+ else -> AVAILABLE
+ }
+
+ @Composable
+ override fun Content() {
+ Column {
+ CallsAndSms()
+ HorizontalDivider()
+ }
+ }
+
+ @Composable
+ private fun CallsAndSms() {
+ val viewModel: SubscriptionInfoListViewModel = viewModel()
+ val subscriptionInfos by viewModel.subscriptionInfoListFlow.collectAsStateWithLifecycle()
+ val summary by remember { summaryFlow(viewModel.subscriptionInfoListFlow) }
+ .collectAsStateWithLifecycle(initialValue = placeholder())
+ RestrictedPreference(
+ model = object : PreferenceModel {
+ override val title = stringResource(R.string.calls_and_sms)
+ override val icon = @Composable { SettingsIcon(Icons.Outlined.PermPhoneMsg) }
+ override val summary = { summary }
+ override val enabled = { subscriptionInfos.isNotEmpty() }
+ override val onClick = {
+ SubSettingLauncher(mContext).apply {
+ setDestination(NetworkProviderCallsSmsFragment::class.qualifiedName)
+ setSourceMetricsCategory(SettingsEnums.SETTINGS_NETWORK_CATEGORY)
+ }.launch()
+ }
+ },
+ restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)),
+ )
+ }
+
+ private fun summaryFlow(subscriptionInfoListFlow: Flow<List<SubscriptionInfo>>) = combine(
+ subscriptionInfoListFlow,
+ mContext.defaultVoiceSubscriptionFlow(),
+ mContext.defaultSmsSubscriptionFlow(),
+ ::getSummary,
+ ).flowOn(Dispatchers.Default)
+
+ @VisibleForTesting
+ fun getSummary(
+ activeSubscriptionInfoList: List<SubscriptionInfo>,
+ defaultVoiceSubscriptionId: Int,
+ defaultSmsSubscriptionId: Int,
+ ): String {
+ if (activeSubscriptionInfoList.isEmpty()) {
+ return mContext.getString(R.string.calls_sms_no_sim)
+ }
+
+ activeSubscriptionInfoList.singleOrNull()?.let {
+ // Set displayName as summary if there is only one valid SIM.
+ if (isInService(it.subscriptionId)) return it.displayName.toString()
+ }
+
+ return activeSubscriptionInfoList.joinToString { subInfo ->
+ val displayName = getDisplayName(subInfo)
+
+ val subId = subInfo.subscriptionId
+ val statusResId = getPreferredStatus(
+ subId = subId,
+ subsSize = activeSubscriptionInfoList.size,
+ isCallPreferred = subId == defaultVoiceSubscriptionId,
+ isSmsPreferred = subId == defaultSmsSubscriptionId,
+ )
+ if (statusResId == null) {
+ // If there are 2 or more SIMs and one of these has no preferred status,
+ // set only its displayName as summary.
+ displayName
+ } else {
+ "$displayName (${mContext.getString(statusResId)})"
+ }
+ }
+ }
+
+ private fun getPreferredStatus(
+ subId: Int,
+ subsSize: Int,
+ isCallPreferred: Boolean,
+ isSmsPreferred: Boolean,
+ ): Int? = when {
+ !isInService(subId) -> {
+ if (subsSize > 1) {
+ R.string.calls_sms_unavailable
+ } else {
+ R.string.calls_sms_temp_unavailable
+ }
+ }
+
+ isCallPreferred && isSmsPreferred -> R.string.calls_sms_preferred
+ isCallPreferred -> R.string.calls_sms_calls_preferred
+ isSmsPreferred -> R.string.calls_sms_sms_preferred
+ else -> null
+ }
+}
+
+private fun Context.defaultVoiceSubscriptionFlow(): Flow<Int> =
+ merge(
+ flowOf(null), // kick an initial value
+ broadcastReceiverFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)
+ ),
+ ).map { SubscriptionManager.getDefaultVoiceSubscriptionId() }
+ .conflate().flowOn(Dispatchers.Default)
+
+private fun Context.defaultSmsSubscriptionFlow(): Flow<Int> =
+ merge(
+ flowOf(null), // kick an initial value
+ broadcastReceiverFlow(
+ IntentFilter(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED)
+ ),
+ ).map { SubscriptionManager.getDefaultSmsSubscriptionId() }
+ .conflate().flowOn(Dispatchers.Default)
+
+private class IsInServiceImpl(context: Context) {
+ private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!!
+
+ fun isInService(subId: Int): Boolean {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) return false
+
+ val serviceState = telephonyManager.createForSubscriptionId(subId).serviceState
+ return Utils.isInService(serviceState)
+ }
+}
diff --git a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
new file mode 100644
index 0000000..d30b21d
--- /dev/null
+++ b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.app.Application
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.plus
+
+class SubscriptionInfoListViewModel(application: Application) : AndroidViewModel(application) {
+ private val scope = viewModelScope + Dispatchers.Default
+
+ val subscriptionInfoListFlow = callbackFlow<List<SubscriptionInfo>> {
+ val subscriptionManager = application.getSystemService(SubscriptionManager::class.java)!!
+
+ val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
+ override fun onSubscriptionsChanged() {
+ trySend(subscriptionManager.activeSubscriptionInfoList ?: emptyList())
+ }
+ }
+
+ subscriptionManager.addOnSubscriptionsChangedListener(
+ Dispatchers.Default.asExecutor(),
+ listener,
+ )
+
+ awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
+ }.conflate().stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.kt
new file mode 100644
index 0000000..110fd5e
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class NetworkProviderCallsSmsControllerTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private var isInService: (Int) -> Boolean = { true }
+
+ private val controller = NetworkProviderCallsSmsController(
+ context = context,
+ preferenceKey = TEST_KEY,
+ getDisplayName = { subInfo -> subInfo.displayName },
+ isInService = { isInService(it) },
+ )
+
+ @Test
+ fun getSummary_noSim_returnNoSim() {
+ val summary = controller.getSummary(
+ activeSubscriptionInfoList = emptyList(),
+ defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ )
+
+ assertThat(summary).isEqualTo(context.getString(R.string.calls_sms_no_sim))
+ }
+
+ @Test
+ fun getSummary_invalidSubId_returnUnavailable() {
+ isInService = { false }
+
+ val summary = controller.getSummary(
+ activeSubscriptionInfoList = listOf(SUB_INFO_1),
+ defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ )
+
+ assertThat(summary).isEqualTo("Sub 1 (Temporarily unavailable)")
+ }
+
+ @Test
+ fun getSummary_oneIsInvalidSubIdTwoIsValidSubId_returnOneIsUnavailable() {
+ isInService = { it == SUB_INFO_2.subscriptionId }
+
+ val summary = controller.getSummary(
+ activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2),
+ defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ )
+
+ assertThat(summary).isEqualTo("Sub 1 (unavailable), Sub 2")
+ }
+
+ @Test
+ fun getSummary_oneSubscription_returnDisplayName() {
+ val summary = controller.getSummary(
+ activeSubscriptionInfoList = listOf(SUB_INFO_1),
+ defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ )
+
+ assertThat(summary).isEqualTo(DISPLAY_NAME_1)
+ }
+
+ @Test
+ fun getSummary_allSubscriptionsHaveNoPreferredStatus_returnDisplayName() {
+ val summary = controller.getSummary(
+ activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2),
+ defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ )
+
+ assertThat(summary).isEqualTo("Sub 1, Sub 2")
+ }
+
+ @Test
+ fun getSummary_oneSubscriptionsIsCallPreferredTwoIsSmsPreferred_returnStatus() {
+ val summary = controller.getSummary(
+ activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2),
+ defaultVoiceSubscriptionId = SUB_INFO_1.subscriptionId,
+ defaultSmsSubscriptionId = SUB_INFO_2.subscriptionId,
+ )
+
+ assertThat(summary).isEqualTo("Sub 1 (preferred for calls), Sub 2 (preferred for SMS)")
+ }
+
+ @Test
+ fun getSummary_oneSubscriptionsIsSmsPreferredTwoIsCallPreferred_returnStatus() {
+ val summary = controller.getSummary(
+ activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2),
+ defaultVoiceSubscriptionId = SUB_INFO_2.subscriptionId,
+ defaultSmsSubscriptionId = SUB_INFO_1.subscriptionId,
+ )
+
+ assertThat(summary).isEqualTo("Sub 1 (preferred for SMS), Sub 2 (preferred for calls)")
+ }
+
+ @Test
+ fun getSummary_oneSubscriptionsIsSmsPreferredAndIsCallPreferred_returnStatus() {
+ val summary = controller.getSummary(
+ activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2),
+ defaultVoiceSubscriptionId = SUB_INFO_1.subscriptionId,
+ defaultSmsSubscriptionId = SUB_INFO_1.subscriptionId,
+ )
+
+ assertThat(summary).isEqualTo("Sub 1 (preferred), Sub 2")
+ }
+
+ private companion object {
+ const val TEST_KEY = "test_key"
+ const val DISPLAY_NAME_1 = "Sub 1"
+ const val DISPLAY_NAME_2 = "Sub 2"
+
+ val SUB_INFO_1: SubscriptionInfo = SubscriptionInfo.Builder().apply {
+ setId(1)
+ setDisplayName(DISPLAY_NAME_1)
+ }.build()
+
+ val SUB_INFO_2: SubscriptionInfo = SubscriptionInfo.Builder().apply {
+ setId(2)
+ setDisplayName(DISPLAY_NAME_2)
+ }.build()
+ }
+}
diff --git a/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java b/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java
deleted file mode 100644
index 51aecc5..0000000
--- a/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright (C) 2020 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 static androidx.lifecycle.Lifecycle.Event;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.os.Looper;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceScreen;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.settings.testutils.ResourcesUtils;
-import com.android.settingslib.RestrictedPreference;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-@RunWith(AndroidJUnit4.class)
-public class NetworkProviderCallsSmsControllerTest {
-
- private static final String SUB_ID_1 = "1";
- private static final String SUB_ID_2 = "2";
- private static final String INVALID_SUB_ID = "-1";
- private static final String KEY_PREFERENCE_CALLS_SMS = "calls_and_sms";
- private static final String DISPLAY_NAME_1 = "Sub 1";
- private static final String DISPLAY_NAME_2 = "Sub 2";
- private static final String SUB_MCC_1 = "123";
- private static final String SUB_MNC_1 = "456";
- private static final String SUB_MCC_2 = "223";
- private static final String SUB_MNC_2 = "456";
- private static final String SUB_COUNTRY_ISO_1 = "Sub 1";
- private static final String SUB_COUNTRY_ISO_2 = "Sub 2";
-
- @Mock
- private SubscriptionInfoEntity mSubInfo1;
- @Mock
- private SubscriptionInfoEntity mSubInfo2;
- @Mock
- private Lifecycle mLifecycle;
- @Mock
- private LifecycleOwner mLifecycleOwner;
-
- private LifecycleRegistry mLifecycleRegistry;
- private MockNetworkProviderCallsSmsController mController;
- private PreferenceManager mPreferenceManager;
- private PreferenceScreen mPreferenceScreen;
- private RestrictedPreference mPreference;
- private Context mContext;
- private List<SubscriptionInfoEntity> mSubscriptionInfoEntityList = new ArrayList<>();
-
- /**
- * Mock the NetworkProviderCallsSmsController that allows one to set a default voice
- * and SMS subscription ID.
- */
- private class MockNetworkProviderCallsSmsController extends
- com.android.settings.network.NetworkProviderCallsSmsController {
- public MockNetworkProviderCallsSmsController(Context context, Lifecycle lifecycle,
- LifecycleOwner lifecycleOwner) {
- super(context, lifecycle, lifecycleOwner);
- }
-
- private List<SubscriptionInfoEntity> mSubscriptionInfoEntity;
- private boolean mIsInService;
- private int mDefaultVoiceSubscriptionId;
- private int mDefaultSmsSubscriptionId;
-
- @Override
- protected List<SubscriptionInfoEntity> getSubscriptionInfoList() {
- return mSubscriptionInfoEntity;
- }
-
- public void setSubscriptionInfoList(List<SubscriptionInfoEntity> list) {
- mSubscriptionInfoEntity = list;
- }
-
- @Override
- protected boolean isInService(int subId) {
- return mIsInService;
- }
-
- public void setInService(boolean inService) {
- mIsInService = inService;
- }
-
- @Override
- protected int getDefaultVoiceSubscriptionId() {
- return mDefaultVoiceSubscriptionId;
- }
-
- @Override
- protected int getDefaultSmsSubscriptionId() {
- return mDefaultSmsSubscriptionId;
- }
-
- public void setDefaultVoiceSubscriptionId(int subscriptionId) {
- mDefaultVoiceSubscriptionId = subscriptionId;
- }
-
- public void setDefaultSmsSubscriptionId(int subscriptionId) {
- mDefaultSmsSubscriptionId = subscriptionId;
- }
- }
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = spy(ApplicationProvider.getApplicationContext());
-
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
- mPreferenceManager = new PreferenceManager(mContext);
- mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
- mPreference = new RestrictedPreference(mContext);
- mPreference.setKey(KEY_PREFERENCE_CALLS_SMS);
- mController = new MockNetworkProviderCallsSmsController(mContext, mLifecycle,
- mLifecycleOwner);
- mController.setInService(true);
- mLifecycleRegistry = new LifecycleRegistry(mLifecycleOwner);
- when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry);
- }
-
- private void displayPreferenceWithLifecycle() {
- mLifecycleRegistry.addObserver(mController);
- mPreferenceScreen.addPreference(mPreference);
- mController.displayPreference(mPreferenceScreen);
- mLifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME);
- }
-
- private String setSummaryResId(String resName) {
- return ResourcesUtils.getResourcesString(mContext, resName);
- }
-
- @Test
- @UiThreadTest
- public void getSummary_noSim_returnNoSim() {
- mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
- displayPreferenceWithLifecycle();
-
- assertTrue(TextUtils.equals(mController.getSummary(),
- setSummaryResId("calls_sms_no_sim")));
- }
-
- private SubscriptionInfoEntity setupSubscriptionInfoEntity(String subId, int slotId,
- int carrierId, String displayName, String mcc, String mnc, String countryIso,
- int cardId, boolean isValid, boolean isActive, boolean isAvailable) {
- return new SubscriptionInfoEntity(subId, slotId, carrierId,
- displayName, displayName, 0, mcc, mnc, countryIso, false, cardId,
- TelephonyManager.DEFAULT_PORT_INDEX, false, null,
- SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, displayName, false,
- "1234567890", true, false, isValid,
- true, isActive, isAvailable, false);
- }
-
- @Test
- @UiThreadTest
- public void getSummary_invalidSubId_returnUnavailable() {
-
- mSubInfo1 = setupSubscriptionInfoEntity(INVALID_SUB_ID,
- SubscriptionManager.INVALID_SIM_SLOT_INDEX, TelephonyManager.UNKNOWN_CARRIER_ID,
- DISPLAY_NAME_1, SUB_MCC_1, SUB_MNC_1, SUB_COUNTRY_ISO_1,
- TelephonyManager.UNINITIALIZED_CARD_ID, false, true, true);
- mSubscriptionInfoEntityList.add(mSubInfo1);
- mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
- displayPreferenceWithLifecycle();
-
- final StringBuilder summary = new StringBuilder();
- summary.append(DISPLAY_NAME_1)
- .append(" (")
- .append(setSummaryResId("calls_sms_temp_unavailable"))
- .append(")");
-
- assertTrue(TextUtils.equals(mController.getSummary(), summary));
- }
-
- @Test
- @UiThreadTest
- public void getSummary_oneIsInvalidSubIdTwoIsValidSubId_returnOneIsUnavailable() {
-
- mSubInfo1 = setupSubscriptionInfoEntity(INVALID_SUB_ID,
- SubscriptionManager.INVALID_SIM_SLOT_INDEX, TelephonyManager.UNKNOWN_CARRIER_ID,
- DISPLAY_NAME_1, SUB_MCC_1, SUB_MNC_1, SUB_COUNTRY_ISO_1,
- TelephonyManager.UNINITIALIZED_CARD_ID, false, true, true);
- mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
- SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true);
- mSubscriptionInfoEntityList.add(mSubInfo1);
- mSubscriptionInfoEntityList.add(mSubInfo2);
- mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
- displayPreferenceWithLifecycle();
-
- final StringBuilder summary = new StringBuilder();
- summary.append(DISPLAY_NAME_1)
- .append(" (")
- .append(setSummaryResId("calls_sms_unavailable"))
- .append(")")
- .append(", ")
- .append(DISPLAY_NAME_2);
-
- assertTrue(TextUtils.equals(mController.getSummary(), summary));
- }
-
- @Test
- @UiThreadTest
- public void getSummary_oneSubscription_returnDisplayName() {
-
- mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
- SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true);
- mSubscriptionInfoEntityList.add(mSubInfo1);
- mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
- displayPreferenceWithLifecycle();
-
- assertThat(mPreference.getSummary()).isEqualTo(DISPLAY_NAME_1);
- }
-
- @Test
- @UiThreadTest
- public void getSummary_allSubscriptionsHaveNoPreferredStatus_returnDisplayName() {
-
- mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
- SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true);
- mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
- SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true);
- mSubscriptionInfoEntityList.add(mSubInfo1);
- mSubscriptionInfoEntityList.add(mSubInfo2);
- mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
- displayPreferenceWithLifecycle();
-
- final StringBuilder summary = new StringBuilder();
- summary.append(DISPLAY_NAME_1).append(", ").append(DISPLAY_NAME_2);
-
- assertTrue(TextUtils.equals(mController.getSummary(), summary));
- }
-
- @Test
- @UiThreadTest
- public void getSummary_oneSubscriptionsIsCallPreferredTwoIsSmsPreferred_returnStatus() {
-
- mController.setDefaultVoiceSubscriptionId(Integer.parseInt(SUB_ID_1));
- mController.setDefaultSmsSubscriptionId(Integer.parseInt(SUB_ID_2));
-
- mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
- SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true);
- mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
- SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true);
- mSubscriptionInfoEntityList.add(mSubInfo1);
- mSubscriptionInfoEntityList.add(mSubInfo2);
- mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
- displayPreferenceWithLifecycle();
-
- final StringBuilder summary = new StringBuilder();
- summary.append(DISPLAY_NAME_1)
- .append(" (")
- .append(setSummaryResId("calls_sms_calls_preferred"))
- .append(")")
- .append(", ")
- .append(DISPLAY_NAME_2)
- .append(" (")
- .append(setSummaryResId("calls_sms_sms_preferred"))
- .append(")");
-
- assertTrue(TextUtils.equals(mController.getSummary(), summary));
- }
-
- @Test
- @UiThreadTest
- public void getSummary_oneSubscriptionsIsSmsPreferredTwoIsCallPreferred_returnStatus() {
-
- mController.setDefaultSmsSubscriptionId(Integer.parseInt(SUB_ID_1));
- mController.setDefaultVoiceSubscriptionId(Integer.parseInt(SUB_ID_2));
-
- mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
- SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true);
- mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 2, 2, DISPLAY_NAME_2, SUB_MCC_2,
- SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true);
- mSubscriptionInfoEntityList.add(mSubInfo1);
- mSubscriptionInfoEntityList.add(mSubInfo2);
- mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
- displayPreferenceWithLifecycle();
-
- final StringBuilder summary = new StringBuilder();
- summary.append(DISPLAY_NAME_1)
- .append(" (")
- .append(setSummaryResId("calls_sms_sms_preferred"))
- .append(")")
- .append(", ")
- .append(DISPLAY_NAME_2)
- .append(" (")
- .append(setSummaryResId("calls_sms_calls_preferred"))
- .append(")");
-
- assertTrue(TextUtils.equals(mController.getSummary(), summary));
- }
-
- @Test
- @UiThreadTest
- public void getSummary_oneSubscriptionsIsSmsPreferredAndIsCallPreferred_returnStatus() {
-
- mController.setDefaultSmsSubscriptionId(Integer.parseInt(SUB_ID_1));
- mController.setDefaultVoiceSubscriptionId(Integer.parseInt(SUB_ID_1));
-
- mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
- SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true);
- mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
- SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true);
- mSubscriptionInfoEntityList.add(mSubInfo1);
- mSubscriptionInfoEntityList.add(mSubInfo2);
- mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
- displayPreferenceWithLifecycle();
-
- final StringBuilder summary = new StringBuilder();
- summary.append(DISPLAY_NAME_1)
- .append(" (")
- .append(setSummaryResId("calls_sms_preferred"))
- .append(")")
- .append(", ")
- .append(DISPLAY_NAME_2);
-
- assertTrue(TextUtils.equals(mController.getSummary(), summary));
- }
-}