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