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