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