Duplicate apn entry
Test: Visual Test
Fix: 319194851
Change-Id: I491655bb80a17cc9fc99d47f1e1ac5824eb11921
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 402f526..47fcf4e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3299,6 +3299,8 @@
<string name="menu_cancel">Cancel</string>
<!-- APN error dialog title -->
<string name="error_title"></string>
+ <!-- APN error dialog messages when the new apn is a duplicate: -->
+ <string name="error_duplicate_apn_entry">Duplicate apn entry.</string>
<!-- APN error dialog messages: -->
<string name="error_name_empty">The Name field can\u2019t be empty.</string>
<!-- APN error dialog messages: -->
diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
index 2600618..5c7d7a4 100644
--- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt
+++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
@@ -19,16 +19,21 @@
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.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 @@
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 e0121b4..2f16e69 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.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 @@
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 @@
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 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 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<String> = 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 e4cb603..6f39305 100644
--- a/src/com/android/settings/network/apn/ApnStatus.kt
+++ b/src/com/android/settings/network/apn/ApnStatus.kt
@@ -72,41 +72,38 @@
val validEnabled: Boolean = false,
val customizedConfig: CustomizedConfig = CustomizedConfig()
) {
+ fun getContentValueMap(context: Context): MutableMap<String, Any> {
+ 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<String> = emptyList(),
@@ -227,20 +224,14 @@
*/
fun validateAndSaveApnData(
apnDataInit: ApnData,
- apnData: ApnData,
+ newApnData: ApnData,
context: Context,
- uriInit: Uri,
- networkTypeSelectedOptionsState: SnapshotStateList<Int>
-): 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 @@
uriInit
)
}
- return true
+ return null
}
/**
@@ -258,7 +249,7 @@
*
* @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 @@
} 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