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