Merge "Duplicate apn entry" into main

This commit is contained in:
Charlotte Lu
2024-01-18 04:00:58 +00:00
committed by Android (Google) Code Review
4 changed files with 99 additions and 51 deletions

View File

@@ -3323,6 +3323,8 @@
<string name="menu_cancel">Cancel</string> <string name="menu_cancel">Cancel</string>
<!-- APN error dialog title --> <!-- APN error dialog title -->
<string name="error_title"></string> <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: --> <!-- APN error dialog messages: -->
<string name="error_name_empty">The Name field can\u2019t be empty.</string> <string name="error_name_empty">The Name field can\u2019t be empty.</string>
<!-- APN error dialog messages: --> <!-- APN error dialog messages: -->

View File

@@ -19,16 +19,21 @@ package com.android.settings.network.apn
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.compose.foundation.layout.Column 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.Icons
import androidx.compose.material.icons.outlined.Done import androidx.compose.material.icons.outlined.Done
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource 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.settings.network.apn.ApnNetworkTypes.getNetworkTypeSelectedOptionsState
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.widget.editor.SettingsExposedDropdownMenuBox import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox
import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuCheckBox import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuCheckBox
import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
@@ -98,25 +104,47 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
getNetworkTypeSelectedOptionsState(apnData.networkType) getNetworkTypeSelectedOptionsState(apnData.networkType)
} }
val navController = LocalNavController.current val navController = LocalNavController.current
var valid: String?
RegularScaffold( RegularScaffold(
title = if (apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit), title = if (apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit),
actions = { actions = {
if (!apnData.customizedConfig.readOnlyApn) { if (!apnData.customizedConfig.readOnlyApn) {
IconButton(onClick = { IconButton(onClick = {
if (!apnData.validEnabled) apnData = apnData.copy(validEnabled = true) apnData = apnData.copy(
val valid = validateAndSaveApnData( networkType = ApnNetworkTypes.getNetworkType(
networkTypeSelectedOptionsState
)
)
valid = validateAndSaveApnData(
apnDataInit, apnDataInit,
apnData, apnData,
context, context,
uriInit, uriInit
networkTypeSelectedOptionsState
) )
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) } }) { Icon(imageVector = Icons.Outlined.Done, contentDescription = null) }
} }
}, },
) { ) {
Column { 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( SettingsOutlinedTextField(
value = apnData.name, value = apnData.name,
label = stringResource(R.string.apn_name), label = stringResource(R.string.apn_name),

View File

@@ -20,6 +20,7 @@ import android.content.ContentValues
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.provider.Telephony import android.provider.Telephony
import android.telephony.TelephonyManager
import android.util.Log import android.util.Log
import com.android.settings.R import com.android.settings.R
import com.android.settingslib.utils.ThreadUtils 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 { private fun convertProtocol2Options(raw: String, context: Context): String {
val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList() val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList()
val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList() val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList()
var uRaw = raw.uppercase(Locale.getDefault()) var uRaw = raw.uppercase(Locale.getDefault())
uRaw = if (uRaw == "IPV4") "IP" else uRaw uRaw = if (uRaw == "IPV4") "IP" else uRaw
val protocolIndex = apnProtocolValues.indexOf(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 { fun convertOptions2Protocol(protocolIndex: Int, context: Context): String {
val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList() val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList()
return if (protocolIndex == -1) { return if (protocolIndex == -1) {
"" ""
} else { } 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 { ThreadUtils.postOnBackgroundThread {
if (newApn) { if (newApn) {
// Add a new apn to the database // Add a new apn to the database
@@ -195,3 +199,23 @@ 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<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
}

View File

@@ -72,41 +72,38 @@ data class ApnData(
val validEnabled: Boolean = false, val validEnabled: Boolean = false,
val customizedConfig: CustomizedConfig = CustomizedConfig() 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 { fun getContentValues(context: Context): ContentValues {
val values = ContentValues() val values = ContentValues()
values.put(Telephony.Carriers.NAME, name) val contentValueMap = getContentValueMap(context)
values.put(Telephony.Carriers.APN, apn) if (!newApn) contentValueMap.remove(Telephony.Carriers.CARRIER_ID)
values.put(Telephony.Carriers.PROXY, proxy) contentValueMap.forEach { (key, value) -> values.putObject(key, value) }
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)
}
return values return values
} }
} }
data class CustomizedConfig( data class CustomizedConfig(
val newApn: Boolean = false,
val readOnlyApn: Boolean = false, val readOnlyApn: Boolean = false,
val isAddApnAllowed: Boolean = true, val isAddApnAllowed: Boolean = true,
val readOnlyApnTypes: List<String> = emptyList(), val readOnlyApnTypes: List<String> = emptyList(),
@@ -227,20 +224,14 @@ fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int
*/ */
fun validateAndSaveApnData( fun validateAndSaveApnData(
apnDataInit: ApnData, apnDataInit: ApnData,
apnData: ApnData, newApnData: ApnData,
context: Context, context: Context,
uriInit: Uri, uriInit: Uri
networkTypeSelectedOptionsState: SnapshotStateList<Int> ): String? {
): Boolean { val errorMsg = validateApnData(uriInit, newApnData, context)
// Nothing to do if it's a read only APN
if (apnData.customizedConfig.readOnlyApn) {
return true
}
val errorMsg = validateApnData(apnData, context)
if (errorMsg != null) { if (errorMsg != null) {
return false return errorMsg
} }
val newApnData = apnData.copy(networkType = getNetworkType(networkTypeSelectedOptionsState))
if (newApnData.newApn || (newApnData != apnDataInit)) { if (newApnData.newApn || (newApnData != apnDataInit)) {
Log.d(TAG, "[validateAndSaveApnData] newApnData.networkType: ${newApnData.networkType}") Log.d(TAG, "[validateAndSaveApnData] newApnData.networkType: ${newApnData.networkType}")
updateApnDataToDatabase( updateApnDataToDatabase(
@@ -250,7 +241,7 @@ fun validateAndSaveApnData(
uriInit uriInit
) )
} }
return true return null
} }
/** /**
@@ -258,7 +249,7 @@ fun validateAndSaveApnData(
* *
* @return An error message if the apn data is invalid, otherwise return null. * @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? var errorMsg: String?
val name = apnData.name val name = apnData.name
val apn = apnData.apn val apn = apnData.apn
@@ -267,11 +258,14 @@ fun validateApnData(apnData: ApnData, context: Context): String? {
} else if (apn == "") { } else if (apn == "") {
context.resources.getString(R.string.error_apn_empty) context.resources.getString(R.string.error_apn_empty)
} else { } else {
validateMMSC(apnData.validEnabled, apnData.mmsc, context) validateMMSC(true, apnData.mmsc, context)
}
if (errorMsg == null) {
errorMsg = isItemExist(uri, apnData, context)
} }
if (errorMsg == null) { if (errorMsg == null) {
errorMsg = validateAPNType( errorMsg = validateAPNType(
apnData.validEnabled, true,
apnData.apnType, apnData.apnType,
apnData.customizedConfig.readOnlyApnTypes, apnData.customizedConfig.readOnlyApnTypes,
context context