Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 93b0dfbf authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Correct the APN type check box

- Correct the pre checked set.
- Correct when read only apn types is set.
  Gray out the read only types, if user try to select them, make a toast
  to tell user this type is not allowed by carrier.

Fix: 326172568
Test: manual - on APN edit
Change-Id: I9c2dac5adf7c16f53d255f7ead215e45c29aa491
parent 6664870e
Loading
Loading
Loading
Loading
+9 −47
Original line number Diff line number Diff line
@@ -38,17 +38,10 @@ import androidx.compose.ui.res.stringResource
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.android.settings.R
import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeDisplayNames
import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeSelectedOptionsState
import com.android.settings.network.apn.ApnTypes.APN_TYPES_OPTIONS
import com.android.settings.network.apn.ApnTypes.APN_TYPE_MMS
import com.android.settings.network.apn.ApnTypes.getApnTypeSelectedOptionsState
import com.android.settings.network.apn.ApnTypes.updateApnType
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
import com.android.settingslib.spa.widget.editor.SettingsTextFieldPassword
import com.android.settingslib.spa.widget.preference.SwitchPreference
@@ -79,7 +72,7 @@ object ApnEditPageProvider : SettingsPageProvider {
        val uriString = arguments!!.getString(URI)
        val uriInit = Uri.parse(String(Base64.getDecoder().decode(uriString)))
        val subId = arguments.getInt(SUB_ID)
        val apnDataInit = getApnDataInit(arguments, LocalContext.current, uriInit, subId)
        val apnDataInit = getApnDataInit(arguments, LocalContext.current, uriInit, subId) ?: return
        val apnDataCur = remember {
            mutableStateOf(apnDataInit)
        }
@@ -101,12 +94,7 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
    val context = LocalContext.current
    val authTypeOptions = stringArrayResource(R.array.apn_auth_entries).toList()
    val apnProtocolOptions = stringArrayResource(R.array.apn_protocol_entries).toList()
    val networkTypeSelectedOptionsState = remember {
        getNetworkTypeSelectedOptionsState(apnData.networkType)
    }
    var apnTypeSelectedOptionsState = remember {
        getApnTypeSelectedOptionsState(apnData.apnType)
    }
    var apnTypeMmsSelected by remember { mutableStateOf(false) }
    val navController = LocalNavController.current
    var valid: String?
    RegularScaffold(
@@ -114,11 +102,6 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
        actions = {
            if (!apnData.customizedConfig.readOnlyApn) {
                Button(onClick = {
                    apnData = apnData.copy(
                        networkType = ApnNetworkTypes.getNetworkType(
                            networkTypeSelectedOptionsState
                        )
                    )
                    valid = validateAndSaveApnData(
                        apnDataInit,
                        apnData,
@@ -193,27 +176,12 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
                label = stringResource(R.string.apn_server),
                enabled = apnData.serverEnabled
            ) { apnData = apnData.copy(server = it) }
            SettingsExposedDropdownMenuCheckBox(
                label = stringResource(R.string.apn_type),
                options = APN_TYPES_OPTIONS,
                selectedOptionsState = apnTypeSelectedOptionsState,
                enabled = apnData.apnTypeEnabled,
                errorMessage = validateAPNType(
                    apnData.validEnabled, apnData.apnType,
                    apnData.customizedConfig.readOnlyApnTypes, context
            ApnTypeCheckBox(
                apnData = apnData,
                onTypeChanged = { apnData = apnData.copy(apnType = it) },
                onMmsSelectedChanged = { apnTypeMmsSelected = it },
            )
            ) {
                val apnType = updateApnType(
                    apnTypeSelectedOptionsState,
                    apnData.customizedConfig.defaultApnTypes,
                    apnData.customizedConfig.readOnlyApnTypes
                )
                apnTypeSelectedOptionsState = getApnTypeSelectedOptionsState(apnType)
                apnData = apnData.copy(
                    apnType = apnType
                )
            }
            if (apnTypeSelectedOptionsState.contains(APN_TYPES_OPTIONS.indexOf(APN_TYPE_MMS))) {
            if (apnTypeMmsSelected) {
                SettingsOutlinedTextField(
                    value = apnData.mmsc,
                    label = stringResource(R.string.apn_mmsc),
@@ -249,13 +217,7 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
                selectedOptionIndex = apnData.apnRoaming,
                enabled = apnData.apnRoamingEnabled
            ) { apnData = apnData.copy(apnRoaming = it) }
            SettingsExposedDropdownMenuCheckBox(
                label = stringResource(R.string.network_type),
                options = getNetworkTypeDisplayNames(),
                selectedOptionsState = networkTypeSelectedOptionsState,
                emptyVal = stringResource(R.string.network_type_unspecified),
                enabled = apnData.networkTypeEnabled
            ) {}
            ApnNetworkTypeCheckBox(apnData) { apnData = apnData.copy(networkType = it) }
            SwitchPreference(
                object : SwitchPreferenceModel {
                    override val title = context.resources.getString(R.string.carrier_enabled)
+41 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.network.apn

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckBox

@Composable
fun ApnNetworkTypeCheckBox(apnData: ApnData, onNetworkTypeChanged: (Long) -> Unit) {
    val options = remember { ApnNetworkTypes.getNetworkTypeOptions() }
    val selectedStateMap = remember {
        ApnNetworkTypes.networkTypeToSelectedStateMap(options, apnData.networkType)
    }
    SettingsDropdownCheckBox(
        label = stringResource(R.string.network_type),
        options = options,
        emptyText = stringResource(R.string.network_type_unspecified),
        enabled = apnData.networkTypeEnabled,
    ) {
        onNetworkTypeChanged(
            ApnNetworkTypes.selectedStateMapToNetworkType(options, selectedStateMap)
        )
    }
}
+21 −12
Original line number Diff line number Diff line
@@ -17,8 +17,9 @@
package com.android.settings.network.apn

import android.telephony.TelephonyManager
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.snapshots.SnapshotStateMap
import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckOption

object ApnNetworkTypes {
    private val Types = listOf(
@@ -39,32 +40,40 @@ object ApnNetworkTypes {
        TelephonyManager.NETWORK_TYPE_NR,
    )

    fun getNetworkTypeDisplayNames(): List<String> =
        Types.map { TelephonyManager.getNetworkTypeName(it) }
    fun getNetworkTypeOptions(): List<SettingsDropdownCheckOption> =
        Types.map { SettingsDropdownCheckOption(TelephonyManager.getNetworkTypeName(it)) }

    /**
     * Gets the selected Network type Selected Options according to network type.
     * @param networkType Initialized network type bitmask, often multiple network type options may
     *                    be included.
     */
    fun getNetworkTypeSelectedOptionsState(networkType: Long): SnapshotStateList<Int> {
        val networkTypeSelectedOptionsState = mutableStateListOf<Int>()
    fun networkTypeToSelectedStateMap(
        options: List<SettingsDropdownCheckOption>,
        networkType: Long,
    ): SnapshotStateMap<SettingsDropdownCheckOption, Boolean> {
        val stateMap = mutableStateMapOf<SettingsDropdownCheckOption, Boolean>()
        Types.forEachIndexed { index, type ->
            if (networkType and TelephonyManager.getBitMaskForNetworkType(type) != 0L) {
                networkTypeSelectedOptionsState.add(index)
                stateMap[options[index]] = true
            }
        }
        return networkTypeSelectedOptionsState
        return stateMap
    }

    /**
     * Gets the network type according to the selected Network type Selected Options.
     * @param networkTypeSelectedOptionsState the selected Network type Selected Options.
     * @param stateMap the selected Network type Selected Options.
     */
    fun getNetworkType(networkTypeSelectedOptionsState: SnapshotStateList<Int>): Long {
    fun selectedStateMapToNetworkType(
        options: List<SettingsDropdownCheckOption>,
        stateMap: SnapshotStateMap<SettingsDropdownCheckOption, Boolean>,
    ): Long {
        var networkType = 0L
        networkTypeSelectedOptionsState.forEach { option ->
            networkType = networkType or TelephonyManager.getBitMaskForNetworkType(Types[option])
        options.forEachIndexed { index, option ->
            if (stateMap[option] == true) {
                networkType = networkType or TelephonyManager.getBitMaskForNetworkType(Types[index])
            }
        }
        return networkType
    }
+54 −149
Original line number Diff line number Diff line
@@ -22,17 +22,9 @@ import android.net.Uri
import android.os.Bundle
import android.provider.Telephony
import android.telephony.CarrierConfigManager
import android.text.TextUtils
import android.util.Log
import com.android.internal.util.ArrayUtils
import com.android.settings.R
import com.android.settings.network.apn.ApnTypes.APN_TYPES
import com.android.settings.network.apn.ApnTypes.APN_TYPE_ALL
import com.android.settings.network.apn.ApnTypes.APN_TYPE_EMERGENCY
import com.android.settings.network.apn.ApnTypes.APN_TYPE_IA
import com.android.settings.network.apn.ApnTypes.APN_TYPE_IMS
import com.android.settings.network.apn.ApnTypes.APN_TYPE_MCX
import java.util.Locale
import com.android.settings.network.apn.ApnTypes.getPreSelectedApnType

private const val TAG = "ApnStatus"

@@ -108,7 +100,7 @@ data class CustomizedConfig(
    val isAddApnAllowed: Boolean = true,
    val readOnlyApnTypes: List<String> = emptyList(),
    val readOnlyApnFields: List<String> = emptyList(),
    val defaultApnTypes: List<String> = emptyList(),
    val defaultApnTypes: List<String>? = null,
    val defaultApnProtocol: String = "",
    val defaultApnRoamingProtocol: String = "",
)
@@ -121,19 +113,18 @@ data class CustomizedConfig(
 *
 * @return Initialized CustomizedConfig information.
 */
fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int): ApnData {

    val uriType = arguments.getString(URI_TYPE)!!
fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int): ApnData? {
    val uriType = arguments.getString(URI_TYPE) ?: return null

    if (!uriInit.isPathPrefixMatch(Telephony.Carriers.CONTENT_URI)) {
        Log.e(TAG, "Insert request not for carrier table. Uri: $uriInit")
        return ApnData() //TODO: finish
        return null
    }

    var apnDataInit = when (uriType) {
        EDIT_URL -> getApnDataFromUri(uriInit, context)
        INSERT_URL -> ApnData()
        else -> ApnData() //TODO: finish
        else -> return null
    }

    if (uriType == INSERT_URL) {
@@ -146,13 +137,18 @@ fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int
    apnDataInit =
        apnDataInit.copy(customizedConfig = getCarrierCustomizedConfig(apnDataInit, configManager))

    if (apnDataInit.newApn) {
        apnDataInit = apnDataInit.copy(
            apnType = getPreSelectedApnType(apnDataInit.customizedConfig)
        )
    }

    apnDataInit = apnDataInit.copy(
        apnEnableEnabled =
        context.resources.getBoolean(R.bool.config_allow_edit_carrier_enabled)
    )
    // TODO: mIsCarrierIdApn
    disableInit(apnDataInit)
    return apnDataInit
    return disableInit(apnDataInit)
}

/**
@@ -202,55 +198,9 @@ fun validateApnData(apnData: ApnData, context: Context): String? {
    if (errorMsg == null) {
        errorMsg = isItemExist(apnData, context)
    }
    if (errorMsg == null) {
        errorMsg = validateAPNType(
            true,
            apnData.apnType,
            apnData.customizedConfig.readOnlyApnTypes,
            context
        )
    }
    return errorMsg?.apply { Log.d(TAG, "APN data not valid, reason: $this") }
}

private fun getUserEnteredApnType(apnType: String, readOnlyApnTypes: List<String>): String {
    // if user has not specified a type, map it to "ALL APN TYPES THAT ARE NOT READ-ONLY"
    // but if user enter empty type, map it just for default
    var userEnteredApnType = apnType
    if (userEnteredApnType != "") userEnteredApnType =
        userEnteredApnType.trim { it <= ' ' }
    if (TextUtils.isEmpty(userEnteredApnType) || APN_TYPE_ALL == userEnteredApnType) {
        userEnteredApnType = getEditableApnType(readOnlyApnTypes)
    }
    Log.d(
        TAG, "getUserEnteredApnType: changed apn type to editable apn types: "
            + userEnteredApnType
    )
    return userEnteredApnType
}

private fun getEditableApnType(readOnlyApnTypes: List<String>): String {
    val editableApnTypes = StringBuilder()
    var first = true
    for (apnType in APN_TYPES) {
        // add APN type if it is not read-only and is not wild-cardable
        if (!readOnlyApnTypes.contains(apnType)
            && apnType != APN_TYPE_IA
            && apnType != APN_TYPE_EMERGENCY
            && apnType != APN_TYPE_MCX
            && apnType != APN_TYPE_IMS
        ) {
            if (first) {
                first = false
            } else {
                editableApnTypes.append(",")
            }
            editableApnTypes.append(apnType)
        }
    }
    return editableApnTypes.toString()
}

/**
 * Initialize CustomizedConfig information through subId.
 * @param subId subId information obtained from arguments.
@@ -261,6 +211,10 @@ fun getCarrierCustomizedConfig(
    apnInit: ApnData,
    configManager: CarrierConfigManager
): CustomizedConfig {
    fun log(message: String) {
        Log.d(TAG, "getCarrierCustomizedConfig: $message")
    }

    val b = configManager.getConfigForSubId(
        apnInit.subId,
        CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY,
@@ -273,72 +227,61 @@ fun getCarrierCustomizedConfig(
    val customizedConfig = CustomizedConfig(
        readOnlyApnTypes = b.getStringArray(
            CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY
        )?.toList() ?: emptyList(), readOnlyApnFields = b.getStringArray(
        )?.toList() ?: emptyList(),
        readOnlyApnFields = b.getStringArray(
            CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY
        )?.toList() ?: emptyList(), defaultApnTypes = b.getStringArray(
        )?.toList() ?: emptyList(),
        defaultApnTypes = b.getStringArray(
            CarrierConfigManager.KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY
        )?.toList() ?: emptyList(), defaultApnProtocol = b.getString(
        )?.toList(),
        defaultApnProtocol = b.getString(
            CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_PROTOCOL_STRING
        ) ?: "", defaultApnRoamingProtocol = b.getString(
        ) ?: "",
        defaultApnRoamingProtocol = b.getString(
            CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING
        ) ?: "", isAddApnAllowed = b.getBoolean(CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL)
    )
    if (!ArrayUtils.isEmpty(customizedConfig.readOnlyApnTypes)) {
        Log.d(
            TAG,
            "getCarrierCustomizedConfig: read only APN type: " + customizedConfig.readOnlyApnTypes.joinToString(
                ", "
            )
        ) ?: "",
        isAddApnAllowed = b.getBoolean(CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL),
    )
    if (customizedConfig.readOnlyApnTypes.isNotEmpty()) {
        log("read only APN type: " + customizedConfig.readOnlyApnTypes)
    }
    if (!ArrayUtils.isEmpty(customizedConfig.defaultApnTypes)) {
        Log.d(
            TAG,
            "getCarrierCustomizedConfig: default apn types: " + customizedConfig.defaultApnTypes.joinToString(
                ", "
            )
        )
    customizedConfig.defaultApnTypes?.takeIf { it.isNotEmpty() }?.let {
        log("default apn types: $it")
    }
    if (!TextUtils.isEmpty(customizedConfig.defaultApnProtocol)) {
        Log.d(
            TAG,
            "getCarrierCustomizedConfig: default apn protocol: ${customizedConfig.defaultApnProtocol}"
        )
    if (customizedConfig.defaultApnProtocol.isNotEmpty()) {
        log("default apn protocol: ${customizedConfig.defaultApnProtocol}")
    }
    if (!TextUtils.isEmpty(customizedConfig.defaultApnRoamingProtocol)) {
        Log.d(
            TAG,
            "getCarrierCustomizedConfig: default apn roaming protocol: ${customizedConfig.defaultApnRoamingProtocol}"
        )
    if (customizedConfig.defaultApnRoamingProtocol.isNotEmpty()) {
        log("default apn roaming protocol: ${customizedConfig.defaultApnRoamingProtocol}")
    }
    if (!customizedConfig.isAddApnAllowed) {
        Log.d(TAG, "getCarrierCustomizedConfig: not allow to add new APN")
        log("not allow to add new APN")
    }
    return customizedConfig
}

fun disableInit(apnDataInit: ApnData): ApnData {
    var apnData = apnDataInit
    val isUserEdited = apnDataInit.edited == Telephony.Carriers.USER_EDITED
    Log.d(TAG, "disableInit: EDITED $isUserEdited")
private fun ApnData.isReadOnly(): Boolean {
    Log.d(TAG, "isReadOnly: edited $edited")
    if (edited == Telephony.Carriers.USER_EDITED) return false
    // if it's not a USER_EDITED apn, check if it's read-only
    if (!isUserEdited && (apnDataInit.userEditable == 0
            || apnTypesMatch(apnDataInit.customizedConfig.readOnlyApnTypes, apnDataInit.apnType))
    ) {
    return userEditable == 0 ||
        ApnTypes.isApnTypeReadOnly(apnType, customizedConfig.readOnlyApnTypes)
}

fun disableInit(apnDataInit: ApnData): ApnData {
    if (apnDataInit.isReadOnly()) {
        Log.d(TAG, "disableInit: read-only APN")
        apnData =
            apnDataInit.copy(customizedConfig = apnDataInit.customizedConfig.copy(readOnlyApn = true))
        apnData = disableAllFields(apnData)
    } else if (!ArrayUtils.isEmpty(apnData.customizedConfig.readOnlyApnFields)) {
        Log.d(
            TAG,
            "disableInit: mReadOnlyApnFields ${
                apnData.customizedConfig.readOnlyApnFields.joinToString(", ")
            })"
        val apnData = apnDataInit.copy(
            customizedConfig = apnDataInit.customizedConfig.copy(readOnlyApn = true)
        )
        apnData = disableFields(apnData.customizedConfig.readOnlyApnFields, apnData)
        return disableAllFields(apnData)
    }
    return apnData
    val readOnlyApnFields = apnDataInit.customizedConfig.readOnlyApnFields
    if (readOnlyApnFields.isNotEmpty()) {
        Log.d(TAG, "disableInit: readOnlyApnFields $readOnlyApnFields)")
        return disableFields(readOnlyApnFields, apnDataInit)
    }
    return apnDataInit
}

/**
@@ -405,23 +348,6 @@ private fun disableByFieldName(apnField: String, apnDataInit: ApnData): ApnData
    return apnData
}

private fun apnTypesMatch(apnTypeList: List<String>, apnType: String): Boolean {
    val normalizeApnTypeList = apnTypeList.map(::normalizeApnType)
    return hasAllApns(normalizeApnTypeList) ||
        apnType.split(",").map(::normalizeApnType).all { it in normalizeApnTypeList }
}

fun hasAllApns(apnTypes: List<String>): Boolean {
    if (APN_TYPE_ALL in apnTypes) {
        Log.d(TAG, "hasAllApns: true because apnTypes.contains(APN_TYPE_ALL)")
        return true
    }
    return APN_TYPES.all { it in apnTypes }
}

private fun normalizeApnType(apnType: String): String =
    apnType.trim().lowercase(Locale.getDefault())

fun deleteApn(uri: Uri, context: Context) {
    val contentResolver = context.contentResolver
    contentResolver.delete(uri, null, null)
@@ -442,24 +368,3 @@ fun validateAPN(validEnabled: Boolean, apn: String, context: Context): String? {
    return if (validEnabled && (apn == "")) context.resources.getString(R.string.error_apn_empty)
    else null
}

fun validateAPNType(
    validEnabled: Boolean,
    apnType: String,
    readOnlyApnTypes: List<String>,
    context: Context
): String? {
    // if carrier does not allow editing certain apn types, make sure type does not include those
    if (validEnabled && !ArrayUtils.isEmpty(readOnlyApnTypes)
        && apnTypesMatch(
            readOnlyApnTypes,
            getUserEnteredApnType(apnType, readOnlyApnTypes)
        )
    ) {
        return String.format(
            context.resources.getString(R.string.error_adding_apn_type),
            readOnlyApnTypes.joinToString(", ")
        )
    }
    return null
}
 No newline at end of file
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.network.apn

import android.telephony.data.ApnSetting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settings.network.apn.ApnTypes.toApnType
import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckBox

@Composable
fun ApnTypeCheckBox(
    apnData: ApnData,
    onTypeChanged: (String) -> Unit,
    onMmsSelectedChanged: (Boolean) -> Unit,
) {
    val context = LocalContext.current
    val apnTypeOptions = remember {
        ApnTypes.getOptions(context, apnData.apnType, apnData.customizedConfig.readOnlyApnTypes)
    }

    fun updateMmsSelected() {
        val apnTypeOptionMms = apnTypeOptions.single { it.text == ApnSetting.TYPE_MMS_STRING }
        onMmsSelectedChanged(apnTypeOptionMms.selected.value)
    }
    LaunchedEffect(Unit) { updateMmsSelected() }
    SettingsDropdownCheckBox(
        label = stringResource(R.string.apn_type),
        options = apnTypeOptions,
        enabled = apnData.apnTypeEnabled,
    ) {
        onTypeChanged(apnTypeOptions.toApnType())
        updateMmsSelected()
    }
}
Loading