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