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