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