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