Merge "Duplicate apn entry" into main
This commit is contained in:
@@ -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: -->
|
||||||
|
@@ -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),
|
||||||
|
@@ -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
|
||||||
|
}
|
@@ -72,41 +72,38 @@ data class ApnData(
|
|||||||
val validEnabled: Boolean = false,
|
val validEnabled: Boolean = false,
|
||||||
val customizedConfig: CustomizedConfig = CustomizedConfig()
|
val customizedConfig: CustomizedConfig = CustomizedConfig()
|
||||||
) {
|
) {
|
||||||
fun getContentValues(context: Context): ContentValues {
|
fun getContentValueMap(context: Context): MutableMap<String, Any> {
|
||||||
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 =
|
val simCarrierId =
|
||||||
context.getSystemService(TelephonyManager::class.java)!!
|
context.getSystemService(TelephonyManager::class.java)!!
|
||||||
.createForSubscriptionId(subId)
|
.createForSubscriptionId(subId)
|
||||||
.getSimCarrierId()
|
.getSimCarrierId()
|
||||||
values.put(Telephony.Carriers.CARRIER_ID, simCarrierId)
|
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()
|
||||||
|
val contentValueMap = getContentValueMap(context)
|
||||||
|
if (!newApn) contentValueMap.remove(Telephony.Carriers.CARRIER_ID)
|
||||||
|
contentValueMap.forEach { (key, value) -> values.putObject(key, value) }
|
||||||
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
|
||||||
|
Reference in New Issue
Block a user