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