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