Merge "Create EuiccRepository" into main

This commit is contained in:
Chaohui Wang
2024-08-29 06:52:07 +00:00
committed by Android (Google) Code Review
6 changed files with 262 additions and 87 deletions

View File

@@ -27,7 +27,7 @@ import com.android.settings.R
import com.android.settings.SettingsPreferenceFragment
import com.android.settings.dashboard.DashboardFragment
import com.android.settings.flags.Flags
import com.android.settings.network.telephony.MobileNetworkUtils
import com.android.settings.network.telephony.euicc.EuiccRepository
import com.android.settings.search.BaseSearchIndexProvider
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.network.NetworkCellularGroupProvider
@@ -58,7 +58,7 @@ class MobileNetworkListFragment : DashboardFragment() {
listView.itemAnimator = null
findPreference<Preference>(KEY_ADD_SIM)!!.isVisible =
MobileNetworkUtils.showEuiccSettings(context)
EuiccRepository(requireContext()).showEuiccSettings()
}
override fun getPreferenceScreenResId() = R.xml.network_provider_sims_list

View File

@@ -35,7 +35,7 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.network.telephony.MobileNetworkUtils;
import com.android.settings.network.telephony.euicc.EuiccRepository;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.Utils;
@@ -118,7 +118,7 @@ public class MobileNetworkSummaryController extends AbstractPreferenceController
if ((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) || (
mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty()) || (
mMobileNetworkInfoEntityList == null || mMobileNetworkInfoEntityList.isEmpty())) {
if (MobileNetworkUtils.showEuiccSettingsDetecting(mContext)) {
if (new EuiccRepository(mContext).showEuiccSettings()) {
return mContext.getResources().getString(
R.string.mobile_network_summary_add_a_network);
}
@@ -168,7 +168,7 @@ public class MobileNetworkSummaryController extends AbstractPreferenceController
|| (mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty())
|| (mMobileNetworkInfoEntityList == null
|| mMobileNetworkInfoEntityList.isEmpty()))) {
if (MobileNetworkUtils.showEuiccSettingsDetecting(mContext)) {
if (new EuiccRepository(mContext).showEuiccSettings()) {
mPreference.setOnPreferenceClickListener((Preference pref) -> {
logPreferenceClick(pref);
startAddSimFlow();

View File

@@ -32,7 +32,6 @@ import static com.android.settings.network.telephony.TelephonyConstants.Telephon
import static com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants.NETWORK_MODE_NR_LTE_GSM_WCDMA;
import android.app.KeyguardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -51,8 +50,6 @@ import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -89,32 +86,17 @@ import com.android.settings.network.ims.WifiCallingQueryImsState;
import com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants;
import com.android.settings.network.telephony.wificalling.WifiCallingRepository;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
import com.android.settingslib.utils.ThreadUtils;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class MobileNetworkUtils {
private static final String TAG = "MobileNetworkUtils";
// CID of the device.
private static final String KEY_CID = "ro.boot.cid";
// CIDs of devices which should not show anything related to eSIM.
private static final String KEY_ESIM_CID_IGNORE = "ro.setupwizard.esim_cid_ignore";
// System Property which is used to decide whether the default eSIM UI will be shown,
// the default value is false.
private static final String KEY_ENABLE_ESIM_UI_BY_DEFAULT =
"esim.enable_esim_system_ui_by_default";
private static final String LEGACY_ACTION_CONFIGURE_PHONE_ACCOUNT =
"android.telecom.action.CONNECTION_SERVICE_CONFIGURE";
private static final String RTL_MARK = "\u200F";
@@ -282,64 +264,6 @@ public class MobileNetworkUtils {
return intent;
}
/**
* Whether to show the entry point to eUICC settings.
*
* <p>We show the entry point on any device which supports eUICC as long as either the eUICC
* was ever provisioned (that is, at least one profile was ever downloaded onto it), or if
* the user has enabled development mode.
*/
public static boolean showEuiccSettings(Context context) {
if (!SubscriptionUtil.isSimHardwareVisible(context)) {
return false;
}
long timeForAccess = SystemClock.elapsedRealtime();
try {
Boolean isShow = ((Future<Boolean>) ThreadUtils.postOnBackgroundThread(() -> {
try {
return showEuiccSettingsDetecting(context);
} catch (Exception threadException) {
Log.w(TAG, "Accessing Euicc failure", threadException);
}
return Boolean.FALSE;
})).get(3, TimeUnit.SECONDS);
return ((isShow != null) && isShow.booleanValue());
} catch (ExecutionException | InterruptedException | TimeoutException exception) {
timeForAccess = SystemClock.elapsedRealtime() - timeForAccess;
Log.w(TAG, "Accessing Euicc takes too long: +" + timeForAccess + "ms");
}
return false;
}
// The same as #showEuiccSettings(Context context)
public static Boolean showEuiccSettingsDetecting(Context context) {
final EuiccManager euiccManager =
(EuiccManager) context.getSystemService(EuiccManager.class);
if (euiccManager == null || !euiccManager.isEnabled()) {
Log.w(TAG, "EuiccManager is not enabled.");
return false;
}
final ContentResolver cr = context.getContentResolver();
final boolean esimIgnoredDevice =
Arrays.asList(TextUtils.split(SystemProperties.get(KEY_ESIM_CID_IGNORE, ""), ","))
.contains(SystemProperties.get(KEY_CID));
final boolean enabledEsimUiByDefault =
SystemProperties.getBoolean(KEY_ENABLE_ESIM_UI_BY_DEFAULT, true);
final boolean euiccProvisioned =
Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) != 0;
final boolean inDeveloperMode =
DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context);
Log.i(TAG,
String.format("showEuiccSettings: esimIgnoredDevice: %b, enabledEsimUiByDefault: "
+ "%b, euiccProvisioned: %b, inDeveloperMode: %b.",
esimIgnoredDevice, enabledEsimUiByDefault, euiccProvisioned, inDeveloperMode));
return (euiccProvisioned
|| (!esimIgnoredDevice && inDeveloperMode)
|| (!esimIgnoredDevice && enabledEsimUiByDefault
&& isCurrentCountrySupported(context)));
}
/**
* Return {@code true} if mobile data is enabled
*/

View File

@@ -0,0 +1,129 @@
/*
* 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.euicc
import android.content.Context
import android.os.SystemProperties
import android.provider.Settings
import android.telephony.TelephonyManager
import android.telephony.euicc.EuiccManager
import android.util.Log
import com.android.settings.network.SubscriptionUtil
import com.android.settingslib.development.DevelopmentSettingsEnabler
import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBoolean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
class EuiccRepository
@JvmOverloads
constructor(
private val context: Context,
private val isEuiccProvisioned: () -> Boolean = {
val euiccProvisioned by context.settingsGlobalBoolean(Settings.Global.EUICC_PROVISIONED)
euiccProvisioned
},
private val isDevelopmentSettingsEnabled: () -> Boolean = {
DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context)
},
) {
private val euiccManager = context.getSystemService(EuiccManager::class.java)
private val telephonyManager = context.getSystemService(TelephonyManager::class.java)
fun showEuiccSettingsFlow() =
flow { emit(showEuiccSettings()) }
.distinctUntilChanged()
.conflate()
.flowOn(Dispatchers.Default)
/**
* Whether to show the entry point to eUICC settings.
*
* We show the entry point on any device which supports eUICC as long as either the eUICC was
* ever provisioned (that is, at least one profile was ever downloaded onto it), or if the user
* has enabled development mode.
*/
fun showEuiccSettings(): Boolean {
if (!SubscriptionUtil.isSimHardwareVisible(context)) return false
if (euiccManager == null || !euiccManager.isEnabled) {
Log.w(TAG, "EuiccManager is not enabled.")
return false
}
if (isEuiccProvisioned()) {
Log.i(TAG, "showEuiccSettings: euicc provisioned")
return true
}
val ignoredCids =
SystemProperties.get(KEY_ESIM_CID_IGNORE).split(',').filter { it.isNotEmpty() }
val cid = SystemProperties.get(KEY_CID)
if (cid in ignoredCids) {
Log.i(TAG, "showEuiccSettings: cid ignored")
return false
}
if (isDevelopmentSettingsEnabled()) {
Log.i(TAG, "showEuiccSettings: development settings enabled")
return true
}
val enabledEsimUiByDefault =
SystemProperties.getBoolean(KEY_ENABLE_ESIM_UI_BY_DEFAULT, true)
Log.i(TAG, "showEuiccSettings: enabledEsimUiByDefault=$enabledEsimUiByDefault")
return enabledEsimUiByDefault && isCurrentCountrySupported()
}
/**
* Loop through all the device logical slots to check whether the user's current country
* supports eSIM.
*/
private fun isCurrentCountrySupported(): Boolean {
val euiccManager = euiccManager ?: return false
val telephonyManager = telephonyManager ?: return false
val visitedCountrySet = mutableSetOf<String>()
for (slotIndex in 0 until telephonyManager.getActiveModemCount()) {
val countryCode = telephonyManager.getNetworkCountryIso(slotIndex)
if (
countryCode.isNotEmpty() &&
visitedCountrySet.add(countryCode) &&
euiccManager.isSupportedCountry(countryCode)
) {
Log.i(TAG, "isCurrentCountrySupported: $countryCode is supported")
return true
}
}
Log.i(TAG, "isCurrentCountrySupported: no country is supported")
return false
}
companion object {
private const val TAG = "EuiccRepository"
/** CID of the device. */
private const val KEY_CID: String = "ro.boot.cid"
/** CIDs of devices which should not show anything related to eSIM. */
private const val KEY_ESIM_CID_IGNORE: String = "ro.setupwizard.esim_cid_ignore"
/**
* System Property which is used to decide whether the default eSIM UI will be shown, the
* default value is false.
*/
private const val KEY_ENABLE_ESIM_UI_BY_DEFAULT: String =
"esim.enable_esim_system_ui_by_default"
}
}

View File

@@ -40,6 +40,7 @@ import com.android.settings.network.SubscriptionUtil
import com.android.settings.network.telephony.MobileNetworkUtils
import com.android.settings.network.telephony.SubscriptionActivationRepository
import com.android.settings.network.telephony.SubscriptionRepository
import com.android.settings.network.telephony.euicc.EuiccRepository
import com.android.settings.network.telephony.phoneNumberFlow
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
@@ -120,13 +121,17 @@ fun phoneNumber(subInfo: SubscriptionInfo): State<String?> {
@Composable
private fun AddSim() {
val context = LocalContext.current
if (remember { MobileNetworkUtils.showEuiccSettings(context) }) {
val isShow by
remember { EuiccRepository(context).showEuiccSettingsFlow() }
.collectAsStateWithLifecycle(initialValue = false)
if (isShow) {
RestrictedPreference(
model = object : PreferenceModel {
override val title = stringResource(id = R.string.mobile_network_list_add_more)
override val icon = @Composable { SettingsIcon(Icons.Outlined.Add) }
override val onClick = { startAddSimFlow(context) }
},
model =
object : PreferenceModel {
override val title = stringResource(id = R.string.mobile_network_list_add_more)
override val icon = @Composable { SettingsIcon(Icons.Outlined.Add) }
override val onClick = { startAddSimFlow(context) }
},
restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)),
)
}

View File

@@ -0,0 +1,117 @@
/*
* 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.euicc
import android.content.Context
import android.telephony.TelephonyManager
import android.telephony.euicc.EuiccManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class EuiccRepositoryTest {
private val mockEuiccManager = mock<EuiccManager> { on { isEnabled } doReturn true }
private val mockTelephonyManager =
mock<TelephonyManager> {
on { activeModemCount } doReturn 1
on { getNetworkCountryIso(any()) } doReturn COUNTRY_CODE
}
private val context: Context =
spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(EuiccManager::class.java) } doReturn mockEuiccManager
on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
}
private val resources =
spy(context.resources) { on { getBoolean(R.bool.config_show_sim_info) } doReturn true }
private var euiccProvisioned = false
private val repository =
EuiccRepository(
context,
isEuiccProvisioned = { euiccProvisioned },
isDevelopmentSettingsEnabled = { false },
)
@Before
fun setUp() {
context.stub { on { resources } doReturn resources }
}
@Test
fun showEuiccSettings_noSim_returnFalse() {
resources.stub { on { getBoolean(R.bool.config_show_sim_info) } doReturn false }
val showEuiccSettings = repository.showEuiccSettings()
assertThat(showEuiccSettings).isFalse()
}
@Test
fun showEuiccSettings_euiccDisabled_returnFalse() {
mockEuiccManager.stub { on { isEnabled } doReturn false }
val showEuiccSettings = repository.showEuiccSettings()
assertThat(showEuiccSettings).isFalse()
}
@Test
fun showEuiccSettings_euiccProvisioned_returnTrue() {
euiccProvisioned = true
val showEuiccSettings = repository.showEuiccSettings()
assertThat(showEuiccSettings).isTrue()
}
@Test
fun showEuiccSettings_countryNotSupported_returnFalse() {
mockEuiccManager.stub { on { isSupportedCountry(COUNTRY_CODE) } doReturn false }
val showEuiccSettings = repository.showEuiccSettings()
assertThat(showEuiccSettings).isFalse()
}
@Test
fun showEuiccSettings_countrySupported_returnTrue() {
mockEuiccManager.stub { on { isSupportedCountry(COUNTRY_CODE) } doReturn true }
val showEuiccSettings = repository.showEuiccSettings()
assertThat(showEuiccSettings).isTrue()
}
private companion object {
const val COUNTRY_CODE = "us"
}
}