SmsMessage.java revision e472090bda6d8c16975807e37b52e6bb558426bf
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
17package com.android.internal.telephony.gsm;
18
19import android.telephony.PhoneNumberUtils;
20import android.text.format.Time;
21import android.telephony.Rlog;
22import android.content.res.Resources;
23import android.text.TextUtils;
24
25import com.android.internal.telephony.EncodeException;
26import com.android.internal.telephony.GsmAlphabet;
27import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
28import com.android.internal.telephony.uicc.IccUtils;
29import com.android.internal.telephony.SmsHeader;
30import com.android.internal.telephony.SmsMessageBase;
31import com.android.internal.telephony.Sms7BitEncodingTranslator;
32
33import java.io.ByteArrayOutputStream;
34import java.io.UnsupportedEncodingException;
35import java.text.ParseException;
36
37import static com.android.internal.telephony.SmsConstants.MessageClass;
38import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
39import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
40import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
41import static com.android.internal.telephony.SmsConstants.ENCODING_16BIT;
42import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601;
43import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS;
44import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES;
45import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
46
47/**
48 * A Short Message Service message.
49 *
50 */
51public 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     */
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     */
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     */
129    public static SmsMessage newFromCMT(String[] lines) {
130        try {
131            SmsMessage msg = new SmsMessage();
132            msg.parsePdu(IccUtils.hexStringToBytes(lines[1]));
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 */
141    public static SmsMessage newFromCDS(String line) {
142        try {
143            SmsMessage msg = new SmsMessage();
144            msg.parsePdu(IccUtils.hexStringToBytes(line));
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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         */
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         */
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         */
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
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         */
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         */
683        byte[] getUserData() {
684            return mUserData;
685        }
686
687        /**
688         * Returns an object representing the user data headers
689         *
690         * {@hide}
691         */
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         */
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         */
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         */
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         */
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
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     */
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
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     */
814    int getDataCodingScheme() {
815        return mDataCodingScheme;
816    }
817
818    /** {@inheritDoc} */
819    @Override
820    public boolean isReplace() {
821        return (mProtocolIdentifier & 0xc0) == 0x40
822                && (mProtocolIdentifier & 0x3f) > 0
823                && (mProtocolIdentifier & 0x3f) < 8;
824    }
825
826    /** {@inheritDoc} */
827    @Override
828    public boolean isCphsMwiMessage() {
829        return ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear()
830                || ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
831    }
832
833    /** {@inheritDoc} */
834    @Override
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
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
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
876    public int getStatus() {
877        return mStatus;
878    }
879
880    /** {@inheritDoc} */
881    @Override
882    public boolean isStatusReportMessage() {
883        return mIsStatusReportMessage;
884    }
885
886    /** {@inheritDoc} */
887    @Override
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     */
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     */
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
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     */
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     */
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
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     */
1342    boolean isUsimDataDownload() {
1343        return messageClass == MessageClass.CLASS_2 &&
1344                (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c);
1345    }
1346
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