1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.network.apn
18 
19 import android.content.ContentValues
20 import android.content.Context
21 import android.net.Uri
22 import android.os.Bundle
23 import android.provider.Telephony
24 import android.telephony.CarrierConfigManager
25 import android.util.Log
26 import com.android.settings.R
27 import com.android.settings.network.apn.ApnTypes.getPreSelectedApnType
28 
29 private const val TAG = "ApnStatus"
30 
31 data class ApnData(
32     val id: Int = -1,
33     val name: String = "",
34     val apn: String = "",
35     val proxy: String = "",
36     val port: String = "",
37     val userName: String = "",
38     val passWord: String = "",
39     val server: String = "",
40     val mmsc: String = "",
41     val mmsProxy: String = "",
42     val mmsPort: String = "",
43     val authType: Int = -1,
44     val apnType: String = "",
45     val apnProtocol: Int = -1,
46     val apnRoaming: Int = -1,
47     val apnEnable: Boolean = true,
48     val networkType: Long = 0,
49     val edited: Int = Telephony.Carriers.USER_EDITED,
50     val userEditable: Int = 1,
51     val apnEnableEnabled: Boolean = true,
52     val newApn: Boolean = false,
53     val subId: Int = -1,
54     val validEnabled: Boolean = false,
55     val customizedConfig: CustomizedConfig = CustomizedConfig()
56 ) {
getContentValueMapnull57     fun getContentValueMap(context: Context): Map<String, Any> = mapOf(
58         Telephony.Carriers.NAME to name,
59         Telephony.Carriers.APN to apn,
60         Telephony.Carriers.PROXY to proxy,
61         Telephony.Carriers.PORT to port,
62         Telephony.Carriers.USER to userName,
63         Telephony.Carriers.SERVER to server,
64         Telephony.Carriers.PASSWORD to passWord,
65         Telephony.Carriers.MMSC to mmsc,
66         Telephony.Carriers.MMSPROXY to mmsProxy,
67         Telephony.Carriers.MMSPORT to mmsPort,
68         Telephony.Carriers.AUTH_TYPE to authType,
69         Telephony.Carriers.PROTOCOL to context.convertOptions2Protocol(apnProtocol),
70         Telephony.Carriers.ROAMING_PROTOCOL to context.convertOptions2Protocol(apnRoaming),
71         Telephony.Carriers.TYPE to apnType,
72         Telephony.Carriers.NETWORK_TYPE_BITMASK to networkType,
73         // Copy network type into lingering network type.
74         Telephony.Carriers.LINGERING_NETWORK_TYPE_BITMASK to networkType,
75         Telephony.Carriers.CARRIER_ENABLED to apnEnable,
76         Telephony.Carriers.EDITED_STATUS to Telephony.Carriers.USER_EDITED,
77     )
78 
79     fun getContentValues(context: Context) = ContentValues().apply {
80         if (newApn) context.getApnIdMap(subId).forEach(::putObject)
81         getContentValueMap(context).forEach(::putObject)
82     }
83 
isFieldEnablednull84     fun isFieldEnabled(vararg fieldName: String): Boolean =
85         !customizedConfig.readOnlyApn &&
86             fieldName.all { it !in customizedConfig.readOnlyApnFields }
87 }
88 
89 data class CustomizedConfig(
90     val readOnlyApn: Boolean = false,
91     val isAddApnAllowed: Boolean = true,
92     val readOnlyApnTypes: List<String> = emptyList(),
93     val readOnlyApnFields: List<String> = emptyList(),
94     val defaultApnTypes: List<String>? = null,
95     val defaultApnProtocol: String = "",
96     val defaultApnRoamingProtocol: String = "",
97 )
98 
99 /**
100  * Initialize ApnData according to the arguments.
101  * @param arguments The data passed in when the user calls PageProvider.
102  * @param uriInit The decoded user incoming uri data in Page.
103  * @param subId The subId obtained in arguments.
104  *
105  * @return Initialized CustomizedConfig information.
106  */
getApnDataInitnull107 fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int): ApnData? {
108     val uriType = arguments.getString(URI_TYPE) ?: return null
109 
110     if (!uriInit.isPathPrefixMatch(Telephony.Carriers.CONTENT_URI)) {
111         Log.e(TAG, "Insert request not for carrier table. Uri: $uriInit")
112         return null
113     }
114 
115     var apnDataInit = when (uriType) {
116         EDIT_URL -> getApnDataFromUri(uriInit, context)
117         INSERT_URL -> ApnData()
118         else -> return null
119     }
120 
121     if (uriType == INSERT_URL) {
122         apnDataInit = apnDataInit.copy(newApn = true)
123     }
124 
125     apnDataInit = apnDataInit.copy(subId = subId)
126     val configManager =
127         context.getSystemService(Context.CARRIER_CONFIG_SERVICE) as CarrierConfigManager
128     apnDataInit =
129         apnDataInit.copy(customizedConfig = getCarrierCustomizedConfig(apnDataInit, configManager))
130 
131     if (apnDataInit.newApn) {
132         apnDataInit = apnDataInit.copy(
133             apnType = getPreSelectedApnType(apnDataInit.customizedConfig)
134         )
135     }
136 
137     apnDataInit = apnDataInit.copy(
138         apnEnableEnabled =
139         context.resources.getBoolean(R.bool.config_allow_edit_carrier_enabled)
140     )
141     // TODO: mIsCarrierIdApn
142     return disableInit(apnDataInit)
143 }
144 
145 /**
146  * Validates the apn data and save it to the database if it's valid.
147  * A dialog with error message will be displayed if the APN data is invalid.
148  *
149  * @return true if there is no error
150  */
validateAndSaveApnDatanull151 fun validateAndSaveApnData(
152     apnDataInit: ApnData,
153     newApnData: ApnData,
154     context: Context,
155     uriInit: Uri
156 ): String? {
157     val errorMsg = validateApnData(newApnData, context)
158     if (errorMsg != null) {
159         return errorMsg
160     }
161     if (newApnData.newApn || (newApnData != apnDataInit)) {
162         Log.d(TAG, "[validateAndSaveApnData] newApnData.networkType: ${newApnData.networkType}")
163         updateApnDataToDatabase(
164             newApnData.newApn,
165             newApnData.getContentValues(context),
166             context,
167             uriInit
168         )
169     }
170     return null
171 }
172 
173 /**
174  * Validates whether the apn data is valid.
175  *
176  * @return An error message if the apn data is invalid, otherwise return null.
177  */
validateApnDatanull178 fun validateApnData(apnData: ApnData, context: Context): String? {
179     val errorMsg: String? = when {
180         apnData.name.isEmpty() -> context.resources.getString(R.string.error_name_empty)
181         apnData.apn.isEmpty() -> context.resources.getString(R.string.error_apn_empty)
182         apnData.apnType.isEmpty() -> context.resources.getString(R.string.error_apn_type_empty)
183         else -> validateMMSC(true, apnData.mmsc, context) ?: isItemExist(apnData, context)
184     }
185     return errorMsg?.also { Log.d(TAG, "APN data not valid, reason: $it") }
186 }
187 
188 /**
189  * Initialize CustomizedConfig information through subId.
190  * @param subId subId information obtained from arguments.
191  *
192  * @return Initialized CustomizedConfig information.
193  */
getCarrierCustomizedConfignull194 fun getCarrierCustomizedConfig(
195     apnInit: ApnData,
196     configManager: CarrierConfigManager
197 ): CustomizedConfig {
198     fun log(message: String) {
199         Log.d(TAG, "getCarrierCustomizedConfig: $message")
200     }
201 
202     val b = configManager.getConfigForSubId(
203         apnInit.subId,
204         CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY,
205         CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY,
206         CarrierConfigManager.KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY,
207         CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_PROTOCOL_STRING,
208         CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING,
209         CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL
210     )
211     val customizedConfig = CustomizedConfig(
212         readOnlyApnTypes = b.getStringArray(
213             CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY
214         )?.toList() ?: emptyList(),
215         readOnlyApnFields = b.getStringArray(
216             CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY
217         )?.toList() ?: emptyList(),
218         defaultApnTypes = b.getStringArray(
219             CarrierConfigManager.KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY
220         )?.toList(),
221         defaultApnProtocol = b.getString(
222             CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_PROTOCOL_STRING
223         ) ?: "",
224         defaultApnRoamingProtocol = b.getString(
225             CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING
226         ) ?: "",
227         isAddApnAllowed = b.getBoolean(CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL),
228     )
229     if (customizedConfig.readOnlyApnTypes.isNotEmpty()) {
230         log("read only APN type: " + customizedConfig.readOnlyApnTypes)
231     }
232     customizedConfig.defaultApnTypes?.takeIf { it.isNotEmpty() }?.let {
233         log("default apn types: $it")
234     }
235     if (customizedConfig.defaultApnProtocol.isNotEmpty()) {
236         log("default apn protocol: ${customizedConfig.defaultApnProtocol}")
237     }
238     if (customizedConfig.defaultApnRoamingProtocol.isNotEmpty()) {
239         log("default apn roaming protocol: ${customizedConfig.defaultApnRoamingProtocol}")
240     }
241     if (!customizedConfig.isAddApnAllowed) {
242         log("not allow to add new APN")
243     }
244     return customizedConfig
245 }
246 
isReadOnlynull247 private fun ApnData.isReadOnly(): Boolean {
248     Log.d(TAG, "isReadOnly: edited $edited")
249     if (edited == Telephony.Carriers.USER_EDITED) return false
250     // if it's not a USER_EDITED apn, check if it's read-only
251     return userEditable == 0 ||
252         ApnTypes.isApnTypeReadOnly(apnType, customizedConfig.readOnlyApnTypes)
253 }
254 
disableInitnull255 fun disableInit(apnDataInit: ApnData): ApnData {
256     if (apnDataInit.isReadOnly()) {
257         Log.d(TAG, "disableInit: read-only APN")
258         return apnDataInit.copy(
259             customizedConfig = apnDataInit.customizedConfig.copy(readOnlyApn = true)
260         )
261     }
262     val readOnlyApnFields = apnDataInit.customizedConfig.readOnlyApnFields
263     if (readOnlyApnFields.isNotEmpty()) {
264         Log.d(TAG, "disableInit: readOnlyApnFields $readOnlyApnFields)")
265     }
266     return apnDataInit
267 }
268 
deleteApnnull269 fun deleteApn(uri: Uri, context: Context) {
270     val contentResolver = context.contentResolver
271     contentResolver.delete(uri, null, null)
272 }
273 
validateMMSCnull274 fun validateMMSC(validEnabled: Boolean, mmsc: String, context: Context): String? {
275     return if (validEnabled && mmsc != "" && !mmsc.matches(Regex("^https?:\\/\\/.+")))
276         context.resources.getString(R.string.error_mmsc_valid)
277     else null
278 }
279 
validateNamenull280 fun validateName(validEnabled: Boolean, name: String, context: Context): String? {
281     return if (validEnabled && (name == "")) context.resources.getString(R.string.error_name_empty)
282     else null
283 }
284 
validateAPNnull285 fun validateAPN(validEnabled: Boolean, apn: String, context: Context): String? {
286     return if (validEnabled && (apn == "")) context.resources.getString(R.string.error_apn_empty)
287     else null
288 }
289