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