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