Loading res/drawable/ic_calls_sms.xmldeleted 100644 → 0 +0 −29 Original line number Diff line number Diff line <!-- 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> res/xml/network_provider_internet.xml +2 −7 Original line number Diff line number Diff line Loading @@ -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" Loading src/com/android/settings/network/NetworkDashboardFragment.java +0 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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); Loading src/com/android/settings/network/NetworkProviderCallsSmsController.javadeleted 100644 → 0 +0 −258 Original line number Diff line number Diff line /* * 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(); } } src/com/android/settings/network/NetworkProviderCallsSmsController.kt 0 → 100644 +196 −0 Original line number Diff line number Diff line /* * 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) } } Loading
res/drawable/ic_calls_sms.xmldeleted 100644 → 0 +0 −29 Original line number Diff line number Diff line <!-- 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>
res/xml/network_provider_internet.xml +2 −7 Original line number Diff line number Diff line Loading @@ -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" Loading
src/com/android/settings/network/NetworkDashboardFragment.java +0 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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); Loading
src/com/android/settings/network/NetworkProviderCallsSmsController.javadeleted 100644 → 0 +0 −258 Original line number Diff line number Diff line /* * 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(); } }
src/com/android/settings/network/NetworkProviderCallsSmsController.kt 0 → 100644 +196 −0 Original line number Diff line number Diff line /* * 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) } }