Home | History | Annotate | Download | only in map
      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 
     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          */
     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         }
    101         public byte[] getData(){
    102             return mData;
    103         }
    104         public byte[] getScAddress(){
    105             return mScAddress;
    106         }
    107         public void setEncoding(int encoding) {
    108             this.mEncoding = encoding;
    109         }
    110         public int getEncoding(){
    111             return mEncoding;
    112         }
    113         public int getType(){
    114             return mType;
    115         }
    116         public int getUserDataMsgOffset() {
    117             return mUserDataMsgOffset;
    118         }
    119         /** The user data message payload size in bytes - excluding the user data header. */
    120         public int getUserDataMsgSize() {
    121             return mData.length - mUserDataMsgOffset;
    122         }
    123 
    124         public int getLanguageShiftTable() {
    125             return mLanguageShiftTable;
    126         }
    127 
    128         public int getLanguageTable() {
    129             return mLanguageTable;
    130         }
    131 
    132         public int getUserDataSeptetPadding() {
    133             return mUserDataSeptetPadding;
    134         }
    135 
    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          */
    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 
    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 
    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 
    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 
    284         public int gsmSubmitGetTpDcs() {
    285             return mData[gsmSubmitGetTpDcsOffset()] & 0xff;
    286         }
    287 
    288         public boolean gsmSubmitHasUserDataHeader() {
    289             return ((mData[0] & 0xff) & TP_UDHI_MASK) == TP_UDHI_MASK;
    290         }
    291 
    292         private int gsmSubmitGetTpDcsOffset() {
    293             return gsmSubmitGetTpPidOffset() + 1;
    294         }
    295 
    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         }
    308         private int gsmSubmitGetTpUdOffset() {
    309             return gsmSubmitGetTpUdlOffset() + 1;
    310         }
    311 
    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 
    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          */
    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 */
    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 
    473     protected static int getNextConcatenatedRef() {
    474         sConcatenatedRef += 1;
    475         return sConcatenatedRef;
    476     }
    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      */
    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      */
    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      */
    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 
    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