From 5b2de59480d8faca483eedb82e5907ca51a94dc9 Mon Sep 17 00:00:00 2001 From: Charlotte Lu Date: Wed, 11 Oct 2023 10:55:20 +0800 Subject: [PATCH] Add validateAndSaveApnData. Fix: 304649927 Test: Visual Test Change-Id: I900a096a6e27f1db66af204201d4ca2523537c0d --- .../network/apn/ApnEditPageProvider.kt | 13 +- .../settings/network/apn/ApnRepository.kt | 2 +- .../android/settings/network/apn/ApnStatus.kt | 155 +++++++++++++++++- .../network/apn/ApnEditPageProviderTest.kt | 52 +++--- 4 files changed, 189 insertions(+), 33 deletions(-) diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt index ad16ae307b1..0b0431d6f2a 100644 --- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt +++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt @@ -19,6 +19,10 @@ package com.android.settings.network.apn import android.net.Uri import android.os.Bundle import androidx.compose.foundation.layout.Column +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Done +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue @@ -70,7 +74,7 @@ object ApnEditPageProvider : SettingsPageProvider { val apnDataCur = remember { mutableStateOf(apnDataInit) } - ApnPage(apnDataCur) + ApnPage(apnDataInit, apnDataCur) } fun getRoute( @@ -83,7 +87,7 @@ object ApnEditPageProvider : SettingsPageProvider { } @Composable -fun ApnPage(apnDataCur: MutableState) { +fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState) { var apnData by apnDataCur val context = LocalContext.current val authTypeOptions = stringArrayResource(R.array.apn_auth_entries).toList() @@ -93,6 +97,11 @@ fun ApnPage(apnDataCur: MutableState) { } RegularScaffold( title = stringResource(id = R.string.apn_edit), + actions = { + IconButton(onClick = { + validateAndSaveApnData(apnDataInit, apnData, context) + }) { Icon(imageVector = Icons.Outlined.Done, contentDescription = "Save APN") } + } ) { Column() { SettingsOutlinedTextField( diff --git a/src/com/android/settings/network/apn/ApnRepository.kt b/src/com/android/settings/network/apn/ApnRepository.kt index f7584398d0f..a518aa5e38b 100644 --- a/src/com/android/settings/network/apn/ApnRepository.kt +++ b/src/com/android/settings/network/apn/ApnRepository.kt @@ -161,4 +161,4 @@ private fun convertProtocol2Options(raw: String, context: Context): String { "" } } -} +} \ 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 c5ca45a1146..5904e98d8bf 100644 --- a/src/com/android/settings/network/apn/ApnStatus.kt +++ b/src/com/android/settings/network/apn/ApnStatus.kt @@ -81,6 +81,55 @@ 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. @@ -123,6 +172,108 @@ fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int return apnDataInit } +/** + * Validates the apn data and save it to the database if it's valid. + * + * + * + * A dialog with error message will be displayed if the APN data is invalid. + * + * @return true if there is no error + */ +fun validateAndSaveApnData(apnDataInit: ApnData, apnData: ApnData, context: Context): Boolean { + // Nothing to do if it's a read only APN + if (apnData.customizedConfig.readOnlyApn) { + return true + } + val errorMsg = validateApnData(apnData, context) + if (errorMsg != null) { + //TODO: showError(this) + return false + } + if (apnData.newApn || (apnData != apnDataInit)) { + Log.d(TAG, "validateAndSaveApnData: apnData ${apnData.name}") + // TODO: updateApnDataToDatabase + } + return true +} + +/** + * Validates whether the apn data is valid. + * + * @return An error message if the apn data is invalid, otherwise return null. + */ +fun validateApnData(apnData: ApnData, context: Context): String? { + var errorMsg: String? = null + val name = apnData.name + val apn = apnData.apn + if (name == "") { + errorMsg = context.resources.getString(R.string.error_name_empty) + } else if (apn == "") { + errorMsg = context.resources.getString(R.string.error_apn_empty) + } + if (errorMsg == null) { + // if carrier does not allow editing certain apn types, make sure type does not include + // those + if (!ArrayUtils.isEmpty(apnData.customizedConfig.readOnlyApnTypes) + && apnTypesMatch(apnData.customizedConfig.readOnlyApnTypes, getUserEnteredApnType(apnData.apnType, apnData.customizedConfig.readOnlyApnTypes)) + ) { + val stringBuilder = StringBuilder() + for (type in apnData.customizedConfig.readOnlyApnTypes) { + stringBuilder.append(type).append(", ") + Log.d(TAG, "validateApnData: appending type: $type") + } + // remove last ", " + if (stringBuilder.length >= 2) { + stringBuilder.delete(stringBuilder.length - 2, stringBuilder.length) + } + errorMsg = String.format( + context.resources.getString(R.string.error_adding_apn_type), + stringBuilder + ) + } + } + return errorMsg +} + +private fun getUserEnteredApnType(apnType: String, readOnlyApnTypes: List): String { + // if user has not specified a type, map it to "ALL APN TYPES THAT ARE NOT READ-ONLY" + // but if user enter empty type, map it just for default + var userEnteredApnType = apnType + if (userEnteredApnType != "") userEnteredApnType = + userEnteredApnType.trim { it <= ' ' } + if (TextUtils.isEmpty(userEnteredApnType) || APN_TYPE_ALL == userEnteredApnType) { + userEnteredApnType = getEditableApnType(readOnlyApnTypes) + } + Log.d( + TAG, "getUserEnteredApnType: changed apn type to editable apn types: " + + userEnteredApnType + ) + return userEnteredApnType +} + +private fun getEditableApnType(readOnlyApnTypes: List): String { + val editableApnTypes = StringBuilder() + var first = true + for (apnType in APN_TYPES) { + // add APN type if it is not read-only and is not wild-cardable + if (!readOnlyApnTypes.contains(apnType) + && apnType != APN_TYPE_IA + && apnType != APN_TYPE_EMERGENCY + && apnType != APN_TYPE_MCX + && apnType != APN_TYPE_IMS + ) { + if (first) { + first = false + } else { + editableApnTypes.append(",") + } + editableApnTypes.append(apnType) + } + } + return editableApnTypes.toString() +} + /** * Initialize CustomizedConfig information through subId. * @param subId subId information obtained from arguments. @@ -299,11 +450,11 @@ fun hasAllApns(apnTypes: Array): Boolean { return false } val apnList: List<*> = Arrays.asList(*apnTypes) - if (apnList.contains(ApnEditor.APN_TYPE_ALL)) { + if (apnList.contains(APN_TYPE_ALL)) { Log.d(TAG, "hasAllApns: true because apnList.contains(APN_TYPE_ALL)") return true } - for (apn in ApnEditor.APN_TYPES) { + for (apn in APN_TYPES) { if (!apnList.contains(apn)) { return false } 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 7cd0f5d5ccf..ddc836473e2 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 @@ -17,7 +17,6 @@ package com.android.settings.network.apn import android.content.Context -import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.test.assertIsDisplayed @@ -58,15 +57,16 @@ class ApnEditPageProviderTest { context.resources.getStringArray(R.array.apn_protocol_entries).toList() private val networkType = context.resources.getString(R.string.network_type) private val passwordTitle = context.resources.getString(R.string.apn_password) + private val apnInit = ApnData( + name = apnName, + mmsc = mmsc, + mmsProxy = mmsProxy, + apnType = apnType, + apnRoaming = apnProtocolOptions.indexOf(apnRoaming), + apnEnable = true + ) private val apnData = mutableStateOf( - ApnData( - name = apnName, - mmsc = mmsc, - mmsProxy = mmsProxy, - apnType = apnType, - apnRoaming = apnProtocolOptions.indexOf(apnRoaming), - apnEnable = true - ) + apnInit ) @Test @@ -77,7 +77,7 @@ class ApnEditPageProviderTest { @Test fun title_displayed() { composeTestRule.setContent { - ApnPage(remember { + ApnPage(apnInit, remember { apnData }) } @@ -87,7 +87,7 @@ class ApnEditPageProviderTest { @Test fun name_displayed() { composeTestRule.setContent { - ApnPage(remember { + ApnPage(apnInit, remember { apnData }) } @@ -97,7 +97,7 @@ class ApnEditPageProviderTest { @Test fun mmsc_displayed() { composeTestRule.setContent { - ApnPage(remember { + ApnPage(apnInit, remember { apnData }) } @@ -109,7 +109,7 @@ class ApnEditPageProviderTest { @Test fun mms_proxy_displayed() { composeTestRule.setContent { - ApnPage(remember { + ApnPage(apnInit, remember { apnData }) } @@ -121,7 +121,7 @@ class ApnEditPageProviderTest { @Test fun apn_type_displayed() { composeTestRule.setContent { - ApnPage(remember { + ApnPage(apnInit, remember { apnData }) } @@ -133,7 +133,7 @@ class ApnEditPageProviderTest { @Test fun apn_roaming_displayed() { composeTestRule.setContent { - ApnPage(remember { + ApnPage(apnInit, remember { apnData }) } @@ -145,7 +145,7 @@ class ApnEditPageProviderTest { @Test fun carrier_enabled_displayed() { composeTestRule.setContent { - ApnPage(remember { + ApnPage(apnInit, remember { apnData }) } @@ -157,7 +157,7 @@ class ApnEditPageProviderTest { @Test fun carrier_enabled_isChecked() { composeTestRule.setContent { - ApnPage(remember { + ApnPage(apnInit, remember { apnData }) } @@ -169,7 +169,7 @@ class ApnEditPageProviderTest { @Test fun carrier_enabled_checkChanged() { composeTestRule.setContent { - ApnPage(remember { + ApnPage(apnInit, remember { apnData }) } @@ -182,7 +182,7 @@ class ApnEditPageProviderTest { @Test fun network_type_displayed() { composeTestRule.setContent { - ApnPage(remember { + ApnPage(apnInit, remember { apnData }) } @@ -193,12 +193,10 @@ class ApnEditPageProviderTest { @Test fun network_type_changed() { - var apnDataa: MutableState = apnData composeTestRule.setContent { - apnDataa = remember { + ApnPage(apnInit, remember { apnData - } - ApnPage(apnDataa) + }) } composeTestRule.onRoot().onChild().onChildAt(0) .performScrollToNode(hasText(networkType, true)) @@ -211,12 +209,10 @@ class ApnEditPageProviderTest { @Test fun network_type_changed_back2Default() { - var apnDataa: MutableState = apnData composeTestRule.setContent { - apnDataa = remember { + ApnPage(apnInit, remember { apnData - } - ApnPage(apnDataa) + }) } composeTestRule.onRoot().onChild().onChildAt(0) .performScrollToNode(hasText(networkType, true)) @@ -234,7 +230,7 @@ class ApnEditPageProviderTest { @Test fun password_displayed() { composeTestRule.setContent { - ApnPage(remember { + ApnPage(apnInit, remember { apnData }) }