Merge "Fix restriction to configure Calls & SMS" into main

This commit is contained in:
Chaohui Wang
2023-11-16 03:32:37 +00:00
committed by Android (Google) Code Review
8 changed files with 399 additions and 659 deletions

View File

@@ -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>

View File

@@ -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"

View File

@@ -22,7 +22,6 @@ import android.app.settings.SettingsEnums;
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 androidx.lifecycle.LifecycleOwner;
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 @@ public class NetworkDashboardFragment extends DashboardFragment implements
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);

View File

@@ -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();
}
}

View File

@@ -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)
}
}

View File

@@ -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())
}

View File

@@ -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()
}
}

View File

@@ -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));
}
}