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 }