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