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