Loading res/values/strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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: --> Loading src/com/android/settings/network/apn/ApnEditPageProvider.kt +33 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -98,25 +104,47 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, 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), Loading src/com/android/settings/network/apn/ApnRepository.kt +27 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 { Loading @@ -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 Loading @@ -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 } No newline at end of file src/com/android/settings/network/apn/ApnStatus.kt +37 −43 Original line number Diff line number Diff line Loading @@ -72,41 +72,38 @@ data class ApnData( val validEnabled: Boolean = false, val customizedConfig: CustomizedConfig = CustomizedConfig() ) { 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) { fun getContentValueMap(context: Context): MutableMap<String, Any> { val simCarrierId = context.getSystemService(TelephonyManager::class.java)!! .createForSubscriptionId(subId) .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 } } data class CustomizedConfig( val newApn: Boolean = false, val readOnlyApn: Boolean = false, val isAddApnAllowed: Boolean = true, val readOnlyApnTypes: List<String> = emptyList(), Loading Loading @@ -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<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( Loading @@ -250,7 +241,7 @@ fun validateAndSaveApnData( uriInit ) } return true return null } /** Loading @@ -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 Loading @@ -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 Loading Loading
res/values/strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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: --> Loading
src/com/android/settings/network/apn/ApnEditPageProvider.kt +33 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -98,25 +104,47 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, 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), Loading
src/com/android/settings/network/apn/ApnRepository.kt +27 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 { Loading @@ -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 Loading @@ -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 } No newline at end of file
src/com/android/settings/network/apn/ApnStatus.kt +37 −43 Original line number Diff line number Diff line Loading @@ -72,41 +72,38 @@ data class ApnData( val validEnabled: Boolean = false, val customizedConfig: CustomizedConfig = CustomizedConfig() ) { 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) { fun getContentValueMap(context: Context): MutableMap<String, Any> { val simCarrierId = context.getSystemService(TelephonyManager::class.java)!! .createForSubscriptionId(subId) .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 } } data class CustomizedConfig( val newApn: Boolean = false, val readOnlyApn: Boolean = false, val isAddApnAllowed: Boolean = true, val readOnlyApnTypes: List<String> = emptyList(), Loading Loading @@ -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<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( Loading @@ -250,7 +241,7 @@ fun validateAndSaveApnData( uriInit ) } return true return null } /** Loading @@ -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 Loading @@ -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 Loading