SmsMessage.java revision faf4413dffdc9079683b951736088ff2a01073a4
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.os.Parcel;
20import android.telephony.PhoneNumberUtils;
21import android.text.format.Time;
22import android.util.Config;
23import android.util.Log;
24import com.android.internal.telephony.IccUtils;
25import com.android.internal.telephony.EncodeException;
26import com.android.internal.telephony.GsmAlphabet;
27import com.android.internal.telephony.SmsHeader;
28import com.android.internal.telephony.SmsMessageBase;
29
30import java.io.ByteArrayOutputStream;
31import java.io.UnsupportedEncodingException;
32
33import static android.telephony.SmsMessage.ENCODING_7BIT;
34import static android.telephony.SmsMessage.ENCODING_8BIT;
35import static android.telephony.SmsMessage.ENCODING_16BIT;
36import static android.telephony.SmsMessage.ENCODING_UNKNOWN;
37import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
38import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
39import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS;
40import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER;
41import static android.telephony.SmsMessage.MessageClass;
42
43/**
44 * A Short Message Service message.
45 *
46 */
47public class SmsMessage extends SmsMessageBase{
48    static final String LOG_TAG = "GSM";
49
50    private MessageClass messageClass;
51
52    /**
53     * TP-Message-Type-Indicator
54     * 9.2.3
55     */
56    private int mti;
57
58    /** TP-Protocol-Identifier (TP-PID) */
59    private int protocolIdentifier;
60
61    // TP-Data-Coding-Scheme
62    // see TS 23.038
63    private int dataCodingScheme;
64
65    // TP-Reply-Path
66    // e.g. 23.040 9.2.2.1
67    private boolean replyPathPresent = false;
68
69    // "Message Marked for Automatic Deletion Group"
70    // 23.038 Section 4
71    private boolean automaticDeletion;
72
73    /** True if Status Report is for SMS-SUBMIT; false for SMS-COMMAND. */
74    private boolean forSubmit;
75
76    /** The address of the receiver. */
77    private GsmSmsAddress recipientAddress;
78
79    /** Time when SMS-SUBMIT was delivered from SC to MSE. */
80    private long dischargeTimeMillis;
81
82    /**
83     *  TP-Status - status of a previously submitted SMS.
84     *  This field applies to SMS-STATUS-REPORT messages.  0 indicates success;
85     *  see TS 23.040, 9.2.3.15 for description of other possible values.
86     */
87    private int status;
88
89    /**
90     *  TP-Status - status of a previously submitted SMS.
91     *  This field is true iff the message is a SMS-STATUS-REPORT message.
92     */
93    private boolean isStatusReportMessage = false;
94
95    public static class SubmitPdu extends SubmitPduBase {
96    }
97
98    /**
99     * Create an SmsMessage from a raw PDU.
100     */
101    public static SmsMessage createFromPdu(byte[] pdu) {
102        try {
103            SmsMessage msg = new SmsMessage();
104            msg.parsePdu(pdu);
105            return msg;
106        } catch (RuntimeException ex) {
107            Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
108            return null;
109        }
110    }
111
112    /**
113     * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
114     * +CMT unsolicited response (PDU mode, of course)
115     *  +CMT: [&lt;alpha>],<length><CR><LF><pdu>
116     *
117     * Only public for debugging
118     *
119     * {@hide}
120     */
121    public static SmsMessage newFromCMT(String[] lines) {
122        try {
123            SmsMessage msg = new SmsMessage();
124            msg.parsePdu(IccUtils.hexStringToBytes(lines[1]));
125            return msg;
126        } catch (RuntimeException ex) {
127            Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
128            return null;
129        }
130    }
131
132    /** @hide */
133    public static SmsMessage newFromCMTI(String line) {
134        // the thinking here is not to read the message immediately
135        // FTA test case
136        Log.e(LOG_TAG, "newFromCMTI: not yet supported");
137        return null;
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            Log.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex);
148            return null;
149        }
150    }
151
152    /**
153     * Note: This functionality is currently not supported in GSM mode.
154     * @hide
155     */
156    public static SmsMessageBase newFromParcel(Parcel p){
157        Log.w(LOG_TAG, "newFromParcel: is not supported in GSM mode.");
158        return null;
159    }
160
161    /**
162     * Create an SmsMessage from an SMS EF record.
163     *
164     * @param index Index of SMS record. This should be index in ArrayList
165     *              returned by SmsManager.getAllMessagesFromSim + 1.
166     * @param data Record data.
167     * @return An SmsMessage representing the record.
168     *
169     * @hide
170     */
171    public static SmsMessage createFromEfRecord(int index, byte[] data) {
172        try {
173            SmsMessage msg = new SmsMessage();
174
175            msg.indexOnIcc = index;
176
177            // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
178            // or STORED_UNSENT
179            // See TS 51.011 10.5.3
180            if ((data[0] & 1) == 0) {
181                Log.w(LOG_TAG,
182                        "SMS parsing failed: Trying to parse a free record");
183                return null;
184            } else {
185                msg.statusOnIcc = data[0] & 0x07;
186            }
187
188            int size = data.length - 1;
189
190            // Note: Data may include trailing FF's.  That's OK; message
191            // should still parse correctly.
192            byte[] pdu = new byte[size];
193            System.arraycopy(data, 1, pdu, 0, size);
194            msg.parsePdu(pdu);
195            return msg;
196        } catch (RuntimeException ex) {
197            Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
198            return null;
199        }
200    }
201
202    /**
203     * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
204     * length in bytes (not hex chars) less the SMSC header
205     */
206    public static int getTPLayerLengthForPDU(String pdu) {
207        int len = pdu.length() / 2;
208        int smscLen = 0;
209
210        smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
211
212        return len - smscLen - 1;
213    }
214
215    /**
216     * Get an SMS-SUBMIT PDU for a destination address and a message
217     *
218     * @param scAddress Service Centre address.  Null means use default.
219     * @return a <code>SubmitPdu</code> containing the encoded SC
220     *         address, if applicable, and the encoded message.
221     *         Returns null on encode error.
222     * @hide
223     */
224    public static SubmitPdu getSubmitPdu(String scAddress,
225            String destinationAddress, String message,
226            boolean statusReportRequested, byte[] header) {
227
228        // Perform null parameter checks.
229        if (message == null || destinationAddress == null) {
230            return null;
231        }
232
233        SubmitPdu ret = new SubmitPdu();
234        // MTI = SMS-SUBMIT, UDHI = header != null
235        byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
236        ByteArrayOutputStream bo = getSubmitPduHead(
237                scAddress, destinationAddress, mtiByte,
238                statusReportRequested, ret);
239
240        try {
241            // First, try encoding it with the GSM alphabet
242
243            // User Data (and length)
244            byte[] userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header);
245
246            if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
247                // Message too long
248                return null;
249            }
250
251            // TP-Data-Coding-Scheme
252            // Default encoding, uncompressed
253            // To test writing messages to the SIM card, change this value 0x00
254            // to 0x12, which means "bits 1 and 0 contain message class, and the
255            // class is 2". Note that this takes effect for the sender. In other
256            // words, messages sent by the phone with this change will end up on
257            // the receiver's SIM card. You can then send messages to yourself
258            // (on a phone with this change) and they'll end up on the SIM card.
259            bo.write(0x00);
260
261            // (no TP-Validity-Period)
262
263            bo.write(userData, 0, userData.length);
264        } catch (EncodeException ex) {
265            byte[] userData, textPart;
266            // Encoding to the 7-bit alphabet failed. Let's see if we can
267            // send it as a UCS-2 encoded message
268
269            try {
270                textPart = message.getBytes("utf-16be");
271            } catch (UnsupportedEncodingException uex) {
272                Log.e(LOG_TAG,
273                      "Implausible UnsupportedEncodingException ",
274                      uex);
275                return null;
276            }
277
278            if (header != null) {
279                userData = new byte[header.length + textPart.length];
280
281                System.arraycopy(header, 0, userData, 0, header.length);
282                System.arraycopy(textPart, 0, userData, header.length, textPart.length);
283            }
284            else {
285                userData = textPart;
286            }
287
288            if (userData.length > MAX_USER_DATA_BYTES) {
289                // Message too long
290                return null;
291            }
292
293            // TP-Data-Coding-Scheme
294            // Class 3, UCS-2 encoding, uncompressed
295            bo.write(0x0b);
296
297            // (no TP-Validity-Period)
298
299            // TP-UDL
300            bo.write(userData.length);
301
302            bo.write(userData, 0, userData.length);
303        }
304
305        ret.encodedMessage = bo.toByteArray();
306        return ret;
307    }
308
309    /**
310     * Get an SMS-SUBMIT PDU for a destination address and a message
311     *
312     * @param scAddress Service Centre address.  Null means use default.
313     * @return a <code>SubmitPdu</code> containing the encoded SC
314     *         address, if applicable, and the encoded message.
315     *         Returns null on encode error.
316     */
317    public static SubmitPdu getSubmitPdu(String scAddress,
318            String destinationAddress, String message,
319            boolean statusReportRequested) {
320
321        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
322    }
323
324    /**
325     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
326     *
327     * @param scAddress Service Centre address. null == use default
328     * @param destinationAddress the address of the destination for the message
329     * @param destinationPort the port to deliver the message to at the
330     *        destination
331     * @param data the dat for the message
332     * @return a <code>SubmitPdu</code> containing the encoded SC
333     *         address, if applicable, and the encoded message.
334     *         Returns null on encode error.
335     */
336    public static SubmitPdu getSubmitPdu(String scAddress,
337            String destinationAddress, short destinationPort, byte[] data,
338            boolean statusReportRequested) {
339
340        SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
341        portAddrs.destPort = destinationPort;
342        portAddrs.origPort = 0;
343        portAddrs.areEightBits = false;
344
345        SmsHeader smsHeader = new SmsHeader();
346        smsHeader.portAddrs = portAddrs;
347
348        byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
349
350        if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
351            Log.e(LOG_TAG, "SMS data message may only contain "
352                    + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes");
353            return null;
354        }
355
356        SubmitPdu ret = new SubmitPdu();
357        ByteArrayOutputStream bo = getSubmitPduHead(
358                scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT,
359                                                            // TP-UDHI = true
360                statusReportRequested, ret);
361
362        // TP-Data-Coding-Scheme
363        // No class, 8 bit data
364        bo.write(0x04);
365
366        // (no TP-Validity-Period)
367
368        // Total size
369        bo.write(data.length + smsHeaderData.length + 1);
370
371        // User data header
372        bo.write(smsHeaderData.length);
373        bo.write(smsHeaderData, 0, smsHeaderData.length);
374
375        // User data
376        bo.write(data, 0, data.length);
377
378        ret.encodedMessage = bo.toByteArray();
379        return ret;
380    }
381
382    /**
383     * Create the beginning of a SUBMIT PDU.  This is the part of the
384     * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu},
385     * one of which takes a byte array and the other of which takes a
386     * <code>String</code>.
387     *
388     * @param scAddress Service Centre address. null == use default
389     * @param destinationAddress the address of the destination for the message
390     * @param mtiByte
391     * @param ret <code>SubmitPdu</code> containing the encoded SC
392     *        address, if applicable, and the encoded message
393     */
394    private static ByteArrayOutputStream getSubmitPduHead(
395            String scAddress, String destinationAddress, byte mtiByte,
396            boolean statusReportRequested, SubmitPdu ret) {
397        ByteArrayOutputStream bo = new ByteArrayOutputStream(
398                MAX_USER_DATA_BYTES + 40);
399
400        // SMSC address with length octet, or 0
401        if (scAddress == null) {
402            ret.encodedScAddress = null;
403        } else {
404            ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
405                    scAddress);
406        }
407
408        // TP-Message-Type-Indicator (and friends)
409        if (statusReportRequested) {
410            // Set TP-Status-Report-Request bit.
411            mtiByte |= 0x20;
412            if (Config.LOGD) Log.d(LOG_TAG, "SMS status report requested");
413        }
414        bo.write(mtiByte);
415
416        // space for TP-Message-Reference
417        bo.write(0);
418
419        byte[] daBytes;
420
421        daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
422
423        // destination address length in BCD digits, ignoring TON byte and pad
424        // TODO Should be better.
425        bo.write((daBytes.length - 1) * 2
426                - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
427
428        // destination address
429        bo.write(daBytes, 0, daBytes.length);
430
431        // TP-Protocol-Identifier
432        bo.write(0);
433        return bo;
434    }
435
436    static class PduParser {
437        byte pdu[];
438        int cur;
439        SmsHeader userDataHeader;
440        byte[] userData;
441        int mUserDataSeptetPadding;
442        int mUserDataSize;
443
444        PduParser(String s) {
445            this(IccUtils.hexStringToBytes(s));
446        }
447
448        PduParser(byte[] pdu) {
449            this.pdu = pdu;
450            cur = 0;
451            mUserDataSeptetPadding = 0;
452        }
453
454        /**
455         * Parse and return the SC address prepended to SMS messages coming via
456         * the TS 27.005 / AT interface.  Returns null on invalid address
457         */
458        String getSCAddress() {
459            int len;
460            String ret;
461
462            // length of SC Address
463            len = getByte();
464
465            if (len == 0) {
466                // no SC address
467                ret = null;
468            } else {
469                // SC address
470                try {
471                    ret = PhoneNumberUtils
472                            .calledPartyBCDToString(pdu, cur, len);
473                } catch (RuntimeException tr) {
474                    Log.d(LOG_TAG, "invalid SC address: ", tr);
475                    ret = null;
476                }
477            }
478
479            cur += len;
480
481            return ret;
482        }
483
484        /**
485         * returns non-sign-extended byte value
486         */
487        int getByte() {
488            return pdu[cur++] & 0xff;
489        }
490
491        /**
492         * Any address except the SC address (eg, originating address) See TS
493         * 23.040 9.1.2.5
494         */
495        GsmSmsAddress getAddress() {
496            GsmSmsAddress ret;
497
498            // "The Address-Length field is an integer representation of
499            // the number field, i.e. excludes any semi octet containing only
500            // fill bits."
501            // The TOA field is not included as part of this
502            int addressLength = pdu[cur] & 0xff;
503            int lengthBytes = 2 + (addressLength + 1) / 2;
504
505            ret = new GsmSmsAddress(pdu, cur, lengthBytes);
506
507            cur += lengthBytes;
508
509            return ret;
510        }
511
512        /**
513         * Parses an SC timestamp and returns a currentTimeMillis()-style
514         * timestamp
515         */
516
517        long getSCTimestampMillis() {
518            // TP-Service-Centre-Time-Stamp
519            int year = IccUtils.bcdByteToInt(pdu[cur++]);
520            int month = IccUtils.bcdByteToInt(pdu[cur++]);
521            int day = IccUtils.bcdByteToInt(pdu[cur++]);
522            int hour = IccUtils.bcdByteToInt(pdu[cur++]);
523            int minute = IccUtils.bcdByteToInt(pdu[cur++]);
524            int second = IccUtils.bcdByteToInt(pdu[cur++]);
525
526            // For the timezone, the most significant bit of the
527            // least signficant nibble is the sign byte
528            // (meaning the max range of this field is 79 quarter-hours,
529            // which is more than enough)
530
531            byte tzByte = pdu[cur++];
532
533            // Mask out sign bit.
534            int timezoneOffset = IccUtils
535                    .bcdByteToInt((byte) (tzByte & (~0x08)));
536
537            timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset
538                    : -timezoneOffset;
539
540            Time time = new Time(Time.TIMEZONE_UTC);
541
542            // It's 2006.  Should I really support years < 2000?
543            time.year = year >= 90 ? year + 1900 : year + 2000;
544            time.month = month - 1;
545            time.monthDay = day;
546            time.hour = hour;
547            time.minute = minute;
548            time.second = second;
549
550            // Timezone offset is in quarter hours.
551            return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
552        }
553
554        /**
555         * Pulls the user data out of the PDU, and separates the payload from
556         * the header if there is one.
557         *
558         * @param hasUserDataHeader true if there is a user data header
559         * @param dataInSeptets true if the data payload is in septets instead
560         *  of octets
561         * @return the number of septets or octets in the user data payload
562         */
563        int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
564            int offset = cur;
565            int userDataLength = pdu[offset++] & 0xff;
566            int headerSeptets = 0;
567            int userDataHeaderLength = 0;
568
569            if (hasUserDataHeader) {
570                userDataHeaderLength = pdu[offset++] & 0xff;
571
572                byte[] udh = new byte[userDataHeaderLength];
573                System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
574                userDataHeader = SmsHeader.fromByteArray(udh);
575                offset += userDataHeaderLength;
576
577                int headerBits = (userDataHeaderLength + 1) * 8;
578                headerSeptets = headerBits / 7;
579                headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
580                mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
581            }
582
583            int bufferLen;
584            if (dataInSeptets) {
585                /*
586                 * Here we just create the user data length to be the remainder of
587                 * the pdu minus the user data header, since userDataLength means
588                 * the number of uncompressed sepets.
589                 */
590                bufferLen = pdu.length - offset;
591            } else {
592                /*
593                 * userDataLength is the count of octets, so just subtract the
594                 * user data header.
595                 */
596                bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
597                if (bufferLen < 0) {
598                    bufferLen = 0;
599                }
600            }
601
602            userData = new byte[bufferLen];
603            System.arraycopy(pdu, offset, userData, 0, userData.length);
604            cur = offset;
605
606            if (dataInSeptets) {
607                // Return the number of septets
608                int count = userDataLength - headerSeptets;
609                // If count < 0, return 0 (means UDL was probably incorrect)
610                return count < 0 ? 0 : count;
611            } else {
612                // Return the number of octets
613                return userData.length;
614            }
615        }
616
617        /**
618         * Returns the user data payload, not including the headers
619         *
620         * @return the user data payload, not including the headers
621         */
622        byte[] getUserData() {
623            return userData;
624        }
625
626        /**
627         * Returns the number of padding bits at the begining of the user data
628         * array before the start of the septets.
629         *
630         * @return the number of padding bits at the begining of the user data
631         * array before the start of the septets
632         */
633        int getUserDataSeptetPadding() {
634            return mUserDataSeptetPadding;
635        }
636
637        /**
638         * Returns an object representing the user data headers
639         *
640         * @return an object representing the user data headers
641         *
642         * {@hide}
643         */
644        SmsHeader getUserDataHeader() {
645            return userDataHeader;
646        }
647
648/*
649        XXX Not sure what this one is supposed to be doing, and no one is using
650        it.
651        String getUserDataGSM8bit() {
652            // System.out.println("remainder of pud:" +
653            // HexDump.dumpHexString(pdu, cur, pdu.length - cur));
654            int count = pdu[cur++] & 0xff;
655            int size = pdu[cur++];
656
657            // skip over header for now
658            cur += size;
659
660            if (pdu[cur - 1] == 0x01) {
661                int tid = pdu[cur++] & 0xff;
662                int type = pdu[cur++] & 0xff;
663
664                size = pdu[cur++] & 0xff;
665
666                int i = cur;
667
668                while (pdu[i++] != '\0') {
669                }
670
671                int length = i - cur;
672                String mimeType = new String(pdu, cur, length);
673
674                cur += length;
675
676                if (false) {
677                    System.out.println("tid = 0x" + HexDump.toHexString(tid));
678                    System.out.println("type = 0x" + HexDump.toHexString(type));
679                    System.out.println("header size = " + size);
680                    System.out.println("mimeType = " + mimeType);
681                    System.out.println("remainder of header:" +
682                     HexDump.dumpHexString(pdu, cur, (size - mimeType.length())));
683                }
684
685                cur += size - mimeType.length();
686
687                // System.out.println("data count = " + count + " cur = " + cur
688                // + " :" + HexDump.dumpHexString(pdu, cur, pdu.length - cur));
689
690                MMSMessage msg = MMSMessage.parseEncoding(mContext, pdu, cur,
691                        pdu.length - cur);
692            } else {
693                System.out.println(new String(pdu, cur, pdu.length - cur - 1));
694            }
695
696            return IccUtils.bytesToHexString(pdu);
697        }
698*/
699
700        /**
701         * Interprets the user data payload as pack GSM 7bit characters, and
702         * decodes them into a String.
703         *
704         * @param septetCount the number of septets in the user data payload
705         * @return a String with the decoded characters
706         */
707        String getUserDataGSM7Bit(int septetCount) {
708            String ret;
709
710            ret = GsmAlphabet.gsm7BitPackedToString(pdu, cur, septetCount,
711                    mUserDataSeptetPadding);
712
713            cur += (septetCount * 7) / 8;
714
715            return ret;
716        }
717
718        /**
719         * Interprets the user data payload as UCS2 characters, and
720         * decodes them into a String.
721         *
722         * @param byteCount the number of bytes in the user data payload
723         * @return a String with the decoded characters
724         */
725        String getUserDataUCS2(int byteCount) {
726            String ret;
727
728            try {
729                ret = new String(pdu, cur, byteCount, "utf-16");
730            } catch (UnsupportedEncodingException ex) {
731                ret = "";
732                Log.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
733            }
734
735            cur += byteCount;
736            return ret;
737        }
738
739        boolean moreDataPresent() {
740            return (pdu.length > cur);
741        }
742    }
743
744    /** {@inheritDoc} */
745    public int getProtocolIdentifier() {
746        return protocolIdentifier;
747    }
748
749    /** {@inheritDoc} */
750    public boolean isReplace() {
751        return (protocolIdentifier & 0xc0) == 0x40
752                && (protocolIdentifier & 0x3f) > 0
753                && (protocolIdentifier & 0x3f) < 8;
754    }
755
756    /** {@inheritDoc} */
757    public boolean isCphsMwiMessage() {
758        return ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear()
759                || ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
760    }
761
762    /** {@inheritDoc} */
763    public boolean isMWIClearMessage() {
764        if (isMwi && (mwiSense == false)) {
765            return true;
766        }
767
768        return originatingAddress != null
769                && ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear();
770    }
771
772    /** {@inheritDoc} */
773    public boolean isMWISetMessage() {
774        if (isMwi && (mwiSense == true)) {
775            return true;
776        }
777
778        return originatingAddress != null
779                && ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
780    }
781
782    /** {@inheritDoc} */
783    public boolean isMwiDontStore() {
784        if (isMwi && mwiDontStore) {
785            return true;
786        }
787
788        if (isCphsMwiMessage()) {
789            // See CPHS 4.2 Section B.4.2.1
790            // If the user data is a single space char, do not store
791            // the message. Otherwise, store and display as usual
792            if (" ".equals(getMessageBody())) {
793                ;
794            }
795            return true;
796        }
797
798        return false;
799    }
800
801    /** {@inheritDoc} */
802    public int getStatus() {
803        return status;
804    }
805
806    /** {@inheritDoc} */
807    public boolean isStatusReportMessage() {
808        return isStatusReportMessage;
809    }
810
811    /** {@inheritDoc} */
812    public boolean isReplyPathPresent() {
813        return replyPathPresent;
814    }
815
816    /**
817     * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
818     * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
819     * ME/TA converts each octet of TP data unit into two IRA character long
820     * hexad number (e.g. octet with integer value 42 is presented to TE as two
821     * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
822     * something else...
823     */
824    private void parsePdu(byte[] pdu) {
825        mPdu = pdu;
826        // Log.d(LOG_TAG, "raw sms mesage:");
827        // Log.d(LOG_TAG, s);
828
829        PduParser p = new PduParser(pdu);
830
831        scAddress = p.getSCAddress();
832
833        if (scAddress != null) {
834            if (Config.LOGD) Log.d(LOG_TAG, "SMS SC address: " + scAddress);
835        }
836
837        // TODO(mkf) support reply path, user data header indicator
838
839        // TP-Message-Type-Indicator
840        // 9.2.3
841        int firstByte = p.getByte();
842
843        mti = firstByte & 0x3;
844        switch (mti) {
845        // TP-Message-Type-Indicator
846        // 9.2.3
847        case 0:
848            parseSmsDeliver(p, firstByte);
849            break;
850        case 2:
851            parseSmsStatusReport(p, firstByte);
852            break;
853        default:
854            // TODO(mkf) the rest of these
855            throw new RuntimeException("Unsupported message type");
856        }
857    }
858
859    /**
860     * Parses a SMS-STATUS-REPORT message.
861     *
862     * @param p A PduParser, cued past the first byte.
863     * @param firstByte The first byte of the PDU, which contains MTI, etc.
864     */
865    private void parseSmsStatusReport(PduParser p, int firstByte) {
866        isStatusReportMessage = true;
867
868        // TP-Status-Report-Qualifier bit == 0 for SUBMIT
869        forSubmit = (firstByte & 0x20) == 0x00;
870        // TP-Message-Reference
871        messageRef = p.getByte();
872        // TP-Recipient-Address
873        recipientAddress = p.getAddress();
874        // TP-Service-Centre-Time-Stamp
875        scTimeMillis = p.getSCTimestampMillis();
876        // TP-Discharge-Time
877        dischargeTimeMillis = p.getSCTimestampMillis();
878        // TP-Status
879        status = p.getByte();
880
881        // The following are optional fields that may or may not be present.
882        if (p.moreDataPresent()) {
883            // TP-Parameter-Indicator
884            int extraParams = p.getByte();
885            int moreExtraParams = extraParams;
886            while ((moreExtraParams & 0x80) != 0) {
887                // We only know how to parse a few extra parameters, all
888                // indicated in the first TP-PI octet, so skip over any
889                // additional TP-PI octets.
890                moreExtraParams = p.getByte();
891            }
892            // TP-Protocol-Identifier
893            if ((extraParams & 0x01) != 0) {
894                protocolIdentifier = p.getByte();
895            }
896            // TP-Data-Coding-Scheme
897            if ((extraParams & 0x02) != 0) {
898                dataCodingScheme = p.getByte();
899            }
900            // TP-User-Data-Length (implies existence of TP-User-Data)
901            if ((extraParams & 0x04) != 0) {
902                boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
903                parseUserData(p, hasUserDataHeader);
904            }
905        }
906    }
907
908    private void parseSmsDeliver(PduParser p, int firstByte) {
909        replyPathPresent = (firstByte & 0x80) == 0x80;
910
911        originatingAddress = p.getAddress();
912
913        if (originatingAddress != null) {
914            if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
915                    + originatingAddress.address);
916        }
917
918        // TP-Protocol-Identifier (TP-PID)
919        // TS 23.040 9.2.3.9
920        protocolIdentifier = p.getByte();
921
922        // TP-Data-Coding-Scheme
923        // see TS 23.038
924        dataCodingScheme = p.getByte();
925
926        if (Config.LOGV) {
927            Log.v(LOG_TAG, "SMS TP-PID:" + protocolIdentifier
928                    + " data coding scheme: " + dataCodingScheme);
929        }
930
931        scTimeMillis = p.getSCTimestampMillis();
932
933        if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
934
935        boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
936
937        parseUserData(p, hasUserDataHeader);
938    }
939
940    /**
941     * Parses the User Data of an SMS.
942     *
943     * @param p The current PduParser.
944     * @param hasUserDataHeader Indicates whether a header is present in the
945     *                          User Data.
946     */
947    private void parseUserData(PduParser p, boolean hasUserDataHeader) {
948        boolean hasMessageClass = false;
949        boolean userDataCompressed = false;
950
951        int encodingType = ENCODING_UNKNOWN;
952
953        // Look up the data encoding scheme
954        if ((dataCodingScheme & 0x80) == 0) {
955            // Bits 7..4 == 0xxx
956            automaticDeletion = (0 != (dataCodingScheme & 0x40));
957            userDataCompressed = (0 != (dataCodingScheme & 0x20));
958            hasMessageClass = (0 != (dataCodingScheme & 0x10));
959
960            if (userDataCompressed) {
961                Log.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
962                        + "(compression) " + (dataCodingScheme & 0xff));
963            } else {
964                switch ((dataCodingScheme >> 2) & 0x3) {
965                case 0: // GSM 7 bit default alphabet
966                    encodingType = ENCODING_7BIT;
967                    break;
968
969                case 2: // UCS 2 (16bit)
970                    encodingType = ENCODING_16BIT;
971                    break;
972
973                case 1: // 8 bit data
974                case 3: // reserved
975                    Log.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
976                            + (dataCodingScheme & 0xff));
977                    encodingType = ENCODING_8BIT;
978                    break;
979                }
980            }
981        } else if ((dataCodingScheme & 0xf0) == 0xf0) {
982            automaticDeletion = false;
983            hasMessageClass = true;
984            userDataCompressed = false;
985
986            if (0 == (dataCodingScheme & 0x04)) {
987                // GSM 7 bit default alphabet
988                encodingType = ENCODING_7BIT;
989            } else {
990                // 8 bit data
991                encodingType = ENCODING_8BIT;
992            }
993        } else if ((dataCodingScheme & 0xF0) == 0xC0
994                || (dataCodingScheme & 0xF0) == 0xD0
995                || (dataCodingScheme & 0xF0) == 0xE0) {
996            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
997
998            // 0xC0 == 7 bit, don't store
999            // 0xD0 == 7 bit, store
1000            // 0xE0 == UCS-2, store
1001
1002            if ((dataCodingScheme & 0xF0) == 0xE0) {
1003                encodingType = ENCODING_16BIT;
1004            } else {
1005                encodingType = ENCODING_7BIT;
1006            }
1007
1008            userDataCompressed = false;
1009            boolean active = ((dataCodingScheme & 0x08) == 0x08);
1010
1011            // bit 0x04 reserved
1012
1013            if ((dataCodingScheme & 0x03) == 0x00) {
1014                isMwi = true;
1015                mwiSense = active;
1016                mwiDontStore = ((dataCodingScheme & 0xF0) == 0xC0);
1017            } else {
1018                isMwi = false;
1019
1020                Log.w(LOG_TAG, "MWI for fax, email, or other "
1021                        + (dataCodingScheme & 0xff));
1022            }
1023        } else {
1024            Log.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
1025                    + (dataCodingScheme & 0xff));
1026        }
1027
1028        // set both the user data and the user data header.
1029        int count = p.constructUserData(hasUserDataHeader,
1030                encodingType == ENCODING_7BIT);
1031        this.userData = p.getUserData();
1032        this.userDataHeader = p.getUserDataHeader();
1033
1034        switch (encodingType) {
1035        case ENCODING_UNKNOWN:
1036        case ENCODING_8BIT:
1037            messageBody = null;
1038            break;
1039
1040        case ENCODING_7BIT:
1041            messageBody = p.getUserDataGSM7Bit(count);
1042            break;
1043
1044        case ENCODING_16BIT:
1045            messageBody = p.getUserDataUCS2(count);
1046            break;
1047        }
1048
1049        if (Config.LOGV) Log.v(LOG_TAG, "SMS message body (raw): '" + messageBody + "'");
1050
1051        if (messageBody != null) {
1052            parseMessageBody();
1053        }
1054
1055        if (!hasMessageClass) {
1056            messageClass = MessageClass.UNKNOWN;
1057        } else {
1058            switch (dataCodingScheme & 0x3) {
1059            case 0:
1060                messageClass = MessageClass.CLASS_0;
1061                break;
1062            case 1:
1063                messageClass = MessageClass.CLASS_1;
1064                break;
1065            case 2:
1066                messageClass = MessageClass.CLASS_2;
1067                break;
1068            case 3:
1069                messageClass = MessageClass.CLASS_3;
1070                break;
1071            }
1072        }
1073    }
1074
1075    /**
1076     * {@inheritDoc}
1077     */
1078    public MessageClass getMessageClass() {
1079        return messageClass;
1080    }
1081
1082}
1083