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