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