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.os.Parcel;
20 import android.os.Parcelable;
21 import android.telephony.PhoneNumberUtils;
22 import android.telephony.Rlog;
23 import android.text.TextUtils;
24 
25 import com.android.internal.telephony.GsmAlphabet;
26 
27 import java.util.Arrays;
28 
29 
30 /**
31  *
32  * Used to load or store ADNs (Abbreviated Dialing Numbers).
33  *
34  * {@hide}
35  *
36  */
37 public class AdnRecord implements Parcelable {
38     static final String LOG_TAG = "AdnRecord";
39 
40     //***** Instance Variables
41 
42     String mAlphaTag = null;
43     String mNumber = null;
44     String[] mEmails;
45     int mExtRecord = 0xff;
46     int mEfid;                   // or 0 if none
47     int mRecordNumber;           // or 0 if none
48 
49 
50     //***** Constants
51 
52     // In an ADN record, everything but the alpha identifier
53     // is in a footer that's 14 bytes
54     static final int FOOTER_SIZE_BYTES = 14;
55 
56     // Maximum size of the un-extended number field
57     static final int MAX_NUMBER_SIZE_BYTES = 11;
58 
59     static final int EXT_RECORD_LENGTH_BYTES = 13;
60     static final int EXT_RECORD_TYPE_ADDITIONAL_DATA = 2;
61     static final int EXT_RECORD_TYPE_MASK = 3;
62     static final int MAX_EXT_CALLED_PARTY_LENGTH = 0xa;
63 
64     // ADN offset
65     static final int ADN_BCD_NUMBER_LENGTH = 0;
66     static final int ADN_TON_AND_NPI = 1;
67     static final int ADN_DIALING_NUMBER_START = 2;
68     static final int ADN_DIALING_NUMBER_END = 11;
69     static final int ADN_CAPABILITY_ID = 12;
70     static final int ADN_EXTENSION_ID = 13;
71 
72     //***** Static Methods
73 
74     public static final Parcelable.Creator<AdnRecord> CREATOR
75             = new Parcelable.Creator<AdnRecord>() {
76         @Override
77         public AdnRecord createFromParcel(Parcel source) {
78             int efid;
79             int recordNumber;
80             String alphaTag;
81             String number;
82             String[] emails;
83 
84             efid = source.readInt();
85             recordNumber = source.readInt();
86             alphaTag = source.readString();
87             number = source.readString();
88             emails = source.readStringArray();
89 
90             return new AdnRecord(efid, recordNumber, alphaTag, number, emails);
91         }
92 
93         @Override
94         public AdnRecord[] newArray(int size) {
95             return new AdnRecord[size];
96         }
97     };
98 
99 
100     //***** Constructor
AdnRecord(byte[] record)101     public AdnRecord (byte[] record) {
102         this(0, 0, record);
103     }
104 
AdnRecord(int efid, int recordNumber, byte[] record)105     public AdnRecord (int efid, int recordNumber, byte[] record) {
106         this.mEfid = efid;
107         this.mRecordNumber = recordNumber;
108         parseRecord(record);
109     }
110 
AdnRecord(String alphaTag, String number)111     public AdnRecord (String alphaTag, String number) {
112         this(0, 0, alphaTag, number);
113     }
114 
AdnRecord(String alphaTag, String number, String[] emails)115     public AdnRecord (String alphaTag, String number, String[] emails) {
116         this(0, 0, alphaTag, number, emails);
117     }
118 
AdnRecord(int efid, int recordNumber, String alphaTag, String number, String[] emails)119     public AdnRecord (int efid, int recordNumber, String alphaTag, String number, String[] emails) {
120         this.mEfid = efid;
121         this.mRecordNumber = recordNumber;
122         this.mAlphaTag = alphaTag;
123         this.mNumber = number;
124         this.mEmails = emails;
125     }
126 
AdnRecord(int efid, int recordNumber, String alphaTag, String number)127     public AdnRecord(int efid, int recordNumber, String alphaTag, String number) {
128         this.mEfid = efid;
129         this.mRecordNumber = recordNumber;
130         this.mAlphaTag = alphaTag;
131         this.mNumber = number;
132         this.mEmails = null;
133     }
134 
135     //***** Instance Methods
136 
getAlphaTag()137     public String getAlphaTag() {
138         return mAlphaTag;
139     }
140 
getEfid()141     public int getEfid() {
142         return mEfid;
143     }
144 
getRecId()145     public int getRecId() {
146         return mRecordNumber;
147     }
148 
getNumber()149     public String getNumber() {
150         return mNumber;
151     }
152 
setNumber(String number)153     public void setNumber(String number) {
154         mNumber = number;
155     }
156 
getEmails()157     public String[] getEmails() {
158         return mEmails;
159     }
160 
setEmails(String[] emails)161     public void setEmails(String[] emails) {
162         this.mEmails = emails;
163     }
164 
165     @Override
toString()166     public String toString() {
167         return "ADN Record '" + mAlphaTag + "' '" + Rlog.pii(LOG_TAG, mNumber) + " "
168                 + Rlog.pii(LOG_TAG, mEmails) + "'";
169     }
170 
isEmpty()171     public boolean isEmpty() {
172         return TextUtils.isEmpty(mAlphaTag) && TextUtils.isEmpty(mNumber) && mEmails == null;
173     }
174 
hasExtendedRecord()175     public boolean hasExtendedRecord() {
176         return mExtRecord != 0 && mExtRecord != 0xff;
177     }
178 
179     /** Helper function for {@link #isEqual}. */
stringCompareNullEqualsEmpty(String s1, String s2)180     private static boolean stringCompareNullEqualsEmpty(String s1, String s2) {
181         if (s1 == s2) {
182             return true;
183         }
184         if (s1 == null) {
185             s1 = "";
186         }
187         if (s2 == null) {
188             s2 = "";
189         }
190         return (s1.equals(s2));
191     }
192 
isEqual(AdnRecord adn)193     public boolean isEqual(AdnRecord adn) {
194         return ( stringCompareNullEqualsEmpty(mAlphaTag, adn.mAlphaTag) &&
195                 stringCompareNullEqualsEmpty(mNumber, adn.mNumber) &&
196                 Arrays.equals(mEmails, adn.mEmails));
197     }
198     //***** Parcelable Implementation
199 
200     @Override
describeContents()201     public int describeContents() {
202         return 0;
203     }
204 
205     @Override
writeToParcel(Parcel dest, int flags)206     public void writeToParcel(Parcel dest, int flags) {
207         dest.writeInt(mEfid);
208         dest.writeInt(mRecordNumber);
209         dest.writeString(mAlphaTag);
210         dest.writeString(mNumber);
211         dest.writeStringArray(mEmails);
212     }
213 
214     /**
215      * Build adn hex byte array based on record size
216      * The format of byte array is defined in 51.011 10.5.1
217      *
218      * @param recordSize is the size X of EF record
219      * @return hex byte[recordSize] to be written to EF record
220      *          return null for wrong format of dialing number or tag
221      */
buildAdnString(int recordSize)222     public byte[] buildAdnString(int recordSize) {
223         byte[] bcdNumber;
224         byte[] byteTag;
225         byte[] adnString;
226         int footerOffset = recordSize - FOOTER_SIZE_BYTES;
227 
228         // create an empty record
229         adnString = new byte[recordSize];
230         for (int i = 0; i < recordSize; i++) {
231             adnString[i] = (byte) 0xFF;
232         }
233 
234         if (TextUtils.isEmpty(mNumber)) {
235             Rlog.w(LOG_TAG, "[buildAdnString] Empty dialing number");
236             return adnString;   // return the empty record (for delete)
237         } else if (mNumber.length()
238                 > (ADN_DIALING_NUMBER_END - ADN_DIALING_NUMBER_START + 1) * 2) {
239             Rlog.w(LOG_TAG,
240                     "[buildAdnString] Max length of dialing number is 20");
241             return null;
242         }
243 
244         byteTag = !TextUtils.isEmpty(mAlphaTag) ? GsmAlphabet.stringToGsm8BitPacked(mAlphaTag)
245                 : new byte[0];
246 
247         if (byteTag.length > footerOffset) {
248             Rlog.w(LOG_TAG, "[buildAdnString] Max length of tag is " + footerOffset);
249             return null;
250         } else {
251             bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
252                     mNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
253 
254             System.arraycopy(bcdNumber, 0, adnString,
255                     footerOffset + ADN_TON_AND_NPI, bcdNumber.length);
256 
257             adnString[footerOffset + ADN_BCD_NUMBER_LENGTH]
258                     = (byte) (bcdNumber.length);
259             adnString[footerOffset + ADN_CAPABILITY_ID]
260                     = (byte) 0xFF; // Capability Id
261             adnString[footerOffset + ADN_EXTENSION_ID]
262                     = (byte) 0xFF; // Extension Record Id
263 
264             if (byteTag.length > 0) {
265                 System.arraycopy(byteTag, 0, adnString, 0, byteTag.length);
266             }
267 
268             return adnString;
269         }
270     }
271 
272     /**
273      * See TS 51.011 10.5.10
274      */
275     public void
appendExtRecord(byte[] extRecord)276     appendExtRecord (byte[] extRecord) {
277         try {
278             if (extRecord.length != EXT_RECORD_LENGTH_BYTES) {
279                 return;
280             }
281 
282             if ((extRecord[0] & EXT_RECORD_TYPE_MASK)
283                     != EXT_RECORD_TYPE_ADDITIONAL_DATA) {
284                 return;
285             }
286 
287             if ((0xff & extRecord[1]) > MAX_EXT_CALLED_PARTY_LENGTH) {
288                 // invalid or empty record
289                 return;
290             }
291 
292             mNumber += PhoneNumberUtils.calledPartyBCDFragmentToString(
293                     extRecord,
294                     2,
295                     0xff & extRecord[1],
296                     PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
297 
298             // We don't support ext record chaining.
299 
300         } catch (RuntimeException ex) {
301             Rlog.w(LOG_TAG, "Error parsing AdnRecord ext record", ex);
302         }
303     }
304 
305     //***** Private Methods
306 
307     /**
308      * alphaTag and number are set to null on invalid format
309      */
310     private void
parseRecord(byte[] record)311     parseRecord(byte[] record) {
312         try {
313             mAlphaTag = IccUtils.adnStringFieldToString(
314                             record, 0, record.length - FOOTER_SIZE_BYTES);
315 
316             int footerOffset = record.length - FOOTER_SIZE_BYTES;
317 
318             int numberLength = 0xff & record[footerOffset];
319 
320             if (numberLength > MAX_NUMBER_SIZE_BYTES) {
321                 // Invalid number length
322                 mNumber = "";
323                 return;
324             }
325 
326             // Please note 51.011 10.5.1:
327             //
328             // "If the Dialling Number/SSC String does not contain
329             // a dialling number, e.g. a control string deactivating
330             // a service, the TON/NPI byte shall be set to 'FF' by
331             // the ME (see note 2)."
332 
333             mNumber = PhoneNumberUtils.calledPartyBCDToString(
334                     record,
335                     footerOffset + 1,
336                     numberLength,
337                     PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
338 
339 
340             mExtRecord = 0xff & record[record.length - 1];
341 
342             mEmails = null;
343 
344         } catch (RuntimeException ex) {
345             Rlog.w(LOG_TAG, "Error parsing AdnRecord", ex);
346             mNumber = "";
347             mAlphaTag = "";
348             mEmails = null;
349         }
350     }
351 }
352