1 /* 2 * Copyright 2017 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 package com.android.internal.telephony; 17 18 import static android.provider.Telephony.CarrierId; 19 import static android.provider.Telephony.Carriers.CONTENT_URI; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.database.ContentObserver; 27 import android.database.Cursor; 28 import android.net.Uri; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.provider.Telephony; 32 import android.service.carrier.CarrierIdentifier; 33 import android.telephony.PhoneStateListener; 34 import android.telephony.SubscriptionManager; 35 import android.telephony.TelephonyManager; 36 import android.text.TextUtils; 37 import android.util.LocalLog; 38 import android.util.Log; 39 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.telephony.metrics.TelephonyMetrics; 42 import com.android.internal.telephony.uicc.IccRecords; 43 import com.android.internal.telephony.uicc.UiccController; 44 import com.android.internal.util.IndentingPrintWriter; 45 import com.android.telephony.Rlog; 46 47 import java.io.FileDescriptor; 48 import java.io.PrintWriter; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.List; 52 53 /** 54 * CarrierResolver identifies the subscription carrier and returns a canonical carrier Id 55 * and a user friendly carrier name. CarrierResolver reads subscription info and check against 56 * all carrier matching rules stored in CarrierIdProvider. It is msim aware, each phone has a 57 * dedicated CarrierResolver. 58 */ 59 public class CarrierResolver extends Handler { 60 private static final String LOG_TAG = CarrierResolver.class.getSimpleName(); 61 private static final boolean DBG = true; 62 private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE); 63 64 // events to trigger carrier identification 65 private static final int SIM_LOAD_EVENT = 1; 66 private static final int ICC_CHANGED_EVENT = 2; 67 private static final int PREFER_APN_UPDATE_EVENT = 3; 68 private static final int CARRIER_ID_DB_UPDATE_EVENT = 4; 69 70 private static final Uri CONTENT_URL_PREFER_APN = Uri.withAppendedPath( 71 Telephony.Carriers.CONTENT_URI, "preferapn"); 72 73 // cached matching rules based mccmnc to speed up resolution 74 private List<CarrierMatchingRule> mCarrierMatchingRulesOnMccMnc = new ArrayList<>(); 75 // cached carrier Id 76 private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; 77 // cached specific carrier Id 78 private int mSpecificCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; 79 // cached MNO carrier Id. mno carrier shares the same mccmnc as cid and can be solely 80 // identified by mccmnc only. If there is no such mno carrier, mno carrier id equals to 81 // the cid. 82 private int mMnoCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; 83 // cached carrier name 84 private String mCarrierName; 85 private String mSpecificCarrierName; 86 // cached preferapn name 87 private String mPreferApn; 88 // override for testing purpose 89 private String mTestOverrideApn; 90 private String mTestOverrideCarrierPriviledgeRule; 91 // cached service provider name. telephonyManager API returns empty string as default value. 92 // some carriers need to target devices with Empty SPN. In that case, carrier matching rule 93 // should specify "" spn explicitly. 94 private String mSpn = ""; 95 96 private Context mContext; 97 private Phone mPhone; 98 private IccRecords mIccRecords; 99 private final LocalLog mCarrierIdLocalLog = new LocalLog(20); 100 private final TelephonyManager mTelephonyMgr; 101 102 private final ContentObserver mContentObserver = new ContentObserver(this) { 103 @Override 104 public void onChange(boolean selfChange, Uri uri) { 105 if (Telephony.Carriers.CONTENT_URI.equals(uri)) { 106 logd("onChange URI: " + uri); 107 sendEmptyMessage(PREFER_APN_UPDATE_EVENT); 108 } else if (CarrierId.All.CONTENT_URI.equals(uri)) { 109 logd("onChange URI: " + uri); 110 sendEmptyMessage(CARRIER_ID_DB_UPDATE_EVENT); 111 } 112 } 113 }; 114 CarrierResolver(Phone phone)115 public CarrierResolver(Phone phone) { 116 logd("Creating CarrierResolver[" + phone.getPhoneId() + "]"); 117 mContext = phone.getContext(); 118 mPhone = phone; 119 mTelephonyMgr = TelephonyManager.from(mContext); 120 121 // register events 122 mContext.getContentResolver().registerContentObserver(CONTENT_URL_PREFER_APN, false, 123 mContentObserver); 124 mContext.getContentResolver().registerContentObserver( 125 CarrierId.All.CONTENT_URI, false, mContentObserver); 126 UiccController.getInstance().registerForIccChanged(this, ICC_CHANGED_EVENT, null); 127 } 128 129 /** 130 * This is triggered from SubscriptionInfoUpdater after sim state change. 131 * The sequence of sim loading would be 132 * 1. ACTION_SUBINFO_CONTENT_CHANGE 133 * 2. ACTION_SIM_STATE_CHANGED/ACTION_SIM_CARD_STATE_CHANGED 134 * /ACTION_SIM_APPLICATION_STATE_CHANGED 135 * 3. ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED 136 * 137 * For SIM refresh either reset or init refresh type, SubscriptionInfoUpdater will re-trigger 138 * carrier identification with sim loaded state. Framework today silently handle single file 139 * refresh type. 140 * TODO: check fileId from single file refresh, if the refresh file is IMSI, gid1 or other 141 * records which might change carrier id, framework should trigger sim loaded state just like 142 * other refresh events: INIT or RESET and which will ultimately trigger carrier 143 * re-identification. 144 */ resolveSubscriptionCarrierId(String simState)145 public void resolveSubscriptionCarrierId(String simState) { 146 logd("[resolveSubscriptionCarrierId] simState: " + simState); 147 switch (simState) { 148 case IccCardConstants.INTENT_VALUE_ICC_ABSENT: 149 case IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR: 150 // only clear carrier id on absent to avoid transition to unknown carrier id during 151 // intermediate states of sim refresh 152 handleSimAbsent(); 153 break; 154 case IccCardConstants.INTENT_VALUE_ICC_LOADED: 155 handleSimLoaded(); 156 break; 157 } 158 } 159 handleSimLoaded()160 private void handleSimLoaded() { 161 if (mIccRecords != null) { 162 /** 163 * returns empty string to be consistent with 164 * {@link TelephonyManager#getSimOperatorName()} 165 */ 166 mSpn = (mIccRecords.getServiceProviderName() == null) ? "" 167 : mIccRecords.getServiceProviderName(); 168 } else { 169 loge("mIccRecords is null on SIM_LOAD_EVENT, could not get SPN"); 170 } 171 mPreferApn = getPreferApn(); 172 loadCarrierMatchingRulesOnMccMnc(); 173 } 174 handleSimAbsent()175 private void handleSimAbsent() { 176 mCarrierMatchingRulesOnMccMnc.clear(); 177 mSpn = null; 178 mPreferApn = null; 179 updateCarrierIdAndName(TelephonyManager.UNKNOWN_CARRIER_ID, null, 180 TelephonyManager.UNKNOWN_CARRIER_ID, null, 181 TelephonyManager.UNKNOWN_CARRIER_ID); 182 } 183 184 private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 185 @Override 186 public void onCallStateChanged(int state, String ignored) { 187 } 188 }; 189 190 /** 191 * Entry point for the carrier identification. 192 * 193 * 1. SIM_LOAD_EVENT 194 * This indicates that all SIM records has been loaded and its first entry point for the 195 * carrier identification. Note, there are other attributes could be changed on the fly 196 * like APN. We cached all carrier matching rules based on MCCMNC to speed 197 * up carrier resolution on following trigger events. 198 * 199 * 2. PREFER_APN_UPDATE_EVENT 200 * This indicates prefer apn has been changed. It could be triggered when user modified 201 * APN settings or when default data connection first establishes on the current carrier. 202 * We follow up on this by querying prefer apn sqlite and re-issue carrier identification 203 * with the updated prefer apn name. 204 * 205 * 3. CARRIER_ID_DB_UPDATE_EVENT 206 * This indicates that carrierIdentification database which stores all matching rules 207 * has been updated. It could be triggered from OTA or assets update. 208 */ 209 @Override handleMessage(Message msg)210 public void handleMessage(Message msg) { 211 if (DBG) logd("handleMessage: " + msg.what); 212 switch (msg.what) { 213 case SIM_LOAD_EVENT: 214 handleSimLoaded(); 215 break; 216 case CARRIER_ID_DB_UPDATE_EVENT: 217 loadCarrierMatchingRulesOnMccMnc(); 218 break; 219 case PREFER_APN_UPDATE_EVENT: 220 String preferApn = getPreferApn(); 221 if (!equals(mPreferApn, preferApn, true)) { 222 logd("[updatePreferApn] from:" + mPreferApn + " to:" + preferApn); 223 mPreferApn = preferApn; 224 matchSubscriptionCarrier(); 225 } 226 break; 227 case ICC_CHANGED_EVENT: 228 // all records used for carrier identification are from SimRecord. 229 final IccRecords newIccRecords = UiccController.getInstance().getIccRecords( 230 mPhone.getPhoneId(), UiccController.APP_FAM_3GPP); 231 if (mIccRecords != newIccRecords) { 232 if (mIccRecords != null) { 233 logd("Removing stale icc objects."); 234 mIccRecords.unregisterForRecordsOverride(this); 235 mIccRecords = null; 236 } 237 if (newIccRecords != null) { 238 logd("new Icc object"); 239 newIccRecords.registerForRecordsOverride(this, SIM_LOAD_EVENT, null); 240 mIccRecords = newIccRecords; 241 } 242 } 243 break; 244 default: 245 loge("invalid msg: " + msg.what); 246 break; 247 } 248 } 249 loadCarrierMatchingRulesOnMccMnc()250 private void loadCarrierMatchingRulesOnMccMnc() { 251 try { 252 String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId()); 253 Cursor cursor = mContext.getContentResolver().query( 254 CarrierId.All.CONTENT_URI, 255 /* projection */ null, 256 /* selection */ CarrierId.All.MCCMNC + "=?", 257 /* selectionArgs */ new String[]{mccmnc}, null); 258 try { 259 if (cursor != null) { 260 if (VDBG) { 261 logd("[loadCarrierMatchingRules]- " + cursor.getCount() 262 + " Records(s) in DB" + " mccmnc: " + mccmnc); 263 } 264 mCarrierMatchingRulesOnMccMnc.clear(); 265 while (cursor.moveToNext()) { 266 mCarrierMatchingRulesOnMccMnc.add(makeCarrierMatchingRule(cursor)); 267 } 268 matchSubscriptionCarrier(); 269 } 270 } finally { 271 if (cursor != null) { 272 cursor.close(); 273 } 274 } 275 } catch (Exception ex) { 276 loge("[loadCarrierMatchingRules]- ex: " + ex); 277 } 278 } 279 getCarrierNameFromId(int cid)280 private String getCarrierNameFromId(int cid) { 281 try { 282 Cursor cursor = mContext.getContentResolver().query( 283 CarrierId.All.CONTENT_URI, 284 /* projection */ null, 285 /* selection */ CarrierId.CARRIER_ID + "=?", 286 /* selectionArgs */ new String[]{cid + ""}, null); 287 try { 288 if (cursor != null) { 289 if (VDBG) { 290 logd("[getCarrierNameFromId]- " + cursor.getCount() 291 + " Records(s) in DB" + " cid: " + cid); 292 } 293 while (cursor.moveToNext()) { 294 return cursor.getString(cursor.getColumnIndex(CarrierId.CARRIER_NAME)); 295 } 296 } 297 } finally { 298 if (cursor != null) { 299 cursor.close(); 300 } 301 } 302 } catch (Exception ex) { 303 loge("[getCarrierNameFromId]- ex: " + ex); 304 } 305 return null; 306 } 307 getCarrierMatchingRulesFromMccMnc( @onNull Context context, String mccmnc)308 private static List<CarrierMatchingRule> getCarrierMatchingRulesFromMccMnc( 309 @NonNull Context context, String mccmnc) { 310 List<CarrierMatchingRule> rules = new ArrayList<>(); 311 try { 312 Cursor cursor = context.getContentResolver().query( 313 CarrierId.All.CONTENT_URI, 314 /* projection */ null, 315 /* selection */ CarrierId.All.MCCMNC + "=?", 316 /* selectionArgs */ new String[]{mccmnc}, null); 317 try { 318 if (cursor != null) { 319 if (VDBG) { 320 logd("[loadCarrierMatchingRules]- " + cursor.getCount() 321 + " Records(s) in DB" + " mccmnc: " + mccmnc); 322 } 323 rules.clear(); 324 while (cursor.moveToNext()) { 325 rules.add(makeCarrierMatchingRule(cursor)); 326 } 327 } 328 } finally { 329 if (cursor != null) { 330 cursor.close(); 331 } 332 } 333 } catch (Exception ex) { 334 loge("[loadCarrierMatchingRules]- ex: " + ex); 335 } 336 return rules; 337 } 338 getPreferApn()339 private String getPreferApn() { 340 // return test overrides if present 341 if (!TextUtils.isEmpty(mTestOverrideApn)) { 342 logd("[getPreferApn]- " + mTestOverrideApn + " test override"); 343 return mTestOverrideApn; 344 } 345 Cursor cursor = mContext.getContentResolver().query( 346 Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "preferapn/subId/" 347 + mPhone.getSubId()), /* projection */ new String[]{Telephony.Carriers.APN}, 348 /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null); 349 try { 350 if (cursor != null) { 351 if (VDBG) { 352 logd("[getPreferApn]- " + cursor.getCount() + " Records(s) in DB"); 353 } 354 while (cursor.moveToNext()) { 355 String apn = cursor.getString(cursor.getColumnIndexOrThrow( 356 Telephony.Carriers.APN)); 357 logd("[getPreferApn]- " + apn); 358 return apn; 359 } 360 } 361 } catch (Exception ex) { 362 loge("[getPreferApn]- exception: " + ex); 363 } finally { 364 if (cursor != null) { 365 cursor.close(); 366 } 367 } 368 return null; 369 } 370 isPreferApnUserEdited(@onNull String preferApn)371 private boolean isPreferApnUserEdited(@NonNull String preferApn) { 372 try (Cursor cursor = mContext.getContentResolver().query( 373 Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, 374 "preferapn/subId/" + mPhone.getSubId()), 375 /* projection */ new String[]{Telephony.Carriers.EDITED_STATUS}, 376 /* selection */ Telephony.Carriers.APN + "=?", 377 /* selectionArgs */ new String[]{preferApn}, /* sortOrder */ null) ) { 378 if (cursor != null && cursor.moveToFirst()) { 379 return cursor.getInt(cursor.getColumnIndexOrThrow( 380 Telephony.Carriers.EDITED_STATUS)) == Telephony.Carriers.USER_EDITED; 381 } 382 } catch (Exception ex) { 383 loge("[isPreferApnUserEdited]- exception: " + ex); 384 } 385 return false; 386 } 387 setTestOverrideApn(String apn)388 public void setTestOverrideApn(String apn) { 389 logd("[setTestOverrideApn]: " + apn); 390 mTestOverrideApn = apn; 391 } 392 setTestOverrideCarrierPriviledgeRule(String rule)393 public void setTestOverrideCarrierPriviledgeRule(String rule) { 394 logd("[setTestOverrideCarrierPriviledgeRule]: " + rule); 395 mTestOverrideCarrierPriviledgeRule = rule; 396 } 397 updateCarrierIdAndName(int cid, String name, int specificCarrierId, String specificCarrierName, int mnoCid)398 private void updateCarrierIdAndName(int cid, String name, 399 int specificCarrierId, String specificCarrierName, 400 int mnoCid) { 401 boolean update = false; 402 if (specificCarrierId != mSpecificCarrierId) { 403 logd("[updateSpecificCarrierId] from:" + mSpecificCarrierId + " to:" 404 + specificCarrierId); 405 mSpecificCarrierId = specificCarrierId; 406 update = true; 407 } 408 if (specificCarrierName != mSpecificCarrierName) { 409 logd("[updateSpecificCarrierName] from:" + mSpecificCarrierName + " to:" 410 + specificCarrierName); 411 mSpecificCarrierName = specificCarrierName; 412 update = true; 413 } 414 if (update) { 415 mCarrierIdLocalLog.log("[updateSpecificCarrierIdAndName] cid:" 416 + mSpecificCarrierId + " name:" + mSpecificCarrierName); 417 final Intent intent = new Intent(TelephonyManager 418 .ACTION_SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED); 419 intent.putExtra(TelephonyManager.EXTRA_SPECIFIC_CARRIER_ID, mSpecificCarrierId); 420 intent.putExtra(TelephonyManager.EXTRA_SPECIFIC_CARRIER_NAME, mSpecificCarrierName); 421 intent.putExtra(TelephonyManager.EXTRA_SUBSCRIPTION_ID, mPhone.getSubId()); 422 mContext.sendBroadcast(intent); 423 424 // notify content observers for specific carrier id change event. 425 ContentValues cv = new ContentValues(); 426 cv.put(CarrierId.SPECIFIC_CARRIER_ID, mSpecificCarrierId); 427 cv.put(CarrierId.SPECIFIC_CARRIER_ID_NAME, mSpecificCarrierName); 428 mContext.getContentResolver().update( 429 Telephony.CarrierId.getSpecificCarrierIdUriForSubscriptionId(mPhone.getSubId()), 430 cv, null, null); 431 } 432 433 update = false; 434 if (!equals(name, mCarrierName, true)) { 435 logd("[updateCarrierName] from:" + mCarrierName + " to:" + name); 436 mCarrierName = name; 437 update = true; 438 } 439 if (cid != mCarrierId) { 440 logd("[updateCarrierId] from:" + mCarrierId + " to:" + cid); 441 mCarrierId = cid; 442 update = true; 443 } 444 if (mnoCid != mMnoCarrierId) { 445 logd("[updateMnoCarrierId] from:" + mMnoCarrierId + " to:" + mnoCid); 446 mMnoCarrierId = mnoCid; 447 update = true; 448 } 449 if (update) { 450 mCarrierIdLocalLog.log("[updateCarrierIdAndName] cid:" + mCarrierId + " name:" 451 + mCarrierName + " mnoCid:" + mMnoCarrierId); 452 final Intent intent = new Intent(TelephonyManager 453 .ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED); 454 intent.putExtra(TelephonyManager.EXTRA_CARRIER_ID, mCarrierId); 455 intent.putExtra(TelephonyManager.EXTRA_CARRIER_NAME, mCarrierName); 456 intent.putExtra(TelephonyManager.EXTRA_SUBSCRIPTION_ID, mPhone.getSubId()); 457 mContext.sendBroadcast(intent); 458 459 // notify content observers for carrier id change event 460 ContentValues cv = new ContentValues(); 461 cv.put(CarrierId.CARRIER_ID, mCarrierId); 462 cv.put(CarrierId.CARRIER_NAME, mCarrierName); 463 mContext.getContentResolver().update( 464 Telephony.CarrierId.getUriForSubscriptionId(mPhone.getSubId()), cv, null, null); 465 } 466 // during esim profile switch, there is no sim absent thus carrier id will persist and 467 // might not trigger an update if switch profiles for the same carrier. thus always update 468 // subscriptioninfo db to make sure we have correct carrier id set. 469 if (SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) { 470 // only persist carrier id to simInfo db when subId is valid. 471 SubscriptionController.getInstance().setCarrierId(mCarrierId, mPhone.getSubId()); 472 } 473 } 474 makeCarrierMatchingRule(Cursor cursor)475 private static CarrierMatchingRule makeCarrierMatchingRule(Cursor cursor) { 476 String certs = cursor.getString( 477 cursor.getColumnIndexOrThrow(CarrierId.All.PRIVILEGE_ACCESS_RULE)); 478 return new CarrierMatchingRule( 479 cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.MCCMNC)), 480 cursor.getString(cursor.getColumnIndexOrThrow( 481 CarrierId.All.IMSI_PREFIX_XPATTERN)), 482 cursor.getString(cursor.getColumnIndexOrThrow( 483 CarrierId.All.ICCID_PREFIX)), 484 cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.GID1)), 485 cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.GID2)), 486 cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.PLMN)), 487 cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.SPN)), 488 cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.APN)), 489 (TextUtils.isEmpty(certs) ? null : new ArrayList<>(Arrays.asList(certs))), 490 cursor.getInt(cursor.getColumnIndexOrThrow(CarrierId.CARRIER_ID)), 491 cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.CARRIER_NAME)), 492 cursor.getInt(cursor.getColumnIndexOrThrow(CarrierId.PARENT_CARRIER_ID))); 493 } 494 495 /** 496 * carrier matching attributes with corresponding cid 497 */ 498 public static class CarrierMatchingRule { 499 /** 500 * These scores provide the hierarchical relationship between the attributes, intended to 501 * resolve conflicts in a deterministic way. The scores are constructed such that a match 502 * from a higher tier will beat any subsequent match which does not match at that tier, 503 * so MCCMNC beats everything else. This avoids problems when two (or more) carriers rule 504 * matches as the score helps to find the best match uniquely. e.g., 505 * rule 1 {mccmnc, imsi} rule 2 {mccmnc, imsi, gid1} and rule 3 {mccmnc, imsi, gid2} all 506 * matches with subscription data. rule 2 wins with the highest matching score. 507 */ 508 private static final int SCORE_MCCMNC = 1 << 8; 509 private static final int SCORE_IMSI_PREFIX = 1 << 7; 510 private static final int SCORE_ICCID_PREFIX = 1 << 6; 511 private static final int SCORE_GID1 = 1 << 5; 512 private static final int SCORE_GID2 = 1 << 4; 513 private static final int SCORE_PLMN = 1 << 3; 514 private static final int SCORE_PRIVILEGE_ACCESS_RULE = 1 << 2; 515 private static final int SCORE_SPN = 1 << 1; 516 private static final int SCORE_APN = 1 << 0; 517 518 private static final int SCORE_INVALID = -1; 519 520 // carrier matching attributes 521 public final String mccMnc; 522 public final String imsiPrefixPattern; 523 public final String iccidPrefix; 524 public final String gid1; 525 public final String gid2; 526 public final String plmn; 527 public final String spn; 528 public final String apn; 529 // there can be multiple certs configured in the UICC 530 public final List<String> privilegeAccessRule; 531 532 // user-facing carrier name 533 private String mName; 534 // unique carrier id 535 private int mCid; 536 // unique parent carrier id 537 private int mParentCid; 538 539 private int mScore = 0; 540 541 @VisibleForTesting CarrierMatchingRule(String mccmnc, String imsiPrefixPattern, String iccidPrefix, String gid1, String gid2, String plmn, String spn, String apn, List<String> privilegeAccessRule, int cid, String name, int parentCid)542 public CarrierMatchingRule(String mccmnc, String imsiPrefixPattern, String iccidPrefix, 543 String gid1, String gid2, String plmn, String spn, String apn, 544 List<String> privilegeAccessRule, int cid, String name, int parentCid) { 545 mccMnc = mccmnc; 546 this.imsiPrefixPattern = imsiPrefixPattern; 547 this.iccidPrefix = iccidPrefix; 548 this.gid1 = gid1; 549 this.gid2 = gid2; 550 this.plmn = plmn; 551 this.spn = spn; 552 this.apn = apn; 553 this.privilegeAccessRule = privilegeAccessRule; 554 mCid = cid; 555 mName = name; 556 mParentCid = parentCid; 557 } 558 CarrierMatchingRule(CarrierMatchingRule rule)559 private CarrierMatchingRule(CarrierMatchingRule rule) { 560 mccMnc = rule.mccMnc; 561 imsiPrefixPattern = rule.imsiPrefixPattern; 562 iccidPrefix = rule.iccidPrefix; 563 gid1 = rule.gid1; 564 gid2 = rule.gid2; 565 plmn = rule.plmn; 566 spn = rule.spn; 567 apn = rule.apn; 568 privilegeAccessRule = rule.privilegeAccessRule; 569 mCid = rule.mCid; 570 mName = rule.mName; 571 mParentCid = rule.mParentCid; 572 } 573 574 // Calculate matching score. Values which aren't set in the rule are considered "wild". 575 // All values in the rule must match in order for the subscription to be considered part of 576 // the carrier. Otherwise, a invalid score -1 will be assigned. A match from a higher tier 577 // will beat any subsequent match which does not match at that tier. When there are multiple 578 // matches at the same tier, the match with highest score will be used. match(CarrierMatchingRule subscriptionRule)579 public void match(CarrierMatchingRule subscriptionRule) { 580 mScore = 0; 581 if (mccMnc != null) { 582 if (!CarrierResolver.equals(subscriptionRule.mccMnc, mccMnc, false)) { 583 mScore = SCORE_INVALID; 584 return; 585 } 586 mScore += SCORE_MCCMNC; 587 } 588 if (imsiPrefixPattern != null) { 589 if (!imsiPrefixMatch(subscriptionRule.imsiPrefixPattern, imsiPrefixPattern)) { 590 mScore = SCORE_INVALID; 591 return; 592 } 593 mScore += SCORE_IMSI_PREFIX; 594 } 595 if (iccidPrefix != null) { 596 if (!iccidPrefixMatch(subscriptionRule.iccidPrefix, iccidPrefix)) { 597 mScore = SCORE_INVALID; 598 return; 599 } 600 mScore += SCORE_ICCID_PREFIX; 601 } 602 if (gid1 != null) { 603 if (!gidMatch(subscriptionRule.gid1, gid1)) { 604 mScore = SCORE_INVALID; 605 return; 606 } 607 mScore += SCORE_GID1; 608 } 609 if (gid2 != null) { 610 if (!gidMatch(subscriptionRule.gid2, gid2)) { 611 mScore = SCORE_INVALID; 612 return; 613 } 614 mScore += SCORE_GID2; 615 } 616 if (plmn != null) { 617 if (!CarrierResolver.equals(subscriptionRule.plmn, plmn, true)) { 618 mScore = SCORE_INVALID; 619 return; 620 } 621 mScore += SCORE_PLMN; 622 } 623 if (spn != null) { 624 if (!CarrierResolver.equals(subscriptionRule.spn, spn, true)) { 625 mScore = SCORE_INVALID; 626 return; 627 } 628 mScore += SCORE_SPN; 629 } 630 631 if (privilegeAccessRule != null && !privilegeAccessRule.isEmpty()) { 632 if (!carrierPrivilegeRulesMatch(subscriptionRule.privilegeAccessRule, 633 privilegeAccessRule)) { 634 mScore = SCORE_INVALID; 635 return; 636 } 637 mScore += SCORE_PRIVILEGE_ACCESS_RULE; 638 } 639 640 if (apn != null) { 641 if (!CarrierResolver.equals(subscriptionRule.apn, apn, true)) { 642 mScore = SCORE_INVALID; 643 return; 644 } 645 mScore += SCORE_APN; 646 } 647 } 648 imsiPrefixMatch(String imsi, String prefixXPattern)649 private boolean imsiPrefixMatch(String imsi, String prefixXPattern) { 650 if (TextUtils.isEmpty(prefixXPattern)) return true; 651 if (TextUtils.isEmpty(imsi)) return false; 652 if (imsi.length() < prefixXPattern.length()) { 653 return false; 654 } 655 for (int i = 0; i < prefixXPattern.length(); i++) { 656 if ((prefixXPattern.charAt(i) != 'x') && (prefixXPattern.charAt(i) != 'X') 657 && (prefixXPattern.charAt(i) != imsi.charAt(i))) { 658 return false; 659 } 660 } 661 return true; 662 } 663 iccidPrefixMatch(String iccid, String prefix)664 private boolean iccidPrefixMatch(String iccid, String prefix) { 665 if (iccid == null || prefix == null) { 666 return false; 667 } 668 return iccid.startsWith(prefix); 669 } 670 671 // We are doing prefix and case insensitive match. 672 // Ideally we should do full string match. However due to SIM manufacture issues 673 // gid from some SIM might has garbage tail. gidMatch(String gidFromSim, String gid)674 private boolean gidMatch(String gidFromSim, String gid) { 675 return (gidFromSim != null) && gidFromSim.toLowerCase().startsWith(gid.toLowerCase()); 676 } 677 carrierPrivilegeRulesMatch(List<String> certsFromSubscription, List<String> certs)678 private boolean carrierPrivilegeRulesMatch(List<String> certsFromSubscription, 679 List<String> certs) { 680 if (certsFromSubscription == null || certsFromSubscription.isEmpty()) { 681 return false; 682 } 683 for (String cert : certs) { 684 for (String certFromSubscription : certsFromSubscription) { 685 if (!TextUtils.isEmpty(cert) 686 && cert.equalsIgnoreCase(certFromSubscription)) { 687 return true; 688 } 689 } 690 } 691 return false; 692 } 693 toString()694 public String toString() { 695 return "[CarrierMatchingRule] -" 696 + " mccmnc: " + mccMnc 697 + " gid1: " + gid1 698 + " gid2: " + gid2 699 + " plmn: " + plmn 700 + " imsi_prefix: " + imsiPrefixPattern 701 + " iccid_prefix" + iccidPrefix 702 + " spn: " + spn 703 + " privilege_access_rule: " + privilegeAccessRule 704 + " apn: " + apn 705 + " name: " + mName 706 + " cid: " + mCid 707 + " score: " + mScore; 708 } 709 } 710 getSubscriptionMatchingRule()711 private CarrierMatchingRule getSubscriptionMatchingRule() { 712 final String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId()); 713 final String iccid = mPhone.getIccSerialNumber(); 714 final String gid1 = mPhone.getGroupIdLevel1(); 715 final String gid2 = mPhone.getGroupIdLevel2(); 716 final String imsi = mPhone.getSubscriberId(); 717 final String plmn = mPhone.getPlmn(); 718 final String spn = mSpn; 719 final String apn = mPreferApn; 720 List<String> accessRules; 721 // check if test override present 722 if (!TextUtils.isEmpty(mTestOverrideCarrierPriviledgeRule)) { 723 accessRules = new ArrayList<>(Arrays.asList(mTestOverrideCarrierPriviledgeRule)); 724 } else { 725 accessRules = mTelephonyMgr.createForSubscriptionId(mPhone.getSubId()) 726 .getCertsFromCarrierPrivilegeAccessRules(); 727 } 728 729 if (VDBG) { 730 logd("[matchSubscriptionCarrier]" 731 + " mnnmnc:" + mccmnc 732 + " gid1: " + gid1 733 + " gid2: " + gid2 734 + " imsi: " + Rlog.pii(LOG_TAG, imsi) 735 + " iccid: " + Rlog.pii(LOG_TAG, iccid) 736 + " plmn: " + plmn 737 + " spn: " + spn 738 + " apn: " + apn 739 + " accessRules: " + ((accessRules != null) ? accessRules : null)); 740 } 741 return new CarrierMatchingRule( 742 mccmnc, imsi, iccid, gid1, gid2, plmn, spn, apn, accessRules, 743 TelephonyManager.UNKNOWN_CARRIER_ID, null, 744 TelephonyManager.UNKNOWN_CARRIER_ID); 745 } 746 747 /** 748 * find the best matching carrier from candidates with matched subscription MCCMNC. 749 */ matchSubscriptionCarrier()750 private void matchSubscriptionCarrier() { 751 if (!SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) { 752 logd("[matchSubscriptionCarrier]" + "skip before sim records loaded"); 753 return; 754 } 755 int maxScore = CarrierMatchingRule.SCORE_INVALID; 756 /** 757 * For child-parent relationship. either child and parent have the same matching 758 * score, or child's matching score > parents' matching score. 759 */ 760 CarrierMatchingRule maxRule = null; 761 CarrierMatchingRule maxRuleParent = null; 762 /** 763 * matching rule with mccmnc only. If mnoRule is found, then mno carrier id equals to the 764 * cid from mnoRule. otherwise, mno carrier id is same as cid. 765 */ 766 CarrierMatchingRule mnoRule = null; 767 CarrierMatchingRule subscriptionRule = getSubscriptionMatchingRule(); 768 769 for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) { 770 rule.match(subscriptionRule); 771 if (rule.mScore > maxScore) { 772 maxScore = rule.mScore; 773 maxRule = rule; 774 maxRuleParent = rule; 775 } else if (maxScore > CarrierMatchingRule.SCORE_INVALID && rule.mScore == maxScore) { 776 // to handle the case that child parent has the same matching score, we need to 777 // differentiate who is child who is parent. 778 if (rule.mParentCid == maxRule.mCid) { 779 maxRule = rule; 780 } else if (maxRule.mParentCid == rule.mCid) { 781 maxRuleParent = rule; 782 } 783 } 784 if (rule.mScore == CarrierMatchingRule.SCORE_MCCMNC) { 785 mnoRule = rule; 786 } 787 } 788 if (maxScore == CarrierMatchingRule.SCORE_INVALID) { 789 logd("[matchSubscriptionCarrier - no match] cid: " + TelephonyManager.UNKNOWN_CARRIER_ID 790 + " name: " + null); 791 updateCarrierIdAndName(TelephonyManager.UNKNOWN_CARRIER_ID, null, 792 TelephonyManager.UNKNOWN_CARRIER_ID, null, 793 TelephonyManager.UNKNOWN_CARRIER_ID); 794 } else { 795 // if there is a single matching result, check if this rule has parent cid assigned. 796 if ((maxRule == maxRuleParent) 797 && maxRule.mParentCid != TelephonyManager.UNKNOWN_CARRIER_ID) { 798 maxRuleParent = new CarrierMatchingRule(maxRule); 799 maxRuleParent.mCid = maxRuleParent.mParentCid; 800 maxRuleParent.mName = getCarrierNameFromId(maxRuleParent.mCid); 801 } 802 logd("[matchSubscriptionCarrier] specific cid: " + maxRule.mCid 803 + " specific name: " + maxRule.mName +" cid: " + maxRuleParent.mCid 804 + " name: " + maxRuleParent.mName); 805 updateCarrierIdAndName(maxRuleParent.mCid, maxRuleParent.mName, 806 maxRule.mCid, maxRule.mName, 807 (mnoRule == null) ? maxRule.mCid : mnoRule.mCid); 808 } 809 810 /* 811 * Write Carrier Identification Matching event, logging with the 812 * carrierId, mccmnc, gid1 and carrier list version to differentiate below cases of metrics: 813 * 1) unknown mccmnc - the Carrier Id provider contains no rule that matches the 814 * read mccmnc. 815 * 2) the Carrier Id provider contains some rule(s) that match the read mccmnc, 816 * but the read gid1 is not matched within the highest-scored rule. 817 * 3) successfully found a matched carrier id in the provider. 818 * 4) use carrier list version to compare the unknown carrier ratio between each version. 819 */ 820 String unknownGid1ToLog = ((maxScore & CarrierMatchingRule.SCORE_GID1) == 0 821 && !TextUtils.isEmpty(subscriptionRule.gid1)) ? subscriptionRule.gid1 : null; 822 String unknownMccmncToLog = ((maxScore == CarrierMatchingRule.SCORE_INVALID 823 || (maxScore & CarrierMatchingRule.SCORE_GID1) == 0) 824 && !TextUtils.isEmpty(subscriptionRule.mccMnc)) ? subscriptionRule.mccMnc : null; 825 826 // pass subscription rule to metrics. scrub all possible PII before uploading. 827 // only log apn if not user edited. 828 String apn = (subscriptionRule.apn != null 829 && !isPreferApnUserEdited(subscriptionRule.apn)) 830 ? subscriptionRule.apn : null; 831 // only log first 7 bits of iccid 832 String iccidPrefix = (subscriptionRule.iccidPrefix != null) 833 && (subscriptionRule.iccidPrefix.length() >= 7) 834 ? subscriptionRule.iccidPrefix.substring(0, 7) : subscriptionRule.iccidPrefix; 835 // only log first 8 bits of imsi 836 String imsiPrefix = (subscriptionRule.imsiPrefixPattern != null) 837 && (subscriptionRule.imsiPrefixPattern.length() >= 8) 838 ? subscriptionRule.imsiPrefixPattern.substring(0, 8) 839 : subscriptionRule.imsiPrefixPattern; 840 841 CarrierMatchingRule simInfo = new CarrierMatchingRule( 842 subscriptionRule.mccMnc, 843 imsiPrefix, 844 iccidPrefix, 845 subscriptionRule.gid1, 846 subscriptionRule.gid2, 847 subscriptionRule.plmn, 848 subscriptionRule.spn, 849 apn, 850 subscriptionRule.privilegeAccessRule, 851 -1, null, -1); 852 853 TelephonyMetrics.getInstance().writeCarrierIdMatchingEvent( 854 mPhone.getPhoneId(), getCarrierListVersion(), mCarrierId, 855 unknownMccmncToLog, unknownGid1ToLog, simInfo); 856 } 857 getCarrierListVersion()858 public int getCarrierListVersion() { 859 final Cursor cursor = mContext.getContentResolver().query( 860 Uri.withAppendedPath(CarrierId.All.CONTENT_URI, 861 "get_version"), null, null, null); 862 cursor.moveToFirst(); 863 return cursor.getInt(0); 864 } 865 getCarrierId()866 public int getCarrierId() { 867 return mCarrierId; 868 } 869 /** 870 * Returns fine-grained carrier id of the current subscription. Carrier ids with a valid parent 871 * id are specific carrier ids. 872 * 873 * A specific carrier ID can represent the fact that a carrier may be in effect an aggregation 874 * of other carriers (ie in an MVNO type scenario) where each of these specific carriers which 875 * are used to make up the actual carrier service may have different carrier configurations. 876 * A specific carrier ID could also be used, for example, in a scenario where a carrier requires 877 * different carrier configuration for different service offering such as a prepaid plan. 878 * e.g, {@link #getCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM, while 879 * {@link #getSpecificCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based on the 880 * IMSI from the current subscription. 881 * 882 * For carriers without any fine-grained carrier ids, return {@link #getCarrierId()} 883 */ getSpecificCarrierId()884 public int getSpecificCarrierId() { 885 return mSpecificCarrierId; 886 } 887 getCarrierName()888 public String getCarrierName() { 889 return mCarrierName; 890 } 891 getSpecificCarrierName()892 public String getSpecificCarrierName() { 893 return mSpecificCarrierName; 894 } 895 getMnoCarrierId()896 public int getMnoCarrierId() { 897 return mMnoCarrierId; 898 } 899 900 /** 901 * a util function to convert carrierIdentifier to the best matching carrier id. 902 * 903 * @return the best matching carrier id. 904 */ getCarrierIdFromIdentifier(@onNull Context context, @NonNull CarrierIdentifier carrierIdentifier)905 public static int getCarrierIdFromIdentifier(@NonNull Context context, 906 @NonNull CarrierIdentifier carrierIdentifier) { 907 final String mccmnc = carrierIdentifier.getMcc() + carrierIdentifier.getMnc(); 908 final String gid1 = carrierIdentifier.getGid1(); 909 final String gid2 = carrierIdentifier.getGid2(); 910 final String imsi = carrierIdentifier.getImsi(); 911 final String spn = carrierIdentifier.getSpn(); 912 if (VDBG) { 913 logd("[getCarrierIdFromIdentifier]" 914 + " mnnmnc:" + mccmnc 915 + " gid1: " + gid1 916 + " gid2: " + gid2 917 + " imsi: " + Rlog.pii(LOG_TAG, imsi) 918 + " spn: " + spn); 919 } 920 // assign null to other fields which are not supported by carrierIdentifier. 921 CarrierMatchingRule targetRule = 922 new CarrierMatchingRule(mccmnc, imsi, null, gid1, gid2, null, 923 spn, null, null, 924 TelephonyManager.UNKNOWN_CARRIER_ID_LIST_VERSION, null, 925 TelephonyManager.UNKNOWN_CARRIER_ID); 926 927 int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID; 928 int maxScore = CarrierMatchingRule.SCORE_INVALID; 929 List<CarrierMatchingRule> rules = getCarrierMatchingRulesFromMccMnc( 930 context, targetRule.mccMnc); 931 for (CarrierMatchingRule rule : rules) { 932 rule.match(targetRule); 933 if (rule.mScore > maxScore) { 934 maxScore = rule.mScore; 935 carrierId = rule.mCid; 936 } 937 } 938 return carrierId; 939 } 940 941 /** 942 * a util function to convert {mccmnc, mvno_type, mvno_data} to all matching carrier ids. 943 * 944 * @return a list of id with matching {mccmnc, mvno_type, mvno_data} 945 */ getCarrierIdsFromApnQuery(@onNull Context context, String mccmnc, String mvnoCase, String mvnoData)946 public static List<Integer> getCarrierIdsFromApnQuery(@NonNull Context context, 947 String mccmnc, String mvnoCase, 948 String mvnoData) { 949 String selection = CarrierId.All.MCCMNC + "=" + mccmnc; 950 // build the proper query 951 if ("spn".equals(mvnoCase) && mvnoData != null) { 952 selection += " AND " + CarrierId.All.SPN + "='" + mvnoData + "'"; 953 } else if ("imsi".equals(mvnoCase) && mvnoData != null) { 954 selection += " AND " + CarrierId.All.IMSI_PREFIX_XPATTERN + "='" + mvnoData + "'"; 955 } else if ("gid1".equals(mvnoCase) && mvnoData != null) { 956 selection += " AND " + CarrierId.All.GID1 + "='" + mvnoData + "'"; 957 } else if ("gid2".equals(mvnoCase) && mvnoData != null) { 958 selection += " AND " + CarrierId.All.GID2 + "='" + mvnoData +"'"; 959 } else { 960 logd("mvno case empty or other invalid values"); 961 } 962 963 List<Integer> ids = new ArrayList<>(); 964 try { 965 Cursor cursor = context.getContentResolver().query( 966 CarrierId.All.CONTENT_URI, 967 /* projection */ null, 968 /* selection */ selection, 969 /* selectionArgs */ null, null); 970 try { 971 if (cursor != null) { 972 if (VDBG) { 973 logd("[getCarrierIdsFromApnQuery]- " + cursor.getCount() 974 + " Records(s) in DB"); 975 } 976 while (cursor.moveToNext()) { 977 int cid = cursor.getInt(cursor.getColumnIndex(CarrierId.CARRIER_ID)); 978 if (!ids.contains(cid)) { 979 ids.add(cid); 980 } 981 } 982 } 983 } finally { 984 if (cursor != null) { 985 cursor.close(); 986 } 987 } 988 } catch (Exception ex) { 989 loge("[getCarrierIdsFromApnQuery]- ex: " + ex); 990 } 991 logd(selection + " " + ids); 992 return ids; 993 } 994 995 // static helper function to get carrier id from mccmnc getCarrierIdFromMccMnc(@onNull Context context, String mccmnc)996 public static int getCarrierIdFromMccMnc(@NonNull Context context, String mccmnc) { 997 try (Cursor cursor = getCursorForMccMnc(context, mccmnc)) { 998 if (cursor == null || !cursor.moveToNext()) return TelephonyManager.UNKNOWN_CARRIER_ID; 999 if (VDBG) { 1000 logd("[getCarrierIdFromMccMnc]- " + cursor.getCount() 1001 + " Records(s) in DB" + " mccmnc: " + mccmnc); 1002 } 1003 return cursor.getInt(cursor.getColumnIndex(CarrierId.CARRIER_ID)); 1004 } catch (Exception ex) { 1005 loge("[getCarrierIdFromMccMnc]- ex: " + ex); 1006 } 1007 return TelephonyManager.UNKNOWN_CARRIER_ID; 1008 } 1009 1010 /** 1011 * Static helper function to get carrier name from mccmnc 1012 * @param context Context 1013 * @param mccmnc PLMN 1014 * @return Carrier name string given mccmnc/PLMN 1015 * 1016 * @hide 1017 */ 1018 @Nullable getCarrierNameFromMccMnc(@onNull Context context, String mccmnc)1019 public static String getCarrierNameFromMccMnc(@NonNull Context context, String mccmnc) { 1020 try (Cursor cursor = getCursorForMccMnc(context, mccmnc)) { 1021 if (cursor == null || !cursor.moveToNext()) return null; 1022 if (VDBG) { 1023 logd("[getCarrierNameFromMccMnc]- " + cursor.getCount() 1024 + " Records(s) in DB" + " mccmnc: " + mccmnc); 1025 } 1026 return cursor.getString(cursor.getColumnIndex(CarrierId.CARRIER_NAME)); 1027 } catch (Exception ex) { 1028 loge("[getCarrierNameFromMccMnc]- ex: " + ex); 1029 } 1030 return null; 1031 } 1032 1033 @Nullable getCursorForMccMnc(@onNull Context context, String mccmnc)1034 private static Cursor getCursorForMccMnc(@NonNull Context context, String mccmnc) { 1035 try { 1036 Cursor cursor = context.getContentResolver().query( 1037 CarrierId.All.CONTENT_URI, 1038 /* projection */ null, 1039 /* selection */ CarrierId.All.MCCMNC + "=? AND " 1040 + CarrierId.All.GID1 + " is NULL AND " 1041 + CarrierId.All.GID2 + " is NULL AND " 1042 + CarrierId.All.IMSI_PREFIX_XPATTERN + " is NULL AND " 1043 + CarrierId.All.SPN + " is NULL AND " 1044 + CarrierId.All.ICCID_PREFIX + " is NULL AND " 1045 + CarrierId.All.PLMN + " is NULL AND " 1046 + CarrierId.All.PRIVILEGE_ACCESS_RULE + " is NULL AND " 1047 + CarrierId.All.APN + " is NULL", 1048 /* selectionArgs */ new String[]{mccmnc}, 1049 null); 1050 return cursor; 1051 } catch (Exception ex) { 1052 loge("[getCursorForMccMnc]- ex: " + ex); 1053 return null; 1054 } 1055 } 1056 equals(String a, String b, boolean ignoreCase)1057 private static boolean equals(String a, String b, boolean ignoreCase) { 1058 if (a == null && b == null) return true; 1059 if (a != null && b != null) { 1060 return (ignoreCase) ? a.equalsIgnoreCase(b) : a.equals(b); 1061 } 1062 return false; 1063 } 1064 logd(String str)1065 private static void logd(String str) { 1066 Rlog.d(LOG_TAG, str); 1067 } loge(String str)1068 private static void loge(String str) { 1069 Rlog.e(LOG_TAG, str); 1070 } dump(FileDescriptor fd, PrintWriter pw, String[] args)1071 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1072 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); 1073 ipw.println("mCarrierResolverLocalLogs:"); 1074 ipw.increaseIndent(); 1075 mCarrierIdLocalLog.dump(fd, pw, args); 1076 ipw.decreaseIndent(); 1077 1078 ipw.println("mCarrierId: " + mCarrierId); 1079 ipw.println("mSpecificCarrierId: " + mSpecificCarrierId); 1080 ipw.println("mMnoCarrierId: " + mMnoCarrierId); 1081 ipw.println("mCarrierName: " + mCarrierName); 1082 ipw.println("mSpecificCarrierName: " + mSpecificCarrierName); 1083 ipw.println("carrier_list_version: " + getCarrierListVersion()); 1084 1085 ipw.println("mCarrierMatchingRules on mccmnc: " 1086 + mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId())); 1087 ipw.increaseIndent(); 1088 for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) { 1089 ipw.println(rule.toString()); 1090 } 1091 ipw.decreaseIndent(); 1092 1093 ipw.println("mSpn: " + mSpn); 1094 ipw.println("mPreferApn: " + mPreferApn); 1095 ipw.flush(); 1096 } 1097 } 1098