Merge "Change apn type into ExposedDropdownMenuCheckBox" into main

This commit is contained in:
Charlotte Lu
2024-01-22 08:39:59 +00:00
committed by Android (Google) Code Review
4 changed files with 212 additions and 123 deletions

View File

@@ -40,6 +40,10 @@ import androidx.navigation.navArgument
import com.android.settings.R import com.android.settings.R
import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeDisplayNames import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeDisplayNames
import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeSelectedOptionsState 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.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.LocalNavController import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -100,6 +104,9 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
val networkTypeSelectedOptionsState = remember { val networkTypeSelectedOptionsState = remember {
getNetworkTypeSelectedOptionsState(apnData.networkType) getNetworkTypeSelectedOptionsState(apnData.networkType)
} }
var apnTypeSelectedOptionsState = remember {
getApnTypeSelectedOptionsState(apnData.apnType)
}
val navController = LocalNavController.current val navController = LocalNavController.current
var valid: String? var valid: String?
RegularScaffold( RegularScaffold(
@@ -191,37 +198,50 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
label = stringResource(R.string.apn_server), label = stringResource(R.string.apn_server),
enabled = apnData.serverEnabled enabled = apnData.serverEnabled
) { apnData = apnData.copy(server = it) } ) { apnData = apnData.copy(server = it) }
SettingsOutlinedTextField( SettingsExposedDropdownMenuCheckBox(
value = apnData.mmsc, label = stringResource(R.string.apn_type),
label = stringResource(R.string.apn_mmsc), options = APN_TYPES_OPTIONS,
errorMessage = validateMMSC(apnData.validEnabled, apnData.mmsc, context), selectedOptionsState = apnTypeSelectedOptionsState,
enabled = apnData.mmscEnabled enabled = apnData.apnTypeEnabled,
) { apnData = apnData.copy(mmsc = it) } errorMessage = validateAPNType(
SettingsOutlinedTextField( apnData.validEnabled, apnData.apnType,
value = apnData.mmsProxy, apnData.customizedConfig.readOnlyApnTypes, context
label = stringResource(R.string.apn_mms_proxy), )
enabled = apnData.mmsProxyEnabled ) {
) { apnData = apnData.copy(mmsProxy = it) } val apnType = updateApnType(
SettingsOutlinedTextField( apnTypeSelectedOptionsState,
value = apnData.mmsPort, apnData.customizedConfig.defaultApnTypes,
label = stringResource(R.string.apn_mms_port), apnData.customizedConfig.readOnlyApnTypes
enabled = apnData.mmsPortEnabled )
) { apnData = apnData.copy(mmsPort = it) } 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( SettingsExposedDropdownMenuBox(
label = stringResource(R.string.apn_auth_type), label = stringResource(R.string.apn_auth_type),
options = authTypeOptions, options = authTypeOptions,
selectedOptionIndex = apnData.authType, selectedOptionIndex = apnData.authType,
enabled = apnData.authTypeEnabled, enabled = apnData.authTypeEnabled,
) { apnData = apnData.copy(authType = it) } ) { 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( SettingsExposedDropdownMenuBox(
label = stringResource(R.string.apn_protocol), label = stringResource(R.string.apn_protocol),
options = apnProtocolOptions, options = apnProtocolOptions,
@@ -234,6 +254,13 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
selectedOptionIndex = apnData.apnRoaming, selectedOptionIndex = apnData.apnRoaming,
enabled = apnData.apnRoamingEnabled enabled = apnData.apnRoamingEnabled
) { apnData = apnData.copy(apnRoaming = it) } ) { 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( SwitchPreference(
object : SwitchPreferenceModel { object : SwitchPreferenceModel {
override val title = context.resources.getString(R.string.carrier_enabled) override val title = context.resources.getString(R.string.carrier_enabled)
@@ -244,13 +271,6 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
} }
} }
) )
SettingsExposedDropdownMenuCheckBox(
label = stringResource(R.string.network_type),
options = getNetworkTypeDisplayNames(),
selectedOptionsState = networkTypeSelectedOptionsState,
emptyVal = stringResource(R.string.network_type_unspecified),
enabled = apnData.networkTypeEnabled
) {}
} }
} }
} }

View File

@@ -29,6 +29,12 @@ import androidx.compose.runtime.snapshots.SnapshotStateList
import com.android.internal.util.ArrayUtils import com.android.internal.util.ArrayUtils
import com.android.settings.R import com.android.settings.R
import com.android.settings.network.apn.ApnNetworkTypes.getNetworkType 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 import java.util.Locale
data class ApnData( data class ApnData(
@@ -113,67 +119,6 @@ data class CustomizedConfig(
val defaultApnRoamingProtocol: String = "", 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.<br></br>
* 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. * Initialize ApnData according to the arguments.
* @param arguments The data passed in when the user calls PageProvider. * @param arguments The data passed in when the user calls PageProvider.
@@ -483,25 +428,6 @@ fun hasAllApns(apnTypes: List<String>): Boolean {
private fun normalizeApnType(apnType: String): String = private fun normalizeApnType(apnType: String): String =
apnType.trim().lowercase(Locale.getDefault()) 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) { fun deleteApn(uri: Uri, context: Context) {
val contentResolver = context.contentResolver val contentResolver = context.contentResolver
contentResolver.delete(uri, null, null) contentResolver.delete(uri, null, null)

View File

@@ -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.<br></br>
* 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<Int> {
val apnTypeSelectedOptionsState = mutableStateListOf<Int>()
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<Int>,
defaultApnTypes: List<String>,
readOnlyApnTypes: List<String>
): 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<String>,
readOnlyApnTypes: List<String>
): String {
return defaultApnTypes.filterNot { apnType ->
readOnlyApnTypes.contains(apnType) || apnType in listOf(
APN_TYPE_IA,
APN_TYPE_EMERGENCY,
APN_TYPE_MCX,
APN_TYPE_IMS,
)
}.joinToString()
}
}

View File

@@ -50,9 +50,9 @@ class ApnEditPageProviderTest {
private val context: Context = ApplicationProvider.getApplicationContext() private val context: Context = ApplicationProvider.getApplicationContext()
private val apnName = "apn_name" private val apnName = "apn_name"
private val mmsc = "mmsc" private val proxy = "proxy"
private val mmsProxy = "mms_proxy" private val port = "port"
private val apnType = "apn_type" private val apnType = context.resources.getString(R.string.apn_type)
private val apnRoaming = "IPv4" private val apnRoaming = "IPv4"
private val apnEnable = context.resources.getString(R.string.carrier_enabled) private val apnEnable = context.resources.getString(R.string.carrier_enabled)
private val apnProtocolOptions = private val apnProtocolOptions =
@@ -61,8 +61,8 @@ class ApnEditPageProviderTest {
private val passwordTitle = context.resources.getString(R.string.apn_password) private val passwordTitle = context.resources.getString(R.string.apn_password)
private val apnInit = ApnData( private val apnInit = ApnData(
name = apnName, name = apnName,
mmsc = mmsc, proxy = proxy,
mmsProxy = mmsProxy, port = port,
apnType = apnType, apnType = apnType,
apnRoaming = apnProtocolOptions.indexOf(apnRoaming), apnRoaming = apnProtocolOptions.indexOf(apnRoaming),
apnEnable = true apnEnable = true
@@ -94,23 +94,23 @@ class ApnEditPageProviderTest {
} }
@Test @Test
fun mmsc_displayed() { fun proxy_displayed() {
composeTestRule.setContent { composeTestRule.setContent {
ApnPage(apnInit, remember { apnData }, uri) ApnPage(apnInit, remember { apnData }, uri)
} }
composeTestRule.onRoot().onChild().onChildAt(0) composeTestRule.onRoot().onChild().onChildAt(0)
.performScrollToNode(hasText(mmsc, true)) .performScrollToNode(hasText(proxy, true))
composeTestRule.onNodeWithText(mmsc, true).assertIsDisplayed() composeTestRule.onNodeWithText(proxy, true).assertIsDisplayed()
} }
@Test @Test
fun mms_proxy_displayed() { fun port_displayed() {
composeTestRule.setContent { composeTestRule.setContent {
ApnPage(apnInit, remember { apnData }, uri) ApnPage(apnInit, remember { apnData }, uri)
} }
composeTestRule.onRoot().onChild().onChildAt(0) composeTestRule.onRoot().onChild().onChildAt(0)
.performScrollToNode(hasText(mmsProxy, true)) .performScrollToNode(hasText(port, true))
composeTestRule.onNodeWithText(mmsProxy, true).assertIsDisplayed() composeTestRule.onNodeWithText(port, true).assertIsDisplayed()
} }
@Test @Test