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