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 
SmsPdu(byte[] data, int type)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          */
SmsPdu(byte[]data, int encoding, int type, int languageTable)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         }
getData()96         public byte[] getData(){
97             return mData;
98         }
getScAddress()99         public byte[] getScAddress(){
100             return mScAddress;
101         }
setEncoding(int encoding)102         public void setEncoding(int encoding) {
103             this.mEncoding = encoding;
104         }
getEncoding()105         public int getEncoding(){
106             return mEncoding;
107         }
getType()108         public int getType(){
109             return mType;
110         }
getUserDataMsgOffset()111         public int getUserDataMsgOffset() {
112             return mUserDataMsgOffset;
113         }
114         /** The user data message payload size in bytes - excluding the user data header. */
getUserDataMsgSize()115         public int getUserDataMsgSize() {
116             return mData.length - mUserDataMsgOffset;
117         }
118 
getLanguageShiftTable()119         public int getLanguageShiftTable() {
120             return mLanguageShiftTable;
121         }
122 
getLanguageTable()123         public int getLanguageTable() {
124             return mLanguageTable;
125         }
126 
getUserDataSeptetPadding()127         public int getUserDataSeptetPadding() {
128             return mUserDataSeptetPadding;
129         }
130 
getMsgSeptetCount()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          */
cdmaGetParameterOffset(byte parameterId)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 
cdmaGetSubParameterOffset(byte subParameterId)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 
cdmaChangeToDeliverPdu(long date)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 
gsmSubmitGetTpPidOffset()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 
gsmSubmitGetTpDcs()279         public int gsmSubmitGetTpDcs() {
280             return mData[gsmSubmitGetTpDcsOffset()] & 0xff;
281         }
282 
gsmSubmitHasUserDataHeader()283         public boolean gsmSubmitHasUserDataHeader() {
284             return ((mData[0] & 0xff) & TP_UDHI_MASK) == TP_UDHI_MASK;
285         }
286 
gsmSubmitGetTpDcsOffset()287         private int gsmSubmitGetTpDcsOffset() {
288             return gsmSubmitGetTpPidOffset() + 1;
289         }
290 
gsmSubmitGetTpUdlOffset()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         }
gsmSubmitGetTpUdOffset()303         private int gsmSubmitGetTpUdOffset() {
304             return gsmSubmitGetTpUdlOffset() + 1;
305         }
306 
gsmDecodeUserDataHeader()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 
gsmWriteDate(ByteArrayOutputStream header, long time)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          */
gsmChangeToDeliverPdu(long date, String originator)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 */
getEncodingString()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 
getNextConcatenatedRef()468     protected static int getNextConcatenatedRef() {
469         sConcatenatedRef += 1;
470         return sConcatenatedRef;
471     }
getSubmitPdus(String messageText, String address)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      */
getDeliverPdus(String messageText, String address, long date)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      */
decodePdu(byte[] data, int type)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      */
gsmStripOffScAddress(byte[] data)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 
gsmParseSubmitPdu(byte[] data)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