1 /* 2 * Copyright (C) 2013 Samsung System LSI 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 package com.android.bluetooth.map; 16 17 import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; 18 19 import android.content.Context; 20 import android.telephony.PhoneNumberUtils; 21 import android.telephony.SmsManager; 22 import android.telephony.SmsMessage; 23 import android.telephony.TelephonyManager; 24 import android.util.Log; 25 26 import com.android.bluetooth.util.GsmAlphabet; 27 import com.android.internal.telephony.SmsConstants; 28 29 import java.io.ByteArrayInputStream; 30 import java.io.ByteArrayOutputStream; 31 import java.io.IOException; 32 import java.io.UnsupportedEncodingException; 33 import java.text.SimpleDateFormat; 34 import java.util.ArrayList; 35 import java.util.Calendar; 36 import java.util.Date; 37 import java.util.Random; 38 39 public class BluetoothMapSmsPdu { 40 41 private static final String TAG = "BluetoothMapSmsPdu"; 42 private static final boolean V = false; 43 private static final int INVALID_VALUE = -1; 44 public static final int SMS_TYPE_GSM = 1; 45 public static final int SMS_TYPE_CDMA = 2; 46 47 /** 48 * from SMS user data header information element identifiers. 49 * (see TS 23.040 9.2.3.24) 50 */ 51 private static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24; 52 private static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25; 53 54 /** 55 * Supported message types for CDMA SMS messages 56 * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1) 57 */ 58 private static final int MESSAGE_TYPE_DELIVER = 0x01; 59 60 /* We need to handle the SC-address mentioned in errata 4335. 61 * Since the definition could be read in three different ways, I have asked 62 * the car working group for clarification, and are awaiting confirmation that 63 * this clarification will go into the MAP spec: 64 * "The native format should be <sc_addr><tpdu> where <sc_addr> is <length><ton><1..10 octet 65 * of address> 66 * coded according to 24.011. The IEI is not to be used, as the fixed order of the data 67 * makes a type 4 LV 68 * information element sufficient. <length> is a single octet which value is the length of 69 * the value-field 70 * in octets including both the <ton> and the <address>." 71 * */ 72 73 74 public static class SmsPdu { 75 private byte[] mData; 76 private byte[] mScAddress = {0}; 77 // At the moment we do not use the scAddress, hence set the length to 0. 78 private int mUserDataMsgOffset = 0; 79 private int mEncoding; 80 private int mLanguageTable; 81 private int mLanguageShiftTable; 82 private int mType; 83 84 /* Members used for pdu decoding */ 85 private int mUserDataSeptetPadding = INVALID_VALUE; 86 private int mMsgSeptetCount = 0; 87 SmsPdu(byte[] data, int type)88 SmsPdu(byte[] data, int type) { 89 this.mData = data; 90 this.mEncoding = INVALID_VALUE; 91 this.mType = type; 92 this.mLanguageTable = INVALID_VALUE; 93 this.mLanguageShiftTable = INVALID_VALUE; 94 this.mUserDataMsgOffset = gsmSubmitGetTpUdOffset(); // Assume no user data header 95 } 96 97 /** 98 * Create a pdu instance based on the data generated on this device. 99 * @param data 100 * @param encoding 101 * @param type 102 * @param languageTable 103 */ SmsPdu(byte[] data, int encoding, int type, int languageTable)104 SmsPdu(byte[] data, int encoding, int type, int languageTable) { 105 this.mData = data; 106 this.mEncoding = encoding; 107 this.mType = type; 108 this.mLanguageTable = languageTable; 109 } 110 getData()111 public byte[] getData() { 112 return mData; 113 } 114 getScAddress()115 public byte[] getScAddress() { 116 return mScAddress; 117 } 118 setEncoding(int encoding)119 public void setEncoding(int encoding) { 120 this.mEncoding = encoding; 121 } 122 getEncoding()123 public int getEncoding() { 124 return mEncoding; 125 } 126 getType()127 public int getType() { 128 return mType; 129 } 130 getUserDataMsgOffset()131 public int getUserDataMsgOffset() { 132 return mUserDataMsgOffset; 133 } 134 135 /** The user data message payload size in bytes - excluding the user data header. */ getUserDataMsgSize()136 public int getUserDataMsgSize() { 137 return mData.length - mUserDataMsgOffset; 138 } 139 getLanguageShiftTable()140 public int getLanguageShiftTable() { 141 return mLanguageShiftTable; 142 } 143 getLanguageTable()144 public int getLanguageTable() { 145 return mLanguageTable; 146 } 147 getUserDataSeptetPadding()148 public int getUserDataSeptetPadding() { 149 return mUserDataSeptetPadding; 150 } 151 getMsgSeptetCount()152 public int getMsgSeptetCount() { 153 return mMsgSeptetCount; 154 } 155 156 157 /* PDU parsing/modification functionality */ 158 private static final byte TELESERVICE_IDENTIFIER = 0x00; 159 private static final byte SERVICE_CATEGORY = 0x01; 160 private static final byte ORIGINATING_ADDRESS = 0x02; 161 private static final byte ORIGINATING_SUB_ADDRESS = 0x03; 162 private static final byte DESTINATION_ADDRESS = 0x04; 163 private static final byte DESTINATION_SUB_ADDRESS = 0x05; 164 private static final byte BEARER_REPLY_OPTION = 0x06; 165 private static final byte CAUSE_CODES = 0x07; 166 private static final byte BEARER_DATA = 0x08; 167 168 /** 169 * Find and return the offset to the specified parameter ID 170 * @param parameterId The parameter ID to find 171 * @return the offset in number of bytes to the parameterID entry in the pdu data. 172 * The byte at the offset contains the parameter ID, the byte following contains the 173 * parameter length, and offset + 2 is the first byte of the parameter data. 174 */ cdmaGetParameterOffset(byte parameterId)175 private int cdmaGetParameterOffset(byte parameterId) { 176 ByteArrayInputStream pdu = new ByteArrayInputStream(mData); 177 int offset = 0; 178 boolean found = false; 179 180 try { 181 pdu.skip(1); // Skip the message type 182 183 while (pdu.available() > 0) { 184 int currentId = pdu.read(); 185 int currentLen = pdu.read(); 186 187 if (currentId == parameterId) { 188 found = true; 189 break; 190 } else { 191 pdu.skip(currentLen); 192 offset += 2 + currentLen; 193 } 194 } 195 pdu.close(); 196 } catch (Exception e) { 197 Log.e(TAG, "cdmaGetParameterOffset: ", e); 198 } 199 200 if (found) { 201 return offset; 202 } else { 203 return 0; 204 } 205 } 206 207 private static final byte BEARER_DATA_MSG_ID = 0x00; 208 cdmaGetSubParameterOffset(byte subParameterId)209 private int cdmaGetSubParameterOffset(byte subParameterId) { 210 ByteArrayInputStream pdu = new ByteArrayInputStream(mData); 211 int offset = 0; 212 boolean found = false; 213 offset = cdmaGetParameterOffset(BEARER_DATA) 214 + 2; // Add to offset the BEARER_DATA parameter id and length bytes 215 pdu.skip(offset); 216 try { 217 218 while (pdu.available() > 0) { 219 int currentId = pdu.read(); 220 int currentLen = pdu.read(); 221 222 if (currentId == subParameterId) { 223 found = true; 224 break; 225 } else { 226 pdu.skip(currentLen); 227 offset += 2 + currentLen; 228 } 229 } 230 pdu.close(); 231 } catch (Exception e) { 232 Log.e(TAG, "cdmaGetParameterOffset: ", e); 233 } 234 235 if (found) { 236 return offset; 237 } else { 238 return 0; 239 } 240 } 241 242 cdmaChangeToDeliverPdu(long date)243 public void cdmaChangeToDeliverPdu(long date) { 244 /* Things to change: 245 * - Message Type in bearer data (Not the overall point-to-point type) 246 * - Change address ID from destination to originating (sub addresses are not used) 247 * - A time stamp is not mandatory. 248 */ 249 int offset; 250 if (mData == null) { 251 throw new IllegalArgumentException("Unable to convert PDU to Deliver type"); 252 } 253 offset = cdmaGetParameterOffset(DESTINATION_ADDRESS); 254 if (mData.length < offset) { 255 throw new IllegalArgumentException("Unable to convert PDU to Deliver type"); 256 } 257 mData[offset] = ORIGINATING_ADDRESS; 258 259 offset = cdmaGetParameterOffset(DESTINATION_SUB_ADDRESS); 260 if (mData.length < offset) { 261 throw new IllegalArgumentException("Unable to convert PDU to Deliver type"); 262 } 263 mData[offset] = ORIGINATING_SUB_ADDRESS; 264 265 offset = cdmaGetSubParameterOffset(BEARER_DATA_MSG_ID); 266 267 if (mData.length > (2 + offset)) { 268 int tmp = mData[offset + 2] 269 & 0xff; // Skip the subParam ID and length, and read the first byte. 270 // Mask out the type 271 tmp &= 0x0f; 272 // Set the new type 273 tmp |= ((MESSAGE_TYPE_DELIVER << 4) & 0xf0); 274 // Store the result 275 mData[offset + 2] = (byte) tmp; 276 277 } else { 278 throw new IllegalArgumentException("Unable to convert PDU to Deliver type"); 279 } 280 /* TODO: Do we need to change anything in the user data? Not sure if the user 281 data is 282 * just encoded using GSM encoding, or it is an actual GSM submit PDU 283 * embedded 284 * in the user data? 285 */ 286 } 287 288 private static final byte TP_MIT_DELIVER = 0x00; // bit 0 and 1 289 private static final byte TP_MMS_NO_MORE = 0x04; // bit 2 290 private static final byte TP_RP_NO_REPLY_PATH = 0x00; // bit 7 291 private static final byte TP_UDHI_MASK = 0x40; // bit 6 292 private static final byte TP_SRI_NO_REPORT = 0x00; // bit 5 293 gsmSubmitGetTpPidOffset()294 private int gsmSubmitGetTpPidOffset() { 295 /* calculate the offset to TP_PID. 296 * The TP-DA has variable length, and the length excludes the 2 byte length and type 297 * headers. 298 * The TP-DA is two bytes within the PDU */ 299 // data[2] is the number of semi-octets in the phone number (ceil result) 300 int offset = 2 + ((mData[2] + 1) & 0xff) / 2 + 2; 301 // max length of TP_DA is 12 bytes + two byte offset. 302 if ((offset > mData.length) || (offset > (2 + 12))) { 303 throw new IllegalArgumentException( 304 "wrongly formatted gsm submit PDU. offset = " + offset); 305 } 306 return offset; 307 } 308 gsmSubmitGetTpDcs()309 public int gsmSubmitGetTpDcs() { 310 return mData[gsmSubmitGetTpDcsOffset()] & 0xff; 311 } 312 gsmSubmitHasUserDataHeader()313 public boolean gsmSubmitHasUserDataHeader() { 314 return ((mData[0] & 0xff) & TP_UDHI_MASK) == TP_UDHI_MASK; 315 } 316 gsmSubmitGetTpDcsOffset()317 private int gsmSubmitGetTpDcsOffset() { 318 return gsmSubmitGetTpPidOffset() + 1; 319 } 320 gsmSubmitGetTpUdlOffset()321 private int gsmSubmitGetTpUdlOffset() { 322 switch (((mData[0] & 0xff) & (0x08 | 0x04)) >> 2) { 323 case 0: // Not TP-VP present 324 return gsmSubmitGetTpPidOffset() + 2; 325 case 1: // TP-VP relative format 326 return gsmSubmitGetTpPidOffset() + 2 + 1; 327 case 2: // TP-VP enhanced format 328 case 3: // TP-VP absolute format 329 break; 330 } 331 return gsmSubmitGetTpPidOffset() + 2 + 7; 332 } 333 gsmSubmitGetTpUdOffset()334 private int gsmSubmitGetTpUdOffset() { 335 return gsmSubmitGetTpUdlOffset() + 1; 336 } 337 gsmDecodeUserDataHeader()338 public void gsmDecodeUserDataHeader() { 339 ByteArrayInputStream pdu = new ByteArrayInputStream(mData); 340 341 pdu.skip(gsmSubmitGetTpUdlOffset()); 342 int userDataLength = pdu.read(); 343 if (gsmSubmitHasUserDataHeader()) { 344 int userDataHeaderLength = pdu.read(); 345 346 // This part is only needed to extract the language info, hence only needed for 7 347 // bit encoding 348 if (mEncoding == SmsConstants.ENCODING_7BIT) { 349 byte[] udh = new byte[userDataHeaderLength]; 350 try { 351 pdu.read(udh); 352 } catch (IOException e) { 353 Log.w(TAG, "unable to read userDataHeader", e); 354 } 355 int[] tableValue = getTableFromByteArray(udh); 356 mLanguageTable = tableValue[0]; 357 mLanguageShiftTable = tableValue[1]; 358 359 int headerBits = (userDataHeaderLength + 1) * 8; 360 int headerSeptets = headerBits / 7; 361 headerSeptets += (headerBits % 7) > 0 ? 1 : 0; 362 mUserDataSeptetPadding = (headerSeptets * 7) - headerBits; 363 mMsgSeptetCount = userDataLength - headerSeptets; 364 } 365 mUserDataMsgOffset = gsmSubmitGetTpUdOffset() + userDataHeaderLength 366 + 1; // Add the byte containing the length 367 } else { 368 mUserDataSeptetPadding = 0; 369 mMsgSeptetCount = userDataLength; 370 mUserDataMsgOffset = gsmSubmitGetTpUdOffset(); 371 } 372 if (V) { 373 Log.v(TAG, "encoding:" + mEncoding); 374 Log.v(TAG, "msgSeptetCount:" + mMsgSeptetCount); 375 Log.v(TAG, "userDataSeptetPadding:" + mUserDataSeptetPadding); 376 Log.v(TAG, "languageShiftTable:" + mLanguageShiftTable); 377 Log.v(TAG, "languageTable:" + mLanguageTable); 378 Log.v(TAG, "userDataMsgOffset:" + mUserDataMsgOffset); 379 } 380 } 381 gsmWriteDate(ByteArrayOutputStream header, long time)382 private void gsmWriteDate(ByteArrayOutputStream header, long time) 383 throws UnsupportedEncodingException { 384 SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmmss"); 385 Date date = new Date(time); 386 String timeStr = format.format(date); // Format to YYMMDDTHHMMSS UTC time 387 if (V) { 388 Log.v(TAG, "Generated time string: " + timeStr); 389 } 390 byte[] timeChars = timeStr.getBytes("US-ASCII"); 391 392 for (int i = 0, n = timeStr.length(); i < n; i += 2) { 393 header.write((timeChars[i + 1] - 0x30) << 4 | (timeChars[i] 394 - 0x30)); // Offset from ascii char to decimal value 395 } 396 397 Calendar cal = Calendar.getInstance(); 398 int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (15 * 60 399 * 1000); /* offset in quarters of an hour */ 400 String offsetString; 401 if (offset < 0) { 402 offsetString = String.format("%1$02d", -(offset)); 403 char[] offsetChars = offsetString.toCharArray(); 404 header.write((offsetChars[1] - 0x30) << 4 | 0x40 | (offsetChars[0] - 0x30)); 405 } else { 406 offsetString = String.format("%1$02d", offset); 407 char[] offsetChars = offsetString.toCharArray(); 408 header.write((offsetChars[1] - 0x30) << 4 | (offsetChars[0] - 0x30)); 409 } 410 } 411 412 /* private void gsmSubmitExtractUserData() { 413 int userDataLength = data[gsmSubmitGetTpUdlOffset()]; 414 userData = new byte[userDataLength]; 415 System.arraycopy(userData, 0, data, gsmSubmitGetTpUdOffset(), userDataLength); 416 417 }*/ 418 419 /** 420 * Change the GSM Submit Pdu data in this object to a deliver PDU: 421 * - Build the new header with deliver PDU type, originator and time stamp. 422 * - Extract encoding details from the submit PDU 423 * - Extract user data length and user data from the submitPdu 424 * - Build the new PDU 425 * @param date the time stamp to include (The value is the number of milliseconds since 426 * Jan. 1, 1970 GMT.) 427 * @param originator the phone number to include in the deliver PDU header. Any undesired 428 * characters, 429 * such as '-' will be striped from this string. 430 */ gsmChangeToDeliverPdu(long date, String originator)431 public void gsmChangeToDeliverPdu(long date, String originator) { 432 ByteArrayOutputStream newPdu = 433 new ByteArrayOutputStream(22); // 22 is the max length of the deliver pdu header 434 byte[] encodedAddress; 435 int userDataLength = 0; 436 try { 437 newPdu.write( 438 TP_MIT_DELIVER | TP_MMS_NO_MORE | TP_RP_NO_REPLY_PATH | TP_SRI_NO_REPORT 439 | (mData[0] & 0xff) & TP_UDHI_MASK); 440 encodedAddress = 441 PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(originator); 442 if (encodedAddress != null) { 443 int padding = 444 (encodedAddress[encodedAddress.length - 1] & 0xf0) == 0xf0 ? 1 : 0; 445 encodedAddress[0] = (byte) ((encodedAddress[0] - 1) * 2 446 - padding); // Convert from octet length to semi octet length 447 // Insert originator address into the header - this includes the length 448 newPdu.write(encodedAddress); 449 } else { 450 newPdu.write(0); /* zero length */ 451 newPdu.write(0x81); /* International type */ 452 } 453 454 newPdu.write(mData[gsmSubmitGetTpPidOffset()]); 455 newPdu.write(mData[gsmSubmitGetTpDcsOffset()]); 456 // Generate service center time stamp 457 gsmWriteDate(newPdu, date); 458 userDataLength = (mData[gsmSubmitGetTpUdlOffset()] & 0xff); 459 newPdu.write(userDataLength); 460 // Copy the pdu user data - keep in mind that the userDataLength is not the 461 // length in bytes for 7-bit encoding. 462 newPdu.write(mData, gsmSubmitGetTpUdOffset(), 463 mData.length - gsmSubmitGetTpUdOffset()); 464 } catch (IOException e) { 465 Log.e(TAG, "", e); 466 throw new IllegalArgumentException("Failed to change type to deliver PDU."); 467 } 468 mData = newPdu.toByteArray(); 469 } 470 471 /* SMS encoding to bmessage strings */ 472 473 /** get the encoding type as a bMessage string */ getEncodingString()474 public String getEncodingString() { 475 if (mType == SMS_TYPE_GSM) { 476 switch (mEncoding) { 477 case SmsMessage.ENCODING_7BIT: 478 if (mLanguageTable == 0) { 479 return "G-7BIT"; 480 } else { 481 return "G-7BITEXT"; 482 } 483 case SmsMessage.ENCODING_8BIT: 484 return "G-8BIT"; 485 case SmsMessage.ENCODING_16BIT: 486 return "G-16BIT"; 487 case SmsMessage.ENCODING_UNKNOWN: 488 default: 489 return ""; 490 } 491 } else /* SMS_TYPE_CDMA */ { 492 switch (mEncoding) { 493 case SmsMessage.ENCODING_7BIT: 494 return "C-7ASCII"; 495 case SmsMessage.ENCODING_8BIT: 496 return "C-8BIT"; 497 case SmsMessage.ENCODING_16BIT: 498 return "C-UNICODE"; 499 case SmsMessage.ENCODING_KSC5601: 500 return "C-KOREAN"; 501 case SmsMessage.ENCODING_UNKNOWN: 502 default: 503 return ""; 504 } 505 } 506 } 507 } 508 509 private static int sConcatenatedRef = new Random().nextInt(256); 510 getNextConcatenatedRef()511 protected static int getNextConcatenatedRef() { 512 sConcatenatedRef += 1; 513 return sConcatenatedRef; 514 } 515 getSubmitPdus(Context context, String messageText, String address)516 public static ArrayList<SmsPdu> getSubmitPdus(Context context, String messageText, 517 String address) { 518 /* Use the generic GSM/CDMA SMS Message functionality within Android to generate the 519 * SMS PDU's as once generated to send the SMS message. 520 */ 521 522 int activePhone = context.getSystemService(TelephonyManager.class) 523 .getCurrentPhoneType(); 524 int phoneType; 525 int[] ted = SmsMessage.calculateLength((CharSequence) messageText, false); 526 527 SmsPdu newPdu; 528 String destinationAddress; 529 int msgCount = ted[0]; 530 int encoding; 531 int languageTable; 532 int languageShiftTable; 533 int refNumber = getNextConcatenatedRef() & 0x00FF; 534 SmsManager smsMng = SmsManager.getDefault(); 535 ArrayList<String> smsFragments = smsMng.divideMessage(messageText); 536 ArrayList<SmsPdu> pdus = new ArrayList<SmsPdu>(msgCount); 537 byte[] data; 538 539 // Default to GSM, as this code should not be used, if we neither have CDMA not GSM. 540 phoneType = (activePhone == PHONE_TYPE_CDMA) ? SMS_TYPE_CDMA : SMS_TYPE_GSM; 541 encoding = ted[3]; 542 languageTable = ted[4]; 543 languageShiftTable = ted[5]; 544 destinationAddress = PhoneNumberUtils.stripSeparators(address); 545 if (destinationAddress == null || destinationAddress.length() < 2) { 546 destinationAddress = 547 "12"; // Ensure we add a number at least 2 digits as specified in the GSM spec. 548 } 549 550 if (msgCount == 1) { 551 data = SmsMessage.getSubmitPdu(null, destinationAddress, smsFragments.get(0), 552 false).encodedMessage; 553 newPdu = new SmsPdu(data, encoding, phoneType, languageTable); 554 pdus.add(newPdu); 555 } else { 556 /* This code is a reduced copy of the actual code used in the Android SMS sub system, 557 * hence the comments have been left untouched. */ 558 for (int i = 0; i < msgCount; i++) { 559 data = SmsMessage.getSubmitPduEncodedMessage(phoneType == SMS_TYPE_GSM, 560 destinationAddress, smsFragments.get(i), encoding, languageTable, 561 languageShiftTable, refNumber, i + 1, msgCount); 562 newPdu = new SmsPdu(data, encoding, phoneType, languageTable); 563 pdus.add(newPdu); 564 } 565 } 566 567 return pdus; 568 } 569 570 /** 571 * Generate a list of deliver PDUs. The messageText and address parameters must be different 572 * from null, 573 * for CDMA the date can be omitted (and will be ignored if supplied) 574 * @param messageText The text to include. 575 * @param address The originator address. 576 * @param date The delivery time stamp. 577 * @return 578 */ getDeliverPdus(Context context, String messageText, String address, long date)579 public static ArrayList<SmsPdu> getDeliverPdus(Context context, String messageText, 580 String address, long date) { 581 ArrayList<SmsPdu> deliverPdus = getSubmitPdus(context, messageText, address); 582 583 /* 584 * For CDMA the only difference between deliver and submit pdus are the messageType, 585 * which is set in encodeMessageId, (the higher 4 bits of the 1st byte 586 * of the Message identification sub parameter data.) and the address type. 587 * 588 * For GSM, a larger part of the header needs to be generated. 589 */ 590 for (SmsPdu currentPdu : deliverPdus) { 591 if (currentPdu.getType() == SMS_TYPE_CDMA) { 592 currentPdu.cdmaChangeToDeliverPdu(date); 593 } else { /* SMS_TYPE_GSM */ 594 currentPdu.gsmChangeToDeliverPdu(date, address); 595 } 596 } 597 598 return deliverPdus; 599 } 600 601 602 /** 603 * The decoding only supports decoding the actual textual content of the PDU received 604 * from the MAP client. (As the Android system has no interface to send pre encoded PDUs) 605 * The destination address must be extracted from the bmessage vCard(s). 606 */ decodePdu(byte[] data, int type)607 public static String decodePdu(byte[] data, int type) { 608 String ret = ""; 609 if (type == SMS_TYPE_CDMA) { 610 /* This is able to handle both submit and deliver PDUs */ 611 SmsMessage smsMessage = SmsMessage.createSmsSubmitPdu(data, true); 612 if (smsMessage != null) { 613 ret = smsMessage.getMessageBody(); 614 } 615 } else { 616 /* For GSM, there is no submit pdu decoder, and most parser utils are private, and 617 only minded for submit pdus */ 618 ret = gsmParseSubmitPdu(data); 619 } 620 return ret; 621 } 622 623 /* At the moment we do not support using a SC-address. Use this function to strip off 624 * the SC-address before parsing it to the SmsPdu. (this was added in errata 4335) 625 */ gsmStripOffScAddress(byte[] data)626 private static byte[] gsmStripOffScAddress(byte[] data) { 627 /* The format of a native GSM SMS is: <sc-address><pdu> where sc-address is: 628 * <length-byte><type-byte><number-bytes> */ 629 int addressLength = data[0] & 0xff; // Treat the byte value as an unsigned value 630 // We could verify that the address-length is no longer than 11 bytes 631 if (addressLength >= data.length) { 632 throw new IllegalArgumentException( 633 "Length of address exeeds the length of the PDU data."); 634 } 635 int pduLength = data.length - (1 + addressLength); 636 byte[] newData = new byte[pduLength]; 637 System.arraycopy(data, 1 + addressLength, newData, 0, pduLength); 638 return newData; 639 } 640 gsmParseSubmitPdu(byte[] data)641 private static String gsmParseSubmitPdu(byte[] data) { 642 /* Things to do: 643 * - extract hasUsrData bit 644 * - extract TP-DCS -> Character set, compressed etc. 645 * - extract user data header to get the language properties 646 * - extract user data 647 * - decode the string */ 648 //Strip off the SC-address before parsing 649 SmsPdu pdu = new SmsPdu(gsmStripOffScAddress(data), SMS_TYPE_GSM); 650 boolean userDataCompressed = false; 651 int dataCodingScheme = pdu.gsmSubmitGetTpDcs(); 652 int encodingType = SmsConstants.ENCODING_UNKNOWN; 653 String messageBody = null; 654 655 // Look up the data encoding scheme 656 if ((dataCodingScheme & 0x80) == 0) { 657 // Bits 7..4 == 0xxx 658 userDataCompressed = (0 != (dataCodingScheme & 0x20)); 659 660 if (userDataCompressed) { 661 Log.w(TAG, "4 - Unsupported SMS data coding scheme " + "(compression) " + ( 662 dataCodingScheme & 0xff)); 663 } else { 664 switch ((dataCodingScheme >> 2) & 0x3) { 665 case 0: // GSM 7 bit default alphabet 666 encodingType = SmsConstants.ENCODING_7BIT; 667 break; 668 669 case 2: // UCS 2 (16bit) 670 encodingType = SmsConstants.ENCODING_16BIT; 671 break; 672 673 case 1: // 8 bit data 674 case 3: // reserved 675 Log.w(TAG, "1 - Unsupported SMS data coding scheme " + (dataCodingScheme 676 & 0xff)); 677 encodingType = SmsConstants.ENCODING_8BIT; 678 break; 679 } 680 } 681 } else if ((dataCodingScheme & 0xf0) == 0xf0) { 682 userDataCompressed = false; 683 684 if (0 == (dataCodingScheme & 0x04)) { 685 // GSM 7 bit default alphabet 686 encodingType = SmsConstants.ENCODING_7BIT; 687 } else { 688 // 8 bit data 689 encodingType = SmsConstants.ENCODING_8BIT; 690 } 691 } else if ((dataCodingScheme & 0xF0) == 0xC0 || (dataCodingScheme & 0xF0) == 0xD0 692 || (dataCodingScheme & 0xF0) == 0xE0) { 693 // 3GPP TS 23.038 V7.0.0 (2006-03) section 4 694 695 // 0xC0 == 7 bit, don't store 696 // 0xD0 == 7 bit, store 697 // 0xE0 == UCS-2, store 698 699 if ((dataCodingScheme & 0xF0) == 0xE0) { 700 encodingType = SmsConstants.ENCODING_16BIT; 701 } else { 702 encodingType = SmsConstants.ENCODING_7BIT; 703 } 704 705 userDataCompressed = false; 706 707 // bit 0x04 reserved 708 } else if ((dataCodingScheme & 0xC0) == 0x80) { 709 // 3GPP TS 23.038 V7.0.0 (2006-03) section 4 710 // 0x80..0xBF == Reserved coding groups 711 if (dataCodingScheme == 0x84) { 712 // This value used for KSC5601 by carriers in Korea. 713 encodingType = SmsConstants.ENCODING_KSC5601; 714 } else { 715 Log.w(TAG, "5 - Unsupported SMS data coding scheme " + (dataCodingScheme & 0xff)); 716 } 717 } else { 718 Log.w(TAG, "3 - Unsupported SMS data coding scheme " + (dataCodingScheme & 0xff)); 719 } 720 721 pdu.setEncoding(encodingType); 722 pdu.gsmDecodeUserDataHeader(); 723 724 try { 725 switch (encodingType) { 726 case SmsConstants.ENCODING_UNKNOWN: 727 case SmsConstants.ENCODING_8BIT: 728 Log.w(TAG, "Unknown encoding type: " + encodingType); 729 messageBody = null; 730 break; 731 732 case SmsConstants.ENCODING_7BIT: 733 messageBody = GsmAlphabet.gsm7BitPackedToString(pdu.getData(), 734 pdu.getUserDataMsgOffset(), pdu.getMsgSeptetCount(), 735 pdu.getUserDataSeptetPadding(), pdu.getLanguageTable(), 736 pdu.getLanguageShiftTable()); 737 Log.i(TAG, "Decoded as 7BIT: " + messageBody); 738 739 break; 740 741 case SmsConstants.ENCODING_16BIT: 742 messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(), 743 pdu.getUserDataMsgSize(), "utf-16"); 744 Log.i(TAG, "Decoded as 16BIT: " + messageBody); 745 break; 746 747 case SmsConstants.ENCODING_KSC5601: 748 messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(), 749 pdu.getUserDataMsgSize(), "KSC5601"); 750 Log.i(TAG, "Decoded as KSC5601: " + messageBody); 751 break; 752 } 753 } catch (UnsupportedEncodingException e) { 754 Log.e(TAG, "Unsupported encoding type???", e); // This should never happen. 755 return null; 756 } 757 758 return messageBody; 759 } 760 getTableFromByteArray(byte[] data)761 private static int[] getTableFromByteArray(byte[] data) { 762 ByteArrayInputStream inStream = new ByteArrayInputStream(data); 763 /** tableValue[0]: languageTable 764 * tableValue[1]: languageShiftTable */ 765 int[] tableValue = new int[2]; 766 while (inStream.available() > 0) { 767 int id = inStream.read(); 768 int length = inStream.read(); 769 switch (id) { 770 case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT: 771 tableValue[1] = inStream.read(); 772 break; 773 case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT: 774 tableValue[0] = inStream.read(); 775 break; 776 default: 777 inStream.skip(length); 778 } 779 } 780 return tableValue; 781 } 782 783 } 784