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