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