From 316e7bf3e6f5189872e1bd952a70805e6afc386c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 16 Apr 2024 08:42:08 +0000 Subject: [PATCH] Disable SIM On/Off operation when device is in Satellite Enabled Mode Cherry-picking ag/26965536 into the 24D1-dev branch caused conflicts. Therefore, manually create this CL to migrate the MobileNetworkSwitchController to Kotlin and utilize Compose. Bug: 315928920 Test: atest, manual Change-Id: I215b5a4615a3b3da6fc160f76c85c814210cc3ef Merged-In: I7aaaf43b4c449129197e7cc92565d274ffdd2d8c --- res/xml/mobile_network_settings.xml | 3 +- .../settings/network/SatelliteManagerUtil.kt | 69 ----- .../settings/network/SatelliteRepository.kt | 138 +++++++++ .../network/SubscriptionInfoListViewModel.kt | 5 +- .../settings/network/SubscriptionUtil.java | 37 +-- .../MobileNetworkSwitchController.java | 147 ---------- .../MobileNetworkSwitchController.kt | 86 ++++++ .../telephony/SubscriptionRepository.kt | 62 +++- .../sim/receivers/SimSlotChangeReceiver.java | 6 +- ...UtilTest.kt => SatelliteRepositoryTest.kt} | 65 ++++- .../MobileNetworkSwitchControllerTest.kt | 169 +++++++++++ .../telephony/SubscriptionRepositoryTest.kt | 90 +++++- .../MobileNetworkSwitchControllerTest.java | 269 ------------------ 13 files changed, 603 insertions(+), 543 deletions(-) delete mode 100644 src/com/android/settings/network/SatelliteManagerUtil.kt create mode 100644 src/com/android/settings/network/SatelliteRepository.kt delete mode 100644 src/com/android/settings/network/telephony/MobileNetworkSwitchController.java create mode 100644 src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt rename tests/robotests/src/com/android/settings/network/{SatelliteManagerUtilTest.kt => SatelliteRepositoryTest.kt} (65%) create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt delete mode 100644 tests/unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml index 1e43ef06f7e..adb84b62743 100644 --- a/res/xml/mobile_network_settings.xml +++ b/res/xml/mobile_network_settings.xml @@ -18,9 +18,8 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:key="mobile_network_pref_screen"> - { - val satelliteManager: SatelliteManager? = - context.getSystemService(SatelliteManager::class.java) - if (satelliteManager == null) { - Log.w(TAG, "SatelliteManager is null") - return immediateFuture(false) - } - - return CallbackToFutureAdapter.getFuture { completer -> - satelliteManager.requestIsEnabled(executor, - object : OutcomeReceiver { - override fun onResult(result: Boolean) { - Log.i(TAG, "Satellite modem enabled status: $result") - completer.set(result) - } - - override fun onError(error: SatelliteManager.SatelliteException) { - super.onError(error) - Log.w(TAG, "Can't get satellite modem enabled status", error) - completer.set(false) - } - }) - "requestIsEnabled" - } - } -} diff --git a/src/com/android/settings/network/SatelliteRepository.kt b/src/com/android/settings/network/SatelliteRepository.kt new file mode 100644 index 00000000000..4145e018f47 --- /dev/null +++ b/src/com/android/settings/network/SatelliteRepository.kt @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network + +import android.content.Context +import android.os.OutcomeReceiver +import android.telephony.satellite.SatelliteManager +import android.telephony.satellite.SatelliteModemStateCallback +import android.util.Log +import androidx.annotation.VisibleForTesting +import androidx.concurrent.futures.CallbackToFutureAdapter +import com.google.common.util.concurrent.Futures.immediateFuture +import com.google.common.util.concurrent.ListenableFuture +import java.util.concurrent.Executor +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flowOf + +/** + * A repository class for interacting with the SatelliteManager API. + */ +class SatelliteRepository( + private val context: Context, +) { + + /** + * Checks if the satellite modem is enabled. + * + * @param executor The executor to run the asynchronous operation on + * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled, + * `false` otherwise. + */ + fun requestIsEnabled(executor: Executor): ListenableFuture { + val satelliteManager: SatelliteManager? = + context.getSystemService(SatelliteManager::class.java) + if (satelliteManager == null) { + Log.w(TAG, "SatelliteManager is null") + return immediateFuture(false) + } + + return CallbackToFutureAdapter.getFuture { completer -> + satelliteManager.requestIsEnabled(executor, + object : OutcomeReceiver { + override fun onResult(result: Boolean) { + Log.i(TAG, "Satellite modem enabled status: $result") + completer.set(result) + } + + override fun onError(error: SatelliteManager.SatelliteException) { + super.onError(error) + Log.w(TAG, "Can't get satellite modem enabled status", error) + completer.set(false) + } + }) + "requestIsEnabled" + } + } + + /** + * Provides a Flow that emits the enabled state of the satellite modem. Updates are triggered + * when the modem state changes. + * + * @param defaultDispatcher The CoroutineDispatcher to use (Defaults to `Dispatchers.Default`). + * @return A Flow emitting `true` when the modem is enabled and `false` otherwise. + */ + fun getIsModemEnabledFlow( + defaultDispatcher: CoroutineDispatcher = Dispatchers.Default, + ): Flow { + val satelliteManager: SatelliteManager? = + context.getSystemService(SatelliteManager::class.java) + if (satelliteManager == null) { + Log.w(TAG, "SatelliteManager is null") + return flowOf(false) + } + + return callbackFlow { + val callback = SatelliteModemStateCallback { state -> + val isEnabled = convertSatelliteModemStateToEnabledState(state) + Log.i(TAG, "Satellite modem state changed: state=$state, isEnabled=$isEnabled") + trySend(isEnabled) + } + + val result = satelliteManager.registerForModemStateChanged( + defaultDispatcher.asExecutor(), + callback + ) + Log.i(TAG, "Call registerForModemStateChanged: result=$result") + + awaitClose { satelliteManager.unregisterForModemStateChanged(callback) } + } + } + + /** + * Converts a [SatelliteManager.SatelliteModemState] to a boolean representing whether the modem + * is enabled. + * + * @param state The SatelliteModemState provided by the SatelliteManager. + * @return `true` if the modem is enabled, `false` otherwise. + */ + @VisibleForTesting + fun convertSatelliteModemStateToEnabledState( + @SatelliteManager.SatelliteModemState state: Int, + ): Boolean { + // Mapping table based on logic from b/315928920#comment24 + return when (state) { + SatelliteManager.SATELLITE_MODEM_STATE_IDLE, + SatelliteManager.SATELLITE_MODEM_STATE_LISTENING, + SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING, + SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING, + SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED, + SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED -> true + else -> false + } + } + + companion object { + private const val TAG: String = "SatelliteRepository" + } +} + diff --git a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt index f6820023c92..df3b8bad278 100644 --- a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt +++ b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt @@ -20,6 +20,7 @@ import android.app.Application import android.telephony.SubscriptionManager import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope +import com.android.settings.network.telephony.getSelectableSubscriptionInfoList import com.android.settings.network.telephony.subscriptionsChangedFlow import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted @@ -41,10 +42,10 @@ class SubscriptionInfoListViewModel(application: Application) : AndroidViewModel }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList()) /** - * Getting the Selectable SubscriptionInfo List from the SubscriptionManager's + * Getting the Selectable SubscriptionInfo List from the SubscriptionRepository's * getAvailableSubscriptionInfoList */ val selectableSubscriptionInfoListFlow = application.subscriptionsChangedFlow().map { - SubscriptionUtil.getSelectableSubscriptionInfoList(application) + application.getSelectableSubscriptionInfoList() }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList()) } diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index 3632ca323c9..56381e263b3 100644 --- a/src/com/android/settings/network/SubscriptionUtil.java +++ b/src/com/android/settings/network/SubscriptionUtil.java @@ -50,12 +50,12 @@ import com.android.settings.network.helper.SelectableSubscriptions; import com.android.settings.network.helper.SubscriptionAnnotation; import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity; import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity; +import com.android.settings.network.telephony.SubscriptionRepositoryKt; import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -505,40 +505,7 @@ public class SubscriptionUtil { * @return list of user selectable subscriptions. */ public static List getSelectableSubscriptionInfoList(Context context) { - SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class); - List availableList = subManager.getAvailableSubscriptionInfoList(); - if (availableList == null) { - return null; - } else { - // Multiple subscriptions in a group should only have one representative. - // It should be the current active primary subscription if any, or any - // primary subscription. - List selectableList = new ArrayList<>(); - Map groupMap = new HashMap<>(); - - for (SubscriptionInfo info : availableList) { - // Opportunistic subscriptions are considered invisible - // to users so they should never be returned. - if (!isSubscriptionVisible(subManager, context, info)) continue; - - ParcelUuid groupUuid = info.getGroupUuid(); - if (groupUuid == null) { - // Doesn't belong to any group. Add in the list. - selectableList.add(info); - } else if (!groupMap.containsKey(groupUuid) - || (groupMap.get(groupUuid).getSimSlotIndex() == INVALID_SIM_SLOT_INDEX - && info.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX)) { - // If it belongs to a group that has never been recorded or it's the current - // active subscription, add it in the list. - selectableList.remove(groupMap.get(groupUuid)); - selectableList.add(info); - groupMap.put(groupUuid, info); - } - - } - Log.d(TAG, "getSelectableSubscriptionInfoList: " + selectableList); - return selectableList; - } + return SubscriptionRepositoryKt.getSelectableSubscriptionInfoList(context); } /** diff --git a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java deleted file mode 100644 index 20a3d8921f7..00000000000 --- a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2019 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.telephony; - -import static android.telephony.TelephonyManager.CALL_STATE_IDLE; - -import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE; -import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; - -import android.content.Context; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyCallback; -import android.telephony.TelephonyManager; - -import androidx.lifecycle.LifecycleObserver; -import androidx.lifecycle.OnLifecycleEvent; -import androidx.preference.PreferenceScreen; - -import com.android.settings.core.BasePreferenceController; -import com.android.settings.network.SubscriptionUtil; -import com.android.settings.network.SubscriptionsChangeListener; -import com.android.settings.widget.SettingsMainSwitchPreference; - -/** This controls a switch to allow enabling/disabling a mobile network */ -public class MobileNetworkSwitchController extends BasePreferenceController implements - SubscriptionsChangeListener.SubscriptionsChangeListenerClient, LifecycleObserver { - private static final String TAG = "MobileNetworkSwitchCtrl"; - private SettingsMainSwitchPreference mSwitchBar; - private int mSubId; - private SubscriptionsChangeListener mChangeListener; - private SubscriptionManager mSubscriptionManager; - private TelephonyManager mTelephonyManager; - private CallStateTelephonyCallback mCallStateCallback; - - public MobileNetworkSwitchController(Context context, String preferenceKey) { - super(context, preferenceKey); - mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); - mTelephonyManager = mContext.getSystemService(TelephonyManager.class); - mChangeListener = new SubscriptionsChangeListener(context, this); - } - - void init(int subId) { - mSubId = subId; - mTelephonyManager = mTelephonyManager.createForSubscriptionId(mSubId); - } - - @OnLifecycleEvent(ON_RESUME) - public void onResume() { - mChangeListener.start(); - - if (mCallStateCallback == null) { - mCallStateCallback = new CallStateTelephonyCallback(); - mTelephonyManager.registerTelephonyCallback( - mContext.getMainExecutor(), mCallStateCallback); - } - update(); - } - - @OnLifecycleEvent(ON_PAUSE) - public void onPause() { - if (mCallStateCallback != null) { - mTelephonyManager.unregisterTelephonyCallback(mCallStateCallback); - mCallStateCallback = null; - } - mChangeListener.stop(); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mSwitchBar = (SettingsMainSwitchPreference) screen.findPreference(mPreferenceKey); - - mSwitchBar.setOnBeforeCheckedChangeListener((isChecked) -> { - // TODO b/135222940: re-evaluate whether to use - // mSubscriptionManager#isSubscriptionEnabled - if (mSubscriptionManager.isActiveSubscriptionId(mSubId) != isChecked) { - SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, mSubId, isChecked); - return true; - } - return false; - }); - update(); - } - - private void update() { - if (mSwitchBar == null) { - return; - } - - SubscriptionInfo subInfo = null; - for (SubscriptionInfo info : SubscriptionUtil.getAvailableSubscriptions(mContext)) { - if (info.getSubscriptionId() == mSubId) { - subInfo = info; - break; - } - } - - // For eSIM, we always want the toggle. If telephony stack support disabling a pSIM - // directly, we show the toggle. - if (subInfo == null || (!subInfo.isEmbedded() && !SubscriptionUtil.showToggleForPhysicalSim( - mSubscriptionManager))) { - mSwitchBar.hide(); - } else { - mSwitchBar.show(); - mSwitchBar.setCheckedInternal(mSubscriptionManager.isActiveSubscriptionId(mSubId)); - } - } - - @Override - public int getAvailabilityStatus() { - return AVAILABLE_UNSEARCHABLE; - - } - - @Override - public void onAirplaneModeChanged(boolean airplaneModeEnabled) { - } - - @Override - public void onSubscriptionsChanged() { - update(); - } - - private class CallStateTelephonyCallback extends TelephonyCallback implements - TelephonyCallback.CallStateListener { - @Override - public void onCallStateChanged(int state) { - mSwitchBar.setSwitchBarEnabled(state == CALL_STATE_IDLE); - } - } -} diff --git a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt new file mode 100644 index 00000000000..3e1aab83ff5 --- /dev/null +++ b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony + +import android.content.Context +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.settings.R +import com.android.settings.network.SatelliteRepository +import com.android.settings.network.SubscriptionUtil +import com.android.settings.spa.preference.ComposePreferenceController +import com.android.settingslib.spa.widget.preference.MainSwitchPreference +import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map + +class MobileNetworkSwitchController @JvmOverloads constructor( + context: Context, + preferenceKey: String, + private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context), + private val satelliteRepository: SatelliteRepository = SatelliteRepository(context) +) : ComposePreferenceController(context, preferenceKey) { + + private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID + + override fun getAvailabilityStatus() = AVAILABLE_UNSEARCHABLE + + fun init(subId: Int) { + this.subId = subId + } + + @Composable + override fun Content() { + val context = LocalContext.current + if (remember { !context.isVisible() }) return + val checked by remember { + subscriptionRepository.isSubscriptionEnabledFlow(subId) + }.collectAsStateWithLifecycle(initialValue = null) + val changeable by remember { + combine( + context.callStateFlow(subId).map { it == TelephonyManager.CALL_STATE_IDLE }, + satelliteRepository.getIsModemEnabledFlow() + ) { isCallStateIdle, isSatelliteModemEnabled -> + isCallStateIdle && !isSatelliteModemEnabled + } + }.collectAsStateWithLifecycle(initialValue = true) + MainSwitchPreference(model = object : SwitchPreferenceModel { + override val title = stringResource(R.string.mobile_network_use_sim_on) + override val changeable = { changeable } + override val checked = { checked } + override val onCheckedChange = { newChecked: Boolean -> + SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, newChecked) + } + }) + } + + private fun Context.isVisible(): Boolean { + val subInfo = subscriptionRepository.getSelectableSubscriptionInfoList() + .firstOrNull { it.subscriptionId == subId } + ?: return false + // For eSIM, we always want the toggle. If telephony stack support disabling a pSIM + // directly, we show the toggle. + return subInfo.isEmbedded || requireSubscriptionManager().canDisablePhysicalSubscription() + } +} + diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt index e44b577de02..8aee2975c09 100644 --- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt +++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt @@ -20,30 +20,49 @@ import android.content.Context import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.util.Log +import androidx.lifecycle.LifecycleOwner import com.android.settings.network.SubscriptionUtil +import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach private const val TAG = "SubscriptionRepository" -fun Context.isSubscriptionEnabledFlow(subId: Int) = subscriptionsChangedFlow().map { - val subscriptionManager = getSystemService(SubscriptionManager::class.java) +class SubscriptionRepository(private val context: Context) { + /** + * Return a list of subscriptions that are available and visible to the user. + * + * @return list of user selectable subscriptions. + */ + fun getSelectableSubscriptionInfoList(): List = + context.getSelectableSubscriptionInfoList() + fun isSubscriptionEnabledFlow(subId: Int) = context.isSubscriptionEnabledFlow(subId) +} + +val Context.subscriptionManager: SubscriptionManager? + get() = getSystemService(SubscriptionManager::class.java) + +fun Context.requireSubscriptionManager(): SubscriptionManager = subscriptionManager!! + +fun Context.isSubscriptionEnabledFlow(subId: Int) = subscriptionsChangedFlow().map { subscriptionManager?.isSubscriptionEnabled(subId) ?: false -}.flowOn(Dispatchers.Default) +}.conflate().onEach { Log.d(TAG, "[$subId] isSubscriptionEnabledFlow: $it") } + .flowOn(Dispatchers.Default) fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsChangedFlow().map { SubscriptionUtil.getFormattedPhoneNumber(this, subscriptionInfo) -}.flowOn(Dispatchers.Default) +}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default) fun Context.subscriptionsChangedFlow() = callbackFlow { - val subscriptionManager = getSystemService(SubscriptionManager::class.java)!! + val subscriptionManager = requireSubscriptionManager() val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() { override fun onSubscriptionsChanged() { @@ -58,3 +77,36 @@ fun Context.subscriptionsChangedFlow() = callbackFlow { awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) } }.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default) + +/** + * Return a list of subscriptions that are available and visible to the user. + * + * @return list of user selectable subscriptions. + */ +fun Context.getSelectableSubscriptionInfoList(): List { + val subscriptionManager = requireSubscriptionManager() + val availableList = subscriptionManager.getAvailableSubscriptionInfoList() ?: return emptyList() + val visibleList = availableList.filter { subInfo -> + // Opportunistic subscriptions are considered invisible + // to users so they should never be returned. + SubscriptionUtil.isSubscriptionVisible(subscriptionManager, this, subInfo) + } + // Multiple subscriptions in a group should only have one representative. + // It should be the current active primary subscription if any, or any primary subscription. + val groupUuidToSelectedIdMap = visibleList + .groupBy { it.groupUuid } + .mapValues { (_, subInfos) -> + subInfos.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX } + .ifEmpty { subInfos } + .minOf { it.subscriptionId } + } + + return visibleList + .filter { subInfo -> + val groupUuid = subInfo.groupUuid ?: return@filter true + groupUuidToSelectedIdMap[groupUuid] == subInfo.subscriptionId + } + .sortedBy { it.subscriptionId } + .also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") } +} + diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java index 9bba2177144..4920bb80e05 100644 --- a/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java +++ b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java @@ -29,7 +29,7 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.settings.R; -import com.android.settings.network.SatelliteManagerUtil; +import com.android.settings.network.SatelliteRepository; import com.google.common.util.concurrent.ListenableFuture; @@ -58,8 +58,8 @@ public class SimSlotChangeReceiver extends BroadcastReceiver { if (shouldHandleSlotChange(context)) { Log.d(TAG, "Checking satellite enabled status"); Executor executor = Executors.newSingleThreadExecutor(); - ListenableFuture satelliteEnabledFuture = SatelliteManagerUtil - .requestIsEnabled(context, executor); + ListenableFuture satelliteEnabledFuture = new SatelliteRepository(context) + .requestIsEnabled(executor); satelliteEnabledFuture.addListener(() -> { boolean isSatelliteEnabled = false; try { diff --git a/tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt b/tests/robotests/src/com/android/settings/network/SatelliteRepositoryTest.kt similarity index 65% rename from tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt rename to tests/robotests/src/com/android/settings/network/SatelliteRepositoryTest.kt index 50d78973c16..432048cbc33 100644 --- a/tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt +++ b/tests/robotests/src/com/android/settings/network/SatelliteRepositoryTest.kt @@ -20,10 +20,12 @@ import android.content.Context import android.os.OutcomeReceiver import android.telephony.satellite.SatelliteManager import android.telephony.satellite.SatelliteManager.SatelliteException +import android.telephony.satellite.SatelliteModemStateCallback import androidx.test.core.app.ApplicationProvider -import com.android.settings.network.SatelliteManagerUtil.requestIsEnabled +import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.ListenableFuture import java.util.concurrent.Executor +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -42,7 +44,7 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) -class SatelliteManagerUtilTest { +class SatelliteRepositoryTest { @JvmField @Rule @@ -57,10 +59,15 @@ class SatelliteManagerUtilTest { @Mock private lateinit var mockExecutor: Executor + private lateinit var repository: SatelliteRepository + + @Before fun setUp() { `when`(this.spyContext.getSystemService(SatelliteManager::class.java)) .thenReturn(mockSatelliteManager) + + repository = SatelliteRepository(spyContext) } @Test @@ -78,7 +85,7 @@ class SatelliteManagerUtilTest { } val result: ListenableFuture = - requestIsEnabled(spyContext, mockExecutor) + repository.requestIsEnabled(mockExecutor) assertTrue(result.get()) } @@ -98,7 +105,7 @@ class SatelliteManagerUtilTest { } val result: ListenableFuture = - requestIsEnabled(spyContext, mockExecutor) + repository.requestIsEnabled(mockExecutor) assertFalse(result.get()) } @@ -117,7 +124,7 @@ class SatelliteManagerUtilTest { null } - val result = requestIsEnabled(spyContext, mockExecutor) + val result = repository.requestIsEnabled(mockExecutor) assertFalse(result.get()) } @@ -126,8 +133,52 @@ class SatelliteManagerUtilTest { fun requestIsEnabled_nullSatelliteManager() = runBlocking { `when`(spyContext.getSystemService(SatelliteManager::class.java)).thenReturn(null) - val result: ListenableFuture = requestIsEnabled(spyContext, mockExecutor) + val result: ListenableFuture = repository.requestIsEnabled(mockExecutor) assertFalse(result.get()) } -} \ No newline at end of file + + @Test + fun getIsModemEnabledFlow_isSatelliteEnabledState() = runBlocking { + `when`( + mockSatelliteManager.registerForModemStateChanged( + any(), + any() + ) + ).thenAnswer { invocation -> + val callback = invocation.getArgument(1) + callback.onSatelliteModemStateChanged(SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED) + SatelliteManager.SATELLITE_RESULT_SUCCESS + } + + val flow = repository.getIsModemEnabledFlow() + + assertThat(flow.first()).isTrue() + } + + @Test + fun getIsModemEnabledFlow_isSatelliteDisabledState() = runBlocking { + `when`( + mockSatelliteManager.registerForModemStateChanged( + any(), + any() + ) + ).thenAnswer { invocation -> + val callback = invocation.getArgument(1) + callback.onSatelliteModemStateChanged(SatelliteManager.SATELLITE_MODEM_STATE_OFF) + SatelliteManager.SATELLITE_RESULT_SUCCESS + } + + val flow = repository.getIsModemEnabledFlow() + + assertThat(flow.first()).isFalse() + } + + @Test + fun getIsModemEnabledFlow_nullSatelliteManager() = runBlocking { + `when`(spyContext.getSystemService(SatelliteManager::class.java)).thenReturn(null) + + val flow = repository.getIsModemEnabledFlow() + assertThat(flow.first()).isFalse() + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt new file mode 100644 index 00000000000..e7313334a6f --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony + +import android.content.Context +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.isOff +import androidx.compose.ui.test.isOn +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.android.settingslib.spa.testutils.waitUntilExists +import kotlinx.coroutines.flow.flowOf +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +class MobileNetworkSwitchControllerTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val mockSubscriptionManager = mock { + on { isSubscriptionEnabled(SUB_ID) } doReturn true + } + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { subscriptionManager } doReturn mockSubscriptionManager + doNothing().whenever(mock).startActivity(any()) + } + + private val mockSubscriptionRepository = mock { + on { getSelectableSubscriptionInfoList() } doReturn listOf(SubInfo) + on { isSubscriptionEnabledFlow(SUB_ID) } doReturn flowOf(false) + } + + private val controller = MobileNetworkSwitchController( + context = context, + preferenceKey = TEST_KEY, + subscriptionRepository = mockSubscriptionRepository, + ).apply { init(SUB_ID) } + + @Test + fun isVisible_pSimAndCanDisablePhysicalSubscription_returnTrue() { + val pSimSubInfo = SubscriptionInfo.Builder().apply { + setId(SUB_ID) + setEmbedded(false) + }.build() + mockSubscriptionManager.stub { + on { canDisablePhysicalSubscription() } doReturn true + } + mockSubscriptionRepository.stub { + on { getSelectableSubscriptionInfoList() } doReturn listOf(pSimSubInfo) + } + + setContent() + + composeTestRule.onNodeWithText(context.getString(R.string.mobile_network_use_sim_on)) + .assertIsDisplayed() + } + + @Test + fun isVisible_pSimAndCannotDisablePhysicalSubscription_returnFalse() { + val pSimSubInfo = SubscriptionInfo.Builder().apply { + setId(SUB_ID) + setEmbedded(false) + }.build() + mockSubscriptionManager.stub { + on { canDisablePhysicalSubscription() } doReturn false + } + mockSubscriptionRepository.stub { + on { getSelectableSubscriptionInfoList() } doReturn listOf(pSimSubInfo) + } + + setContent() + + composeTestRule.onNodeWithText(context.getString(R.string.mobile_network_use_sim_on)) + .assertDoesNotExist() + } + + @Test + fun isVisible_eSim_returnTrue() { + val eSimSubInfo = SubscriptionInfo.Builder().apply { + setId(SUB_ID) + setEmbedded(true) + }.build() + mockSubscriptionRepository.stub { + on { getSelectableSubscriptionInfoList() } doReturn listOf(eSimSubInfo) + } + + setContent() + + composeTestRule.onNodeWithText(context.getString(R.string.mobile_network_use_sim_on)) + .assertIsDisplayed() + } + + @Test + fun isChecked_subscriptionEnabled_switchIsOn() { + mockSubscriptionRepository.stub { + on { isSubscriptionEnabledFlow(SUB_ID) } doReturn flowOf(true) + } + + setContent() + + composeTestRule.waitUntilExists( + hasText(context.getString(R.string.mobile_network_use_sim_on)) and isOn() + ) + } + + @Test + fun isChecked_subscriptionNotEnabled_switchIsOff() { + mockSubscriptionRepository.stub { + on { isSubscriptionEnabledFlow(SUB_ID) } doReturn flowOf(false) + } + + setContent() + + composeTestRule.waitUntilExists( + hasText(context.getString(R.string.mobile_network_use_sim_on)) and isOff() + ) + } + + private fun setContent() { + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + controller.Content() + } + } + } + + private companion object { + const val TEST_KEY = "test_key" + const val SUB_ID = 123 + + val SubInfo: SubscriptionInfo = SubscriptionInfo.Builder().apply { + setId(SUB_ID) + setEmbedded(true) + }.build() + } +} + diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt index a59bf932508..3f69155a204 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt @@ -17,12 +17,14 @@ package com.android.settings.network.telephony 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.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spa.testutils.toListWithTimeout import com.google.common.truth.Truth.assertThat +import java.util.UUID import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking @@ -47,16 +49,16 @@ class SubscriptionRepositoryTest { } private val context: Context = spy(ApplicationProvider.getApplicationContext()) { - on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager + on { subscriptionManager } doReturn mockSubscriptionManager } @Test fun isSubscriptionEnabledFlow() = runBlocking { mockSubscriptionManager.stub { - on { isSubscriptionEnabled(SUB_ID) } doReturn true + on { isSubscriptionEnabled(SUB_ID_1) } doReturn true } - val isEnabled = context.isSubscriptionEnabledFlow(SUB_ID).firstWithTimeoutOrNull() + val isEnabled = context.isSubscriptionEnabledFlow(SUB_ID_1).firstWithTimeoutOrNull() assertThat(isEnabled).isTrue() } @@ -80,7 +82,87 @@ class SubscriptionRepositoryTest { assertThat(listDeferred.await()).hasSize(2) } + @Test + fun getSelectableSubscriptionInfoList_sortedBySubId() { + mockSubscriptionManager.stub { + on { getAvailableSubscriptionInfoList() } doReturn listOf( + SubscriptionInfo.Builder().apply { + setId(SUB_ID_2) + }.build(), + SubscriptionInfo.Builder().apply { + setId(SUB_ID_1) + }.build(), + ) + } + + val subInfos = context.getSelectableSubscriptionInfoList() + + assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_1, SUB_ID_2).inOrder() + } + + @Test + fun getSelectableSubscriptionInfoList_sameGroupAndOneHasSlot_returnTheOneWithSimSlotIndex() { + mockSubscriptionManager.stub { + on { getAvailableSubscriptionInfoList() } doReturn listOf( + SubscriptionInfo.Builder().apply { + setId(SUB_ID_1) + setGroupUuid(GROUP_UUID) + }.build(), + SubscriptionInfo.Builder().apply { + setId(SUB_ID_2) + setGroupUuid(GROUP_UUID) + setSimSlotIndex(SIM_SLOT_INDEX) + }.build(), + ) + } + + val subInfos = context.getSelectableSubscriptionInfoList() + + assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_2) + } + + @Test + fun getSelectableSubscriptionInfoList_sameGroupAndNonHasSlot_returnTheOneWithMinimumSubId() { + mockSubscriptionManager.stub { + on { getAvailableSubscriptionInfoList() } doReturn listOf( + SubscriptionInfo.Builder().apply { + setId(SUB_ID_2) + setGroupUuid(GROUP_UUID) + }.build(), + SubscriptionInfo.Builder().apply { + setId(SUB_ID_1) + setGroupUuid(GROUP_UUID) + }.build(), + ) + } + + val subInfos = context.getSelectableSubscriptionInfoList() + + assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_1) + } + + @Test + fun phoneNumberFlow() = runBlocking { + mockSubscriptionManager.stub { + on { getPhoneNumber(SUB_ID_1) } doReturn NUMBER_1 + } + val subInfo = SubscriptionInfo.Builder().apply { + setId(SUB_ID_1) + setMcc(MCC) + }.build() + + val phoneNumber = context.phoneNumberFlow(subInfo).firstWithTimeoutOrNull() + + assertThat(phoneNumber).isEqualTo(NUMBER_1) + } + private companion object { - const val SUB_ID = 1 + const val SUB_ID_1 = 1 + const val SUB_ID_2 = 2 + val GROUP_UUID = UUID.randomUUID().toString() + const val SIM_SLOT_INDEX = 1 + const val NUMBER_1 = "000000001" + const val MCC = "310" } } + diff --git a/tests/unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java deleted file mode 100644 index 3cdd23ab1f5..00000000000 --- a/tests/unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java +++ /dev/null @@ -1,269 +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.telephony; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Looper; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyCallback; -import android.telephony.TelephonyManager; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.LinearLayout; - -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceScreen; -import androidx.preference.PreferenceViewHolder; -import androidx.test.annotation.UiThreadTest; -import androidx.test.core.app.ApplicationProvider; - -import com.android.settings.network.SubscriptionUtil; -import com.android.settings.widget.SettingsMainSwitchPreference; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.util.Arrays; -import java.util.concurrent.Executor; - -public class MobileNetworkSwitchControllerTest { - @Rule - public final MockitoRule mMockitoRule = MockitoJUnit.rule(); - - @Mock - private SubscriptionManager mSubscriptionManager; - @Mock - private SubscriptionInfo mSubscription; - @Mock - private TelephonyManager mTelephonyManager; - - private PreferenceScreen mScreen; - private PreferenceManager mPreferenceManager; - private SettingsMainSwitchPreference mSwitchBar; - private Context mContext; - private MobileNetworkSwitchController mController; - private int mSubId = 123; - - @Before - public void setUp() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mContext = spy(ApplicationProvider.getApplicationContext()); - when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); - when(mSubscriptionManager.setSubscriptionEnabled(eq(mSubId), anyBoolean())) - .thenReturn(true); - - when(mSubscription.isEmbedded()).thenReturn(true); - when(mSubscription.getSubscriptionId()).thenReturn(mSubId); - // Most tests want to have 2 available subscriptions so that the switch bar will show. - final SubscriptionInfo sub2 = mock(SubscriptionInfo.class); - when(sub2.getSubscriptionId()).thenReturn(456); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription, sub2)); - - when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); - when(mTelephonyManager.createForSubscriptionId(mSubId)) - .thenReturn(mTelephonyManager); - - final String key = "prefKey"; - mController = new MobileNetworkSwitchController(mContext, key); - mController.init(mSubscription.getSubscriptionId()); - - mPreferenceManager = new PreferenceManager(mContext); - mScreen = mPreferenceManager.createPreferenceScreen(mContext); - mSwitchBar = new SettingsMainSwitchPreference(mContext); - mSwitchBar.setKey(key); - mSwitchBar.setTitle("123"); - mScreen.addPreference(mSwitchBar); - - final LayoutInflater inflater = LayoutInflater.from(mContext); - final View view = inflater.inflate(mSwitchBar.getLayoutResource(), - new LinearLayout(mContext), false); - final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view); - mSwitchBar.onBindViewHolder(holder); - } - - @After - public void cleanUp() { - SubscriptionUtil.setAvailableSubscriptionsForTesting(null); - } - - @Test - @UiThreadTest - public void isAvailable_pSIM_isNotAvailable() { - when(mSubscription.isEmbedded()).thenReturn(false); - mController.displayPreference(mScreen); - assertThat(mSwitchBar.isShowing()).isFalse(); - - when(mSubscriptionManager.canDisablePhysicalSubscription()).thenReturn(true); - mController.displayPreference(mScreen); - assertThat(mSwitchBar.isShowing()).isTrue(); - } - - @Test - @UiThreadTest - public void displayPreference_oneEnabledSubscription_switchBarNotHidden() { - doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(mSubId); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription)); - mController.displayPreference(mScreen); - assertThat(mSwitchBar.isShowing()).isTrue(); - } - - @Test - @UiThreadTest - public void displayPreference_oneDisabledSubscription_switchBarNotHidden() { - doReturn(false).when(mSubscriptionManager).isActiveSubscriptionId(mSubId); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription)); - - mController.displayPreference(mScreen); - - assertThat(mSwitchBar.isShowing()).isTrue(); - } - - @Test - @UiThreadTest - public void displayPreference_subscriptionEnabled_switchIsOn() { - when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(true); - mController.displayPreference(mScreen); - assertThat(mSwitchBar.isShowing()).isTrue(); - assertThat(mSwitchBar.isChecked()).isTrue(); - } - - @Test - @UiThreadTest - public void displayPreference_subscriptionDisabled_switchIsOff() { - when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(false); - - mController.displayPreference(mScreen); - - assertThat(mSwitchBar.isShowing()).isTrue(); - assertThat(mSwitchBar.isChecked()).isFalse(); - } - - @Test - @UiThreadTest - public void switchChangeListener_fromEnabledToDisabled_setSubscriptionEnabledCalledCorrectly() { - when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(true); - mController.displayPreference(mScreen); - assertThat(mSwitchBar.isShowing()).isTrue(); - assertThat(mSwitchBar.isChecked()).isTrue(); - - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - doNothing().when(mContext).startActivity(intentCaptor.capture()); - - // set switch off then should start a Activity. - mSwitchBar.setChecked(false); - - when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(false); - // Simulate action of back from previous activity. - mController.displayPreference(mScreen); - Bundle extra = intentCaptor.getValue().getExtras(); - - verify(mContext, times(1)).startActivity(any()); - assertThat(extra.getInt(ToggleSubscriptionDialogActivity.ARG_SUB_ID)).isEqualTo(mSubId); - assertThat(extra.getBoolean(ToggleSubscriptionDialogActivity.ARG_enable)) - .isEqualTo(false); - assertThat(mSwitchBar.isChecked()).isFalse(); - } - - @Test - @UiThreadTest - public void switchChangeListener_fromEnabledToDisabled_setSubscriptionEnabledFailed() { - when(mSubscriptionManager.setSubscriptionEnabled(eq(mSubId), anyBoolean())) - .thenReturn(false); - when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(true); - mController.displayPreference(mScreen); - assertThat(mSwitchBar.isShowing()).isTrue(); - assertThat(mSwitchBar.isChecked()).isTrue(); - - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - doNothing().when(mContext).startActivity(intentCaptor.capture()); - - // set switch off then should start a Activity. - mSwitchBar.setChecked(false); - - // Simulate action of back from previous activity. - mController.displayPreference(mScreen); - Bundle extra = intentCaptor.getValue().getExtras(); - - verify(mContext, times(1)).startActivity(any()); - assertThat(extra.getInt(ToggleSubscriptionDialogActivity.ARG_SUB_ID)).isEqualTo(mSubId); - assertThat(extra.getBoolean(ToggleSubscriptionDialogActivity.ARG_enable)) - .isEqualTo(false); - assertThat(mSwitchBar.isChecked()).isTrue(); - } - - @Test - @UiThreadTest - public void switchChangeListener_fromDisabledToEnabled_setSubscriptionEnabledCalledCorrectly() { - when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(false); - mController.displayPreference(mScreen); - assertThat(mSwitchBar.isShowing()).isTrue(); - assertThat(mSwitchBar.isChecked()).isFalse(); - - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - doNothing().when(mContext).startActivity(intentCaptor.capture()); - mSwitchBar.setChecked(true); - Bundle extra = intentCaptor.getValue().getExtras(); - - verify(mContext, times(1)).startActivity(any()); - assertThat(extra.getInt(ToggleSubscriptionDialogActivity.ARG_SUB_ID)).isEqualTo(mSubId); - assertThat(extra.getBoolean(ToggleSubscriptionDialogActivity.ARG_enable)).isEqualTo(true); - } - @Test - @UiThreadTest - public void onResumeAndonPause_registerAndUnregisterTelephonyCallback() { - mController.onResume(); - - verify(mTelephonyManager) - .registerTelephonyCallback(any(Executor.class), any(TelephonyCallback.class)); - - mController.onPause(); - verify(mTelephonyManager) - .unregisterTelephonyCallback(any(TelephonyCallback.class)); - } - - @Test - @UiThreadTest - public void onPause_doNotRegisterAndUnregisterTelephonyCallback() { - mController.onPause(); - verify(mTelephonyManager, times(0)) - .unregisterTelephonyCallback(any(TelephonyCallback.class)); - } -}