From 031aa3417d72025ad2d09246902b24ccb1df01e6 Mon Sep 17 00:00:00 2001 From: Charlotte Lu Date: Tue, 9 Jan 2024 15:33:27 +0800 Subject: [PATCH] Duplicate apn entry Test: Visual Test Fix: 319194851 Change-Id: I491655bb80a17cc9fc99d47f1e1ac5824eb11921 --- res/values/strings.xml | 2 + .../network/apn/ApnEditPageProvider.kt | 38 +++++++-- .../settings/network/apn/ApnRepository.kt | 30 ++++++- .../android/settings/network/apn/ApnStatus.kt | 80 +++++++++---------- 4 files changed, 99 insertions(+), 51 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 402f5263e51..47fcf4ef863 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3299,6 +3299,8 @@ Cancel + + Duplicate apn entry. The Name field can\u2019t be empty. diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt index 2600618caae..5c7d7a4a56e 100644 --- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt +++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt @@ -19,16 +19,21 @@ package com.android.settings.network.apn import android.net.Uri import android.os.Bundle import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding 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.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource @@ -39,6 +44,7 @@ import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeDisplayNam import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeSelectedOptionsState import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.LocalNavController +import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuCheckBox import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField @@ -98,25 +104,47 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur getNetworkTypeSelectedOptionsState(apnData.networkType) } val navController = LocalNavController.current + var valid: String? RegularScaffold( title = if (apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit), actions = { if (!apnData.customizedConfig.readOnlyApn) { IconButton(onClick = { - if (!apnData.validEnabled) apnData = apnData.copy(validEnabled = true) - val valid = validateAndSaveApnData( + apnData = apnData.copy( + networkType = ApnNetworkTypes.getNetworkType( + networkTypeSelectedOptionsState + ) + ) + valid = validateAndSaveApnData( apnDataInit, apnData, context, - uriInit, - networkTypeSelectedOptionsState + uriInit ) - if (valid) navController.navigateBack() + if (valid == null) navController.navigateBack() + else if (!apnData.validEnabled) apnData = apnData.copy(validEnabled = true) }) { Icon(imageVector = Icons.Outlined.Done, contentDescription = null) } } }, ) { Column { + if (apnData.validEnabled) { + apnData = apnData.copy( + networkType = ApnNetworkTypes.getNetworkType( + networkTypeSelectedOptionsState + ) + ) + valid = validateApnData(uriInit, apnData, context) + valid?.let { + Text( + text = it, + modifier = Modifier + .fillMaxWidth() + .padding(SettingsDimension.menuFieldPadding), + color = MaterialTheme.colorScheme.primary + ) + } + } SettingsOutlinedTextField( value = apnData.name, label = stringResource(R.string.apn_name), diff --git a/src/com/android/settings/network/apn/ApnRepository.kt b/src/com/android/settings/network/apn/ApnRepository.kt index e0121b4926a..2f16e693adb 100644 --- a/src/com/android/settings/network/apn/ApnRepository.kt +++ b/src/com/android/settings/network/apn/ApnRepository.kt @@ -20,6 +20,7 @@ import android.content.ContentValues import android.content.Context import android.net.Uri import android.provider.Telephony +import android.telephony.TelephonyManager import android.util.Log import com.android.settings.R import com.android.settingslib.utils.ThreadUtils @@ -150,7 +151,6 @@ fun getApnDataFromUri(uri: Uri, context: Context): ApnData { private fun convertProtocol2Options(raw: String, context: Context): String { val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList() val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList() - var uRaw = raw.uppercase(Locale.getDefault()) uRaw = if (uRaw == "IPV4") "IP" else uRaw val protocolIndex = apnProtocolValues.indexOf(uRaw) @@ -167,7 +167,6 @@ private fun convertProtocol2Options(raw: String, context: Context): String { fun convertOptions2Protocol(protocolIndex: Int, context: Context): String { val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList() - return if (protocolIndex == -1) { "" } else { @@ -179,7 +178,12 @@ fun convertOptions2Protocol(protocolIndex: Int, context: Context): String { } } -fun updateApnDataToDatabase(newApn: Boolean, values: ContentValues, context: Context, uriInit: Uri) { +fun updateApnDataToDatabase( + newApn: Boolean, + values: ContentValues, + context: Context, + uriInit: Uri +) { ThreadUtils.postOnBackgroundThread { if (newApn) { // Add a new apn to the database @@ -194,4 +198,24 @@ fun updateApnDataToDatabase(newApn: Boolean, values: ContentValues, context: Con ) } } +} + +fun isItemExist(uri: Uri, apnData: ApnData, context: Context): String? { + val contentValueMap = apnData.getContentValueMap(context) + contentValueMap.remove(Telephony.Carriers.CARRIER_ENABLED) + val list = contentValueMap.entries.toList() + val selection = list.joinToString(" AND ") { "${it.key} = ?" } + val selectionArgs: Array = list.map { it.value.toString() }.toTypedArray() + context.contentResolver.query( + uri, + sProjection, + selection /* selection */, + selectionArgs /* selectionArgs */, + null /* sortOrder */ + )?.use { cursor -> + if (cursor.count > 0) { + return context.resources.getString(R.string.error_duplicate_apn_entry) + } + } + return null } \ 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 e4cb603fcb7..6f39305f3cf 100644 --- a/src/com/android/settings/network/apn/ApnStatus.kt +++ b/src/com/android/settings/network/apn/ApnStatus.kt @@ -72,41 +72,38 @@ data class ApnData( val validEnabled: Boolean = false, val customizedConfig: CustomizedConfig = CustomizedConfig() ) { + fun getContentValueMap(context: Context): MutableMap { + val simCarrierId = + context.getSystemService(TelephonyManager::class.java)!! + .createForSubscriptionId(subId) + .getSimCarrierId() + return mutableMapOf( + Telephony.Carriers.NAME to name, Telephony.Carriers.APN to apn, + Telephony.Carriers.PROXY to proxy, Telephony.Carriers.PORT to port, + Telephony.Carriers.MMSPROXY to mmsProxy, Telephony.Carriers.MMSPORT to mmsPort, + Telephony.Carriers.USER to userName, Telephony.Carriers.SERVER to server, + Telephony.Carriers.PASSWORD to passWord, Telephony.Carriers.MMSC to mmsc, + Telephony.Carriers.AUTH_TYPE to authType, + Telephony.Carriers.PROTOCOL to convertOptions2Protocol(apnProtocol, context), + Telephony.Carriers.ROAMING_PROTOCOL to convertOptions2Protocol(apnRoaming, context), + Telephony.Carriers.TYPE to apnType, + Telephony.Carriers.NETWORK_TYPE_BITMASK to networkType, + Telephony.Carriers.CARRIER_ENABLED to apnEnable, + Telephony.Carriers.EDITED_STATUS to Telephony.Carriers.USER_EDITED, + Telephony.Carriers.CARRIER_ID to simCarrierId + ) + } + fun getContentValues(context: Context): ContentValues { val values = ContentValues() - values.put(Telephony.Carriers.NAME, name) - values.put(Telephony.Carriers.APN, apn) - values.put(Telephony.Carriers.PROXY, proxy) - values.put(Telephony.Carriers.PORT, port) - values.put(Telephony.Carriers.MMSPROXY, mmsProxy) - values.put(Telephony.Carriers.MMSPORT, mmsPort) - values.put(Telephony.Carriers.USER, userName) - values.put(Telephony.Carriers.SERVER, server) - values.put(Telephony.Carriers.PASSWORD, passWord) - values.put(Telephony.Carriers.MMSC, mmsc) - values.put(Telephony.Carriers.AUTH_TYPE, authType) - values.put(Telephony.Carriers.PROTOCOL, convertOptions2Protocol(apnProtocol, context)) - values.put( - Telephony.Carriers.ROAMING_PROTOCOL, - convertOptions2Protocol(apnRoaming, context) - ) - values.put(Telephony.Carriers.TYPE, apnType) - values.put(Telephony.Carriers.NETWORK_TYPE_BITMASK, networkType) - values.put(Telephony.Carriers.CARRIER_ENABLED, apnEnable) - values.put(Telephony.Carriers.EDITED_STATUS, Telephony.Carriers.USER_EDITED) - if (newApn) { - val simCarrierId = - context.getSystemService(TelephonyManager::class.java)!! - .createForSubscriptionId(subId) - .getSimCarrierId() - values.put(Telephony.Carriers.CARRIER_ID, simCarrierId) - } + val contentValueMap = getContentValueMap(context) + if (!newApn) contentValueMap.remove(Telephony.Carriers.CARRIER_ID) + contentValueMap.forEach { (key, value) -> values.putObject(key, value) } return values } } data class CustomizedConfig( - val newApn: Boolean = false, val readOnlyApn: Boolean = false, val isAddApnAllowed: Boolean = true, val readOnlyApnTypes: List = emptyList(), @@ -227,20 +224,14 @@ fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int */ fun validateAndSaveApnData( apnDataInit: ApnData, - apnData: ApnData, + newApnData: ApnData, context: Context, - uriInit: Uri, - networkTypeSelectedOptionsState: SnapshotStateList -): Boolean { - // Nothing to do if it's a read only APN - if (apnData.customizedConfig.readOnlyApn) { - return true - } - val errorMsg = validateApnData(apnData, context) + uriInit: Uri +): String? { + val errorMsg = validateApnData(uriInit, newApnData, context) if (errorMsg != null) { - return false + return errorMsg } - val newApnData = apnData.copy(networkType = getNetworkType(networkTypeSelectedOptionsState)) if (newApnData.newApn || (newApnData != apnDataInit)) { Log.d(TAG, "[validateAndSaveApnData] newApnData.networkType: ${newApnData.networkType}") updateApnDataToDatabase( @@ -250,7 +241,7 @@ fun validateAndSaveApnData( uriInit ) } - return true + return null } /** @@ -258,7 +249,7 @@ fun validateAndSaveApnData( * * @return An error message if the apn data is invalid, otherwise return null. */ -fun validateApnData(apnData: ApnData, context: Context): String? { +fun validateApnData(uri: Uri, apnData: ApnData, context: Context): String? { var errorMsg: String? val name = apnData.name val apn = apnData.apn @@ -267,11 +258,14 @@ fun validateApnData(apnData: ApnData, context: Context): String? { } else if (apn == "") { context.resources.getString(R.string.error_apn_empty) } else { - validateMMSC(apnData.validEnabled, apnData.mmsc, context) + validateMMSC(true, apnData.mmsc, context) + } + if (errorMsg == null) { + errorMsg = isItemExist(uri, apnData, context) } if (errorMsg == null) { errorMsg = validateAPNType( - apnData.validEnabled, + true, apnData.apnType, apnData.customizedConfig.readOnlyApnTypes, context