1 /* 2 * Copyright (C) 2006 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; 18 19 import android.app.Dialog; 20 import android.app.settings.SettingsEnums; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.database.Cursor; 25 import android.net.Uri; 26 import android.os.Bundle; 27 import android.os.PersistableBundle; 28 import android.provider.Telephony; 29 import android.telephony.CarrierConfigManager; 30 import android.telephony.SubscriptionInfo; 31 import android.telephony.SubscriptionManager; 32 import android.telephony.TelephonyManager; 33 import android.text.TextUtils; 34 import android.util.Log; 35 import android.view.KeyEvent; 36 import android.view.Menu; 37 import android.view.MenuInflater; 38 import android.view.MenuItem; 39 import android.view.View; 40 import android.view.View.OnKeyListener; 41 42 import androidx.annotation.Nullable; 43 import androidx.annotation.VisibleForTesting; 44 import androidx.appcompat.app.AlertDialog; 45 import androidx.preference.EditTextPreference; 46 import androidx.preference.ListPreference; 47 import androidx.preference.MultiSelectListPreference; 48 import androidx.preference.Preference; 49 import androidx.preference.Preference.OnPreferenceChangeListener; 50 import androidx.preference.SwitchPreference; 51 52 import com.android.internal.util.ArrayUtils; 53 import com.android.settings.R; 54 import com.android.settings.SettingsPreferenceFragment; 55 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 56 import com.android.settingslib.utils.ThreadUtils; 57 58 import java.util.Arrays; 59 import java.util.HashSet; 60 import java.util.List; 61 import java.util.Objects; 62 import java.util.Set; 63 64 public class ApnEditor extends SettingsPreferenceFragment 65 implements OnPreferenceChangeListener, OnKeyListener { 66 67 private final static String TAG = ApnEditor.class.getSimpleName(); 68 private final static boolean VDBG = false; // STOPSHIP if true 69 70 private final static String KEY_AUTH_TYPE = "auth_type"; 71 private final static String KEY_PROTOCOL = "apn_protocol"; 72 private final static String KEY_ROAMING_PROTOCOL = "apn_roaming_protocol"; 73 private final static String KEY_CARRIER_ENABLED = "carrier_enabled"; 74 private final static String KEY_BEARER_MULTI = "bearer_multi"; 75 private final static String KEY_MVNO_TYPE = "mvno_type"; 76 private final static String KEY_PASSWORD = "apn_password"; 77 78 private static final int MENU_DELETE = Menu.FIRST; 79 private static final int MENU_SAVE = Menu.FIRST + 1; 80 private static final int MENU_CANCEL = Menu.FIRST + 2; 81 82 @VisibleForTesting 83 static String sNotSet; 84 @VisibleForTesting 85 EditTextPreference mName; 86 @VisibleForTesting 87 EditTextPreference mApn; 88 @VisibleForTesting 89 EditTextPreference mProxy; 90 @VisibleForTesting 91 EditTextPreference mPort; 92 @VisibleForTesting 93 EditTextPreference mUser; 94 @VisibleForTesting 95 EditTextPreference mServer; 96 @VisibleForTesting 97 EditTextPreference mPassword; 98 @VisibleForTesting 99 EditTextPreference mMmsc; 100 @VisibleForTesting 101 EditTextPreference mMcc; 102 @VisibleForTesting 103 EditTextPreference mMnc; 104 @VisibleForTesting 105 EditTextPreference mMmsProxy; 106 @VisibleForTesting 107 EditTextPreference mMmsPort; 108 @VisibleForTesting 109 ListPreference mAuthType; 110 @VisibleForTesting 111 EditTextPreference mApnType; 112 @VisibleForTesting 113 ListPreference mProtocol; 114 @VisibleForTesting 115 ListPreference mRoamingProtocol; 116 @VisibleForTesting 117 SwitchPreference mCarrierEnabled; 118 @VisibleForTesting 119 MultiSelectListPreference mBearerMulti; 120 @VisibleForTesting 121 ListPreference mMvnoType; 122 @VisibleForTesting 123 EditTextPreference mMvnoMatchData; 124 125 @VisibleForTesting 126 ApnData mApnData; 127 128 private String mCurMnc; 129 private String mCurMcc; 130 131 private boolean mNewApn; 132 private int mSubId; 133 private ProxySubscriptionManager mProxySubscriptionMgr; 134 private int mBearerInitialVal = 0; 135 private String mMvnoTypeStr; 136 private String mMvnoMatchDataStr; 137 @VisibleForTesting 138 String[] mReadOnlyApnTypes; 139 @VisibleForTesting 140 String[] mDefaultApnTypes; 141 private String[] mReadOnlyApnFields; 142 private boolean mReadOnlyApn; 143 private Uri mCarrierUri; 144 145 /** 146 * APN types for data connections. These are usage categories for an APN 147 * entry. One APN entry may support multiple APN types, eg, a single APN 148 * may service regular internet traffic ("default") as well as MMS-specific 149 * connections.<br/> 150 * APN_TYPE_ALL is a special type to indicate that this APN entry can 151 * service all data connections. 152 */ 153 public static final String APN_TYPE_ALL = "*"; 154 /** APN type for default data traffic */ 155 public static final String APN_TYPE_DEFAULT = "default"; 156 /** APN type for MMS traffic */ 157 public static final String APN_TYPE_MMS = "mms"; 158 /** APN type for SUPL assisted GPS */ 159 public static final String APN_TYPE_SUPL = "supl"; 160 /** APN type for DUN traffic */ 161 public static final String APN_TYPE_DUN = "dun"; 162 /** APN type for HiPri traffic */ 163 public static final String APN_TYPE_HIPRI = "hipri"; 164 /** APN type for FOTA */ 165 public static final String APN_TYPE_FOTA = "fota"; 166 /** APN type for IMS */ 167 public static final String APN_TYPE_IMS = "ims"; 168 /** APN type for CBS */ 169 public static final String APN_TYPE_CBS = "cbs"; 170 /** APN type for IA Initial Attach APN */ 171 public static final String APN_TYPE_IA = "ia"; 172 /** APN type for Emergency PDN. This is not an IA apn, but is used 173 * for access to carrier services in an emergency call situation. */ 174 public static final String APN_TYPE_EMERGENCY = "emergency"; 175 /** APN type for Mission Critical Services */ 176 public static final String APN_TYPE_MCX = "mcx"; 177 /** APN type for XCAP */ 178 public static final String APN_TYPE_XCAP = "xcap"; 179 /** Array of all APN types */ 180 public static final String[] APN_TYPES = {APN_TYPE_DEFAULT, 181 APN_TYPE_MMS, 182 APN_TYPE_SUPL, 183 APN_TYPE_DUN, 184 APN_TYPE_HIPRI, 185 APN_TYPE_FOTA, 186 APN_TYPE_IMS, 187 APN_TYPE_CBS, 188 APN_TYPE_IA, 189 APN_TYPE_EMERGENCY, 190 APN_TYPE_MCX, 191 APN_TYPE_XCAP, 192 }; 193 194 /** 195 * Standard projection for the interesting columns of a normal note. 196 */ 197 private static final String[] sProjection = new String[] { 198 Telephony.Carriers._ID, // 0 199 Telephony.Carriers.NAME, // 1 200 Telephony.Carriers.APN, // 2 201 Telephony.Carriers.PROXY, // 3 202 Telephony.Carriers.PORT, // 4 203 Telephony.Carriers.USER, // 5 204 Telephony.Carriers.SERVER, // 6 205 Telephony.Carriers.PASSWORD, // 7 206 Telephony.Carriers.MMSC, // 8 207 Telephony.Carriers.MCC, // 9 208 Telephony.Carriers.MNC, // 10 209 Telephony.Carriers.NUMERIC, // 11 210 Telephony.Carriers.MMSPROXY,// 12 211 Telephony.Carriers.MMSPORT, // 13 212 Telephony.Carriers.AUTH_TYPE, // 14 213 Telephony.Carriers.TYPE, // 15 214 Telephony.Carriers.PROTOCOL, // 16 215 Telephony.Carriers.CARRIER_ENABLED, // 17 216 Telephony.Carriers.BEARER, // 18 217 Telephony.Carriers.BEARER_BITMASK, // 19 218 Telephony.Carriers.ROAMING_PROTOCOL, // 20 219 Telephony.Carriers.MVNO_TYPE, // 21 220 Telephony.Carriers.MVNO_MATCH_DATA, // 22 221 Telephony.Carriers.EDITED_STATUS, // 23 222 Telephony.Carriers.USER_EDITABLE //24 223 }; 224 225 private static final int ID_INDEX = 0; 226 @VisibleForTesting 227 static final int NAME_INDEX = 1; 228 @VisibleForTesting 229 static final int APN_INDEX = 2; 230 private static final int PROXY_INDEX = 3; 231 private static final int PORT_INDEX = 4; 232 private static final int USER_INDEX = 5; 233 private static final int SERVER_INDEX = 6; 234 private static final int PASSWORD_INDEX = 7; 235 private static final int MMSC_INDEX = 8; 236 @VisibleForTesting 237 static final int MCC_INDEX = 9; 238 @VisibleForTesting 239 static final int MNC_INDEX = 10; 240 private static final int MMSPROXY_INDEX = 12; 241 private static final int MMSPORT_INDEX = 13; 242 private static final int AUTH_TYPE_INDEX = 14; 243 @VisibleForTesting 244 static final int TYPE_INDEX = 15; 245 private static final int PROTOCOL_INDEX = 16; 246 @VisibleForTesting 247 static final int CARRIER_ENABLED_INDEX = 17; 248 private static final int BEARER_INDEX = 18; 249 private static final int BEARER_BITMASK_INDEX = 19; 250 private static final int ROAMING_PROTOCOL_INDEX = 20; 251 private static final int MVNO_TYPE_INDEX = 21; 252 private static final int MVNO_MATCH_DATA_INDEX = 22; 253 private static final int EDITED_INDEX = 23; 254 private static final int USER_EDITABLE_INDEX = 24; 255 256 @Override onCreate(Bundle icicle)257 public void onCreate(Bundle icicle) { 258 super.onCreate(icicle); 259 260 // enable ProxySubscriptionMgr with Lifecycle support for all controllers 261 // live within this fragment 262 mProxySubscriptionMgr = ProxySubscriptionManager.getInstance(getContext()); 263 mProxySubscriptionMgr.setLifecycle(getLifecycle()); 264 265 addPreferencesFromResource(R.xml.apn_editor); 266 267 sNotSet = getResources().getString(R.string.apn_not_set); 268 mName = (EditTextPreference) findPreference("apn_name"); 269 mApn = (EditTextPreference) findPreference("apn_apn"); 270 mProxy = (EditTextPreference) findPreference("apn_http_proxy"); 271 mPort = (EditTextPreference) findPreference("apn_http_port"); 272 mUser = (EditTextPreference) findPreference("apn_user"); 273 mServer = (EditTextPreference) findPreference("apn_server"); 274 mPassword = (EditTextPreference) findPreference(KEY_PASSWORD); 275 mMmsProxy = (EditTextPreference) findPreference("apn_mms_proxy"); 276 mMmsPort = (EditTextPreference) findPreference("apn_mms_port"); 277 mMmsc = (EditTextPreference) findPreference("apn_mmsc"); 278 mMcc = (EditTextPreference) findPreference("apn_mcc"); 279 mMnc = (EditTextPreference) findPreference("apn_mnc"); 280 mApnType = (EditTextPreference) findPreference("apn_type"); 281 mAuthType = (ListPreference) findPreference(KEY_AUTH_TYPE); 282 mProtocol = (ListPreference) findPreference(KEY_PROTOCOL); 283 mRoamingProtocol = (ListPreference) findPreference(KEY_ROAMING_PROTOCOL); 284 mCarrierEnabled = (SwitchPreference) findPreference(KEY_CARRIER_ENABLED); 285 mBearerMulti = (MultiSelectListPreference) findPreference(KEY_BEARER_MULTI); 286 mMvnoType = (ListPreference) findPreference(KEY_MVNO_TYPE); 287 mMvnoMatchData = (EditTextPreference) findPreference("mvno_match_data"); 288 289 final Intent intent = getIntent(); 290 final String action = intent.getAction(); 291 if (TextUtils.isEmpty(action)) { 292 finish(); 293 return; 294 } 295 296 mSubId = intent.getIntExtra(ApnSettings.SUB_ID, 297 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 298 mReadOnlyApn = false; 299 mReadOnlyApnTypes = null; 300 mReadOnlyApnFields = null; 301 302 final CarrierConfigManager configManager = (CarrierConfigManager) 303 getSystemService(Context.CARRIER_CONFIG_SERVICE); 304 if (configManager != null) { 305 final PersistableBundle b = configManager.getConfigForSubId(mSubId); 306 if (b != null) { 307 mReadOnlyApnTypes = b.getStringArray( 308 CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY); 309 if (!ArrayUtils.isEmpty(mReadOnlyApnTypes)) { 310 Log.d(TAG, 311 "onCreate: read only APN type: " + Arrays.toString(mReadOnlyApnTypes)); 312 } 313 mReadOnlyApnFields = b.getStringArray( 314 CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY); 315 316 mDefaultApnTypes = b.getStringArray( 317 CarrierConfigManager.KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY); 318 if (!ArrayUtils.isEmpty(mDefaultApnTypes)) { 319 Log.d(TAG, "onCreate: default apn types: " + Arrays.toString(mDefaultApnTypes)); 320 } 321 } 322 } 323 324 Uri uri = null; 325 if (action.equals(Intent.ACTION_EDIT)) { 326 uri = intent.getData(); 327 if (!uri.isPathPrefixMatch(Telephony.Carriers.CONTENT_URI)) { 328 Log.e(TAG, "Edit request not for carrier table. Uri: " + uri); 329 finish(); 330 return; 331 } 332 } else if (action.equals(Intent.ACTION_INSERT)) { 333 mCarrierUri = intent.getData(); 334 if (!mCarrierUri.isPathPrefixMatch(Telephony.Carriers.CONTENT_URI)) { 335 Log.e(TAG, "Insert request not for carrier table. Uri: " + mCarrierUri); 336 finish(); 337 return; 338 } 339 mNewApn = true; 340 mMvnoTypeStr = intent.getStringExtra(ApnSettings.MVNO_TYPE); 341 mMvnoMatchDataStr = intent.getStringExtra(ApnSettings.MVNO_MATCH_DATA); 342 } else { 343 finish(); 344 return; 345 } 346 347 // Creates an ApnData to store the apn data temporary, so that we don't need the cursor to 348 // get the apn data. The uri is null if the action is ACTION_INSERT, that mean there is no 349 // record in the database, so create a empty ApnData to represent a empty row of database. 350 if (uri != null) { 351 mApnData = getApnDataFromUri(uri); 352 } else { 353 mApnData = new ApnData(sProjection.length); 354 } 355 356 final boolean isUserEdited = mApnData.getInteger(EDITED_INDEX, 357 Telephony.Carriers.USER_EDITED) == Telephony.Carriers.USER_EDITED; 358 359 Log.d(TAG, "onCreate: EDITED " + isUserEdited); 360 // if it's not a USER_EDITED apn, check if it's read-only 361 if (!isUserEdited && (mApnData.getInteger(USER_EDITABLE_INDEX, 1) == 0 362 || apnTypesMatch(mReadOnlyApnTypes, mApnData.getString(TYPE_INDEX)))) { 363 Log.d(TAG, "onCreate: apnTypesMatch; read-only APN"); 364 mReadOnlyApn = true; 365 disableAllFields(); 366 } else if (!ArrayUtils.isEmpty(mReadOnlyApnFields)) { 367 disableFields(mReadOnlyApnFields); 368 } 369 370 for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) { 371 getPreferenceScreen().getPreference(i).setOnPreferenceChangeListener(this); 372 } 373 } 374 375 @Override onViewStateRestored(@ullable Bundle savedInstanceState)376 public void onViewStateRestored(@Nullable Bundle savedInstanceState) { 377 super.onViewStateRestored(savedInstanceState); 378 fillUI(savedInstanceState == null); 379 } 380 381 @VisibleForTesting formatInteger(String value)382 static String formatInteger(String value) { 383 try { 384 final int intValue = Integer.parseInt(value); 385 return String.format(getCorrectDigitsFormat(value), intValue); 386 } catch (NumberFormatException e) { 387 return value; 388 } 389 } 390 391 /** 392 * Get the digits format so we preserve leading 0's. 393 * MCCs are 3 digits and MNCs are either 2 or 3. 394 */ getCorrectDigitsFormat(String value)395 static String getCorrectDigitsFormat(String value) { 396 if (value.length() == 2) return "%02d"; 397 else return "%03d"; 398 } 399 400 401 /** 402 * Check if passed in array of APN types indicates all APN types 403 * @param apnTypes array of APN types. "*" indicates all types. 404 * @return true if all apn types are included in the array, false otherwise 405 */ hasAllApns(String[] apnTypes)406 static boolean hasAllApns(String[] apnTypes) { 407 if (ArrayUtils.isEmpty(apnTypes)) { 408 return false; 409 } 410 411 final List apnList = Arrays.asList(apnTypes); 412 if (apnList.contains(APN_TYPE_ALL)) { 413 Log.d(TAG, "hasAllApns: true because apnList.contains(APN_TYPE_ALL)"); 414 return true; 415 } 416 for (String apn : APN_TYPES) { 417 if (!apnList.contains(apn)) { 418 return false; 419 } 420 } 421 422 Log.d(TAG, "hasAllApns: true"); 423 return true; 424 } 425 426 /** 427 * Check if APN types overlap. 428 * @param apnTypesArray1 array of APNs. Empty array indicates no APN type; "*" indicates all 429 * types 430 * @param apnTypes2 comma separated string of APN types. Empty string represents all types. 431 * @return if any apn type matches return true, otherwise return false 432 */ apnTypesMatch(String[] apnTypesArray1, String apnTypes2)433 private boolean apnTypesMatch(String[] apnTypesArray1, String apnTypes2) { 434 if (ArrayUtils.isEmpty(apnTypesArray1)) { 435 return false; 436 } 437 438 if (hasAllApns(apnTypesArray1) || TextUtils.isEmpty(apnTypes2)) { 439 return true; 440 } 441 442 final List apnTypesList1 = Arrays.asList(apnTypesArray1); 443 final String[] apnTypesArray2 = apnTypes2.split(","); 444 445 for (String apn : apnTypesArray2) { 446 if (apnTypesList1.contains(apn.trim())) { 447 Log.d(TAG, "apnTypesMatch: true because match found for " + apn.trim()); 448 return true; 449 } 450 } 451 452 Log.d(TAG, "apnTypesMatch: false"); 453 return false; 454 } 455 456 /** 457 * Function to get Preference obj corresponding to an apnField 458 * @param apnField apn field name for which pref is needed 459 * @return Preference obj corresponding to passed in apnField 460 */ getPreferenceFromFieldName(String apnField)461 private Preference getPreferenceFromFieldName(String apnField) { 462 switch (apnField) { 463 case Telephony.Carriers.NAME: 464 return mName; 465 case Telephony.Carriers.APN: 466 return mApn; 467 case Telephony.Carriers.PROXY: 468 return mProxy; 469 case Telephony.Carriers.PORT: 470 return mPort; 471 case Telephony.Carriers.USER: 472 return mUser; 473 case Telephony.Carriers.SERVER: 474 return mServer; 475 case Telephony.Carriers.PASSWORD: 476 return mPassword; 477 case Telephony.Carriers.MMSPROXY: 478 return mMmsProxy; 479 case Telephony.Carriers.MMSPORT: 480 return mMmsPort; 481 case Telephony.Carriers.MMSC: 482 return mMmsc; 483 case Telephony.Carriers.MCC: 484 return mMcc; 485 case Telephony.Carriers.MNC: 486 return mMnc; 487 case Telephony.Carriers.TYPE: 488 return mApnType; 489 case Telephony.Carriers.AUTH_TYPE: 490 return mAuthType; 491 case Telephony.Carriers.PROTOCOL: 492 return mProtocol; 493 case Telephony.Carriers.ROAMING_PROTOCOL: 494 return mRoamingProtocol; 495 case Telephony.Carriers.CARRIER_ENABLED: 496 return mCarrierEnabled; 497 case Telephony.Carriers.BEARER: 498 case Telephony.Carriers.BEARER_BITMASK: 499 return mBearerMulti; 500 case Telephony.Carriers.MVNO_TYPE: 501 return mMvnoType; 502 case Telephony.Carriers.MVNO_MATCH_DATA: 503 return mMvnoMatchData; 504 } 505 return null; 506 } 507 508 /** 509 * Disables given fields so that user cannot modify them 510 * 511 * @param apnFields fields to be disabled 512 */ disableFields(String[] apnFields)513 private void disableFields(String[] apnFields) { 514 for (String apnField : apnFields) { 515 final Preference preference = getPreferenceFromFieldName(apnField); 516 if (preference != null) { 517 preference.setEnabled(false); 518 } 519 } 520 } 521 522 /** 523 * Disables all fields so that user cannot modify the APN 524 */ disableAllFields()525 private void disableAllFields() { 526 mName.setEnabled(false); 527 mApn.setEnabled(false); 528 mProxy.setEnabled(false); 529 mPort.setEnabled(false); 530 mUser.setEnabled(false); 531 mServer.setEnabled(false); 532 mPassword.setEnabled(false); 533 mMmsProxy.setEnabled(false); 534 mMmsPort.setEnabled(false); 535 mMmsc.setEnabled(false); 536 mMcc.setEnabled(false); 537 mMnc.setEnabled(false); 538 mApnType.setEnabled(false); 539 mAuthType.setEnabled(false); 540 mProtocol.setEnabled(false); 541 mRoamingProtocol.setEnabled(false); 542 mCarrierEnabled.setEnabled(false); 543 mBearerMulti.setEnabled(false); 544 mMvnoType.setEnabled(false); 545 mMvnoMatchData.setEnabled(false); 546 } 547 548 @Override getMetricsCategory()549 public int getMetricsCategory() { 550 return SettingsEnums.APN_EDITOR; 551 } 552 553 @VisibleForTesting fillUI(boolean firstTime)554 void fillUI(boolean firstTime) { 555 if (firstTime) { 556 // Fill in all the values from the db in both text editor and summary 557 mName.setText(mApnData.getString(NAME_INDEX)); 558 mApn.setText(mApnData.getString(APN_INDEX)); 559 mProxy.setText(mApnData.getString(PROXY_INDEX)); 560 mPort.setText(mApnData.getString(PORT_INDEX)); 561 mUser.setText(mApnData.getString(USER_INDEX)); 562 mServer.setText(mApnData.getString(SERVER_INDEX)); 563 mPassword.setText(mApnData.getString(PASSWORD_INDEX)); 564 mMmsProxy.setText(mApnData.getString(MMSPROXY_INDEX)); 565 mMmsPort.setText(mApnData.getString(MMSPORT_INDEX)); 566 mMmsc.setText(mApnData.getString(MMSC_INDEX)); 567 mMcc.setText(mApnData.getString(MCC_INDEX)); 568 mMnc.setText(mApnData.getString(MNC_INDEX)); 569 mApnType.setText(mApnData.getString(TYPE_INDEX)); 570 if (mNewApn) { 571 final SubscriptionInfo subInfo = 572 mProxySubscriptionMgr.getAccessibleSubscriptionInfo(mSubId); 573 574 // Country code 575 final String mcc = (subInfo == null) ? null : subInfo.getMccString(); 576 // Network code 577 final String mnc = (subInfo == null) ? null : subInfo.getMncString(); 578 579 if ((!TextUtils.isEmpty(mcc)) && (!TextUtils.isEmpty(mcc))) { 580 // Auto populate MNC and MCC for new entries, based on what SIM reports 581 mMcc.setText(mcc); 582 mMnc.setText(mnc); 583 mCurMnc = mnc; 584 mCurMcc = mcc; 585 } 586 } 587 final int authVal = mApnData.getInteger(AUTH_TYPE_INDEX, -1); 588 if (authVal != -1) { 589 mAuthType.setValueIndex(authVal); 590 } else { 591 mAuthType.setValue(null); 592 } 593 594 mProtocol.setValue(mApnData.getString(PROTOCOL_INDEX)); 595 mRoamingProtocol.setValue(mApnData.getString(ROAMING_PROTOCOL_INDEX)); 596 mCarrierEnabled.setChecked(mApnData.getInteger(CARRIER_ENABLED_INDEX, 1) == 1); 597 mBearerInitialVal = mApnData.getInteger(BEARER_INDEX, 0); 598 599 final HashSet<String> bearers = new HashSet<String>(); 600 int bearerBitmask = mApnData.getInteger(BEARER_BITMASK_INDEX, 0); 601 if (bearerBitmask == 0) { 602 if (mBearerInitialVal == 0) { 603 bearers.add("" + 0); 604 } 605 } else { 606 int i = 1; 607 while (bearerBitmask != 0) { 608 if ((bearerBitmask & 1) == 1) { 609 bearers.add("" + i); 610 } 611 bearerBitmask >>= 1; 612 i++; 613 } 614 } 615 616 if (mBearerInitialVal != 0 && bearers.contains("" + mBearerInitialVal) == false) { 617 // add mBearerInitialVal to bearers 618 bearers.add("" + mBearerInitialVal); 619 } 620 mBearerMulti.setValues(bearers); 621 622 mMvnoType.setValue(mApnData.getString(MVNO_TYPE_INDEX)); 623 mMvnoMatchData.setEnabled(false); 624 mMvnoMatchData.setText(mApnData.getString(MVNO_MATCH_DATA_INDEX)); 625 if (mNewApn && mMvnoTypeStr != null && mMvnoMatchDataStr != null) { 626 mMvnoType.setValue(mMvnoTypeStr); 627 mMvnoMatchData.setText(mMvnoMatchDataStr); 628 } 629 } 630 631 mName.setSummary(checkNull(mName.getText())); 632 mApn.setSummary(checkNull(mApn.getText())); 633 mProxy.setSummary(checkNull(mProxy.getText())); 634 mPort.setSummary(checkNull(mPort.getText())); 635 mUser.setSummary(checkNull(mUser.getText())); 636 mServer.setSummary(checkNull(mServer.getText())); 637 mPassword.setSummary(starify(mPassword.getText())); 638 mMmsProxy.setSummary(checkNull(mMmsProxy.getText())); 639 mMmsPort.setSummary(checkNull(mMmsPort.getText())); 640 mMmsc.setSummary(checkNull(mMmsc.getText())); 641 mMcc.setSummary(formatInteger(checkNull(mMcc.getText()))); 642 mMnc.setSummary(formatInteger(checkNull(mMnc.getText()))); 643 mApnType.setSummary(checkNull(mApnType.getText())); 644 645 final String authVal = mAuthType.getValue(); 646 if (authVal != null) { 647 final int authValIndex = Integer.parseInt(authVal); 648 mAuthType.setValueIndex(authValIndex); 649 650 final String[] values = getResources().getStringArray(R.array.apn_auth_entries); 651 mAuthType.setSummary(values[authValIndex]); 652 } else { 653 mAuthType.setSummary(sNotSet); 654 } 655 656 mProtocol.setSummary(checkNull(protocolDescription(mProtocol.getValue(), mProtocol))); 657 mRoamingProtocol.setSummary( 658 checkNull(protocolDescription(mRoamingProtocol.getValue(), mRoamingProtocol))); 659 mBearerMulti.setSummary( 660 checkNull(bearerMultiDescription(mBearerMulti.getValues()))); 661 mMvnoType.setSummary( 662 checkNull(mvnoDescription(mMvnoType.getValue()))); 663 mMvnoMatchData.setSummary(checkNull(mMvnoMatchData.getText())); 664 // allow user to edit carrier_enabled for some APN 665 final boolean ceEditable = getResources().getBoolean( 666 R.bool.config_allow_edit_carrier_enabled); 667 if (ceEditable) { 668 mCarrierEnabled.setEnabled(true); 669 } else { 670 mCarrierEnabled.setEnabled(false); 671 } 672 } 673 674 /** 675 * Returns the UI choice (e.g., "IPv4/IPv6") corresponding to the given 676 * raw value of the protocol preference (e.g., "IPV4V6"). If unknown, 677 * return null. 678 */ protocolDescription(String raw, ListPreference protocol)679 private String protocolDescription(String raw, ListPreference protocol) { 680 final int protocolIndex = protocol.findIndexOfValue(raw); 681 if (protocolIndex == -1) { 682 return null; 683 } else { 684 final String[] values = getResources().getStringArray(R.array.apn_protocol_entries); 685 try { 686 return values[protocolIndex]; 687 } catch (ArrayIndexOutOfBoundsException e) { 688 return null; 689 } 690 } 691 } 692 bearerMultiDescription(Set<String> raw)693 private String bearerMultiDescription(Set<String> raw) { 694 final String[] values = getResources().getStringArray(R.array.bearer_entries); 695 final StringBuilder retVal = new StringBuilder(); 696 boolean first = true; 697 for (String bearer : raw) { 698 int bearerIndex = mBearerMulti.findIndexOfValue(bearer); 699 try { 700 if (first) { 701 retVal.append(values[bearerIndex]); 702 first = false; 703 } else { 704 retVal.append(", " + values[bearerIndex]); 705 } 706 } catch (ArrayIndexOutOfBoundsException e) { 707 // ignore 708 } 709 } 710 final String val = retVal.toString(); 711 if (!TextUtils.isEmpty(val)) { 712 return val; 713 } 714 return null; 715 } 716 mvnoDescription(String newValue)717 private String mvnoDescription(String newValue) { 718 final int mvnoIndex = mMvnoType.findIndexOfValue(newValue); 719 final String oldValue = mMvnoType.getValue(); 720 721 if (mvnoIndex == -1) { 722 return null; 723 } else { 724 final String[] values = getResources().getStringArray(R.array.mvno_type_entries); 725 final boolean mvnoMatchDataUneditable = 726 mReadOnlyApn || (mReadOnlyApnFields != null 727 && Arrays.asList(mReadOnlyApnFields) 728 .contains(Telephony.Carriers.MVNO_MATCH_DATA)); 729 mMvnoMatchData.setEnabled(!mvnoMatchDataUneditable && mvnoIndex != 0); 730 if (newValue != null && newValue.equals(oldValue) == false) { 731 if (values[mvnoIndex].equals("SPN")) { 732 TelephonyManager telephonyManager = (TelephonyManager) 733 getContext().getSystemService(TelephonyManager.class); 734 final TelephonyManager telephonyManagerForSubId = 735 telephonyManager.createForSubscriptionId(mSubId); 736 if (telephonyManagerForSubId != null) { 737 telephonyManager = telephonyManagerForSubId; 738 } 739 mMvnoMatchData.setText(telephonyManager.getSimOperatorName()); 740 } else if (values[mvnoIndex].equals("IMSI")) { 741 final SubscriptionInfo subInfo = 742 mProxySubscriptionMgr.getAccessibleSubscriptionInfo(mSubId); 743 final String mcc = (subInfo == null) ? "" : 744 Objects.toString(subInfo.getMccString(), ""); 745 final String mnc = (subInfo == null) ? "" : 746 Objects.toString(subInfo.getMncString(), ""); 747 mMvnoMatchData.setText(mcc + mnc + "x"); 748 } else if (values[mvnoIndex].equals("GID")) { 749 TelephonyManager telephonyManager = (TelephonyManager) 750 getContext().getSystemService(TelephonyManager.class); 751 final TelephonyManager telephonyManagerForSubId = 752 telephonyManager.createForSubscriptionId(mSubId); 753 if (telephonyManagerForSubId != null) { 754 telephonyManager = telephonyManagerForSubId; 755 } 756 mMvnoMatchData.setText(telephonyManager.getGroupIdLevel1()); 757 } else { 758 // mvno type 'none' case. At this time, mvnoIndex should be 0. 759 mMvnoMatchData.setText(""); 760 } 761 } 762 763 try { 764 return values[mvnoIndex]; 765 } catch (ArrayIndexOutOfBoundsException e) { 766 return null; 767 } 768 } 769 } 770 onPreferenceChange(Preference preference, Object newValue)771 public boolean onPreferenceChange(Preference preference, Object newValue) { 772 String key = preference.getKey(); 773 if (KEY_AUTH_TYPE.equals(key)) { 774 try { 775 final int index = Integer.parseInt((String) newValue); 776 mAuthType.setValueIndex(index); 777 778 final String[] values = getResources().getStringArray(R.array.apn_auth_entries); 779 mAuthType.setSummary(values[index]); 780 } catch (NumberFormatException e) { 781 return false; 782 } 783 } else if (KEY_PROTOCOL.equals(key)) { 784 final String protocol = protocolDescription((String) newValue, mProtocol); 785 if (protocol == null) { 786 return false; 787 } 788 mProtocol.setSummary(protocol); 789 mProtocol.setValue((String) newValue); 790 } else if (KEY_ROAMING_PROTOCOL.equals(key)) { 791 final String protocol = protocolDescription((String) newValue, mRoamingProtocol); 792 if (protocol == null) { 793 return false; 794 } 795 mRoamingProtocol.setSummary(protocol); 796 mRoamingProtocol.setValue((String) newValue); 797 } else if (KEY_BEARER_MULTI.equals(key)) { 798 final String bearer = bearerMultiDescription((Set<String>) newValue); 799 if (bearer == null) { 800 return false; 801 } 802 mBearerMulti.setValues((Set<String>) newValue); 803 mBearerMulti.setSummary(bearer); 804 } else if (KEY_MVNO_TYPE.equals(key)) { 805 final String mvno = mvnoDescription((String) newValue); 806 if (mvno == null) { 807 return false; 808 } 809 mMvnoType.setValue((String) newValue); 810 mMvnoType.setSummary(mvno); 811 mMvnoMatchData.setSummary(checkNull(mMvnoMatchData.getText())); 812 } else if (KEY_PASSWORD.equals(key)) { 813 mPassword.setSummary(starify(newValue != null ? String.valueOf(newValue) : "")); 814 } else if (KEY_CARRIER_ENABLED.equals(key)) { 815 // do nothing 816 } else { 817 preference.setSummary(checkNull(newValue != null ? String.valueOf(newValue) : null)); 818 } 819 820 return true; 821 } 822 823 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)824 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 825 super.onCreateOptionsMenu(menu, inflater); 826 // If it's a new APN, then cancel will delete the new entry in onPause 827 if (!mNewApn && !mReadOnlyApn) { 828 menu.add(0, MENU_DELETE, 0, R.string.menu_delete) 829 .setIcon(R.drawable.ic_delete); 830 } 831 menu.add(0, MENU_SAVE, 0, R.string.menu_save) 832 .setIcon(android.R.drawable.ic_menu_save); 833 menu.add(0, MENU_CANCEL, 0, R.string.menu_cancel) 834 .setIcon(android.R.drawable.ic_menu_close_clear_cancel); 835 } 836 837 @Override onOptionsItemSelected(MenuItem item)838 public boolean onOptionsItemSelected(MenuItem item) { 839 switch (item.getItemId()) { 840 case MENU_DELETE: 841 deleteApn(); 842 finish(); 843 return true; 844 case MENU_SAVE: 845 if (validateAndSaveApnData()) { 846 finish(); 847 } 848 return true; 849 case MENU_CANCEL: 850 finish(); 851 return true; 852 default: 853 return super.onOptionsItemSelected(item); 854 } 855 } 856 857 @Override onViewCreated(View view, Bundle savedInstanceState)858 public void onViewCreated(View view, Bundle savedInstanceState) { 859 super.onViewCreated(view, savedInstanceState); 860 view.setOnKeyListener(this); 861 view.setFocusableInTouchMode(true); 862 view.requestFocus(); 863 } 864 865 /** 866 * Try to save the apn data when pressed the back button. An error message will be displayed if 867 * the apn data is invalid. 868 * 869 * TODO(b/77339593): Try to keep the same behavior between back button and up navigate button. 870 * We will save the valid apn data to the database when pressed the back button, but discard all 871 * user changed when pressed the up navigate button. 872 */ 873 @Override onKey(View v, int keyCode, KeyEvent event)874 public boolean onKey(View v, int keyCode, KeyEvent event) { 875 if (event.getAction() != KeyEvent.ACTION_DOWN) return false; 876 switch (keyCode) { 877 case KeyEvent.KEYCODE_BACK: { 878 if (validateAndSaveApnData()) { 879 finish(); 880 } 881 return true; 882 } 883 } 884 return false; 885 } 886 887 /** 888 * Add key, value to {@code cv} and compare the value against the value at index in 889 * {@link #mApnData}. 890 * 891 * <p> 892 * The key, value will not add to {@code cv} if value is null. 893 * 894 * @return true if values are different. {@code assumeDiff} indicates if values can be assumed 895 * different in which case no comparison is needed. 896 */ setStringValueAndCheckIfDiff( ContentValues cv, String key, String value, boolean assumeDiff, int index)897 boolean setStringValueAndCheckIfDiff( 898 ContentValues cv, String key, String value, boolean assumeDiff, int index) { 899 final String valueFromLocalCache = mApnData.getString(index); 900 if (VDBG) { 901 Log.d(TAG, "setStringValueAndCheckIfDiff: assumeDiff: " + assumeDiff 902 + " key: " + key 903 + " value: '" + value 904 + "' valueFromDb: '" + valueFromLocalCache + "'"); 905 } 906 final boolean isDiff = assumeDiff 907 || !((TextUtils.isEmpty(value) && TextUtils.isEmpty(valueFromLocalCache)) 908 || (value != null && value.equals(valueFromLocalCache))); 909 910 if (isDiff && value != null) { 911 cv.put(key, value); 912 } 913 return isDiff; 914 } 915 916 /** 917 * Add key, value to {@code cv} and compare the value against the value at index in 918 * {@link #mApnData}. 919 * 920 * @return true if values are different. {@code assumeDiff} indicates if values can be assumed 921 * different in which case no comparison is needed. 922 */ setIntValueAndCheckIfDiff( ContentValues cv, String key, int value, boolean assumeDiff, int index)923 boolean setIntValueAndCheckIfDiff( 924 ContentValues cv, String key, int value, boolean assumeDiff, int index) { 925 final Integer valueFromLocalCache = mApnData.getInteger(index); 926 if (VDBG) { 927 Log.d(TAG, "setIntValueAndCheckIfDiff: assumeDiff: " + assumeDiff 928 + " key: " + key 929 + " value: '" + value 930 + "' valueFromDb: '" + valueFromLocalCache + "'"); 931 } 932 933 final boolean isDiff = assumeDiff || value != valueFromLocalCache; 934 if (isDiff) { 935 cv.put(key, value); 936 } 937 return isDiff; 938 } 939 940 /** 941 * Validates the apn data and save it to the database if it's valid. 942 * 943 * <p> 944 * A dialog with error message will be displayed if the APN data is invalid. 945 * 946 * @return true if there is no error 947 */ 948 @VisibleForTesting validateAndSaveApnData()949 boolean validateAndSaveApnData() { 950 // Nothing to do if it's a read only APN 951 if (mReadOnlyApn) { 952 return true; 953 } 954 955 final String name = checkNotSet(mName.getText()); 956 final String apn = checkNotSet(mApn.getText()); 957 final String mcc = checkNotSet(mMcc.getText()); 958 final String mnc = checkNotSet(mMnc.getText()); 959 960 final String errorMsg = validateApnData(); 961 if (errorMsg != null) { 962 showError(); 963 return false; 964 } 965 966 final ContentValues values = new ContentValues(); 967 // call update() if it's a new APN. If not, check if any field differs from the db value; 968 // if any diff is found update() should be called 969 boolean callUpdate = mNewApn; 970 callUpdate = setStringValueAndCheckIfDiff(values, 971 Telephony.Carriers.NAME, 972 name, 973 callUpdate, 974 NAME_INDEX); 975 976 callUpdate = setStringValueAndCheckIfDiff(values, 977 Telephony.Carriers.APN, 978 apn, 979 callUpdate, 980 APN_INDEX); 981 982 callUpdate = setStringValueAndCheckIfDiff(values, 983 Telephony.Carriers.PROXY, 984 checkNotSet(mProxy.getText()), 985 callUpdate, 986 PROXY_INDEX); 987 988 callUpdate = setStringValueAndCheckIfDiff(values, 989 Telephony.Carriers.PORT, 990 checkNotSet(mPort.getText()), 991 callUpdate, 992 PORT_INDEX); 993 994 callUpdate = setStringValueAndCheckIfDiff(values, 995 Telephony.Carriers.MMSPROXY, 996 checkNotSet(mMmsProxy.getText()), 997 callUpdate, 998 MMSPROXY_INDEX); 999 1000 callUpdate = setStringValueAndCheckIfDiff(values, 1001 Telephony.Carriers.MMSPORT, 1002 checkNotSet(mMmsPort.getText()), 1003 callUpdate, 1004 MMSPORT_INDEX); 1005 1006 callUpdate = setStringValueAndCheckIfDiff(values, 1007 Telephony.Carriers.USER, 1008 checkNotSet(mUser.getText()), 1009 callUpdate, 1010 USER_INDEX); 1011 1012 callUpdate = setStringValueAndCheckIfDiff(values, 1013 Telephony.Carriers.SERVER, 1014 checkNotSet(mServer.getText()), 1015 callUpdate, 1016 SERVER_INDEX); 1017 1018 callUpdate = setStringValueAndCheckIfDiff(values, 1019 Telephony.Carriers.PASSWORD, 1020 checkNotSet(mPassword.getText()), 1021 callUpdate, 1022 PASSWORD_INDEX); 1023 1024 callUpdate = setStringValueAndCheckIfDiff(values, 1025 Telephony.Carriers.MMSC, 1026 checkNotSet(mMmsc.getText()), 1027 callUpdate, 1028 MMSC_INDEX); 1029 1030 final String authVal = mAuthType.getValue(); 1031 if (authVal != null) { 1032 callUpdate = setIntValueAndCheckIfDiff(values, 1033 Telephony.Carriers.AUTH_TYPE, 1034 Integer.parseInt(authVal), 1035 callUpdate, 1036 AUTH_TYPE_INDEX); 1037 } 1038 1039 callUpdate = setStringValueAndCheckIfDiff(values, 1040 Telephony.Carriers.PROTOCOL, 1041 checkNotSet(mProtocol.getValue()), 1042 callUpdate, 1043 PROTOCOL_INDEX); 1044 1045 callUpdate = setStringValueAndCheckIfDiff(values, 1046 Telephony.Carriers.ROAMING_PROTOCOL, 1047 checkNotSet(mRoamingProtocol.getValue()), 1048 callUpdate, 1049 ROAMING_PROTOCOL_INDEX); 1050 1051 callUpdate = setStringValueAndCheckIfDiff(values, 1052 Telephony.Carriers.TYPE, 1053 checkNotSet(getUserEnteredApnType()), 1054 callUpdate, 1055 TYPE_INDEX); 1056 1057 callUpdate = setStringValueAndCheckIfDiff(values, 1058 Telephony.Carriers.MCC, 1059 mcc, 1060 callUpdate, 1061 MCC_INDEX); 1062 1063 callUpdate = setStringValueAndCheckIfDiff(values, 1064 Telephony.Carriers.MNC, 1065 mnc, 1066 callUpdate, 1067 MNC_INDEX); 1068 1069 values.put(Telephony.Carriers.NUMERIC, mcc + mnc); 1070 1071 if (mCurMnc != null && mCurMcc != null) { 1072 if (mCurMnc.equals(mnc) && mCurMcc.equals(mcc)) { 1073 values.put(Telephony.Carriers.CURRENT, 1); 1074 } 1075 } 1076 1077 final Set<String> bearerSet = mBearerMulti.getValues(); 1078 int bearerBitmask = 0; 1079 for (String bearer : bearerSet) { 1080 if (Integer.parseInt(bearer) == 0) { 1081 bearerBitmask = 0; 1082 break; 1083 } else { 1084 bearerBitmask |= getBitmaskForTech(Integer.parseInt(bearer)); 1085 } 1086 } 1087 callUpdate = setIntValueAndCheckIfDiff(values, 1088 Telephony.Carriers.BEARER_BITMASK, 1089 bearerBitmask, 1090 callUpdate, 1091 BEARER_BITMASK_INDEX); 1092 1093 int bearerVal; 1094 if (bearerBitmask == 0 || mBearerInitialVal == 0) { 1095 bearerVal = 0; 1096 } else if (bitmaskHasTech(bearerBitmask, mBearerInitialVal)) { 1097 bearerVal = mBearerInitialVal; 1098 } else { 1099 // bearer field was being used but bitmask has changed now and does not include the 1100 // initial bearer value -- setting bearer to 0 but maybe better behavior is to choose a 1101 // random tech from the new bitmask?? 1102 bearerVal = 0; 1103 } 1104 callUpdate = setIntValueAndCheckIfDiff(values, 1105 Telephony.Carriers.BEARER, 1106 bearerVal, 1107 callUpdate, 1108 BEARER_INDEX); 1109 1110 callUpdate = setStringValueAndCheckIfDiff(values, 1111 Telephony.Carriers.MVNO_TYPE, 1112 checkNotSet(mMvnoType.getValue()), 1113 callUpdate, 1114 MVNO_TYPE_INDEX); 1115 1116 callUpdate = setStringValueAndCheckIfDiff(values, 1117 Telephony.Carriers.MVNO_MATCH_DATA, 1118 checkNotSet(mMvnoMatchData.getText()), 1119 callUpdate, 1120 MVNO_MATCH_DATA_INDEX); 1121 1122 callUpdate = setIntValueAndCheckIfDiff(values, 1123 Telephony.Carriers.CARRIER_ENABLED, 1124 mCarrierEnabled.isChecked() ? 1 : 0, 1125 callUpdate, 1126 CARRIER_ENABLED_INDEX); 1127 1128 values.put(Telephony.Carriers.EDITED_STATUS, Telephony.Carriers.USER_EDITED); 1129 1130 if (callUpdate) { 1131 final Uri uri = mApnData.getUri() == null ? mCarrierUri : mApnData.getUri(); 1132 updateApnDataToDatabase(uri, values); 1133 } else { 1134 if (VDBG) Log.d(TAG, "validateAndSaveApnData: not calling update()"); 1135 } 1136 1137 return true; 1138 } 1139 updateApnDataToDatabase(Uri uri, ContentValues values)1140 private void updateApnDataToDatabase(Uri uri, ContentValues values) { 1141 ThreadUtils.postOnBackgroundThread(() -> { 1142 if (uri.equals(mCarrierUri)) { 1143 // Add a new apn to the database 1144 final Uri newUri = getContentResolver().insert(mCarrierUri, values); 1145 if (newUri == null) { 1146 Log.e(TAG, "Can't add a new apn to database " + mCarrierUri); 1147 } 1148 } else { 1149 // Update the existing apn 1150 getContentResolver().update( 1151 uri, values, null /* where */, null /* selection Args */); 1152 } 1153 }); 1154 } 1155 1156 /** 1157 * Validates whether the apn data is valid. 1158 * 1159 * @return An error message if the apn data is invalid, otherwise return null. 1160 */ 1161 @VisibleForTesting validateApnData()1162 String validateApnData() { 1163 String errorMsg = null; 1164 1165 final String name = checkNotSet(mName.getText()); 1166 final String apn = checkNotSet(mApn.getText()); 1167 final String mcc = checkNotSet(mMcc.getText()); 1168 final String mnc = checkNotSet(mMnc.getText()); 1169 1170 if (TextUtils.isEmpty(name)) { 1171 errorMsg = getResources().getString(R.string.error_name_empty); 1172 } else if (TextUtils.isEmpty(apn)) { 1173 errorMsg = getResources().getString(R.string.error_apn_empty); 1174 } else if (mcc == null || mcc.length() != 3) { 1175 errorMsg = getResources().getString(R.string.error_mcc_not3); 1176 } else if ((mnc == null || (mnc.length() & 0xFFFE) != 2)) { 1177 errorMsg = getResources().getString(R.string.error_mnc_not23); 1178 } 1179 1180 if (errorMsg == null) { 1181 // if carrier does not allow editing certain apn types, make sure type does not include 1182 // those 1183 if (!ArrayUtils.isEmpty(mReadOnlyApnTypes) 1184 && apnTypesMatch(mReadOnlyApnTypes, getUserEnteredApnType())) { 1185 final StringBuilder stringBuilder = new StringBuilder(); 1186 for (String type : mReadOnlyApnTypes) { 1187 stringBuilder.append(type).append(", "); 1188 Log.d(TAG, "validateApnData: appending type: " + type); 1189 } 1190 // remove last ", " 1191 if (stringBuilder.length() >= 2) { 1192 stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length()); 1193 } 1194 errorMsg = String.format(getResources().getString(R.string.error_adding_apn_type), 1195 stringBuilder); 1196 } 1197 } 1198 1199 return errorMsg; 1200 } 1201 1202 @VisibleForTesting showError()1203 void showError() { 1204 ErrorDialog.showError(this); 1205 } 1206 deleteApn()1207 private void deleteApn() { 1208 if (mApnData.getUri() != null) { 1209 getContentResolver().delete(mApnData.getUri(), null, null); 1210 mApnData = new ApnData(sProjection.length); 1211 } 1212 } 1213 starify(String value)1214 private String starify(String value) { 1215 if (value == null || value.length() == 0) { 1216 return sNotSet; 1217 } else { 1218 final char[] password = new char[value.length()]; 1219 for (int i = 0; i < password.length; i++) { 1220 password[i] = '*'; 1221 } 1222 return new String(password); 1223 } 1224 } 1225 1226 /** 1227 * Returns {@link #sNotSet} if the given string {@code value} is null or empty. The string 1228 * {@link #sNotSet} typically used as the default display when an entry in the preference is 1229 * null or empty. 1230 */ checkNull(String value)1231 private String checkNull(String value) { 1232 return TextUtils.isEmpty(value) ? sNotSet : value; 1233 } 1234 1235 /** 1236 * Returns null if the given string {@code value} equals to {@link #sNotSet}. This method 1237 * should be used when convert a string value from preference to database. 1238 */ checkNotSet(String value)1239 private String checkNotSet(String value) { 1240 return sNotSet.equals(value) ? null : value; 1241 } 1242 1243 @VisibleForTesting getUserEnteredApnType()1244 String getUserEnteredApnType() { 1245 // if user has not specified a type, map it to "ALL APN TYPES THAT ARE NOT READ-ONLY" 1246 // but if user enter empty type, map it just for default 1247 String userEnteredApnType = mApnType.getText(); 1248 if (userEnteredApnType != null) userEnteredApnType = userEnteredApnType.trim(); 1249 if ((TextUtils.isEmpty(userEnteredApnType) 1250 || APN_TYPE_ALL.equals(userEnteredApnType)) 1251 && !ArrayUtils.isEmpty(mReadOnlyApnTypes)) { 1252 String[] apnTypeList = APN_TYPES; 1253 if (TextUtils.isEmpty(userEnteredApnType) && !ArrayUtils.isEmpty(mDefaultApnTypes)) { 1254 apnTypeList = mDefaultApnTypes; 1255 } 1256 1257 final StringBuilder editableApnTypes = new StringBuilder(); 1258 final List<String> readOnlyApnTypes = Arrays.asList(mReadOnlyApnTypes); 1259 boolean first = true; 1260 for (String apnType : apnTypeList) { 1261 // add APN type if it is not read-only and is not wild-cardable 1262 if (!readOnlyApnTypes.contains(apnType) 1263 && !apnType.equals(APN_TYPE_IA) 1264 && !apnType.equals(APN_TYPE_EMERGENCY) 1265 && !apnType.equals(APN_TYPE_MCX)) { 1266 if (first) { 1267 first = false; 1268 } else { 1269 editableApnTypes.append(","); 1270 } 1271 editableApnTypes.append(apnType); 1272 } 1273 } 1274 userEnteredApnType = editableApnTypes.toString(); 1275 Log.d(TAG, "getUserEnteredApnType: changed apn type to editable apn types: " 1276 + userEnteredApnType); 1277 } 1278 1279 return userEnteredApnType; 1280 } 1281 1282 public static class ErrorDialog extends InstrumentedDialogFragment { 1283 showError(ApnEditor editor)1284 public static void showError(ApnEditor editor) { 1285 final ErrorDialog dialog = new ErrorDialog(); 1286 dialog.setTargetFragment(editor, 0); 1287 dialog.show(editor.getFragmentManager(), "error"); 1288 } 1289 1290 @Override onCreateDialog(Bundle savedInstanceState)1291 public Dialog onCreateDialog(Bundle savedInstanceState) { 1292 final String msg = ((ApnEditor) getTargetFragment()).validateApnData(); 1293 1294 return new AlertDialog.Builder(getContext()) 1295 .setTitle(R.string.error_title) 1296 .setPositiveButton(android.R.string.ok, null) 1297 .setMessage(msg) 1298 .create(); 1299 } 1300 1301 @Override getMetricsCategory()1302 public int getMetricsCategory() { 1303 return SettingsEnums.DIALOG_APN_EDITOR_ERROR; 1304 } 1305 } 1306 1307 @VisibleForTesting getApnDataFromUri(Uri uri)1308 ApnData getApnDataFromUri(Uri uri) { 1309 ApnData apnData = null; 1310 try (Cursor cursor = getContentResolver().query( 1311 uri, 1312 sProjection, 1313 null /* selection */, 1314 null /* selectionArgs */, 1315 null /* sortOrder */)) { 1316 if (cursor != null) { 1317 cursor.moveToFirst(); 1318 apnData = new ApnData(uri, cursor); 1319 } 1320 } 1321 1322 if (apnData == null) { 1323 Log.d(TAG, "Can't get apnData from Uri " + uri); 1324 } 1325 1326 return apnData; 1327 } 1328 1329 @VisibleForTesting 1330 static class ApnData { 1331 /** 1332 * The uri correspond to a database row of the apn data. This should be null if the apn 1333 * is not in the database. 1334 */ 1335 Uri mUri; 1336 1337 /** Each element correspond to a column of the database row. */ 1338 Object[] mData; 1339 ApnData(int numberOfField)1340 ApnData(int numberOfField) { 1341 mData = new Object[numberOfField]; 1342 } 1343 ApnData(Uri uri, Cursor cursor)1344 ApnData(Uri uri, Cursor cursor) { 1345 mUri = uri; 1346 mData = new Object[cursor.getColumnCount()]; 1347 for (int i = 0; i < mData.length; i++) { 1348 switch (cursor.getType(i)) { 1349 case Cursor.FIELD_TYPE_FLOAT: 1350 mData[i] = cursor.getFloat(i); 1351 break; 1352 case Cursor.FIELD_TYPE_INTEGER: 1353 mData[i] = cursor.getInt(i); 1354 break; 1355 case Cursor.FIELD_TYPE_STRING: 1356 mData[i] = cursor.getString(i); 1357 break; 1358 case Cursor.FIELD_TYPE_BLOB: 1359 mData[i] = cursor.getBlob(i); 1360 break; 1361 default: 1362 mData[i] = null; 1363 } 1364 } 1365 } 1366 getUri()1367 Uri getUri() { 1368 return mUri; 1369 } 1370 setUri(Uri uri)1371 void setUri(Uri uri) { 1372 mUri = uri; 1373 } 1374 getInteger(int index)1375 Integer getInteger(int index) { 1376 return (Integer) mData[index]; 1377 } 1378 getInteger(int index, Integer defaultValue)1379 Integer getInteger(int index, Integer defaultValue) { 1380 final Integer val = getInteger(index); 1381 return val == null ? defaultValue : val; 1382 } 1383 getString(int index)1384 String getString(int index) { 1385 return (String) mData[index]; 1386 } 1387 } 1388 getBitmaskForTech(int radioTech)1389 private static int getBitmaskForTech(int radioTech) { 1390 if (radioTech >= 1) { 1391 return (1 << (radioTech - 1)); 1392 } 1393 return 0; 1394 } 1395 bitmaskHasTech(int bearerBitmask, int radioTech)1396 private static boolean bitmaskHasTech(int bearerBitmask, int radioTech) { 1397 if (bearerBitmask == 0) { 1398 return true; 1399 } else if (radioTech >= 1) { 1400 return ((bearerBitmask & (1 << (radioTech - 1))) != 0); 1401 } 1402 return false; 1403 } 1404 } 1405