From 0f577f7ddde46e9d290fe1a2b00402792c031238 Mon Sep 17 00:00:00 2001 From: Charlotte Lu Date: Thu, 18 Jan 2024 17:14:35 +0800 Subject: [PATCH] Change apn type into ExposedDropdownMenuCheckBox Test: Visual Test Fix: 320891903,320621937 Change-Id: Ia6e4dbbcb568afae04776658a025e30c69e1ddd8 --- .../network/apn/ApnEditPageProvider.kt | 84 ++++++---- .../android/settings/network/apn/ApnStatus.kt | 86 +---------- .../android/settings/network/apn/ApnTypes.kt | 143 ++++++++++++++++++ .../network/apn/ApnEditPageProviderTest.kt | 22 +-- 4 files changed, 212 insertions(+), 123 deletions(-) create mode 100644 src/com/android/settings/network/apn/ApnTypes.kt diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt index 924e311ffdc..79ce3422f6d 100644 --- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt +++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt @@ -40,6 +40,10 @@ import androidx.navigation.navArgument import com.android.settings.R import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeDisplayNames import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeSelectedOptionsState +import com.android.settings.network.apn.ApnTypes.APN_TYPES_OPTIONS +import com.android.settings.network.apn.ApnTypes.APN_TYPE_MMS +import com.android.settings.network.apn.ApnTypes.getApnTypeSelectedOptionsState +import com.android.settings.network.apn.ApnTypes.updateApnType import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.LocalNavController import com.android.settingslib.spa.framework.theme.SettingsDimension @@ -100,6 +104,9 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur val networkTypeSelectedOptionsState = remember { getNetworkTypeSelectedOptionsState(apnData.networkType) } + var apnTypeSelectedOptionsState = remember { + getApnTypeSelectedOptionsState(apnData.apnType) + } val navController = LocalNavController.current var valid: String? RegularScaffold( @@ -191,37 +198,50 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur label = stringResource(R.string.apn_server), enabled = apnData.serverEnabled ) { apnData = apnData.copy(server = it) } - SettingsOutlinedTextField( - value = apnData.mmsc, - label = stringResource(R.string.apn_mmsc), - errorMessage = validateMMSC(apnData.validEnabled, apnData.mmsc, context), - enabled = apnData.mmscEnabled - ) { apnData = apnData.copy(mmsc = it) } - SettingsOutlinedTextField( - value = apnData.mmsProxy, - label = stringResource(R.string.apn_mms_proxy), - enabled = apnData.mmsProxyEnabled - ) { apnData = apnData.copy(mmsProxy = it) } - SettingsOutlinedTextField( - value = apnData.mmsPort, - label = stringResource(R.string.apn_mms_port), - enabled = apnData.mmsPortEnabled - ) { apnData = apnData.copy(mmsPort = it) } + SettingsExposedDropdownMenuCheckBox( + label = stringResource(R.string.apn_type), + options = APN_TYPES_OPTIONS, + selectedOptionsState = apnTypeSelectedOptionsState, + enabled = apnData.apnTypeEnabled, + errorMessage = validateAPNType( + apnData.validEnabled, apnData.apnType, + apnData.customizedConfig.readOnlyApnTypes, context + ) + ) { + val apnType = updateApnType( + apnTypeSelectedOptionsState, + apnData.customizedConfig.defaultApnTypes, + apnData.customizedConfig.readOnlyApnTypes + ) + apnTypeSelectedOptionsState = getApnTypeSelectedOptionsState(apnType) + apnData = apnData.copy( + apnType = apnType + ) + } + if (apnTypeSelectedOptionsState.contains(APN_TYPES_OPTIONS.indexOf(APN_TYPE_MMS))) { + SettingsOutlinedTextField( + value = apnData.mmsc, + label = stringResource(R.string.apn_mmsc), + errorMessage = validateMMSC(apnData.validEnabled, apnData.mmsc, context), + enabled = apnData.mmscEnabled + ) { apnData = apnData.copy(mmsc = it) } + SettingsOutlinedTextField( + value = apnData.mmsProxy, + label = stringResource(R.string.apn_mms_proxy), + enabled = apnData.mmsProxyEnabled + ) { apnData = apnData.copy(mmsProxy = it) } + SettingsOutlinedTextField( + value = apnData.mmsPort, + label = stringResource(R.string.apn_mms_port), + enabled = apnData.mmsPortEnabled + ) { apnData = apnData.copy(mmsPort = it) } + } SettingsExposedDropdownMenuBox( label = stringResource(R.string.apn_auth_type), options = authTypeOptions, selectedOptionIndex = apnData.authType, enabled = apnData.authTypeEnabled, ) { apnData = apnData.copy(authType = it) } - SettingsOutlinedTextField( - value = apnData.apnType, - label = stringResource(R.string.apn_type), - enabled = apnData.apnTypeEnabled, - errorMessage = validateAPNType( - apnData.validEnabled, apnData.apnType, - apnData.customizedConfig.readOnlyApnTypes, context - ) - ) { apnData = apnData.copy(apnType = updateApnType(apnData.copy(apnType = it))) } SettingsExposedDropdownMenuBox( label = stringResource(R.string.apn_protocol), options = apnProtocolOptions, @@ -234,6 +254,13 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur selectedOptionIndex = apnData.apnRoaming, enabled = apnData.apnRoamingEnabled ) { apnData = apnData.copy(apnRoaming = it) } + SettingsExposedDropdownMenuCheckBox( + label = stringResource(R.string.network_type), + options = getNetworkTypeDisplayNames(), + selectedOptionsState = networkTypeSelectedOptionsState, + emptyVal = stringResource(R.string.network_type_unspecified), + enabled = apnData.networkTypeEnabled + ) {} SwitchPreference( object : SwitchPreferenceModel { override val title = context.resources.getString(R.string.carrier_enabled) @@ -244,13 +271,6 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur } } ) - SettingsExposedDropdownMenuCheckBox( - label = stringResource(R.string.network_type), - options = getNetworkTypeDisplayNames(), - selectedOptionsState = networkTypeSelectedOptionsState, - emptyVal = stringResource(R.string.network_type_unspecified), - enabled = apnData.networkTypeEnabled - ) {} } } } \ No newline at end of file diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt index 6f39305f3cf..f9135fd86a1 100644 --- a/src/com/android/settings/network/apn/ApnStatus.kt +++ b/src/com/android/settings/network/apn/ApnStatus.kt @@ -29,6 +29,12 @@ import androidx.compose.runtime.snapshots.SnapshotStateList import com.android.internal.util.ArrayUtils import com.android.settings.R import com.android.settings.network.apn.ApnNetworkTypes.getNetworkType +import com.android.settings.network.apn.ApnTypes.APN_TYPES +import com.android.settings.network.apn.ApnTypes.APN_TYPE_ALL +import com.android.settings.network.apn.ApnTypes.APN_TYPE_EMERGENCY +import com.android.settings.network.apn.ApnTypes.APN_TYPE_IA +import com.android.settings.network.apn.ApnTypes.APN_TYPE_IMS +import com.android.settings.network.apn.ApnTypes.APN_TYPE_MCX import java.util.Locale data class ApnData( @@ -113,67 +119,6 @@ data class CustomizedConfig( val defaultApnRoamingProtocol: String = "", ) -/** - * APN types for data connections. These are usage categories for an APN - * entry. One APN entry may support multiple APN types, eg, a single APN - * may service regular internet traffic ("default") as well as MMS-specific - * connections.

- * APN_TYPE_ALL is a special type to indicate that this APN entry can - * service all data connections. - */ -const val APN_TYPE_ALL = "*" - -/** APN type for default data traffic */ -const val APN_TYPE_DEFAULT = "default" - -/** APN type for MMS traffic */ -const val APN_TYPE_MMS = "mms" - -/** APN type for SUPL assisted GPS */ -const val APN_TYPE_SUPL = "supl" - -/** APN type for DUN traffic */ -const val APN_TYPE_DUN = "dun" - -/** APN type for HiPri traffic */ -const val APN_TYPE_HIPRI = "hipri" - -/** APN type for FOTA */ -const val APN_TYPE_FOTA = "fota" - -/** APN type for IMS */ -const val APN_TYPE_IMS = "ims" - -/** APN type for CBS */ -const val APN_TYPE_CBS = "cbs" - -/** APN type for IA Initial Attach APN */ -const val APN_TYPE_IA = "ia" - -/** APN type for Emergency PDN. This is not an IA apn, but is used - * for access to carrier services in an emergency call situation. */ -const val APN_TYPE_EMERGENCY = "emergency" - -/** APN type for Mission Critical Services */ -const val APN_TYPE_MCX = "mcx" - -/** APN type for XCAP */ -const val APN_TYPE_XCAP = "xcap" -val APN_TYPES = arrayOf( - APN_TYPE_DEFAULT, - APN_TYPE_MMS, - APN_TYPE_SUPL, - APN_TYPE_DUN, - APN_TYPE_HIPRI, - APN_TYPE_FOTA, - APN_TYPE_IMS, - APN_TYPE_CBS, - APN_TYPE_IA, - APN_TYPE_EMERGENCY, - APN_TYPE_MCX, - APN_TYPE_XCAP -) - /** * Initialize ApnData according to the arguments. * @param arguments The data passed in when the user calls PageProvider. @@ -483,25 +428,6 @@ fun hasAllApns(apnTypes: List): Boolean { private fun normalizeApnType(apnType: String): String = apnType.trim().lowercase(Locale.getDefault()) -fun updateApnType(apnData: ApnData): String { - return if (apnData.apnType == "" && apnData.customizedConfig.defaultApnTypes.isNotEmpty()) - getEditableApnType(apnData) - else - apnData.apnType -} - -private fun getEditableApnType(apnData: ApnData): String { - val customizedConfig = apnData.customizedConfig - return customizedConfig.defaultApnTypes.filterNot { apnType -> - customizedConfig.readOnlyApnTypes.contains(apnType) || apnType in listOf( - APN_TYPE_IA, - APN_TYPE_EMERGENCY, - APN_TYPE_MCX, - APN_TYPE_IMS, - ) - }.joinToString() -} - fun deleteApn(uri: Uri, context: Context) { val contentResolver = context.contentResolver contentResolver.delete(uri, null, null) diff --git a/src/com/android/settings/network/apn/ApnTypes.kt b/src/com/android/settings/network/apn/ApnTypes.kt new file mode 100644 index 00000000000..d3dbe38b426 --- /dev/null +++ b/src/com/android/settings/network/apn/ApnTypes.kt @@ -0,0 +1,143 @@ +/* + * 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.apn + +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.snapshots.SnapshotStateList + +object ApnTypes { + /** + * APN types for data connections. These are usage categories for an APN + * entry. One APN entry may support multiple APN types, eg, a single APN + * may service regular internet traffic ("default") as well as MMS-specific + * connections.

+ * APN_TYPE_ALL is a special type to indicate that this APN entry can + * service all data connections. + */ + const val APN_TYPE_ALL = "*" + + /** APN type for default data traffic */ + const val APN_TYPE_DEFAULT = "default" + + /** APN type for MMS traffic */ + const val APN_TYPE_MMS = "mms" + + /** APN type for SUPL assisted GPS */ + const val APN_TYPE_SUPL = "supl" + + /** APN type for DUN traffic */ + const val APN_TYPE_DUN = "dun" + + /** APN type for HiPri traffic */ + const val APN_TYPE_HIPRI = "hipri" + + /** APN type for FOTA */ + const val APN_TYPE_FOTA = "fota" + + /** APN type for IMS */ + const val APN_TYPE_IMS = "ims" + + /** APN type for CBS */ + const val APN_TYPE_CBS = "cbs" + + /** APN type for IA Initial Attach APN */ + const val APN_TYPE_IA = "ia" + + /** APN type for Emergency PDN. This is not an IA apn, but is used + * for access to carrier services in an emergency call situation. */ + const val APN_TYPE_EMERGENCY = "emergency" + + /** APN type for Mission Critical Services */ + const val APN_TYPE_MCX = "mcx" + + /** APN type for XCAP */ + const val APN_TYPE_XCAP = "xcap" + + /** APN type for VSIM */ + const val APN_TYPE_VSIM = "vsim" + + /** APN type for BIP */ + const val APN_TYPE_BIP = "bip" + + /** APN type for ENTERPRISE */ + const val APN_TYPE_ENTERPRISE = "enterprise" + + val APN_TYPES = arrayOf( + APN_TYPE_DEFAULT, + APN_TYPE_MMS, + APN_TYPE_SUPL, + APN_TYPE_DUN, + APN_TYPE_HIPRI, + APN_TYPE_FOTA, + APN_TYPE_IMS, + APN_TYPE_CBS, + APN_TYPE_IA, + APN_TYPE_EMERGENCY, + APN_TYPE_MCX, + APN_TYPE_XCAP, + APN_TYPE_VSIM, + APN_TYPE_BIP, + APN_TYPE_ENTERPRISE + ) + + val APN_TYPES_OPTIONS = listOf(APN_TYPE_ALL) + APN_TYPES + + fun getApnTypeSelectedOptionsState(apnType: String): SnapshotStateList { + val apnTypeSelectedOptionsState = mutableStateListOf() + if (apnType.contains(APN_TYPE_ALL)) + APN_TYPES_OPTIONS.forEachIndexed { index, _ -> + apnTypeSelectedOptionsState.add(index) + } + else { + APN_TYPES_OPTIONS.forEachIndexed { index, type -> + if (apnType.contains(type)) { + apnTypeSelectedOptionsState.add(index) + } + } + if (apnTypeSelectedOptionsState.size == APN_TYPES.size) + apnTypeSelectedOptionsState.add(APN_TYPES_OPTIONS.indexOf(APN_TYPE_ALL)) + } + return apnTypeSelectedOptionsState + } + + fun updateApnType( + apnTypeSelectedOptionsState: SnapshotStateList, + defaultApnTypes: List, + readOnlyApnTypes: List + ): String { + val apnType = apnTypeSelectedOptionsState.joinToString { APN_TYPES_OPTIONS[it] } + if (apnType.contains(APN_TYPE_ALL)) return APN_TYPE_ALL + return if (apnType == "" && defaultApnTypes.isNotEmpty()) + getEditableApnType(defaultApnTypes, readOnlyApnTypes) + else + apnType + } + + private fun getEditableApnType( + defaultApnTypes: List, + readOnlyApnTypes: List + ): String { + return defaultApnTypes.filterNot { apnType -> + readOnlyApnTypes.contains(apnType) || apnType in listOf( + APN_TYPE_IA, + APN_TYPE_EMERGENCY, + APN_TYPE_MCX, + APN_TYPE_IMS, + ) + }.joinToString() + } +} \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt index 20d67fc2a22..03fddb5a393 100644 --- a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt @@ -50,9 +50,9 @@ class ApnEditPageProviderTest { private val context: Context = ApplicationProvider.getApplicationContext() private val apnName = "apn_name" - private val mmsc = "mmsc" - private val mmsProxy = "mms_proxy" - private val apnType = "apn_type" + private val proxy = "proxy" + private val port = "port" + private val apnType = context.resources.getString(R.string.apn_type) private val apnRoaming = "IPv4" private val apnEnable = context.resources.getString(R.string.carrier_enabled) private val apnProtocolOptions = @@ -61,8 +61,8 @@ class ApnEditPageProviderTest { private val passwordTitle = context.resources.getString(R.string.apn_password) private val apnInit = ApnData( name = apnName, - mmsc = mmsc, - mmsProxy = mmsProxy, + proxy = proxy, + port = port, apnType = apnType, apnRoaming = apnProtocolOptions.indexOf(apnRoaming), apnEnable = true @@ -94,23 +94,23 @@ class ApnEditPageProviderTest { } @Test - fun mmsc_displayed() { + fun proxy_displayed() { composeTestRule.setContent { ApnPage(apnInit, remember { apnData }, uri) } composeTestRule.onRoot().onChild().onChildAt(0) - .performScrollToNode(hasText(mmsc, true)) - composeTestRule.onNodeWithText(mmsc, true).assertIsDisplayed() + .performScrollToNode(hasText(proxy, true)) + composeTestRule.onNodeWithText(proxy, true).assertIsDisplayed() } @Test - fun mms_proxy_displayed() { + fun port_displayed() { composeTestRule.setContent { ApnPage(apnInit, remember { apnData }, uri) } composeTestRule.onRoot().onChild().onChildAt(0) - .performScrollToNode(hasText(mmsProxy, true)) - composeTestRule.onNodeWithText(mmsProxy, true).assertIsDisplayed() + .performScrollToNode(hasText(port, true)) + composeTestRule.onNodeWithText(port, true).assertIsDisplayed() } @Test