1 /*
<lambda>null2  * 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.app.settings.SettingsEnums
20 import android.net.Uri
21 import android.os.Bundle
22 import android.provider.Telephony
23 import androidx.compose.foundation.layout.Column
24 import androidx.compose.foundation.layout.fillMaxWidth
25 import androidx.compose.foundation.layout.padding
26 import androidx.compose.material3.Button
27 import androidx.compose.material3.DropdownMenuItem
28 import androidx.compose.material3.MaterialTheme
29 import androidx.compose.material3.Text
30 import androidx.compose.runtime.Composable
31 import androidx.compose.runtime.LaunchedEffect
32 import androidx.compose.runtime.MutableState
33 import androidx.compose.runtime.getValue
34 import androidx.compose.runtime.mutableStateOf
35 import androidx.compose.runtime.remember
36 import androidx.compose.runtime.setValue
37 import androidx.compose.ui.Modifier
38 import androidx.compose.ui.platform.LocalContext
39 import androidx.compose.ui.res.stringArrayResource
40 import androidx.compose.ui.res.stringResource
41 import androidx.navigation.NavType
42 import androidx.navigation.navArgument
43 import com.android.settings.R
44 import com.android.settings.network.telephony.SubscriptionRepository
45 import com.android.settingslib.spa.framework.common.SettingsPageProvider
46 import com.android.settingslib.spa.framework.compose.LocalNavController
47 import com.android.settingslib.spa.framework.theme.SettingsDimension
48 import com.android.settingslib.spa.widget.editor.SettingsDropdownBox
49 import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
50 import com.android.settingslib.spa.widget.editor.SettingsTextFieldPassword
51 import com.android.settingslib.spa.widget.preference.SwitchPreference
52 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
53 import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
54 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
55 import java.util.Base64
56 
57 const val URI_TYPE = "uriType"
58 const val URI = "uri"
59 const val SUB_ID = "subId"
60 const val EDIT_URL = "editUrl"
61 const val INSERT_URL = "insertUrl"
62 
63 object ApnEditPageProvider : SettingsPageProvider {
64 
65     override val name = "ApnEdit"
66     override val metricsCategory = SettingsEnums.APN_EDITOR
67     const val TAG = "ApnEditPageProvider"
68 
69     override val parameter = listOf(
70         navArgument(URI_TYPE) { type = NavType.StringType },
71         navArgument(URI) { type = NavType.StringType },
72         navArgument(SUB_ID) { type = NavType.IntType },
73     )
74 
75     @Composable
76     override fun Page(arguments: Bundle?) {
77         val uriString = arguments!!.getString(URI)
78         val uriInit = Uri.parse(String(Base64.getDecoder().decode(uriString)))
79         val subId = arguments.getInt(SUB_ID)
80         val apnDataInit = getApnDataInit(arguments, LocalContext.current, uriInit, subId) ?: return
81         val apnDataCur = remember {
82             mutableStateOf(apnDataInit)
83         }
84         ApnPage(apnDataInit, apnDataCur, uriInit)
85         SubscriptionNotEnabledEffect(subId)
86     }
87 
88     @Composable
89     private fun SubscriptionNotEnabledEffect(subId: Int) {
90         val context = LocalContext.current
91         val navController = LocalNavController.current
92         LaunchedEffect(subId) {
93             SubscriptionRepository(context).isSubscriptionEnabledFlow(subId).collect { isEnabled ->
94                 if (!isEnabled) navController.navigateBack()
95             }
96         }
97     }
98 
99     fun getRoute(
100         uriType: String,
101         uri: Uri,
102         subId: Int
103     ): String = "${name}/$uriType/${
104         Base64.getUrlEncoder().encodeToString(uri.toString().toByteArray())
105     }/$subId"
106 }
107 
108 @Composable
ApnPagenull109 fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Uri) {
110     var apnData by apnDataCur
111     val context = LocalContext.current
112     val authTypeOptions = stringArrayResource(R.array.apn_auth_entries).toList()
113     val apnProtocolOptions = stringArrayResource(R.array.apn_protocol_entries).toList()
114     var apnTypeMmsSelected by remember { mutableStateOf(false) }
115     val navController = LocalNavController.current
116     var valid: String?
117     RegularScaffold(
118         title = if (apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit),
119         actions = {
120             if (!apnData.customizedConfig.readOnlyApn) {
121                 Button(onClick = {
122                     valid = validateAndSaveApnData(
123                         apnDataInit,
124                         apnData,
125                         context,
126                         uriInit
127                     )
128                     if (valid == null) navController.navigateBack()
129                     else if (!apnData.validEnabled) apnData = apnData.copy(validEnabled = true)
130                 }) { Text(text = stringResource(id = R.string.save)) }
131             }
132             if (!apnData.newApn && !apnData.customizedConfig.readOnlyApn
133                 && apnData.customizedConfig.isAddApnAllowed
134             ) {
135                 MoreOptionsAction {
136                     DropdownMenuItem(
137                         text = { Text(stringResource(R.string.menu_delete)) },
138                         onClick = {
139                             deleteApn(uriInit, context)
140                             navController.navigateBack()
141                         })
142                 }
143             }
144         },
145     ) {
146         Column {
147             if (apnData.validEnabled) {
148                 valid = validateApnData(apnData, context)
149                 valid?.let {
150                     Text(
151                         text = it,
152                         modifier = Modifier
153                             .fillMaxWidth()
154                             .padding(SettingsDimension.menuFieldPadding),
155                         color = MaterialTheme.colorScheme.primary
156                     )
157                 }
158             }
159             SettingsOutlinedTextField(
160                 value = apnData.name,
161                 label = stringResource(R.string.apn_name),
162                 enabled = apnData.isFieldEnabled(Telephony.Carriers.NAME),
163                 errorMessage = validateName(apnData.validEnabled, apnData.name, context)
164             ) { apnData = apnData.copy(name = it) }
165             SettingsOutlinedTextField(
166                 value = apnData.apn,
167                 label = stringResource(R.string.apn_apn),
168                 enabled = apnData.isFieldEnabled(Telephony.Carriers.APN),
169                 errorMessage = validateAPN(apnData.validEnabled, apnData.apn, context)
170             ) { apnData = apnData.copy(apn = it) }
171             SettingsOutlinedTextField(
172                 value = apnData.proxy,
173                 label = stringResource(R.string.apn_http_proxy),
174                 enabled = apnData.isFieldEnabled(Telephony.Carriers.PROXY),
175             ) { apnData = apnData.copy(proxy = it) }
176             SettingsOutlinedTextField(
177                 value = apnData.port,
178                 label = stringResource(R.string.apn_http_port),
179                 enabled = apnData.isFieldEnabled(Telephony.Carriers.PORT),
180             ) { apnData = apnData.copy(port = it) }
181             SettingsOutlinedTextField(
182                 value = apnData.userName,
183                 label = stringResource(R.string.apn_user),
184                 enabled = apnData.isFieldEnabled(Telephony.Carriers.USER),
185             ) { apnData = apnData.copy(userName = it) }
186             SettingsTextFieldPassword(
187                 value = apnData.passWord,
188                 label = stringResource(R.string.apn_password),
189                 enabled = apnData.isFieldEnabled(Telephony.Carriers.PASSWORD),
190             ) { apnData = apnData.copy(passWord = it) }
191             SettingsOutlinedTextField(
192                 value = apnData.server,
193                 label = stringResource(R.string.apn_server),
194                 enabled = apnData.isFieldEnabled(Telephony.Carriers.SERVER),
195             ) { apnData = apnData.copy(server = it) }
196             ApnTypeCheckBox(
197                 apnData = apnData,
198                 onTypeChanged = { apnData = apnData.copy(apnType = it) },
199                 onMmsSelectedChanged = { apnTypeMmsSelected = it },
200             )
201             if (apnTypeMmsSelected) {
202                 SettingsOutlinedTextField(
203                     value = apnData.mmsc,
204                     label = stringResource(R.string.apn_mmsc),
205                     errorMessage = validateMMSC(apnData.validEnabled, apnData.mmsc, context),
206                     enabled = apnData.isFieldEnabled(Telephony.Carriers.MMSC),
207                 ) { apnData = apnData.copy(mmsc = it) }
208                 SettingsOutlinedTextField(
209                     value = apnData.mmsProxy,
210                     label = stringResource(R.string.apn_mms_proxy),
211                     enabled = apnData.isFieldEnabled(Telephony.Carriers.MMSPROXY),
212                 ) { apnData = apnData.copy(mmsProxy = it) }
213                 SettingsOutlinedTextField(
214                     value = apnData.mmsPort,
215                     label = stringResource(R.string.apn_mms_port),
216                     enabled = apnData.isFieldEnabled(Telephony.Carriers.MMSPORT),
217                 ) { apnData = apnData.copy(mmsPort = it) }
218             }
219             SettingsDropdownBox(
220                 label = stringResource(R.string.apn_auth_type),
221                 options = authTypeOptions,
222                 selectedOptionIndex = apnData.authType,
223                 enabled = apnData.isFieldEnabled(Telephony.Carriers.AUTH_TYPE),
224             ) { apnData = apnData.copy(authType = it) }
225             SettingsDropdownBox(
226                 label = stringResource(R.string.apn_protocol),
227                 options = apnProtocolOptions,
228                 selectedOptionIndex = apnData.apnProtocol,
229                 enabled = apnData.isFieldEnabled(Telephony.Carriers.PROTOCOL),
230             ) { apnData = apnData.copy(apnProtocol = it) }
231             SettingsDropdownBox(
232                 label = stringResource(R.string.apn_roaming_protocol),
233                 options = apnProtocolOptions,
234                 selectedOptionIndex = apnData.apnRoaming,
235                 enabled = apnData.isFieldEnabled(Telephony.Carriers.ROAMING_PROTOCOL),
236             ) { apnData = apnData.copy(apnRoaming = it) }
237             ApnNetworkTypeCheckBox(apnData) { apnData = apnData.copy(networkType = it) }
238             SwitchPreference(
239                 object : SwitchPreferenceModel {
240                     override val title = stringResource(R.string.carrier_enabled)
241                     override val changeable = {
242                         apnData.apnEnableEnabled &&
243                             apnData.isFieldEnabled(Telephony.Carriers.CARRIER_ENABLED)
244                     }
245                     override val checked = { apnData.apnEnable }
246                     override val onCheckedChange = { newChecked: Boolean ->
247                         apnData = apnData.copy(apnEnable = newChecked)
248                     }
249                 }
250             )
251         }
252     }
253 }