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