Check if ECBMode when deactivate SIM card

If in ECBMode, start ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS to show a
dialog instead.

This align with the current airplane mode switch.

Fix: 191943857
Test: adb shell cmd phone emergency-callback-mode
Test: unit test
Change-Id: Icf646cd76990d621121b4367ec0fd02a3880b85c
This commit is contained in:
Chaohui Wang
2024-05-30 16:37:51 +08:00
parent ffa3d11e26
commit 1bfea5d472
4 changed files with 111 additions and 11 deletions

View File

@@ -21,14 +21,15 @@ import android.telephony.SubscriptionManager
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R import com.android.settings.R
import com.android.settings.network.SubscriptionUtil
import com.android.settings.spa.preference.ComposePreferenceController import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.MainSwitchPreference import com.android.settingslib.spa.widget.preference.MainSwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import kotlinx.coroutines.launch
class MobileNetworkSwitchController @JvmOverloads constructor( class MobileNetworkSwitchController @JvmOverloads constructor(
context: Context, context: Context,
@@ -56,12 +57,15 @@ class MobileNetworkSwitchController @JvmOverloads constructor(
val changeable by remember { val changeable by remember {
subscriptionActivationRepository.isActivationChangeableFlow() subscriptionActivationRepository.isActivationChangeableFlow()
}.collectAsStateWithLifecycle(initialValue = true) }.collectAsStateWithLifecycle(initialValue = true)
val coroutineScope = rememberCoroutineScope()
MainSwitchPreference(model = object : SwitchPreferenceModel { MainSwitchPreference(model = object : SwitchPreferenceModel {
override val title = stringResource(R.string.mobile_network_use_sim_on) override val title = stringResource(R.string.mobile_network_use_sim_on)
override val changeable = { changeable } override val changeable = { changeable }
override val checked = { checked } override val checked = { checked }
override val onCheckedChange = { newChecked: Boolean -> override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, newChecked) coroutineScope.launch {
subscriptionActivationRepository.setActive(subId, newChecked)
}
} }
}) })
} }

View File

@@ -17,9 +17,18 @@
package com.android.settings.network.telephony package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.content.Intent
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS
import android.util.Log
import com.android.settings.Utils
import com.android.settings.flags.Flags
import com.android.settings.network.SatelliteRepository import com.android.settings.network.SatelliteRepository
import com.android.settings.network.SimOnboardingActivity.Companion.startSimOnboardingActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.withContext
class SubscriptionActivationRepository( class SubscriptionActivationRepository(
private val context: Context, private val context: Context,
@@ -32,4 +41,36 @@ class SubscriptionActivationRepository(
) { isInCall, isSatelliteModemEnabled -> ) { isInCall, isSatelliteModemEnabled ->
!isInCall && !isSatelliteModemEnabled !isInCall && !isSatelliteModemEnabled
} }
/**
* Starts a dialog activity to handle SIM enabling / disabling.
* @param subId The id of subscription need to be enabled or disabled.
* @param active Whether the subscription with [subId] should be enabled or disabled.
*/
suspend fun setActive(subId: Int, active: Boolean) {
if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
Log.i(TAG, "Unable to toggle subscription due to unusable subscription ID.")
return
}
if (!active && isEmergencyCallbackMode(subId)) {
val intent = Intent(ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS).apply {
setPackage(Utils.PHONE_PACKAGE_NAME)
}
context.startActivity(intent)
return
}
if (active && Flags.isDualSimOnboardingEnabled()) {
startSimOnboardingActivity(context, subId)
return
}
context.startActivity(ToggleSubscriptionDialogActivity.getIntent(context, subId, active))
}
private suspend fun isEmergencyCallbackMode(subId: Int) = withContext(Dispatchers.Default) {
context.telephonyManager(subId).emergencyCallbackMode
}
private companion object {
private const val TAG = "SubscriptionActivationR"
}
} }

View File

@@ -30,6 +30,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -47,6 +48,7 @@ import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference
import com.android.settingslib.spaprivileged.template.preference.RestrictedTwoTargetSwitchPreference import com.android.settingslib.spaprivileged.template.preference.RestrictedTwoTargetSwitchPreference
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
@Composable @Composable
fun SimsSection(subscriptionInfoList: List<SubscriptionInfo>) { fun SimsSection(subscriptionInfoList: List<SubscriptionInfo>) {
@@ -71,9 +73,11 @@ private fun SimPreference(subInfo: SubscriptionInfo) {
emit(SubscriptionUtil.isConvertedPsimSubscription(subInfo)) emit(SubscriptionUtil.isConvertedPsimSubscription(subInfo))
} }
}.collectAsStateWithLifecycle(initialValue = false) }.collectAsStateWithLifecycle(initialValue = false)
val subscriptionActivationRepository = remember { SubscriptionActivationRepository(context) }
val isActivationChangeable by remember { val isActivationChangeable by remember {
SubscriptionActivationRepository(context).isActivationChangeableFlow() subscriptionActivationRepository.isActivationChangeableFlow()
}.collectAsStateWithLifecycle(initialValue = false) }.collectAsStateWithLifecycle(initialValue = false)
val coroutineScope = rememberCoroutineScope()
RestrictedTwoTargetSwitchPreference( RestrictedTwoTargetSwitchPreference(
model = object : SwitchPreferenceModel { model = object : SwitchPreferenceModel {
override val title = subInfo.displayName.toString() override val title = subInfo.displayName.toString()
@@ -87,12 +91,10 @@ private fun SimPreference(subInfo: SubscriptionInfo) {
override val icon = @Composable { SimIcon(subInfo.isEmbedded) } override val icon = @Composable { SimIcon(subInfo.isEmbedded) }
override val changeable = { isActivationChangeable && !isConvertedPsim } override val changeable = { isActivationChangeable && !isConvertedPsim }
override val checked = { checked.value } override val checked = { checked.value }
override val onCheckedChange = { newChecked: Boolean -> override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
SubscriptionUtil.startToggleSubscriptionDialogActivity( coroutineScope.launch {
context, subscriptionActivationRepository.setActive(subInfo.subscriptionId, newChecked)
subInfo.subscriptionId, }
newChecked,
)
} }
}, },
restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)), restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)),

View File

@@ -17,6 +17,9 @@
package com.android.settings.network.telephony package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.network.SatelliteRepository import com.android.settings.network.SatelliteRepository
@@ -26,14 +29,29 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.doNothing
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class SubscriptionActivationRepositoryTest { class SubscriptionActivationRepositoryTest {
private val context: Context = ApplicationProvider.getApplicationContext() private val mockTelephonyManager = mock<TelephonyManager> {
on { createForSubscriptionId(SUB_ID) } doReturn mock
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
doNothing().whenever(mock).startActivity(any())
on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
}
private val mockCallStateRepository = mock<CallStateRepository>() private val mockCallStateRepository = mock<CallStateRepository>()
private val mockSatelliteRepository = mock<SatelliteRepository>() private val mockSatelliteRepository = mock<SatelliteRepository>()
@@ -81,4 +99,39 @@ class SubscriptionActivationRepositoryTest {
assertThat(changeable).isFalse() assertThat(changeable).isFalse()
} }
@Test
fun setActive_defaultSubId_doNothing() = runBlocking {
repository.setActive(subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, active = true)
verify(context, never()).startActivity(any())
}
@Test
fun setActive_turnOffAndIsEmergencyCallbackMode() = runBlocking {
mockTelephonyManager.stub {
on { emergencyCallbackMode } doReturn true
}
repository.setActive(subId = SUB_ID, active = false)
verify(context).startActivity(argThat { action == ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS })
}
@Test
fun setActive_turnOffAndNotEmergencyCallbackMode() = runBlocking {
mockTelephonyManager.stub {
on { emergencyCallbackMode } doReturn false
}
repository.setActive(subId = SUB_ID, active = false)
verify(context).startActivity(argThat {
component?.className == ToggleSubscriptionDialogActivity::class.qualifiedName
})
}
private companion object {
const val SUB_ID = 1
}
} }