1 /*
2  * Copyright (C) 2006 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 com.android.internal.telephony.uicc;
18 
19 import android.annotation.NonNull;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.os.Build;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.telephony.PhoneNumberUtils;
25 import android.text.TextUtils;
26 
27 import com.android.internal.util.ArrayUtils;
28 import com.android.telephony.Rlog;
29 
30 import java.util.Arrays;
31 import java.util.List;
32 
33 /**
34  *
35  * Used to load or store ADNs (Abbreviated Dialing Numbers).
36  *
37  * {@hide}
38  *
39  */
40 public class AdnRecord implements Parcelable {
41     static final String LOG_TAG = "AdnRecord";
42 
43     //***** Instance Variables
44 
45     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
46     String mAlphaTag = null;
47     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
48     String mNumber = null;
49     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
50     String[] mEmails;
51     String[] mAdditionalNumbers = null;
52     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
53     int mExtRecord = 0xff;
54     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
55     int mEfid;                   // or 0 if none
56     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
57     int mRecordNumber;           // or 0 if none
58 
59 
60     //***** Constants
61 
62     // In an ADN record, everything but the alpha identifier
63     // is in a footer that's 14 bytes
64     static final int FOOTER_SIZE_BYTES = 14;
65 
66     // Maximum size of the un-extended number field
67     static final int MAX_NUMBER_SIZE_BYTES = 11;
68 
69     static final int EXT_RECORD_LENGTH_BYTES = 13;
70     static final int EXT_RECORD_TYPE_ADDITIONAL_DATA = 2;
71     static final int EXT_RECORD_TYPE_MASK = 3;
72     static final int MAX_EXT_CALLED_PARTY_LENGTH = 0xa;
73 
74     // ADN offset
75     static final int ADN_BCD_NUMBER_LENGTH = 0;
76     static final int ADN_TON_AND_NPI = 1;
77     static final int ADN_DIALING_NUMBER_START = 2;
78     static final int ADN_DIALING_NUMBER_END = 11;
79     static final int ADN_CAPABILITY_ID = 12;
80     static final int ADN_EXTENSION_ID = 13;
81 
82     //***** Static Methods
83 
84     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
85     public static final Parcelable.Creator<AdnRecord> CREATOR
86             = new Parcelable.Creator<AdnRecord>() {
87         @Override
88         public AdnRecord createFromParcel(Parcel source) {
89             int efid;
90             int recordNumber;
91             String alphaTag;
92             String number;
93             String[] emails;
94             String[] additionalNumbers;
95 
96             efid = source.readInt();
97             recordNumber = source.readInt();
98             alphaTag = source.readString();
99             number = source.readString();
100             emails = source.createStringArray();
101             additionalNumbers = source.createStringArray();
102 
103             return new AdnRecord(efid, recordNumber, alphaTag, number, emails, additionalNumbers);
104         }
105 
106         @Override
107         public AdnRecord[] newArray(int size) {
108             return new AdnRecord[size];
109         }
110     };
111 
112     /**
113      * Returns the maximum number of characters that supported by the alpha tag for a record with
114      * the specified maximum size.
115      */
getMaxAlphaTagBytes(int maxRecordLength)116     public static int getMaxAlphaTagBytes(int maxRecordLength) {
117         return Math.max(0, maxRecordLength - FOOTER_SIZE_BYTES);
118     }
119 
120     /**
121      * Encodes the alphaTag to a binary representation supported by the SIM.
122      *
123      * <p>This is the same representation as is used for this field in buildAdnString but there
124      * is no restriction on the length.
125      */
126     @NonNull
encodeAlphaTag(String alphaTag)127     public static byte[] encodeAlphaTag(String alphaTag) {
128         if (TextUtils.isEmpty(alphaTag)) {
129             return new byte[0];
130         }
131         return IccUtils.stringToAdnStringField(alphaTag);
132     }
133 
134     /**
135      * Decodes an encoded alphaTag from a record or encoded tag.
136      *
137      * <p>This is the same as is used to construct an AdnRecord from byte[]
138      */
decodeAlphaTag(byte[] encodedTagOrRecord, int offset, int length)139     public static String decodeAlphaTag(byte[] encodedTagOrRecord, int offset, int length) {
140         return IccUtils.adnStringFieldToString(encodedTagOrRecord, offset, length);
141     }
142 
143     /**
144      * Returns the maximum number of digits (or other dialable characters) that can be stored in
145      * the phone number.
146      *
147      * <p>Additional length is supported via the ext1 entity file but the current implementation
148      * doesn't support writing of that file so it is not included in this calculation.
149      */
getMaxPhoneNumberDigits()150     public static int getMaxPhoneNumberDigits() {
151         // Multiply by 2 because it is packed BCD encoded (2 digits per byte).
152         return (ADN_DIALING_NUMBER_END - ADN_DIALING_NUMBER_START + 1) * 2;
153     }
154 
155     //***** Constructor
156     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
AdnRecord(byte[] record)157     public AdnRecord (byte[] record) {
158         this(0, 0, record);
159     }
160 
161     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
AdnRecord(int efid, int recordNumber, byte[] record)162     public AdnRecord (int efid, int recordNumber, byte[] record) {
163         this.mEfid = efid;
164         this.mRecordNumber = recordNumber;
165         parseRecord(record);
166     }
167 
168     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
AdnRecord(String alphaTag, String number)169     public AdnRecord (String alphaTag, String number) {
170         this(0, 0, alphaTag, number);
171     }
172 
173     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
AdnRecord(String alphaTag, String number, String[] emails)174     public AdnRecord (String alphaTag, String number, String[] emails) {
175         this(0, 0, alphaTag, number, emails);
176     }
177 
AdnRecord(String alphaTag, String number, String[] emails, String[] additionalNumbers)178     public AdnRecord(String alphaTag, String number, String[] emails, String[] additionalNumbers) {
179         this(0, 0, alphaTag, number, emails, additionalNumbers);
180     }
181 
182     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
AdnRecord(int efid, int recordNumber, String alphaTag, String number, String[] emails)183     public AdnRecord (int efid, int recordNumber, String alphaTag, String number, String[] emails) {
184         this.mEfid = efid;
185         this.mRecordNumber = recordNumber;
186         this.mAlphaTag = alphaTag;
187         this.mNumber = number;
188         this.mEmails = emails;
189         this.mAdditionalNumbers = null;
190     }
191 
AdnRecord(int efid, int recordNumber, String alphaTag, String number, String[] emails, String[] additionalNumbers)192     public AdnRecord(int efid, int recordNumber, String alphaTag, String number, String[] emails,
193             String[] additionalNumbers) {
194         this.mEfid = efid;
195         this.mRecordNumber = recordNumber;
196         this.mAlphaTag = alphaTag;
197         this.mNumber = number;
198         this.mEmails = emails;
199         this.mAdditionalNumbers = additionalNumbers;
200     }
201 
202     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
AdnRecord(int efid, int recordNumber, String alphaTag, String number)203     public AdnRecord(int efid, int recordNumber, String alphaTag, String number) {
204         this.mEfid = efid;
205         this.mRecordNumber = recordNumber;
206         this.mAlphaTag = alphaTag;
207         this.mNumber = number;
208         this.mEmails = null;
209         this.mAdditionalNumbers = null;
210     }
211 
212     //***** Instance Methods
213 
getAlphaTag()214     public String getAlphaTag() {
215         return mAlphaTag;
216     }
217 
getEfid()218     public int getEfid() {
219         return mEfid;
220     }
221 
getRecId()222     public int getRecId() {
223         return mRecordNumber;
224     }
225 
setRecId(int recordId)226     public void setRecId(int recordId) {
227         mRecordNumber = recordId;
228     }
229 
230     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getNumber()231     public String getNumber() {
232         return mNumber;
233     }
234 
setNumber(String number)235     public void setNumber(String number) {
236         mNumber = number;
237     }
238 
239     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getEmails()240     public String[] getEmails() {
241         return mEmails;
242     }
243 
244     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
setEmails(String[] emails)245     public void setEmails(String[] emails) {
246         this.mEmails = emails;
247     }
248 
getAdditionalNumbers()249     public String[] getAdditionalNumbers() {
250         return mAdditionalNumbers;
251     }
252 
setAdditionalNumbers(String[] additionalNumbers)253     public void setAdditionalNumbers(String[] additionalNumbers) {
254         mAdditionalNumbers = additionalNumbers;
255     }
256 
257     @Override
toString()258     public String toString() {
259         return "ADN Record '" + mAlphaTag + "' '" + Rlog.pii(LOG_TAG, mNumber) + " "
260                 + Rlog.pii(LOG_TAG, mEmails) + " "
261                 + Rlog.pii(LOG_TAG, mAdditionalNumbers) + "'";
262     }
263 
264     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
isEmpty()265     public boolean isEmpty() {
266         return TextUtils.isEmpty(mAlphaTag) && TextUtils.isEmpty(mNumber)
267                 && mEmails == null && mAdditionalNumbers == null;
268     }
269 
hasExtendedRecord()270     public boolean hasExtendedRecord() {
271         return mExtRecord != 0 && mExtRecord != 0xff;
272     }
273 
274     /** Helper function for {@link #isEqual}. */
stringCompareNullEqualsEmpty(String s1, String s2)275     private static boolean stringCompareNullEqualsEmpty(String s1, String s2) {
276         if (s1 == s2) {
277             return true;
278         }
279         if (s1 == null) {
280             s1 = "";
281         }
282         if (s2 == null) {
283             s2 = "";
284         }
285         return (s1.equals(s2));
286     }
287 
288     /** Help function for ANR/EMAIL array compare. */
arrayCompareNullEqualsEmpty(String s1[], String s2[])289     private static boolean arrayCompareNullEqualsEmpty(String s1[], String s2[]) {
290         if (s1 == s2) {
291             return true;
292         }
293 
294         s1 = ArrayUtils.emptyIfNull(s1, String.class);
295         s2 = ArrayUtils.emptyIfNull(s2, String.class);
296 
297         List<String> src = Arrays.asList(s1);
298         List<String> dest = Arrays.asList(s2);
299 
300         if (src.size() != dest.size()) {
301             return false;
302         }
303 
304         return src.containsAll(dest);
305     }
306 
isEqual(AdnRecord adn)307     public boolean isEqual(AdnRecord adn) {
308         return ( stringCompareNullEqualsEmpty(mAlphaTag, adn.mAlphaTag) &&
309                 stringCompareNullEqualsEmpty(mNumber, adn.mNumber) &&
310                 arrayCompareNullEqualsEmpty(mEmails, adn.mEmails) &&
311                 arrayCompareNullEqualsEmpty(mAdditionalNumbers, adn.mAdditionalNumbers));
312     }
313     //***** Parcelable Implementation
314 
315     @Override
describeContents()316     public int describeContents() {
317         return 0;
318     }
319 
320     @Override
writeToParcel(Parcel dest, int flags)321     public void writeToParcel(Parcel dest, int flags) {
322         dest.writeInt(mEfid);
323         dest.writeInt(mRecordNumber);
324         dest.writeString(mAlphaTag);
325         dest.writeString(mNumber);
326         dest.writeStringArray(mEmails);
327         dest.writeStringArray(mAdditionalNumbers);
328     }
329 
330     /**
331      * Build adn hex byte array based on record size
332      * The format of byte array is defined in 51.011 10.5.1
333      *
334      * @param recordSize is the size X of EF record
335      * @return hex byte[recordSize] to be written to EF record
336      *          return null for wrong format of dialing number or tag
337      */
338     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
buildAdnString(int recordSize)339     public byte[] buildAdnString(int recordSize) {
340         byte[] bcdNumber;
341         byte[] byteTag;
342         byte[] adnString;
343         int footerOffset = recordSize - FOOTER_SIZE_BYTES;
344 
345         // create an empty record
346         adnString = new byte[recordSize];
347         for (int i = 0; i < recordSize; i++) {
348             adnString[i] = (byte) 0xFF;
349         }
350 
351         if (TextUtils.isEmpty(mNumber) && TextUtils.isEmpty(mAlphaTag)) {
352             Rlog.w(LOG_TAG, "[buildAdnString] Empty dialing number");
353             return adnString;   // return the empty record (for delete)
354         } else if (mNumber != null && mNumber.length()
355                 > (ADN_DIALING_NUMBER_END - ADN_DIALING_NUMBER_START + 1) * 2) {
356             Rlog.w(LOG_TAG,
357                     "[buildAdnString] Max length of dialing number is 20");
358             return null;
359         }
360 
361         byteTag = encodeAlphaTag(mAlphaTag);
362 
363         if (byteTag.length > footerOffset) {
364             Rlog.w(LOG_TAG, "[buildAdnString] Max length of tag is " + footerOffset);
365             return null;
366         } else {
367             if (!TextUtils.isEmpty(mNumber)) {
368                 bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
369                         mNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
370 
371                 System.arraycopy(bcdNumber, 0, adnString,
372                         footerOffset + ADN_TON_AND_NPI, bcdNumber.length);
373 
374                 adnString[footerOffset + ADN_BCD_NUMBER_LENGTH]
375                         = (byte) (bcdNumber.length);
376             }
377             adnString[footerOffset + ADN_CAPABILITY_ID]
378                     = (byte) 0xFF; // Capability Id
379             adnString[footerOffset + ADN_EXTENSION_ID]
380                     = (byte) 0xFF; // Extension Record Id
381 
382             if (byteTag.length > 0) {
383                 System.arraycopy(byteTag, 0, adnString, 0, byteTag.length);
384             }
385 
386             return adnString;
387         }
388     }
389 
390     /**
391      * See TS 51.011 10.5.10
392      */
393     public void
appendExtRecord(byte[] extRecord)394     appendExtRecord (byte[] extRecord) {
395         try {
396             if (extRecord.length != EXT_RECORD_LENGTH_BYTES) {
397                 return;
398             }
399 
400             if ((extRecord[0] & EXT_RECORD_TYPE_MASK)
401                     != EXT_RECORD_TYPE_ADDITIONAL_DATA) {
402                 return;
403             }
404 
405             if ((0xff & extRecord[1]) > MAX_EXT_CALLED_PARTY_LENGTH) {
406                 // invalid or empty record
407                 return;
408             }
409 
410             mNumber += PhoneNumberUtils.calledPartyBCDFragmentToString(
411                     extRecord,
412                     2,
413                     0xff & extRecord[1],
414                     PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
415 
416             // We don't support ext record chaining.
417 
418         } catch (RuntimeException ex) {
419             Rlog.w(LOG_TAG, "Error parsing AdnRecord ext record", ex);
420         }
421     }
422 
423     //***** Private Methods
424 
425     /**
426      * alphaTag and number are set to null on invalid format
427      */
428     private void
parseRecord(byte[] record)429     parseRecord(byte[] record) {
430         try {
431             mAlphaTag = decodeAlphaTag(
432                             record, 0, record.length - FOOTER_SIZE_BYTES);
433 
434             int footerOffset = record.length - FOOTER_SIZE_BYTES;
435 
436             int numberLength = 0xff & record[footerOffset];
437 
438             if (numberLength > MAX_NUMBER_SIZE_BYTES) {
439                 // Invalid number length
440                 mNumber = "";
441                 return;
442             }
443 
444             // Please note 51.011 10.5.1:
445             //
446             // "If the Dialling Number/SSC String does not contain
447             // a dialling number, e.g. a control string deactivating
448             // a service, the TON/NPI byte shall be set to 'FF' by
449             // the ME (see note 2)."
450 
451             mNumber = PhoneNumberUtils.calledPartyBCDToString(
452                     record,
453                     footerOffset + 1,
454                     numberLength,
455                     PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
456 
457 
458             mExtRecord = 0xff & record[record.length - 1];
459 
460             mEmails = null;
461             mAdditionalNumbers = null;
462         } catch (RuntimeException ex) {
463             Rlog.w(LOG_TAG, "Error parsing AdnRecord", ex);
464             mNumber = "";
465             mAlphaTag = "";
466             mEmails = null;
467             mAdditionalNumbers = null;
468         }
469     }
470 }
471