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