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