1 /*
2  * Copyright (C) 2018 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 
17 package android.telephony;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.SystemApi;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.service.carrier.CarrierIdentifier;
25 
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Objects;
31 
32 /**
33  * Contains the list of carrier restrictions.
34  * Allowed list: it indicates the list of carriers that are allowed.
35  * Excluded list: it indicates the list of carriers that are excluded.
36  * Default carrier restriction: it indicates the default behavior and the priority between the two
37  * lists:
38  *  - not allowed: the device only allows usage of carriers that are present in the allowed list
39  *    and not present in the excluded list. This implies that if a carrier is not present in either
40  *    list, it is not allowed.
41  *  - allowed: the device allows all carriers, except those present in the excluded list and not
42  *    present in the allowed list. This implies that if a carrier is not present in either list,
43  *    it is allowed.
44  * MultiSim policy: it indicates the behavior in case of devices with two or more SIM cards.
45  *  - MULTISIM_POLICY_NONE: the same configuration is applied to all SIM slots independently. This
46  *    is the default value if none is set.
47  *  - MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT: it indicates that any SIM card can be used
48  *    as far as one SIM card matching the configuration is present in the device.
49  *
50  * Both lists support the character '?' as wild character. For example, an entry indicating
51  * MCC=310 and MNC=??? will match all networks with MCC=310.
52  *
53  * Example 1: Allowed list contains MCC and MNC of operator A. Excluded list contains operator B,
54  *            which has same MCC and MNC, but also GID1 value. The priority allowed list is set
55  *            to true. Only SIM cards of operator A are allowed, but not those of B or any other
56  *            operator.
57  * Example 2: Allowed list contains MCC and MNC of operator A. Excluded list contains an entry
58  *            with same MCC, and '???' as MNC. The priority allowed list is set to false.
59  *            SIM cards of operator A and all SIM cards with a different MCC value are allowed.
60  *            SIM cards of operators with same MCC value and different MNC are not allowed.
61  * @hide
62  */
63 @SystemApi
64 public final class CarrierRestrictionRules implements Parcelable {
65     /**
66      * The device only allows usage of carriers that are present in the allowed list and not
67      * present in the excluded list. This implies that if a carrier is not present in either list,
68      * it is not allowed.
69      */
70     public static final int CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED = 0;
71 
72     /**
73      * The device allows all carriers, except those present in the excluded list and not present
74      * in the allowed list. This implies that if a carrier is not present in either list, it is
75      * allowed.
76      */
77     public static final int CARRIER_RESTRICTION_DEFAULT_ALLOWED = 1;
78 
79     /** The same configuration is applied to all SIM slots independently. */
80     public static final int MULTISIM_POLICY_NONE = 0;
81 
82     /** Any SIM card can be used as far as one SIM card matching the configuration is present. */
83     public static final int MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT = 1;
84 
85     /** @hide */
86     @Retention(RetentionPolicy.SOURCE)
87     @IntDef(prefix = "MULTISIM_POLICY_",
88             value = {MULTISIM_POLICY_NONE, MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT})
89     public @interface MultiSimPolicy {}
90 
91     /** @hide */
92     @Retention(RetentionPolicy.SOURCE)
93     @IntDef(prefix = "CARRIER_RESTRICTION_DEFAULT_",
94             value = {CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED, CARRIER_RESTRICTION_DEFAULT_ALLOWED})
95     public @interface CarrierRestrictionDefault {}
96 
97     /* Wild character for comparison */
98     private static final char WILD_CHARACTER = '?';
99 
100     private List<CarrierIdentifier> mAllowedCarriers;
101     private List<CarrierIdentifier> mExcludedCarriers;
102     @CarrierRestrictionDefault
103     private int mCarrierRestrictionDefault;
104     @MultiSimPolicy
105     private int mMultiSimPolicy;
106 
CarrierRestrictionRules()107     private CarrierRestrictionRules() {
108         mAllowedCarriers = new ArrayList<CarrierIdentifier>();
109         mExcludedCarriers = new ArrayList<CarrierIdentifier>();
110         mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
111         mMultiSimPolicy = MULTISIM_POLICY_NONE;
112     }
113 
CarrierRestrictionRules(Parcel in)114     private CarrierRestrictionRules(Parcel in) {
115         mAllowedCarriers = new ArrayList<CarrierIdentifier>();
116         mExcludedCarriers = new ArrayList<CarrierIdentifier>();
117 
118         in.readTypedList(mAllowedCarriers, CarrierIdentifier.CREATOR);
119         in.readTypedList(mExcludedCarriers, CarrierIdentifier.CREATOR);
120         mCarrierRestrictionDefault = in.readInt();
121         mMultiSimPolicy = in.readInt();
122     }
123 
124     /**
125      * Creates a new builder for this class
126      * @hide
127      */
newBuilder()128     public static Builder newBuilder() {
129         return new Builder();
130     }
131 
132     /**
133      * Indicates if all carriers are allowed
134      */
isAllCarriersAllowed()135     public boolean isAllCarriersAllowed() {
136         return (mAllowedCarriers.isEmpty() && mExcludedCarriers.isEmpty()
137                 && mCarrierRestrictionDefault == CARRIER_RESTRICTION_DEFAULT_ALLOWED);
138     }
139 
140     /**
141      * Retrieves list of allowed carriers
142      *
143      * @return the list of allowed carriers
144      */
getAllowedCarriers()145     public @NonNull List<CarrierIdentifier> getAllowedCarriers() {
146         return mAllowedCarriers;
147     }
148 
149     /**
150      * Retrieves list of excluded carriers
151      *
152      * @return the list of excluded carriers
153      */
getExcludedCarriers()154     public @NonNull List<CarrierIdentifier> getExcludedCarriers() {
155         return mExcludedCarriers;
156     }
157 
158     /**
159      * Retrieves the default behavior of carrier restrictions
160      */
getDefaultCarrierRestriction()161     public @CarrierRestrictionDefault int getDefaultCarrierRestriction() {
162         return mCarrierRestrictionDefault;
163     }
164 
165     /**
166      * @return The policy used for multi-SIM devices
167      */
getMultiSimPolicy()168     public @MultiSimPolicy int getMultiSimPolicy() {
169         return mMultiSimPolicy;
170     }
171 
172     /**
173      * Tests an array of carriers with the carrier restriction configuration. The list of carrier
174      * ids passed as argument does not need to be the same as currently present in the device.
175      *
176      * @param carrierIds list of {@link CarrierIdentifier}, one for each SIM slot on the device
177      * @return a list of boolean with the same size as input, indicating if each
178      * {@link CarrierIdentifier} is allowed or not.
179      */
areCarrierIdentifiersAllowed( @onNull List<CarrierIdentifier> carrierIds)180     public @NonNull List<Boolean> areCarrierIdentifiersAllowed(
181             @NonNull List<CarrierIdentifier> carrierIds) {
182         ArrayList<Boolean> result = new ArrayList<>(carrierIds.size());
183 
184         // First calculate the result for each slot independently
185         for (int i = 0; i < carrierIds.size(); i++) {
186             boolean inAllowedList = isCarrierIdInList(carrierIds.get(i), mAllowedCarriers);
187             boolean inExcludedList = isCarrierIdInList(carrierIds.get(i), mExcludedCarriers);
188             if (mCarrierRestrictionDefault == CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED) {
189                 result.add((inAllowedList && !inExcludedList) ? true : false);
190             } else {
191                 result.add((inExcludedList && !inAllowedList) ? false : true);
192             }
193         }
194         // Apply the multi-slot policy, if needed.
195         if (mMultiSimPolicy == MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT) {
196             for (boolean b : result) {
197                 if (b) {
198                     result.replaceAll(x -> true);
199                     break;
200                 }
201             }
202         }
203         return result;
204     }
205 
206     /**
207      * Indicates if a certain carrier {@code id} is present inside a {@code list}
208      *
209      * @return true if the carrier {@code id} is present, false otherwise
210      */
isCarrierIdInList(CarrierIdentifier id, List<CarrierIdentifier> list)211     private static boolean isCarrierIdInList(CarrierIdentifier id, List<CarrierIdentifier> list) {
212         for (CarrierIdentifier listItem : list) {
213             // Compare MCC and MNC
214             if (!patternMatch(id.getMcc(), listItem.getMcc())
215                     || !patternMatch(id.getMnc(), listItem.getMnc())) {
216                 continue;
217             }
218 
219             // Compare SPN. Comparison is on the complete strings, case insensitive and with wild
220             // characters.
221             String listItemValue = convertNullToEmpty(listItem.getSpn());
222             String idValue = convertNullToEmpty(id.getSpn());
223             if (!listItemValue.isEmpty()) {
224                 if (!patternMatch(idValue, listItemValue)) {
225                     continue;
226                 }
227             }
228 
229             // The IMSI of the configuration can be shorter than actual IMSI in the SIM card.
230             listItemValue = convertNullToEmpty(listItem.getImsi());
231             idValue = convertNullToEmpty(id.getImsi());
232             if (!patternMatch(
233                     idValue.substring(0, Math.min(idValue.length(), listItemValue.length())),
234                     listItemValue)) {
235                 continue;
236             }
237 
238             // The GID1 of the configuration can be shorter than actual GID1 in the SIM card.
239             listItemValue = convertNullToEmpty(listItem.getGid1());
240             idValue = convertNullToEmpty(id.getGid1());
241             if (!patternMatch(
242                     idValue.substring(0, Math.min(idValue.length(), listItemValue.length())),
243                     listItemValue)) {
244                 continue;
245             }
246 
247             // The GID2 of the configuration can be shorter than actual GID2 in the SIM card.
248             listItemValue = convertNullToEmpty(listItem.getGid2());
249             idValue = convertNullToEmpty(id.getGid2());
250             if (!patternMatch(
251                     idValue.substring(0, Math.min(idValue.length(), listItemValue.length())),
252                     listItemValue)) {
253                 continue;
254             }
255 
256             // Valid match was found in the list
257             return true;
258         }
259         return false;
260     }
261 
convertNullToEmpty(String value)262     private static String convertNullToEmpty(String value) {
263         return Objects.toString(value, "");
264     }
265 
266     /**
267      * Performs a case insensitive string comparison against a given pattern. The character '?'
268      * is used in the pattern as wild character in the comparison. The string must have the same
269      * length as the pattern.
270      *
271      * @param str string to match
272      * @param pattern string containing the pattern
273      * @return true in case of match, false otherwise
274      */
patternMatch(String str, String pattern)275     private static boolean patternMatch(String str, String pattern) {
276         if (str.length() != pattern.length()) {
277             return false;
278         }
279         String lowerCaseStr = str.toLowerCase();
280         String lowerCasePattern = pattern.toLowerCase();
281 
282         for (int i = 0; i < lowerCasePattern.length(); i++) {
283             if (lowerCasePattern.charAt(i) != lowerCaseStr.charAt(i)
284                     && lowerCasePattern.charAt(i) != WILD_CHARACTER) {
285                 return false;
286             }
287         }
288         return true;
289     }
290 
291     /**
292      * {@link Parcelable#writeToParcel}
293      */
294     @Override
writeToParcel(Parcel out, int flags)295     public void writeToParcel(Parcel out, int flags) {
296         out.writeTypedList(mAllowedCarriers);
297         out.writeTypedList(mExcludedCarriers);
298         out.writeInt(mCarrierRestrictionDefault);
299         out.writeInt(mMultiSimPolicy);
300     }
301 
302     /**
303      * {@link Parcelable#describeContents}
304      */
305     @Override
describeContents()306     public int describeContents() {
307         return 0;
308     }
309 
310     /**
311      * {@link Parcelable.Creator}
312      */
313     public static final @android.annotation.NonNull Creator<CarrierRestrictionRules> CREATOR =
314             new Creator<CarrierRestrictionRules>() {
315         @Override
316         public CarrierRestrictionRules createFromParcel(Parcel in) {
317             return new CarrierRestrictionRules(in);
318         }
319 
320         @Override
321         public CarrierRestrictionRules[] newArray(int size) {
322             return new CarrierRestrictionRules[size];
323         }
324     };
325 
326     @NonNull
327     @Override
toString()328     public String toString() {
329         return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:"
330                 + mExcludedCarriers + ", default:" + mCarrierRestrictionDefault
331                 + ", multisim policy:" + mMultiSimPolicy + ")";
332     }
333 
334     /**
335      * Builder for a {@link CarrierRestrictionRules}.
336      */
337     public static final class Builder {
338         private final CarrierRestrictionRules mRules;
339 
Builder()340         public Builder() {
341             mRules = new CarrierRestrictionRules();
342         }
343 
344         /** build command */
build()345         public @NonNull CarrierRestrictionRules build() {
346             return mRules;
347         }
348 
349         /**
350          * Indicate that all carriers are allowed.
351          */
setAllCarriersAllowed()352         public @NonNull Builder setAllCarriersAllowed() {
353             mRules.mAllowedCarriers.clear();
354             mRules.mExcludedCarriers.clear();
355             mRules.mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_ALLOWED;
356             return this;
357         }
358 
359         /**
360          * Set list of allowed carriers.
361          *
362          * @param allowedCarriers list of allowed carriers
363          */
setAllowedCarriers( @onNull List<CarrierIdentifier> allowedCarriers)364         public @NonNull Builder setAllowedCarriers(
365                 @NonNull List<CarrierIdentifier> allowedCarriers) {
366             mRules.mAllowedCarriers = new ArrayList<CarrierIdentifier>(allowedCarriers);
367             return this;
368         }
369 
370         /**
371          * Set list of excluded carriers.
372          *
373          * @param excludedCarriers list of excluded carriers
374          */
setExcludedCarriers( @onNull List<CarrierIdentifier> excludedCarriers)375         public @NonNull Builder setExcludedCarriers(
376                 @NonNull List<CarrierIdentifier> excludedCarriers) {
377             mRules.mExcludedCarriers = new ArrayList<CarrierIdentifier>(excludedCarriers);
378             return this;
379         }
380 
381         /**
382          * Set the default behavior of the carrier restrictions
383          *
384          * @param carrierRestrictionDefault prioritized carrier list
385          */
setDefaultCarrierRestriction( @arrierRestrictionDefault int carrierRestrictionDefault)386         public @NonNull Builder setDefaultCarrierRestriction(
387                 @CarrierRestrictionDefault int carrierRestrictionDefault) {
388             mRules.mCarrierRestrictionDefault = carrierRestrictionDefault;
389             return this;
390         }
391 
392         /**
393          * Set the policy to be used for multi-SIM devices
394          *
395          * @param multiSimPolicy multi SIM policy
396          */
setMultiSimPolicy(@ultiSimPolicy int multiSimPolicy)397         public @NonNull Builder setMultiSimPolicy(@MultiSimPolicy int multiSimPolicy) {
398             mRules.mMultiSimPolicy = multiSimPolicy;
399             return this;
400         }
401     }
402 }
403