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