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