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