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