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