diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt index 7ffa0c920d4..820a9d5c60f 100644 --- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt +++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt @@ -50,6 +50,7 @@ const val SUB_ID = "subId" const val MVNO_TYPE = "mvnoType" const val MVNO_MATCH_DATA = "mvnoMatchData" const val EDIT_URL = "editUrl" +const val INSERT_URL = "insertUrl" object ApnEditPageProvider : SettingsPageProvider { @@ -66,7 +67,10 @@ object ApnEditPageProvider : SettingsPageProvider { @Composable override fun Page(arguments: Bundle?) { - val apnDataInit = ApnData() + 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 apnDataCur = remember { mutableStateOf(apnDataInit) } diff --git a/src/com/android/settings/network/apn/ApnRepository.kt b/src/com/android/settings/network/apn/ApnRepository.kt new file mode 100644 index 00000000000..3ce756773a3 --- /dev/null +++ b/src/com/android/settings/network/apn/ApnRepository.kt @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2023 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.content.Context +import android.net.Uri +import android.provider.Telephony +import android.util.Log +import com.android.settings.R +import java.util.Locale + +const val NAME_INDEX = 1 +const val APN_INDEX = 2 +const val PROXY_INDEX = 3 +const val PORT_INDEX = 4 +const val USER_INDEX = 5 +const val SERVER_INDEX = 6 +const val PASSWORD_INDEX = 7 +const val MMSC_INDEX = 8 +const val MCC_INDEX = 9 +const val MNC_INDEX = 10 +const val MMSPROXY_INDEX = 12 +const val MMSPORT_INDEX = 13 +const val AUTH_TYPE_INDEX = 14 +const val TYPE_INDEX = 15 +const val PROTOCOL_INDEX = 16 +const val CARRIER_ENABLED_INDEX = 17 +const val NETWORK_TYPE_INDEX = 18 +const val ROAMING_PROTOCOL_INDEX = 19 +const val MVNO_TYPE_INDEX = 20 +const val MVNO_MATCH_DATA_INDEX = 21 +const val EDITED_INDEX = 22 +const val USER_EDITABLE_INDEX = 23 +const val CARRIER_ID_INDEX = 24 + +val sProjection = arrayOf( + Telephony.Carriers._ID, // 0 + Telephony.Carriers.NAME, // 1 + Telephony.Carriers.APN, // 2 + Telephony.Carriers.PROXY, // 3 + Telephony.Carriers.PORT, // 4 + Telephony.Carriers.USER, // 5 + Telephony.Carriers.SERVER, // 6 + Telephony.Carriers.PASSWORD, // 7 + Telephony.Carriers.MMSC, // 8 + Telephony.Carriers.MCC, // 9 + Telephony.Carriers.MNC, // 10 + Telephony.Carriers.NUMERIC, // 11 + Telephony.Carriers.MMSPROXY, // 12 + Telephony.Carriers.MMSPORT, // 13 + Telephony.Carriers.AUTH_TYPE, // 14 + Telephony.Carriers.TYPE, // 15 + Telephony.Carriers.PROTOCOL, // 16 + Telephony.Carriers.CARRIER_ENABLED, // 17 + Telephony.Carriers.NETWORK_TYPE_BITMASK, // 18 + Telephony.Carriers.ROAMING_PROTOCOL, // 19 + Telephony.Carriers.MVNO_TYPE, // 20 + Telephony.Carriers.MVNO_MATCH_DATA, // 21 + Telephony.Carriers.EDITED_STATUS, // 22 + Telephony.Carriers.USER_EDITABLE, // 23 + Telephony.Carriers.CARRIER_ID // 24 +) + +const val TAG = "ApnRepository" + +/** + * Query apn related information based on uri. + * @param uri URI data used for query. + * + * @return Stored apn related information. + */ +fun getApnDataFromUri(uri: Uri, context: Context): ApnData { + var apnData = ApnData() + val contentResolver = context.contentResolver + val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList() + val mvnoTypeOptions = context.resources.getStringArray(R.array.mvno_type_entries).toList() + + contentResolver.query( + uri, + sProjection, + null /* selection */, + null /* selectionArgs */, + null /* sortOrder */ + ).use { cursor -> + if (cursor != null && cursor.moveToFirst()) { + val name = cursor.getString(NAME_INDEX) + val apn = cursor.getString(APN_INDEX) + val proxy = cursor.getString(PROXY_INDEX) + val port = cursor.getString(PORT_INDEX) + val userName = cursor.getString(USER_INDEX) + val server = cursor.getString(SERVER_INDEX) + val passWord = cursor.getString(PASSWORD_INDEX) + val mmsc = cursor.getString(MMSC_INDEX) + val mcc = cursor.getString(MCC_INDEX) + val mnc = cursor.getString(MNC_INDEX) + val mmsProxy = cursor.getString(MMSPROXY_INDEX) + val mmsPort = cursor.getString(MMSPORT_INDEX) + val authType = cursor.getInt(AUTH_TYPE_INDEX) + val apnType = cursor.getString(TYPE_INDEX) + val apnProtocol = convertProtocol2Options(cursor.getString(PROTOCOL_INDEX), context) + val apnRoaming = + convertProtocol2Options(cursor.getString(ROAMING_PROTOCOL_INDEX), context) + val apnEnable = cursor.getInt(CARRIER_ENABLED_INDEX) == 1 + val networkType = cursor.getLong(NETWORK_TYPE_INDEX) + val mvnoType = cursor.getString(MVNO_TYPE_INDEX) + val mvnoValue = cursor.getString(MVNO_MATCH_DATA_INDEX) + + val edited = cursor.getInt(EDITED_INDEX) + val userEditable = cursor.getInt(USER_EDITABLE_INDEX) + val carrierId = cursor.getInt(CARRIER_ID_INDEX) + + apnData = apnData.copy( + name = name, + apn = apn, + proxy = proxy, + port = port, + userName = userName, + passWord = passWord, + server = server, + mmsc = mmsc, + mmsProxy = mmsProxy, + mmsPort = mmsPort, + mcc = mcc, + mnc = mnc, + authType = authType, + apnType = apnType, + apnProtocol = apnProtocolOptions.indexOf(apnProtocol), + apnRoaming = apnProtocolOptions.indexOf(apnRoaming), + apnEnable = apnEnable, + networkType = networkType, + mvnoType = mvnoTypeOptions.indexOf(mvnoType), + mvnoValue = mvnoValue, + edited = edited, + userEditable = userEditable, + carrierId = carrierId + ) + } + } + if (apnData.name == "") { + Log.d(TAG, "Can't get apnData from Uri $uri") + } + return apnData +} + +/** + * Returns The UI choice (e.g., "IPv4/IPv6") corresponding to the given + * raw value of the protocol preference (e.g., "IPV4V6"). If unknown, + * return null. + * + * @return UI choice + */ +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) + return if (protocolIndex == -1) { + "" + } else { + try { + apnProtocolOptions[protocolIndex] + } catch (e: ArrayIndexOutOfBoundsException) { + "" + } + } +} diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt index c8d1b83c455..ac9f28b36e4 100644 --- a/src/com/android/settings/network/apn/ApnStatus.kt +++ b/src/com/android/settings/network/apn/ApnStatus.kt @@ -16,8 +16,16 @@ package com.android.settings.network.apn +import android.content.Context +import android.net.Uri +import android.os.Bundle import android.provider.Telephony +import android.telephony.CarrierConfigManager import android.telephony.TelephonyManager +import android.text.TextUtils +import android.util.Log +import com.android.internal.util.ArrayUtils +import com.android.settings.R data class ApnData( val name: String = "", @@ -42,26 +50,147 @@ data class ApnData( var mvnoValue: String = "", val edited: Int = Telephony.Carriers.USER_EDITED, val userEditable: Int = 1, - val carrierId: Int = TelephonyManager.UNKNOWN_CARRIER_ID -) { - var nameEnabled = true - var apnEnabled = true - var proxyEnabled = true - var portEnabled = true - var userNameEnabled = true - var passWordEnabled = true - var serverEnabled = true - var mmscEnabled = true - var mmsProxyEnabled = true - var mmsPortEnabled = true - var mccEnabled = true - var mncEnabled = true - var authTypeEnabled = true - var apnTypeEnabled = true - var apnProtocolEnabled = true - var apnRoamingEnabled = true - var apnEnableEnabled = true - var networkTypeEnabled = true - var mvnoTypeEnabled = true - var mvnoValueEnabled = false + val carrierId: Int = TelephonyManager.UNKNOWN_CARRIER_ID, + val nameEnabled: Boolean = true, + val apnEnabled: Boolean = true, + val proxyEnabled: Boolean = true, + val portEnabled: Boolean = true, + val userNameEnabled: Boolean = true, + val passWordEnabled: Boolean = true, + val serverEnabled: Boolean = true, + val mmscEnabled: Boolean = true, + val mmsProxyEnabled: Boolean = true, + val mmsPortEnabled: Boolean = true, + val mccEnabled: Boolean = true, + val mncEnabled: Boolean = true, + val authTypeEnabled: Boolean = true, + val apnTypeEnabled: Boolean = true, + val apnProtocolEnabled: Boolean = true, + val apnRoamingEnabled: Boolean = true, + val apnEnableEnabled: Boolean = true, + val networkTypeEnabled: Boolean = true, + val mvnoTypeEnabled: Boolean = true, + val mvnoValueEnabled: Boolean = false, + val newApn: Boolean = false, +) + +data class CustomizedConfig( + val newApn: Boolean = false, + val readOnlyApn: Boolean = false, + val isAddApnAllowed: Boolean = true, + val readOnlyApnTypes: List = emptyList(), + val readOnlyApnFields: List = emptyList(), + val defaultApnTypes: List = emptyList(), + val defaultApnProtocol: String = "", + val defaultApnRoamingProtocol: String = "", +) + +/** + * Initialize ApnData according to the arguments. + * @param arguments The data passed in when the user calls PageProvider. + * @param uriInit The decoded user incoming uri data in Page. + * @param subId The subId obtained in arguments. + * + * @return Initialized CustomizedConfig information. + */ +fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int): ApnData { + + val uriType = arguments.getString(URI_TYPE)!! + val mvnoType = arguments.getString(MVNO_TYPE) + val mvnoValue = arguments.getString(MVNO_MATCH_DATA) + val mvnoTypeOptions = context.resources.getStringArray(R.array.mvno_type_entries).toList() + + val configManager = + context.getSystemService(Context.CARRIER_CONFIG_SERVICE) as CarrierConfigManager + getCarrierCustomizedConfig(configManager, subId) + + if (!uriInit.isPathPrefixMatch(Telephony.Carriers.CONTENT_URI)) { + Log.e(TAG, "Insert request not for carrier table. Uri: $uriInit") + return ApnData() //TODO: finish + } + + var apnDataInit = when (uriType) { + EDIT_URL -> getApnDataFromUri(uriInit, context) + INSERT_URL -> ApnData( + mvnoType = mvnoTypeOptions.indexOf(mvnoType!!), + mvnoValue = mvnoValue!! + ) + + else -> ApnData() //TODO: finish + } + + if (uriType == INSERT_URL) { + apnDataInit = apnDataInit.copy(newApn = true) + } + + // TODO: mvnoDescription + apnDataInit = apnDataInit.copy( + apnEnableEnabled = + context.resources.getBoolean(R.bool.config_allow_edit_carrier_enabled) + ) + // TODO: mIsCarrierIdApn & disableInit(apnDataInit) + return apnDataInit } + +/** + * Initialize CustomizedConfig information through subId. + * @param subId subId information obtained from arguments. + * + * @return Initialized CustomizedConfig information. + */ +fun getCarrierCustomizedConfig(configManager: CarrierConfigManager, subId: Int): CustomizedConfig { + val b = configManager.getConfigForSubId( + subId, + CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY, + CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY, + CarrierConfigManager.KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY, + CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_PROTOCOL_STRING, + CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING, + CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL + ) + val customizedConfig = CustomizedConfig( + readOnlyApnTypes = b.getStringArray( + CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY + )?.toList() ?: emptyList(), readOnlyApnFields = b.getStringArray( + CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY + )?.toList() ?: emptyList(), defaultApnTypes = b.getStringArray( + CarrierConfigManager.KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY + )?.toList() ?: emptyList(), defaultApnProtocol = b.getString( + CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_PROTOCOL_STRING + ) ?: "", 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( + ", " + ) + ) + } + if (!ArrayUtils.isEmpty(customizedConfig.defaultApnTypes)) { + Log.d( + TAG, + "getCarrierCustomizedConfig: default apn types: " + customizedConfig.defaultApnTypes.joinToString( + ", " + ) + ) + } + if (!TextUtils.isEmpty(customizedConfig.defaultApnProtocol)) { + Log.d( + TAG, + "getCarrierCustomizedConfig: default apn protocol: ${customizedConfig.defaultApnProtocol}" + ) + } + if (!TextUtils.isEmpty(customizedConfig.defaultApnRoamingProtocol)) { + Log.d( + TAG, + "getCarrierCustomizedConfig: default apn roaming protocol: ${customizedConfig.defaultApnRoamingProtocol}" + ) + } + if (!customizedConfig.isAddApnAllowed) { + Log.d(TAG, "getCarrierCustomizedConfig: not allow to add new APN") + } + return customizedConfig +} \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt index 8902c73dddd..8d1b223108d 100644 --- a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt @@ -256,9 +256,9 @@ class ApnEditPageProviderTest { .performScrollToNode(hasText(passwordTitle, true)) composeTestRule.onNodeWithText(passwordTitle, true).assertIsDisplayed() } - + private companion object { const val NETWORK_TYPE_UNSPECIFIED = "Unspecified" const val NETWORK_TYPE_LTE = "LTE" } -} +} \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt new file mode 100644 index 00000000000..47f2a7f6c6f --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 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.content.ContentResolver +import android.content.Context +import android.database.MatrixCursor +import android.net.Uri +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +class ApnRepositoryTest { + + private val context: Context = ApplicationProvider.getApplicationContext() + private val mContentResolver = mock {} + private val uri = mock {} + + @Test + fun getApnDataFromUri() { + // mock out resources and the feature provider + val cursor = MatrixCursor(sProjection) + cursor.addRow( + arrayOf( + 0, "name", "apn", "proxy", "port", + "userName", "server", "passWord", "mmsc", "mcc", "mnc", "numeric", + "mmsProxy", "mmsPort", 0, "apnType", "apnProtocol", 0, + 0, "apnRoaming", "mvnoType", "mvnoValue", 0, 1, 0 + ) + ) + val context = Mockito.spy(context) + whenever(context.contentResolver).thenReturn(mContentResolver) + whenever(mContentResolver.query(uri, sProjection, null, null, null)).thenReturn(cursor) + assert(getApnDataFromUri(uri, context).name == "name") + } +} \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnStatusTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnStatusTest.kt new file mode 100644 index 00000000000..e590c28a314 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnStatusTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 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.os.PersistableBundle +import android.telephony.CarrierConfigManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +class ApnStatusTest { + private val subId = 1 + private val configManager = mock { + val p = PersistableBundle() + p.putBoolean(CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL, true) + on { + getConfigForSubId( + subId, + CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY, + CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY, + CarrierConfigManager.KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY, + CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_PROTOCOL_STRING, + CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING, + CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL + ) + } doReturn p + } + + @Test + fun getCarrierCustomizedConfig_test() { + assert(getCarrierCustomizedConfig(configManager, subId).isAddApnAllowed) + } +} \ No newline at end of file