1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony.gsm;
18 
19 import android.telephony.PhoneNumberUtils;
20 import android.text.format.Time;
21 import android.telephony.Rlog;
22 import android.content.res.Resources;
23 import android.text.TextUtils;
24 
25 import com.android.internal.telephony.EncodeException;
26 import com.android.internal.telephony.GsmAlphabet;
27 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
28 import com.android.internal.telephony.uicc.IccUtils;
29 import com.android.internal.telephony.SmsHeader;
30 import com.android.internal.telephony.SmsMessageBase;
31 import com.android.internal.telephony.Sms7BitEncodingTranslator;
32 
33 import java.io.ByteArrayOutputStream;
34 import java.io.UnsupportedEncodingException;
35 import java.text.ParseException;
36 
37 import static com.android.internal.telephony.SmsConstants.MessageClass;
38 import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
39 import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
40 import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
41 import static com.android.internal.telephony.SmsConstants.ENCODING_16BIT;
42 import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601;
43 import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS;
44 import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES;
45 import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
46 
47 /**
48  * A Short Message Service message.
49  *
50  */
51 public class SmsMessage extends SmsMessageBase {
52     static final String LOG_TAG = "SmsMessage";
53     private static final boolean VDBG = false;
54 
55     private MessageClass messageClass;
56 
57     /**
58      * TP-Message-Type-Indicator
59      * 9.2.3
60      */
61     private int mMti;
62 
63     /** TP-Protocol-Identifier (TP-PID) */
64     private int mProtocolIdentifier;
65 
66     // TP-Data-Coding-Scheme
67     // see TS 23.038
68     private int mDataCodingScheme;
69 
70     // TP-Reply-Path
71     // e.g. 23.040 9.2.2.1
72     private boolean mReplyPathPresent = false;
73 
74     /** The address of the receiver. */
75     private GsmSmsAddress mRecipientAddress;
76 
77     /**
78      *  TP-Status - status of a previously submitted SMS.
79      *  This field applies to SMS-STATUS-REPORT messages.  0 indicates success;
80      *  see TS 23.040, 9.2.3.15 for description of other possible values.
81      */
82     private int mStatus;
83 
84     /**
85      *  TP-Status - status of a previously submitted SMS.
86      *  This field is true iff the message is a SMS-STATUS-REPORT message.
87      */
88     private boolean mIsStatusReportMessage = false;
89 
90     private int mVoiceMailCount = 0;
91 
92     public static class SubmitPdu extends SubmitPduBase {
93     }
94 
95     /**
96      * Create an SmsMessage from a raw PDU.
97      */
createFromPdu(byte[] pdu)98     public static SmsMessage createFromPdu(byte[] pdu) {
99         try {
100             SmsMessage msg = new SmsMessage();
101             msg.parsePdu(pdu);
102             return msg;
103         } catch (RuntimeException ex) {
104             Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
105             return null;
106         } catch (OutOfMemoryError e) {
107             Rlog.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
108             return null;
109         }
110     }
111 
112     /**
113      * 3GPP TS 23.040 9.2.3.9 specifies that Type Zero messages are indicated
114      * by TP_PID field set to value 0x40
115      */
isTypeZero()116     public boolean isTypeZero() {
117         return (mProtocolIdentifier == 0x40);
118     }
119 
120     /**
121      * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
122      * +CMT unsolicited response (PDU mode, of course)
123      *  +CMT: [&lt;alpha>],<length><CR><LF><pdu>
124      *
125      * Only public for debugging
126      *
127      * {@hide}
128      */
newFromCMT(byte[] pdu)129     public static SmsMessage newFromCMT(byte[] pdu) {
130         try {
131             SmsMessage msg = new SmsMessage();
132             msg.parsePdu(pdu);
133             return msg;
134         } catch (RuntimeException ex) {
135             Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
136             return null;
137         }
138     }
139 
140     /** @hide */
newFromCDS(byte[] pdu)141     public static SmsMessage newFromCDS(byte[] pdu) {
142         try {
143             SmsMessage msg = new SmsMessage();
144             msg.parsePdu(pdu);
145             return msg;
146         } catch (RuntimeException ex) {
147             Rlog.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex);
148             return null;
149         }
150     }
151 
152     /**
153      * Create an SmsMessage from an SMS EF record.
154      *
155      * @param index Index of SMS record. This should be index in ArrayList
156      *              returned by SmsManager.getAllMessagesFromSim + 1.
157      * @param data Record data.
158      * @return An SmsMessage representing the record.
159      *
160      * @hide
161      */
createFromEfRecord(int index, byte[] data)162     public static SmsMessage createFromEfRecord(int index, byte[] data) {
163         try {
164             SmsMessage msg = new SmsMessage();
165 
166             msg.mIndexOnIcc = index;
167 
168             // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
169             // or STORED_UNSENT
170             // See TS 51.011 10.5.3
171             if ((data[0] & 1) == 0) {
172                 Rlog.w(LOG_TAG,
173                         "SMS parsing failed: Trying to parse a free record");
174                 return null;
175             } else {
176                 msg.mStatusOnIcc = data[0] & 0x07;
177             }
178 
179             int size = data.length - 1;
180 
181             // Note: Data may include trailing FF's.  That's OK; message
182             // should still parse correctly.
183             byte[] pdu = new byte[size];
184             System.arraycopy(data, 1, pdu, 0, size);
185             msg.parsePdu(pdu);
186             return msg;
187         } catch (RuntimeException ex) {
188             Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
189             return null;
190         }
191     }
192 
193     /**
194      * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
195      * length in bytes (not hex chars) less the SMSC header
196      */
getTPLayerLengthForPDU(String pdu)197     public static int getTPLayerLengthForPDU(String pdu) {
198         int len = pdu.length() / 2;
199         int smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
200 
201         return len - smscLen - 1;
202     }
203 
204     /**
205      * Get an SMS-SUBMIT PDU for a destination address and a message
206      *
207      * @param scAddress Service Centre address.  Null means use default.
208      * @return a <code>SubmitPdu</code> containing the encoded SC
209      *         address, if applicable, and the encoded message.
210      *         Returns null on encode error.
211      * @hide
212      */
getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested, byte[] header)213     public static SubmitPdu getSubmitPdu(String scAddress,
214             String destinationAddress, String message,
215             boolean statusReportRequested, byte[] header) {
216         return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
217                 ENCODING_UNKNOWN, 0, 0);
218     }
219 
220 
221     /**
222      * Get an SMS-SUBMIT PDU for a destination address and a message using the
223      * specified encoding.
224      *
225      * @param scAddress Service Centre address.  Null means use default.
226      * @param encoding Encoding defined by constants in
227      *        com.android.internal.telephony.SmsConstants.ENCODING_*
228      * @param languageTable
229      * @param languageShiftTable
230      * @return a <code>SubmitPdu</code> containing the encoded SC
231      *         address, if applicable, and the encoded message.
232      *         Returns null on encode error.
233      * @hide
234      */
getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested, byte[] header, int encoding, int languageTable, int languageShiftTable)235     public static SubmitPdu getSubmitPdu(String scAddress,
236             String destinationAddress, String message,
237             boolean statusReportRequested, byte[] header, int encoding,
238             int languageTable, int languageShiftTable) {
239 
240         // Perform null parameter checks.
241         if (message == null || destinationAddress == null) {
242             return null;
243         }
244 
245         if (encoding == ENCODING_UNKNOWN) {
246             // Find the best encoding to use
247             TextEncodingDetails ted = calculateLength(message, false);
248             encoding = ted.codeUnitSize;
249             languageTable = ted.languageTable;
250             languageShiftTable = ted.languageShiftTable;
251 
252             if (encoding == ENCODING_7BIT &&
253                     (languageTable != 0 || languageShiftTable != 0)) {
254                 if (header != null) {
255                     SmsHeader smsHeader = SmsHeader.fromByteArray(header);
256                     if (smsHeader.languageTable != languageTable
257                             || smsHeader.languageShiftTable != languageShiftTable) {
258                         Rlog.w(LOG_TAG, "Updating language table in SMS header: "
259                                 + smsHeader.languageTable + " -> " + languageTable + ", "
260                                 + smsHeader.languageShiftTable + " -> " + languageShiftTable);
261                         smsHeader.languageTable = languageTable;
262                         smsHeader.languageShiftTable = languageShiftTable;
263                         header = SmsHeader.toByteArray(smsHeader);
264                     }
265                 } else {
266                     SmsHeader smsHeader = new SmsHeader();
267                     smsHeader.languageTable = languageTable;
268                     smsHeader.languageShiftTable = languageShiftTable;
269                     header = SmsHeader.toByteArray(smsHeader);
270                 }
271             }
272         }
273 
274         SubmitPdu ret = new SubmitPdu();
275         // MTI = SMS-SUBMIT, UDHI = header != null
276         byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
277         ByteArrayOutputStream bo = getSubmitPduHead(
278                 scAddress, destinationAddress, mtiByte,
279                 statusReportRequested, ret);
280 
281         // User Data (and length)
282         byte[] userData;
283         try {
284             if (encoding == ENCODING_7BIT) {
285                 userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
286                         languageTable, languageShiftTable);
287             } else { //assume UCS-2
288                 try {
289                     userData = encodeUCS2(message, header);
290                 } catch(UnsupportedEncodingException uex) {
291                     Rlog.e(LOG_TAG,
292                             "Implausible UnsupportedEncodingException ",
293                             uex);
294                     return null;
295                 }
296             }
297         } catch (EncodeException ex) {
298             // Encoding to the 7-bit alphabet failed. Let's see if we can
299             // send it as a UCS-2 encoded message
300             try {
301                 userData = encodeUCS2(message, header);
302                 encoding = ENCODING_16BIT;
303             } catch(UnsupportedEncodingException uex) {
304                 Rlog.e(LOG_TAG,
305                         "Implausible UnsupportedEncodingException ",
306                         uex);
307                 return null;
308             }
309         }
310 
311         if (encoding == ENCODING_7BIT) {
312             if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
313                 // Message too long
314                 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
315                 return null;
316             }
317             // TP-Data-Coding-Scheme
318             // Default encoding, uncompressed
319             // To test writing messages to the SIM card, change this value 0x00
320             // to 0x12, which means "bits 1 and 0 contain message class, and the
321             // class is 2". Note that this takes effect for the sender. In other
322             // words, messages sent by the phone with this change will end up on
323             // the receiver's SIM card. You can then send messages to yourself
324             // (on a phone with this change) and they'll end up on the SIM card.
325             bo.write(0x00);
326         } else { // assume UCS-2
327             if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
328                 // Message too long
329                 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
330                 return null;
331             }
332             // TP-Data-Coding-Scheme
333             // UCS-2 encoding, uncompressed
334             bo.write(0x08);
335         }
336 
337         // (no TP-Validity-Period)
338         bo.write(userData, 0, userData.length);
339         ret.encodedMessage = bo.toByteArray();
340         return ret;
341     }
342 
343     /**
344      * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary
345      *
346      * @return encoded message as UCS2
347      * @throws UnsupportedEncodingException
348      */
encodeUCS2(String message, byte[] header)349     private static byte[] encodeUCS2(String message, byte[] header)
350         throws UnsupportedEncodingException {
351         byte[] userData, textPart;
352         textPart = message.getBytes("utf-16be");
353 
354         if (header != null) {
355             // Need 1 byte for UDHL
356             userData = new byte[header.length + textPart.length + 1];
357 
358             userData[0] = (byte)header.length;
359             System.arraycopy(header, 0, userData, 1, header.length);
360             System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
361         }
362         else {
363             userData = textPart;
364         }
365         byte[] ret = new byte[userData.length+1];
366         ret[0] = (byte) (userData.length & 0xff );
367         System.arraycopy(userData, 0, ret, 1, userData.length);
368         return ret;
369     }
370 
371     /**
372      * Get an SMS-SUBMIT PDU for a destination address and a message
373      *
374      * @param scAddress Service Centre address.  Null means use default.
375      * @return a <code>SubmitPdu</code> containing the encoded SC
376      *         address, if applicable, and the encoded message.
377      *         Returns null on encode error.
378      */
getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested)379     public static SubmitPdu getSubmitPdu(String scAddress,
380             String destinationAddress, String message,
381             boolean statusReportRequested) {
382 
383         return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
384     }
385 
386     /**
387      * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
388      *
389      * @param scAddress Service Centre address. null == use default
390      * @param destinationAddress the address of the destination for the message
391      * @param destinationPort the port to deliver the message to at the
392      *        destination
393      * @param data the data for the message
394      * @return a <code>SubmitPdu</code> containing the encoded SC
395      *         address, if applicable, and the encoded message.
396      *         Returns null on encode error.
397      */
getSubmitPdu(String scAddress, String destinationAddress, int destinationPort, byte[] data, boolean statusReportRequested)398     public static SubmitPdu getSubmitPdu(String scAddress,
399             String destinationAddress, int destinationPort, byte[] data,
400             boolean statusReportRequested) {
401 
402         SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
403         portAddrs.destPort = destinationPort;
404         portAddrs.origPort = 0;
405         portAddrs.areEightBits = false;
406 
407         SmsHeader smsHeader = new SmsHeader();
408         smsHeader.portAddrs = portAddrs;
409 
410         byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
411 
412         if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
413             Rlog.e(LOG_TAG, "SMS data message may only contain "
414                     + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes");
415             return null;
416         }
417 
418         SubmitPdu ret = new SubmitPdu();
419         ByteArrayOutputStream bo = getSubmitPduHead(
420                 scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT,
421                                                             // TP-UDHI = true
422                 statusReportRequested, ret);
423 
424         // TP-Data-Coding-Scheme
425         // No class, 8 bit data
426         bo.write(0x04);
427 
428         // (no TP-Validity-Period)
429 
430         // Total size
431         bo.write(data.length + smsHeaderData.length + 1);
432 
433         // User data header
434         bo.write(smsHeaderData.length);
435         bo.write(smsHeaderData, 0, smsHeaderData.length);
436 
437         // User data
438         bo.write(data, 0, data.length);
439 
440         ret.encodedMessage = bo.toByteArray();
441         return ret;
442     }
443 
444     /**
445      * Create the beginning of a SUBMIT PDU.  This is the part of the
446      * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu},
447      * one of which takes a byte array and the other of which takes a
448      * <code>String</code>.
449      *
450      * @param scAddress Service Centre address. null == use default
451      * @param destinationAddress the address of the destination for the message
452      * @param mtiByte
453      * @param ret <code>SubmitPdu</code> containing the encoded SC
454      *        address, if applicable, and the encoded message
455      */
getSubmitPduHead( String scAddress, String destinationAddress, byte mtiByte, boolean statusReportRequested, SubmitPdu ret)456     private static ByteArrayOutputStream getSubmitPduHead(
457             String scAddress, String destinationAddress, byte mtiByte,
458             boolean statusReportRequested, SubmitPdu ret) {
459         ByteArrayOutputStream bo = new ByteArrayOutputStream(
460                 MAX_USER_DATA_BYTES + 40);
461 
462         // SMSC address with length octet, or 0
463         if (scAddress == null) {
464             ret.encodedScAddress = null;
465         } else {
466             ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
467                     scAddress);
468         }
469 
470         // TP-Message-Type-Indicator (and friends)
471         if (statusReportRequested) {
472             // Set TP-Status-Report-Request bit.
473             mtiByte |= 0x20;
474             if (VDBG) Rlog.d(LOG_TAG, "SMS status report requested");
475         }
476         bo.write(mtiByte);
477 
478         // space for TP-Message-Reference
479         bo.write(0);
480 
481         byte[] daBytes;
482 
483         daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
484 
485         // destination address length in BCD digits, ignoring TON byte and pad
486         // TODO Should be better.
487         bo.write((daBytes.length - 1) * 2
488                 - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
489 
490         // destination address
491         bo.write(daBytes, 0, daBytes.length);
492 
493         // TP-Protocol-Identifier
494         bo.write(0);
495         return bo;
496     }
497 
498     private static class PduParser {
499         byte mPdu[];
500         int mCur;
501         SmsHeader mUserDataHeader;
502         byte[] mUserData;
503         int mUserDataSeptetPadding;
504 
PduParser(byte[] pdu)505         PduParser(byte[] pdu) {
506             mPdu = pdu;
507             mCur = 0;
508             mUserDataSeptetPadding = 0;
509         }
510 
511         /**
512          * Parse and return the SC address prepended to SMS messages coming via
513          * the TS 27.005 / AT interface.  Returns null on invalid address
514          */
getSCAddress()515         String getSCAddress() {
516             int len;
517             String ret;
518 
519             // length of SC Address
520             len = getByte();
521 
522             if (len == 0) {
523                 // no SC address
524                 ret = null;
525             } else {
526                 // SC address
527                 try {
528                     ret = PhoneNumberUtils
529                             .calledPartyBCDToString(mPdu, mCur, len);
530                 } catch (RuntimeException tr) {
531                     Rlog.d(LOG_TAG, "invalid SC address: ", tr);
532                     ret = null;
533                 }
534             }
535 
536             mCur += len;
537 
538             return ret;
539         }
540 
541         /**
542          * returns non-sign-extended byte value
543          */
getByte()544         int getByte() {
545             return mPdu[mCur++] & 0xff;
546         }
547 
548         /**
549          * Any address except the SC address (eg, originating address) See TS
550          * 23.040 9.1.2.5
551          */
getAddress()552         GsmSmsAddress getAddress() {
553             GsmSmsAddress ret;
554 
555             // "The Address-Length field is an integer representation of
556             // the number field, i.e. excludes any semi-octet containing only
557             // fill bits."
558             // The TOA field is not included as part of this
559             int addressLength = mPdu[mCur] & 0xff;
560             int lengthBytes = 2 + (addressLength + 1) / 2;
561 
562             try {
563                 ret = new GsmSmsAddress(mPdu, mCur, lengthBytes);
564             } catch (ParseException e) {
565                 ret = null;
566                 //This is caught by createFromPdu(byte[] pdu)
567                 throw new RuntimeException(e.getMessage());
568             }
569 
570             mCur += lengthBytes;
571 
572             return ret;
573         }
574 
575         /**
576          * Parses an SC timestamp and returns a currentTimeMillis()-style
577          * timestamp
578          */
579 
getSCTimestampMillis()580         long getSCTimestampMillis() {
581             // TP-Service-Centre-Time-Stamp
582             int year = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
583             int month = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
584             int day = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
585             int hour = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
586             int minute = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
587             int second = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
588 
589             // For the timezone, the most significant bit of the
590             // least significant nibble is the sign byte
591             // (meaning the max range of this field is 79 quarter-hours,
592             // which is more than enough)
593 
594             byte tzByte = mPdu[mCur++];
595 
596             // Mask out sign bit.
597             int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
598 
599             timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
600 
601             Time time = new Time(Time.TIMEZONE_UTC);
602 
603             // It's 2006.  Should I really support years < 2000?
604             time.year = year >= 90 ? year + 1900 : year + 2000;
605             time.month = month - 1;
606             time.monthDay = day;
607             time.hour = hour;
608             time.minute = minute;
609             time.second = second;
610 
611             // Timezone offset is in quarter hours.
612             return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
613         }
614 
615         /**
616          * Pulls the user data out of the PDU, and separates the payload from
617          * the header if there is one.
618          *
619          * @param hasUserDataHeader true if there is a user data header
620          * @param dataInSeptets true if the data payload is in septets instead
621          *  of octets
622          * @return the number of septets or octets in the user data payload
623          */
constructUserData(boolean hasUserDataHeader, boolean dataInSeptets)624         int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
625             int offset = mCur;
626             int userDataLength = mPdu[offset++] & 0xff;
627             int headerSeptets = 0;
628             int userDataHeaderLength = 0;
629 
630             if (hasUserDataHeader) {
631                 userDataHeaderLength = mPdu[offset++] & 0xff;
632 
633                 byte[] udh = new byte[userDataHeaderLength];
634                 System.arraycopy(mPdu, offset, udh, 0, userDataHeaderLength);
635                 mUserDataHeader = SmsHeader.fromByteArray(udh);
636                 offset += userDataHeaderLength;
637 
638                 int headerBits = (userDataHeaderLength + 1) * 8;
639                 headerSeptets = headerBits / 7;
640                 headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
641                 mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
642             }
643 
644             int bufferLen;
645             if (dataInSeptets) {
646                 /*
647                  * Here we just create the user data length to be the remainder of
648                  * the pdu minus the user data header, since userDataLength means
649                  * the number of uncompressed septets.
650                  */
651                 bufferLen = mPdu.length - offset;
652             } else {
653                 /*
654                  * userDataLength is the count of octets, so just subtract the
655                  * user data header.
656                  */
657                 bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
658                 if (bufferLen < 0) {
659                     bufferLen = 0;
660                 }
661             }
662 
663             mUserData = new byte[bufferLen];
664             System.arraycopy(mPdu, offset, mUserData, 0, mUserData.length);
665             mCur = offset;
666 
667             if (dataInSeptets) {
668                 // Return the number of septets
669                 int count = userDataLength - headerSeptets;
670                 // If count < 0, return 0 (means UDL was probably incorrect)
671                 return count < 0 ? 0 : count;
672             } else {
673                 // Return the number of octets
674                 return mUserData.length;
675             }
676         }
677 
678         /**
679          * Returns the user data payload, not including the headers
680          *
681          * @return the user data payload, not including the headers
682          */
getUserData()683         byte[] getUserData() {
684             return mUserData;
685         }
686 
687         /**
688          * Returns an object representing the user data headers
689          *
690          * {@hide}
691          */
getUserDataHeader()692         SmsHeader getUserDataHeader() {
693             return mUserDataHeader;
694         }
695 
696         /**
697          * Interprets the user data payload as packed GSM 7bit characters, and
698          * decodes them into a String.
699          *
700          * @param septetCount the number of septets in the user data payload
701          * @return a String with the decoded characters
702          */
getUserDataGSM7Bit(int septetCount, int languageTable, int languageShiftTable)703         String getUserDataGSM7Bit(int septetCount, int languageTable,
704                 int languageShiftTable) {
705             String ret;
706 
707             ret = GsmAlphabet.gsm7BitPackedToString(mPdu, mCur, septetCount,
708                     mUserDataSeptetPadding, languageTable, languageShiftTable);
709 
710             mCur += (septetCount * 7) / 8;
711 
712             return ret;
713         }
714 
715         /**
716          * Interprets the user data payload as pack GSM 8-bit (a GSM alphabet string that's
717          * stored in 8-bit unpacked format) characters, and decodes them into a String.
718          *
719          * @param byteCount the number of byest in the user data payload
720          * @return a String with the decoded characters
721          */
getUserDataGSM8bit(int byteCount)722         String getUserDataGSM8bit(int byteCount) {
723             String ret;
724 
725             ret = GsmAlphabet.gsm8BitUnpackedToString(mPdu, mCur, byteCount);
726 
727             mCur += byteCount;
728 
729             return ret;
730         }
731 
732         /**
733          * Interprets the user data payload as UCS2 characters, and
734          * decodes them into a String.
735          *
736          * @param byteCount the number of bytes in the user data payload
737          * @return a String with the decoded characters
738          */
getUserDataUCS2(int byteCount)739         String getUserDataUCS2(int byteCount) {
740             String ret;
741 
742             try {
743                 ret = new String(mPdu, mCur, byteCount, "utf-16");
744             } catch (UnsupportedEncodingException ex) {
745                 ret = "";
746                 Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
747             }
748 
749             mCur += byteCount;
750             return ret;
751         }
752 
753         /**
754          * Interprets the user data payload as KSC-5601 characters, and
755          * decodes them into a String.
756          *
757          * @param byteCount the number of bytes in the user data payload
758          * @return a String with the decoded characters
759          */
getUserDataKSC5601(int byteCount)760         String getUserDataKSC5601(int byteCount) {
761             String ret;
762 
763             try {
764                 ret = new String(mPdu, mCur, byteCount, "KSC5601");
765             } catch (UnsupportedEncodingException ex) {
766                 ret = "";
767                 Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
768             }
769 
770             mCur += byteCount;
771             return ret;
772         }
773 
moreDataPresent()774         boolean moreDataPresent() {
775             return (mPdu.length > mCur);
776         }
777     }
778 
779     /**
780      * Calculates the number of SMS's required to encode the message body and
781      * the number of characters remaining until the next message.
782      *
783      * @param msgBody the message to encode
784      * @param use7bitOnly ignore (but still count) illegal characters if true
785      * @return TextEncodingDetails
786      */
calculateLength(CharSequence msgBody, boolean use7bitOnly)787     public static TextEncodingDetails calculateLength(CharSequence msgBody,
788             boolean use7bitOnly) {
789         CharSequence newMsgBody = null;
790         Resources r = Resources.getSystem();
791         if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
792             newMsgBody  = Sms7BitEncodingTranslator.translate(msgBody);
793         }
794         if (TextUtils.isEmpty(newMsgBody)) {
795             newMsgBody = msgBody;
796         }
797         TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(newMsgBody, use7bitOnly);
798         if (ted == null) {
799             return SmsMessageBase.calcUnicodeEncodingDetails(newMsgBody);
800         }
801         return ted;
802     }
803 
804     /** {@inheritDoc} */
805     @Override
getProtocolIdentifier()806     public int getProtocolIdentifier() {
807         return mProtocolIdentifier;
808     }
809 
810     /**
811      * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages.
812      * @return the TP-DCS field of the SMS header
813      */
getDataCodingScheme()814     int getDataCodingScheme() {
815         return mDataCodingScheme;
816     }
817 
818     /** {@inheritDoc} */
819     @Override
isReplace()820     public boolean isReplace() {
821         return (mProtocolIdentifier & 0xc0) == 0x40
822                 && (mProtocolIdentifier & 0x3f) > 0
823                 && (mProtocolIdentifier & 0x3f) < 8;
824     }
825 
826     /** {@inheritDoc} */
827     @Override
isCphsMwiMessage()828     public boolean isCphsMwiMessage() {
829         return ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear()
830                 || ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
831     }
832 
833     /** {@inheritDoc} */
834     @Override
isMWIClearMessage()835     public boolean isMWIClearMessage() {
836         if (mIsMwi && !mMwiSense) {
837             return true;
838         }
839 
840         return mOriginatingAddress != null
841                 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear();
842     }
843 
844     /** {@inheritDoc} */
845     @Override
isMWISetMessage()846     public boolean isMWISetMessage() {
847         if (mIsMwi && mMwiSense) {
848             return true;
849         }
850 
851         return mOriginatingAddress != null
852                 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
853     }
854 
855     /** {@inheritDoc} */
856     @Override
isMwiDontStore()857     public boolean isMwiDontStore() {
858         if (mIsMwi && mMwiDontStore) {
859             return true;
860         }
861 
862         if (isCphsMwiMessage()) {
863             // See CPHS 4.2 Section B.4.2.1
864             // If the user data is a single space char, do not store
865             // the message. Otherwise, store and display as usual
866             if (" ".equals(getMessageBody())) {
867                 return true;
868             }
869         }
870 
871         return false;
872     }
873 
874     /** {@inheritDoc} */
875     @Override
getStatus()876     public int getStatus() {
877         return mStatus;
878     }
879 
880     /** {@inheritDoc} */
881     @Override
isStatusReportMessage()882     public boolean isStatusReportMessage() {
883         return mIsStatusReportMessage;
884     }
885 
886     /** {@inheritDoc} */
887     @Override
isReplyPathPresent()888     public boolean isReplyPathPresent() {
889         return mReplyPathPresent;
890     }
891 
892     /**
893      * TS 27.005 3.1, &lt;pdu&gt; definition "In the case of SMS: 3GPP TS 24.011 [6]
894      * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
895      * ME/TA converts each octet of TP data unit into two IRA character long
896      * hex number (e.g. octet with integer value 42 is presented to TE as two
897      * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
898      * something else...
899      */
parsePdu(byte[] pdu)900     private void parsePdu(byte[] pdu) {
901         mPdu = pdu;
902         // Rlog.d(LOG_TAG, "raw sms message:");
903         // Rlog.d(LOG_TAG, s);
904 
905         PduParser p = new PduParser(pdu);
906 
907         mScAddress = p.getSCAddress();
908 
909         if (mScAddress != null) {
910             if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress);
911         }
912 
913         // TODO(mkf) support reply path, user data header indicator
914 
915         // TP-Message-Type-Indicator
916         // 9.2.3
917         int firstByte = p.getByte();
918 
919         mMti = firstByte & 0x3;
920         switch (mMti) {
921         // TP-Message-Type-Indicator
922         // 9.2.3
923         case 0:
924         case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
925                 //This should be processed in the same way as MTI == 0 (Deliver)
926             parseSmsDeliver(p, firstByte);
927             break;
928         case 1:
929             parseSmsSubmit(p, firstByte);
930             break;
931         case 2:
932             parseSmsStatusReport(p, firstByte);
933             break;
934         default:
935             // TODO(mkf) the rest of these
936             throw new RuntimeException("Unsupported message type");
937         }
938     }
939 
940     /**
941      * Parses a SMS-STATUS-REPORT message.
942      *
943      * @param p A PduParser, cued past the first byte.
944      * @param firstByte The first byte of the PDU, which contains MTI, etc.
945      */
parseSmsStatusReport(PduParser p, int firstByte)946     private void parseSmsStatusReport(PduParser p, int firstByte) {
947         mIsStatusReportMessage = true;
948 
949         // TP-Message-Reference
950         mMessageRef = p.getByte();
951         // TP-Recipient-Address
952         mRecipientAddress = p.getAddress();
953         // TP-Service-Centre-Time-Stamp
954         mScTimeMillis = p.getSCTimestampMillis();
955         p.getSCTimestampMillis();
956         // TP-Status
957         mStatus = p.getByte();
958 
959         // The following are optional fields that may or may not be present.
960         if (p.moreDataPresent()) {
961             // TP-Parameter-Indicator
962             int extraParams = p.getByte();
963             int moreExtraParams = extraParams;
964             while ((moreExtraParams & 0x80) != 0) {
965                 // We only know how to parse a few extra parameters, all
966                 // indicated in the first TP-PI octet, so skip over any
967                 // additional TP-PI octets.
968                 moreExtraParams = p.getByte();
969             }
970             // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator,
971             // only process the byte if the reserved bits (bits3 to 6) are zero.
972             if ((extraParams & 0x78) == 0) {
973                 // TP-Protocol-Identifier
974                 if ((extraParams & 0x01) != 0) {
975                     mProtocolIdentifier = p.getByte();
976                 }
977                 // TP-Data-Coding-Scheme
978                 if ((extraParams & 0x02) != 0) {
979                     mDataCodingScheme = p.getByte();
980                 }
981                 // TP-User-Data-Length (implies existence of TP-User-Data)
982                 if ((extraParams & 0x04) != 0) {
983                     boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
984                     parseUserData(p, hasUserDataHeader);
985                 }
986             }
987         }
988     }
989 
parseSmsDeliver(PduParser p, int firstByte)990     private void parseSmsDeliver(PduParser p, int firstByte) {
991         mReplyPathPresent = (firstByte & 0x80) == 0x80;
992 
993         mOriginatingAddress = p.getAddress();
994 
995         if (mOriginatingAddress != null) {
996             if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
997                     + mOriginatingAddress.address);
998         }
999 
1000         // TP-Protocol-Identifier (TP-PID)
1001         // TS 23.040 9.2.3.9
1002         mProtocolIdentifier = p.getByte();
1003 
1004         // TP-Data-Coding-Scheme
1005         // see TS 23.038
1006         mDataCodingScheme = p.getByte();
1007 
1008         if (VDBG) {
1009             Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
1010                     + " data coding scheme: " + mDataCodingScheme);
1011         }
1012 
1013         mScTimeMillis = p.getSCTimestampMillis();
1014 
1015         if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
1016 
1017         boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
1018 
1019         parseUserData(p, hasUserDataHeader);
1020     }
1021 
1022     /**
1023      * Parses a SMS-SUBMIT message.
1024      *
1025      * @param p A PduParser, cued past the first byte.
1026      * @param firstByte The first byte of the PDU, which contains MTI, etc.
1027      */
parseSmsSubmit(PduParser p, int firstByte)1028     private void parseSmsSubmit(PduParser p, int firstByte) {
1029         mReplyPathPresent = (firstByte & 0x80) == 0x80;
1030 
1031         // TP-MR (TP-Message Reference)
1032         mMessageRef = p.getByte();
1033 
1034         mRecipientAddress = p.getAddress();
1035 
1036         if (mRecipientAddress != null) {
1037             if (VDBG) Rlog.v(LOG_TAG, "SMS recipient address: " + mRecipientAddress.address);
1038         }
1039 
1040         // TP-Protocol-Identifier (TP-PID)
1041         // TS 23.040 9.2.3.9
1042         mProtocolIdentifier = p.getByte();
1043 
1044         // TP-Data-Coding-Scheme
1045         // see TS 23.038
1046         mDataCodingScheme = p.getByte();
1047 
1048         if (VDBG) {
1049             Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
1050                     + " data coding scheme: " + mDataCodingScheme);
1051         }
1052 
1053         // TP-Validity-Period-Format
1054         int validityPeriodLength = 0;
1055         int validityPeriodFormat = ((firstByte>>3) & 0x3);
1056         if (0x0 == validityPeriodFormat) /* 00, TP-VP field not present*/
1057         {
1058             validityPeriodLength = 0;
1059         }
1060         else if (0x2 == validityPeriodFormat) /* 10, TP-VP: relative format*/
1061         {
1062             validityPeriodLength = 1;
1063         }
1064         else /* other case, 11 or 01, TP-VP: absolute or enhanced format*/
1065         {
1066             validityPeriodLength = 7;
1067         }
1068 
1069         // TP-Validity-Period is not used on phone, so just ignore it for now.
1070         while (validityPeriodLength-- > 0)
1071         {
1072             p.getByte();
1073         }
1074 
1075         boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
1076 
1077         parseUserData(p, hasUserDataHeader);
1078     }
1079 
1080     /**
1081      * Parses the User Data of an SMS.
1082      *
1083      * @param p The current PduParser.
1084      * @param hasUserDataHeader Indicates whether a header is present in the
1085      *                          User Data.
1086      */
parseUserData(PduParser p, boolean hasUserDataHeader)1087     private void parseUserData(PduParser p, boolean hasUserDataHeader) {
1088         boolean hasMessageClass = false;
1089         boolean userDataCompressed = false;
1090 
1091         int encodingType = ENCODING_UNKNOWN;
1092 
1093         // Look up the data encoding scheme
1094         if ((mDataCodingScheme & 0x80) == 0) {
1095             userDataCompressed = (0 != (mDataCodingScheme & 0x20));
1096             hasMessageClass = (0 != (mDataCodingScheme & 0x10));
1097 
1098             if (userDataCompressed) {
1099                 Rlog.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
1100                         + "(compression) " + (mDataCodingScheme & 0xff));
1101             } else {
1102                 switch ((mDataCodingScheme >> 2) & 0x3) {
1103                 case 0: // GSM 7 bit default alphabet
1104                     encodingType = ENCODING_7BIT;
1105                     break;
1106 
1107                 case 2: // UCS 2 (16bit)
1108                     encodingType = ENCODING_16BIT;
1109                     break;
1110 
1111                 case 1: // 8 bit data
1112                     //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
1113                     //that's stored in 8-bit unpacked format) characters.
1114                     Resources r = Resources.getSystem();
1115                     if (r.getBoolean(com.android.internal.
1116                             R.bool.config_sms_decode_gsm_8bit_data)) {
1117                         encodingType = ENCODING_8BIT;
1118                         break;
1119                     }
1120 
1121                 case 3: // reserved
1122                     Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
1123                             + (mDataCodingScheme & 0xff));
1124                     encodingType = ENCODING_8BIT;
1125                     break;
1126                 }
1127             }
1128         } else if ((mDataCodingScheme & 0xf0) == 0xf0) {
1129             hasMessageClass = true;
1130             userDataCompressed = false;
1131 
1132             if (0 == (mDataCodingScheme & 0x04)) {
1133                 // GSM 7 bit default alphabet
1134                 encodingType = ENCODING_7BIT;
1135             } else {
1136                 // 8 bit data
1137                 encodingType = ENCODING_8BIT;
1138             }
1139         } else if ((mDataCodingScheme & 0xF0) == 0xC0
1140                 || (mDataCodingScheme & 0xF0) == 0xD0
1141                 || (mDataCodingScheme & 0xF0) == 0xE0) {
1142             // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
1143 
1144             // 0xC0 == 7 bit, don't store
1145             // 0xD0 == 7 bit, store
1146             // 0xE0 == UCS-2, store
1147 
1148             if ((mDataCodingScheme & 0xF0) == 0xE0) {
1149                 encodingType = ENCODING_16BIT;
1150             } else {
1151                 encodingType = ENCODING_7BIT;
1152             }
1153 
1154             userDataCompressed = false;
1155             boolean active = ((mDataCodingScheme & 0x08) == 0x08);
1156             // bit 0x04 reserved
1157 
1158             // VM - If TP-UDH is present, these values will be overwritten
1159             if ((mDataCodingScheme & 0x03) == 0x00) {
1160                 mIsMwi = true; /* Indicates vmail */
1161                 mMwiSense = active;/* Indicates vmail notification set/clear */
1162                 mMwiDontStore = ((mDataCodingScheme & 0xF0) == 0xC0);
1163 
1164                 /* Set voice mail count based on notification bit */
1165                 if (active == true) {
1166                     mVoiceMailCount = -1; // unknown number of messages waiting
1167                 } else {
1168                     mVoiceMailCount = 0; // no unread messages
1169                 }
1170 
1171                 Rlog.w(LOG_TAG, "MWI in DCS for Vmail. DCS = "
1172                         + (mDataCodingScheme & 0xff) + " Dont store = "
1173                         + mMwiDontStore + " vmail count = " + mVoiceMailCount);
1174 
1175             } else {
1176                 mIsMwi = false;
1177                 Rlog.w(LOG_TAG, "MWI in DCS for fax/email/other: "
1178                         + (mDataCodingScheme & 0xff));
1179             }
1180         } else if ((mDataCodingScheme & 0xC0) == 0x80) {
1181             // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
1182             // 0x80..0xBF == Reserved coding groups
1183             if (mDataCodingScheme == 0x84) {
1184                 // This value used for KSC5601 by carriers in Korea.
1185                 encodingType = ENCODING_KSC5601;
1186             } else {
1187                 Rlog.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
1188                         + (mDataCodingScheme & 0xff));
1189             }
1190         } else {
1191             Rlog.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
1192                     + (mDataCodingScheme & 0xff));
1193         }
1194 
1195         // set both the user data and the user data header.
1196         int count = p.constructUserData(hasUserDataHeader,
1197                 encodingType == ENCODING_7BIT);
1198         this.mUserData = p.getUserData();
1199         this.mUserDataHeader = p.getUserDataHeader();
1200 
1201         /*
1202          * Look for voice mail indication in TP_UDH TS23.040 9.2.3.24
1203          * ieid = 1 (0x1) (SPECIAL_SMS_MSG_IND)
1204          * ieidl =2 octets
1205          * ieda msg_ind_type = 0x00 (voice mail; discard sms )or
1206          *                   = 0x80 (voice mail; store sms)
1207          * msg_count = 0x00 ..0xFF
1208          */
1209         if (hasUserDataHeader && (mUserDataHeader.specialSmsMsgList.size() != 0)) {
1210             for (SmsHeader.SpecialSmsMsg msg : mUserDataHeader.specialSmsMsgList) {
1211                 int msgInd = msg.msgIndType & 0xff;
1212                 /*
1213                  * TS 23.040 V6.8.1 Sec 9.2.3.24.2
1214                  * bits 1 0 : basic message indication type
1215                  * bits 4 3 2 : extended message indication type
1216                  * bits 6 5 : Profile id bit 7 storage type
1217                  */
1218                 if ((msgInd == 0) || (msgInd == 0x80)) {
1219                     mIsMwi = true;
1220                     if (msgInd == 0x80) {
1221                         /* Store message because TP_UDH indicates so*/
1222                         mMwiDontStore = false;
1223                     } else if (mMwiDontStore == false) {
1224                         /* Storage bit is not set by TP_UDH
1225                          * Check for conflict
1226                          * between message storage bit in TP_UDH
1227                          * & DCS. The message shall be stored if either of
1228                          * the one indicates so.
1229                          * TS 23.040 V6.8.1 Sec 9.2.3.24.2
1230                          */
1231                         if (!((((mDataCodingScheme & 0xF0) == 0xD0)
1232                                || ((mDataCodingScheme & 0xF0) == 0xE0))
1233                                && ((mDataCodingScheme & 0x03) == 0x00))) {
1234                             /* Even DCS did not have voice mail with Storage bit
1235                              * 3GPP TS 23.038 V7.0.0 section 4
1236                              * So clear this flag*/
1237                             mMwiDontStore = true;
1238                         }
1239                     }
1240 
1241                     mVoiceMailCount = msg.msgCount & 0xff;
1242 
1243                     /*
1244                      * In the event of a conflict between message count setting
1245                      * and DCS then the Message Count in the TP-UDH shall
1246                      * override the indication in the TP-DCS. Set voice mail
1247                      * notification based on count in TP-UDH
1248                      */
1249                     if (mVoiceMailCount > 0)
1250                         mMwiSense = true;
1251                     else
1252                         mMwiSense = false;
1253 
1254                     Rlog.w(LOG_TAG, "MWI in TP-UDH for Vmail. Msg Ind = " + msgInd
1255                             + " Dont store = " + mMwiDontStore + " Vmail count = "
1256                             + mVoiceMailCount);
1257 
1258                     /*
1259                      * There can be only one IE for each type of message
1260                      * indication in TP_UDH. In the event they are duplicated
1261                      * last occurence will be used. Hence the for loop
1262                      */
1263                 } else {
1264                     Rlog.w(LOG_TAG, "TP_UDH fax/email/"
1265                             + "extended msg/multisubscriber profile. Msg Ind = " + msgInd);
1266                 }
1267             } // end of for
1268         } // end of if UDH
1269 
1270         switch (encodingType) {
1271         case ENCODING_UNKNOWN:
1272             mMessageBody = null;
1273             break;
1274 
1275         case ENCODING_8BIT:
1276             //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
1277             //that's stored in 8-bit unpacked format) characters.
1278             Resources r = Resources.getSystem();
1279             if (r.getBoolean(com.android.internal.
1280                     R.bool.config_sms_decode_gsm_8bit_data)) {
1281                 mMessageBody = p.getUserDataGSM8bit(count);
1282             } else {
1283                 mMessageBody = null;
1284             }
1285             break;
1286 
1287         case ENCODING_7BIT:
1288             mMessageBody = p.getUserDataGSM7Bit(count,
1289                     hasUserDataHeader ? mUserDataHeader.languageTable : 0,
1290                     hasUserDataHeader ? mUserDataHeader.languageShiftTable : 0);
1291             break;
1292 
1293         case ENCODING_16BIT:
1294             mMessageBody = p.getUserDataUCS2(count);
1295             break;
1296 
1297         case ENCODING_KSC5601:
1298             mMessageBody = p.getUserDataKSC5601(count);
1299             break;
1300         }
1301 
1302         if (VDBG) Rlog.v(LOG_TAG, "SMS message body (raw): '" + mMessageBody + "'");
1303 
1304         if (mMessageBody != null) {
1305             parseMessageBody();
1306         }
1307 
1308         if (!hasMessageClass) {
1309             messageClass = MessageClass.UNKNOWN;
1310         } else {
1311             switch (mDataCodingScheme & 0x3) {
1312             case 0:
1313                 messageClass = MessageClass.CLASS_0;
1314                 break;
1315             case 1:
1316                 messageClass = MessageClass.CLASS_1;
1317                 break;
1318             case 2:
1319                 messageClass = MessageClass.CLASS_2;
1320                 break;
1321             case 3:
1322                 messageClass = MessageClass.CLASS_3;
1323                 break;
1324             }
1325         }
1326     }
1327 
1328     /**
1329      * {@inheritDoc}
1330      */
1331     @Override
getMessageClass()1332     public MessageClass getMessageClass() {
1333         return messageClass;
1334     }
1335 
1336     /**
1337      * Returns true if this is a (U)SIM data download type SM.
1338      * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9.
1339      *
1340      * @return true if this is a USIM data download message; false otherwise
1341      */
isUsimDataDownload()1342     boolean isUsimDataDownload() {
1343         return messageClass == MessageClass.CLASS_2 &&
1344                 (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c);
1345     }
1346 
getNumOfVoicemails()1347     public int getNumOfVoicemails() {
1348         /*
1349          * Order of priority if multiple indications are present is 1.UDH,
1350          *      2.DCS, 3.CPHS.
1351          * Voice mail count if voice mail present indication is
1352          * received
1353          *  1. UDH (or both UDH & DCS): mVoiceMailCount = 0 to 0xff. Ref[TS 23. 040]
1354          *  2. DCS only: count is unknown mVoiceMailCount= -1
1355          *  3. CPHS only: count is unknown mVoiceMailCount = 0xff. Ref[GSM-BTR-1-4700]
1356          * Voice mail clear, mVoiceMailCount = 0.
1357          */
1358         if ((!mIsMwi) && isCphsMwiMessage()) {
1359             if (mOriginatingAddress != null
1360                     && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet()) {
1361                 mVoiceMailCount = 0xff;
1362             } else {
1363                 mVoiceMailCount = 0;
1364             }
1365             Rlog.v(LOG_TAG, "CPHS voice mail message");
1366         }
1367         return mVoiceMailCount;
1368     }
1369 }
1370