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