SmsMessage.java revision a94945d3a1cf23caf33759eb1de84195d3fcb37b
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         * {@hide}
641         */
642        SmsHeader getUserDataHeader() {
643            return userDataHeader;
644        }
645
646/*
647        XXX Not sure what this one is supposed to be doing, and no one is using
648        it.
649        String getUserDataGSM8bit() {
650            // System.out.println("remainder of pud:" +
651            // HexDump.dumpHexString(pdu, cur, pdu.length - cur));
652            int count = pdu[cur++] & 0xff;
653            int size = pdu[cur++];
654
655            // skip over header for now
656            cur += size;
657
658            if (pdu[cur - 1] == 0x01) {
659                int tid = pdu[cur++] & 0xff;
660                int type = pdu[cur++] & 0xff;
661
662                size = pdu[cur++] & 0xff;
663
664                int i = cur;
665
666                while (pdu[i++] != '\0') {
667                }
668
669                int length = i - cur;
670                String mimeType = new String(pdu, cur, length);
671
672                cur += length;
673
674                if (false) {
675                    System.out.println("tid = 0x" + HexDump.toHexString(tid));
676                    System.out.println("type = 0x" + HexDump.toHexString(type));
677                    System.out.println("header size = " + size);
678                    System.out.println("mimeType = " + mimeType);
679                    System.out.println("remainder of header:" +
680                     HexDump.dumpHexString(pdu, cur, (size - mimeType.length())));
681                }
682
683                cur += size - mimeType.length();
684
685                // System.out.println("data count = " + count + " cur = " + cur
686                // + " :" + HexDump.dumpHexString(pdu, cur, pdu.length - cur));
687
688                MMSMessage msg = MMSMessage.parseEncoding(mContext, pdu, cur,
689                        pdu.length - cur);
690            } else {
691                System.out.println(new String(pdu, cur, pdu.length - cur - 1));
692            }
693
694            return IccUtils.bytesToHexString(pdu);
695        }
696*/
697
698        /**
699         * Interprets the user data payload as pack GSM 7bit characters, and
700         * decodes them into a String.
701         *
702         * @param septetCount the number of septets in the user data payload
703         * @return a String with the decoded characters
704         */
705        String getUserDataGSM7Bit(int septetCount) {
706            String ret;
707
708            ret = GsmAlphabet.gsm7BitPackedToString(pdu, cur, septetCount,
709                    mUserDataSeptetPadding);
710
711            cur += (septetCount * 7) / 8;
712
713            return ret;
714        }
715
716        /**
717         * Interprets the user data payload as UCS2 characters, and
718         * decodes them into a String.
719         *
720         * @param byteCount the number of bytes in the user data payload
721         * @return a String with the decoded characters
722         */
723        String getUserDataUCS2(int byteCount) {
724            String ret;
725
726            try {
727                ret = new String(pdu, cur, byteCount, "utf-16");
728            } catch (UnsupportedEncodingException ex) {
729                ret = "";
730                Log.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
731            }
732
733            cur += byteCount;
734            return ret;
735        }
736
737        boolean moreDataPresent() {
738            return (pdu.length > cur);
739        }
740    }
741
742    /**
743     * Calculate the number of septets needed to encode the message.
744     *
745     * @param messageBody the message to encode
746     * @param force ignore (but still count) illegal characters if true
747     * @return septet count, or -1 on failure
748     */
749    public static int calc7bitEncodedLength(CharSequence messageBody, boolean force) {
750        try {
751            return GsmAlphabet.countGsmSeptets(messageBody, !force);
752        } catch (EncodeException ex) {
753            /* Just fall through to the -1 error result below. */
754        }
755        return -1;
756    }
757
758    /** {@inheritDoc} */
759    public int getProtocolIdentifier() {
760        return protocolIdentifier;
761    }
762
763    /** {@inheritDoc} */
764    public boolean isReplace() {
765        return (protocolIdentifier & 0xc0) == 0x40
766                && (protocolIdentifier & 0x3f) > 0
767                && (protocolIdentifier & 0x3f) < 8;
768    }
769
770    /** {@inheritDoc} */
771    public boolean isCphsMwiMessage() {
772        return ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear()
773                || ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
774    }
775
776    /** {@inheritDoc} */
777    public boolean isMWIClearMessage() {
778        if (isMwi && (mwiSense == false)) {
779            return true;
780        }
781
782        return originatingAddress != null
783                && ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear();
784    }
785
786    /** {@inheritDoc} */
787    public boolean isMWISetMessage() {
788        if (isMwi && (mwiSense == true)) {
789            return true;
790        }
791
792        return originatingAddress != null
793                && ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
794    }
795
796    /** {@inheritDoc} */
797    public boolean isMwiDontStore() {
798        if (isMwi && mwiDontStore) {
799            return true;
800        }
801
802        if (isCphsMwiMessage()) {
803            // See CPHS 4.2 Section B.4.2.1
804            // If the user data is a single space char, do not store
805            // the message. Otherwise, store and display as usual
806            if (" ".equals(getMessageBody())) {
807                ;
808            }
809            return true;
810        }
811
812        return false;
813    }
814
815    /** {@inheritDoc} */
816    public int getStatus() {
817        return status;
818    }
819
820    /** {@inheritDoc} */
821    public boolean isStatusReportMessage() {
822        return isStatusReportMessage;
823    }
824
825    /** {@inheritDoc} */
826    public boolean isReplyPathPresent() {
827        return replyPathPresent;
828    }
829
830    /**
831     * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
832     * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
833     * ME/TA converts each octet of TP data unit into two IRA character long
834     * hexad number (e.g. octet with integer value 42 is presented to TE as two
835     * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
836     * something else...
837     */
838    private void parsePdu(byte[] pdu) {
839        mPdu = pdu;
840        // Log.d(LOG_TAG, "raw sms mesage:");
841        // Log.d(LOG_TAG, s);
842
843        PduParser p = new PduParser(pdu);
844
845        scAddress = p.getSCAddress();
846
847        if (scAddress != null) {
848            if (Config.LOGD) Log.d(LOG_TAG, "SMS SC address: " + scAddress);
849        }
850
851        // TODO(mkf) support reply path, user data header indicator
852
853        // TP-Message-Type-Indicator
854        // 9.2.3
855        int firstByte = p.getByte();
856
857        mti = firstByte & 0x3;
858        switch (mti) {
859        // TP-Message-Type-Indicator
860        // 9.2.3
861        case 0:
862            parseSmsDeliver(p, firstByte);
863            break;
864        case 2:
865            parseSmsStatusReport(p, firstByte);
866            break;
867        default:
868            // TODO(mkf) the rest of these
869            throw new RuntimeException("Unsupported message type");
870        }
871    }
872
873    /**
874     * Parses a SMS-STATUS-REPORT message.
875     *
876     * @param p A PduParser, cued past the first byte.
877     * @param firstByte The first byte of the PDU, which contains MTI, etc.
878     */
879    private void parseSmsStatusReport(PduParser p, int firstByte) {
880        isStatusReportMessage = true;
881
882        // TP-Status-Report-Qualifier bit == 0 for SUBMIT
883        forSubmit = (firstByte & 0x20) == 0x00;
884        // TP-Message-Reference
885        messageRef = p.getByte();
886        // TP-Recipient-Address
887        recipientAddress = p.getAddress();
888        // TP-Service-Centre-Time-Stamp
889        scTimeMillis = p.getSCTimestampMillis();
890        // TP-Discharge-Time
891        dischargeTimeMillis = p.getSCTimestampMillis();
892        // TP-Status
893        status = p.getByte();
894
895        // The following are optional fields that may or may not be present.
896        if (p.moreDataPresent()) {
897            // TP-Parameter-Indicator
898            int extraParams = p.getByte();
899            int moreExtraParams = extraParams;
900            while ((moreExtraParams & 0x80) != 0) {
901                // We only know how to parse a few extra parameters, all
902                // indicated in the first TP-PI octet, so skip over any
903                // additional TP-PI octets.
904                moreExtraParams = p.getByte();
905            }
906            // TP-Protocol-Identifier
907            if ((extraParams & 0x01) != 0) {
908                protocolIdentifier = p.getByte();
909            }
910            // TP-Data-Coding-Scheme
911            if ((extraParams & 0x02) != 0) {
912                dataCodingScheme = p.getByte();
913            }
914            // TP-User-Data-Length (implies existence of TP-User-Data)
915            if ((extraParams & 0x04) != 0) {
916                boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
917                parseUserData(p, hasUserDataHeader);
918            }
919        }
920    }
921
922    private void parseSmsDeliver(PduParser p, int firstByte) {
923        replyPathPresent = (firstByte & 0x80) == 0x80;
924
925        originatingAddress = p.getAddress();
926
927        if (originatingAddress != null) {
928            if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
929                    + originatingAddress.address);
930        }
931
932        // TP-Protocol-Identifier (TP-PID)
933        // TS 23.040 9.2.3.9
934        protocolIdentifier = p.getByte();
935
936        // TP-Data-Coding-Scheme
937        // see TS 23.038
938        dataCodingScheme = p.getByte();
939
940        if (Config.LOGV) {
941            Log.v(LOG_TAG, "SMS TP-PID:" + protocolIdentifier
942                    + " data coding scheme: " + dataCodingScheme);
943        }
944
945        scTimeMillis = p.getSCTimestampMillis();
946
947        if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
948
949        boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
950
951        parseUserData(p, hasUserDataHeader);
952    }
953
954    /**
955     * Parses the User Data of an SMS.
956     *
957     * @param p The current PduParser.
958     * @param hasUserDataHeader Indicates whether a header is present in the
959     *                          User Data.
960     */
961    private void parseUserData(PduParser p, boolean hasUserDataHeader) {
962        boolean hasMessageClass = false;
963        boolean userDataCompressed = false;
964
965        int encodingType = ENCODING_UNKNOWN;
966
967        // Look up the data encoding scheme
968        if ((dataCodingScheme & 0x80) == 0) {
969            // Bits 7..4 == 0xxx
970            automaticDeletion = (0 != (dataCodingScheme & 0x40));
971            userDataCompressed = (0 != (dataCodingScheme & 0x20));
972            hasMessageClass = (0 != (dataCodingScheme & 0x10));
973
974            if (userDataCompressed) {
975                Log.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
976                        + "(compression) " + (dataCodingScheme & 0xff));
977            } else {
978                switch ((dataCodingScheme >> 2) & 0x3) {
979                case 0: // GSM 7 bit default alphabet
980                    encodingType = ENCODING_7BIT;
981                    break;
982
983                case 2: // UCS 2 (16bit)
984                    encodingType = ENCODING_16BIT;
985                    break;
986
987                case 1: // 8 bit data
988                case 3: // reserved
989                    Log.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
990                            + (dataCodingScheme & 0xff));
991                    encodingType = ENCODING_8BIT;
992                    break;
993                }
994            }
995        } else if ((dataCodingScheme & 0xf0) == 0xf0) {
996            automaticDeletion = false;
997            hasMessageClass = true;
998            userDataCompressed = false;
999
1000            if (0 == (dataCodingScheme & 0x04)) {
1001                // GSM 7 bit default alphabet
1002                encodingType = ENCODING_7BIT;
1003            } else {
1004                // 8 bit data
1005                encodingType = ENCODING_8BIT;
1006            }
1007        } else if ((dataCodingScheme & 0xF0) == 0xC0
1008                || (dataCodingScheme & 0xF0) == 0xD0
1009                || (dataCodingScheme & 0xF0) == 0xE0) {
1010            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
1011
1012            // 0xC0 == 7 bit, don't store
1013            // 0xD0 == 7 bit, store
1014            // 0xE0 == UCS-2, store
1015
1016            if ((dataCodingScheme & 0xF0) == 0xE0) {
1017                encodingType = ENCODING_16BIT;
1018            } else {
1019                encodingType = ENCODING_7BIT;
1020            }
1021
1022            userDataCompressed = false;
1023            boolean active = ((dataCodingScheme & 0x08) == 0x08);
1024
1025            // bit 0x04 reserved
1026
1027            if ((dataCodingScheme & 0x03) == 0x00) {
1028                isMwi = true;
1029                mwiSense = active;
1030                mwiDontStore = ((dataCodingScheme & 0xF0) == 0xC0);
1031            } else {
1032                isMwi = false;
1033
1034                Log.w(LOG_TAG, "MWI for fax, email, or other "
1035                        + (dataCodingScheme & 0xff));
1036            }
1037        } else {
1038            Log.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
1039                    + (dataCodingScheme & 0xff));
1040        }
1041
1042        // set both the user data and the user data header.
1043        int count = p.constructUserData(hasUserDataHeader,
1044                encodingType == ENCODING_7BIT);
1045        this.userData = p.getUserData();
1046        this.userDataHeader = p.getUserDataHeader();
1047
1048        switch (encodingType) {
1049        case ENCODING_UNKNOWN:
1050        case ENCODING_8BIT:
1051            messageBody = null;
1052            break;
1053
1054        case ENCODING_7BIT:
1055            messageBody = p.getUserDataGSM7Bit(count);
1056            break;
1057
1058        case ENCODING_16BIT:
1059            messageBody = p.getUserDataUCS2(count);
1060            break;
1061        }
1062
1063        if (Config.LOGV) Log.v(LOG_TAG, "SMS message body (raw): '" + messageBody + "'");
1064
1065        if (messageBody != null) {
1066            parseMessageBody();
1067        }
1068
1069        if (!hasMessageClass) {
1070            messageClass = MessageClass.UNKNOWN;
1071        } else {
1072            switch (dataCodingScheme & 0x3) {
1073            case 0:
1074                messageClass = MessageClass.CLASS_0;
1075                break;
1076            case 1:
1077                messageClass = MessageClass.CLASS_1;
1078                break;
1079            case 2:
1080                messageClass = MessageClass.CLASS_2;
1081                break;
1082            case 3:
1083                messageClass = MessageClass.CLASS_3;
1084                break;
1085            }
1086        }
1087    }
1088
1089    /**
1090     * {@inheritDoc}
1091     */
1092    public MessageClass getMessageClass() {
1093        return messageClass;
1094    }
1095
1096}
1097