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