1767a662ecde33c3979bf02b793d392aca0403162Wink Saville/*
2767a662ecde33c3979bf02b793d392aca0403162Wink Saville * Copyright (C) 2006 The Android Open Source Project
3767a662ecde33c3979bf02b793d392aca0403162Wink Saville *
4767a662ecde33c3979bf02b793d392aca0403162Wink Saville * Licensed under the Apache License, Version 2.0 (the "License");
5767a662ecde33c3979bf02b793d392aca0403162Wink Saville * you may not use this file except in compliance with the License.
6767a662ecde33c3979bf02b793d392aca0403162Wink Saville * You may obtain a copy of the License at
7767a662ecde33c3979bf02b793d392aca0403162Wink Saville *
8767a662ecde33c3979bf02b793d392aca0403162Wink Saville *      http://www.apache.org/licenses/LICENSE-2.0
9767a662ecde33c3979bf02b793d392aca0403162Wink Saville *
10767a662ecde33c3979bf02b793d392aca0403162Wink Saville * Unless required by applicable law or agreed to in writing, software
11767a662ecde33c3979bf02b793d392aca0403162Wink Saville * distributed under the License is distributed on an "AS IS" BASIS,
12767a662ecde33c3979bf02b793d392aca0403162Wink Saville * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13767a662ecde33c3979bf02b793d392aca0403162Wink Saville * See the License for the specific language governing permissions and
14767a662ecde33c3979bf02b793d392aca0403162Wink Saville * limitations under the License.
15767a662ecde33c3979bf02b793d392aca0403162Wink Saville */
16767a662ecde33c3979bf02b793d392aca0403162Wink Saville
17767a662ecde33c3979bf02b793d392aca0403162Wink Savillepackage com.android.internal.telephony.gsm;
18767a662ecde33c3979bf02b793d392aca0403162Wink Saville
19767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport android.telephony.PhoneNumberUtils;
20767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport android.text.format.Time;
21767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport android.util.Log;
2287d14a1756f2ff4abdc107de35d06739245a606eJake Hamby
23767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport com.android.internal.telephony.EncodeException;
24767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport com.android.internal.telephony.GsmAlphabet;
2587d14a1756f2ff4abdc107de35d06739245a606eJake Hambyimport com.android.internal.telephony.IccUtils;
26767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport com.android.internal.telephony.SmsHeader;
27767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport com.android.internal.telephony.SmsMessageBase;
28767a662ecde33c3979bf02b793d392aca0403162Wink Saville
29767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport java.io.ByteArrayOutputStream;
30767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport java.io.UnsupportedEncodingException;
31767a662ecde33c3979bf02b793d392aca0403162Wink Saville
3287d14a1756f2ff4abdc107de35d06739245a606eJake Hambyimport static android.telephony.SmsMessage.ENCODING_16BIT;
33767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport static android.telephony.SmsMessage.ENCODING_7BIT;
34767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport static android.telephony.SmsMessage.ENCODING_8BIT;
35b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Leeimport static android.telephony.SmsMessage.ENCODING_KSC5601;
36767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport static android.telephony.SmsMessage.ENCODING_UNKNOWN;
37767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
38767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
39767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS;
40767a662ecde33c3979bf02b793d392aca0403162Wink Savilleimport static android.telephony.SmsMessage.MessageClass;
41767a662ecde33c3979bf02b793d392aca0403162Wink Saville
42767a662ecde33c3979bf02b793d392aca0403162Wink Saville/**
43767a662ecde33c3979bf02b793d392aca0403162Wink Saville * A Short Message Service message.
44767a662ecde33c3979bf02b793d392aca0403162Wink Saville *
45767a662ecde33c3979bf02b793d392aca0403162Wink Saville */
46145ff609de3206b585819ef974fab20cdc2d9f5eJake Hambypublic class SmsMessage extends SmsMessageBase {
47767a662ecde33c3979bf02b793d392aca0403162Wink Saville    static final String LOG_TAG = "GSM";
48767a662ecde33c3979bf02b793d392aca0403162Wink Saville
49767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private MessageClass messageClass;
50767a662ecde33c3979bf02b793d392aca0403162Wink Saville
51767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
52767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * TP-Message-Type-Indicator
53767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * 9.2.3
54767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
55767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private int mti;
56767a662ecde33c3979bf02b793d392aca0403162Wink Saville
57767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** TP-Protocol-Identifier (TP-PID) */
58767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private int protocolIdentifier;
59767a662ecde33c3979bf02b793d392aca0403162Wink Saville
60767a662ecde33c3979bf02b793d392aca0403162Wink Saville    // TP-Data-Coding-Scheme
61767a662ecde33c3979bf02b793d392aca0403162Wink Saville    // see TS 23.038
62767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private int dataCodingScheme;
63767a662ecde33c3979bf02b793d392aca0403162Wink Saville
64767a662ecde33c3979bf02b793d392aca0403162Wink Saville    // TP-Reply-Path
65767a662ecde33c3979bf02b793d392aca0403162Wink Saville    // e.g. 23.040 9.2.2.1
66767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private boolean replyPathPresent = false;
67767a662ecde33c3979bf02b793d392aca0403162Wink Saville
68767a662ecde33c3979bf02b793d392aca0403162Wink Saville    // "Message Marked for Automatic Deletion Group"
69767a662ecde33c3979bf02b793d392aca0403162Wink Saville    // 23.038 Section 4
70767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private boolean automaticDeletion;
71767a662ecde33c3979bf02b793d392aca0403162Wink Saville
72767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** True if Status Report is for SMS-SUBMIT; false for SMS-COMMAND. */
73767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private boolean forSubmit;
74767a662ecde33c3979bf02b793d392aca0403162Wink Saville
75767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** The address of the receiver. */
76767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private GsmSmsAddress recipientAddress;
77767a662ecde33c3979bf02b793d392aca0403162Wink Saville
78767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** Time when SMS-SUBMIT was delivered from SC to MSE. */
79767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private long dischargeTimeMillis;
80767a662ecde33c3979bf02b793d392aca0403162Wink Saville
81767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
82767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *  TP-Status - status of a previously submitted SMS.
83767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *  This field applies to SMS-STATUS-REPORT messages.  0 indicates success;
84767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *  see TS 23.040, 9.2.3.15 for description of other possible values.
85767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
86767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private int status;
87767a662ecde33c3979bf02b793d392aca0403162Wink Saville
88767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
89767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *  TP-Status - status of a previously submitted SMS.
90767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *  This field is true iff the message is a SMS-STATUS-REPORT message.
91767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
92767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private boolean isStatusReportMessage = false;
93767a662ecde33c3979bf02b793d392aca0403162Wink Saville
94767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public static class SubmitPdu extends SubmitPduBase {
95767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
96767a662ecde33c3979bf02b793d392aca0403162Wink Saville
97767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
98767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * Create an SmsMessage from a raw PDU.
99767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
100767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public static SmsMessage createFromPdu(byte[] pdu) {
101767a662ecde33c3979bf02b793d392aca0403162Wink Saville        try {
102767a662ecde33c3979bf02b793d392aca0403162Wink Saville            SmsMessage msg = new SmsMessage();
103767a662ecde33c3979bf02b793d392aca0403162Wink Saville            msg.parsePdu(pdu);
104767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return msg;
105767a662ecde33c3979bf02b793d392aca0403162Wink Saville        } catch (RuntimeException ex) {
106767a662ecde33c3979bf02b793d392aca0403162Wink Saville            Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
107767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return null;
108767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
109767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
110767a662ecde33c3979bf02b793d392aca0403162Wink Saville
111767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
112c16889dc0034daaf50b2307c4de6c16c4964f30cNaveen Kalla     * 3GPP TS 23.040 9.2.3.9 specifies that Type Zero messages are indicated
113c16889dc0034daaf50b2307c4de6c16c4964f30cNaveen Kalla     * by TP_PID field set to value 0x40
114c16889dc0034daaf50b2307c4de6c16c4964f30cNaveen Kalla     */
115c16889dc0034daaf50b2307c4de6c16c4964f30cNaveen Kalla    public boolean isTypeZero() {
116c16889dc0034daaf50b2307c4de6c16c4964f30cNaveen Kalla        return (protocolIdentifier == 0x40);
117c16889dc0034daaf50b2307c4de6c16c4964f30cNaveen Kalla    }
118c16889dc0034daaf50b2307c4de6c16c4964f30cNaveen Kalla
119c16889dc0034daaf50b2307c4de6c16c4964f30cNaveen Kalla    /**
120767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
121767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * +CMT unsolicited response (PDU mode, of course)
122767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *  +CMT: [&lt;alpha>],<length><CR><LF><pdu>
123767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *
124767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * Only public for debugging
125767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *
126767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * {@hide}
127767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
128767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public static SmsMessage newFromCMT(String[] lines) {
129767a662ecde33c3979bf02b793d392aca0403162Wink Saville        try {
130767a662ecde33c3979bf02b793d392aca0403162Wink Saville            SmsMessage msg = new SmsMessage();
131767a662ecde33c3979bf02b793d392aca0403162Wink Saville            msg.parsePdu(IccUtils.hexStringToBytes(lines[1]));
132767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return msg;
133767a662ecde33c3979bf02b793d392aca0403162Wink Saville        } catch (RuntimeException ex) {
134767a662ecde33c3979bf02b793d392aca0403162Wink Saville            Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
135767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return null;
136767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
137767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
138767a662ecde33c3979bf02b793d392aca0403162Wink Saville
139767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** @hide */
140767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public static SmsMessage newFromCDS(String line) {
141767a662ecde33c3979bf02b793d392aca0403162Wink Saville        try {
142767a662ecde33c3979bf02b793d392aca0403162Wink Saville            SmsMessage msg = new SmsMessage();
143767a662ecde33c3979bf02b793d392aca0403162Wink Saville            msg.parsePdu(IccUtils.hexStringToBytes(line));
144767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return msg;
145767a662ecde33c3979bf02b793d392aca0403162Wink Saville        } catch (RuntimeException ex) {
146767a662ecde33c3979bf02b793d392aca0403162Wink Saville            Log.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex);
147767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return null;
148767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
149767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
150767a662ecde33c3979bf02b793d392aca0403162Wink Saville
151767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
152767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * Create an SmsMessage from an SMS EF record.
153767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *
154767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param index Index of SMS record. This should be index in ArrayList
155767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *              returned by SmsManager.getAllMessagesFromSim + 1.
156767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param data Record data.
157767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @return An SmsMessage representing the record.
158767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *
159767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @hide
160767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
161767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public static SmsMessage createFromEfRecord(int index, byte[] data) {
162767a662ecde33c3979bf02b793d392aca0403162Wink Saville        try {
163767a662ecde33c3979bf02b793d392aca0403162Wink Saville            SmsMessage msg = new SmsMessage();
164767a662ecde33c3979bf02b793d392aca0403162Wink Saville
165767a662ecde33c3979bf02b793d392aca0403162Wink Saville            msg.indexOnIcc = index;
166767a662ecde33c3979bf02b793d392aca0403162Wink Saville
167767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
168767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // or STORED_UNSENT
169767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // See TS 51.011 10.5.3
170767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if ((data[0] & 1) == 0) {
171767a662ecde33c3979bf02b793d392aca0403162Wink Saville                Log.w(LOG_TAG,
172767a662ecde33c3979bf02b793d392aca0403162Wink Saville                        "SMS parsing failed: Trying to parse a free record");
173767a662ecde33c3979bf02b793d392aca0403162Wink Saville                return null;
174767a662ecde33c3979bf02b793d392aca0403162Wink Saville            } else {
175767a662ecde33c3979bf02b793d392aca0403162Wink Saville                msg.statusOnIcc = data[0] & 0x07;
176767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
177767a662ecde33c3979bf02b793d392aca0403162Wink Saville
178767a662ecde33c3979bf02b793d392aca0403162Wink Saville            int size = data.length - 1;
179767a662ecde33c3979bf02b793d392aca0403162Wink Saville
180767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // Note: Data may include trailing FF's.  That's OK; message
181767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // should still parse correctly.
182767a662ecde33c3979bf02b793d392aca0403162Wink Saville            byte[] pdu = new byte[size];
183767a662ecde33c3979bf02b793d392aca0403162Wink Saville            System.arraycopy(data, 1, pdu, 0, size);
184767a662ecde33c3979bf02b793d392aca0403162Wink Saville            msg.parsePdu(pdu);
185767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return msg;
186767a662ecde33c3979bf02b793d392aca0403162Wink Saville        } catch (RuntimeException ex) {
187767a662ecde33c3979bf02b793d392aca0403162Wink Saville            Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
188767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return null;
189767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
190767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
191767a662ecde33c3979bf02b793d392aca0403162Wink Saville
192767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
193767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
194767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * length in bytes (not hex chars) less the SMSC header
195767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
196767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public static int getTPLayerLengthForPDU(String pdu) {
197767a662ecde33c3979bf02b793d392aca0403162Wink Saville        int len = pdu.length() / 2;
198b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby        int smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
199767a662ecde33c3979bf02b793d392aca0403162Wink Saville
200767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return len - smscLen - 1;
201767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
202767a662ecde33c3979bf02b793d392aca0403162Wink Saville
203767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
204767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * Get an SMS-SUBMIT PDU for a destination address and a message
205767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *
206767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param scAddress Service Centre address.  Null means use default.
207767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @return a <code>SubmitPdu</code> containing the encoded SC
208767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *         address, if applicable, and the encoded message.
209767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *         Returns null on encode error.
210767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @hide
211767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
212767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public static SubmitPdu getSubmitPdu(String scAddress,
213767a662ecde33c3979bf02b793d392aca0403162Wink Saville            String destinationAddress, String message,
214767a662ecde33c3979bf02b793d392aca0403162Wink Saville            boolean statusReportRequested, byte[] header) {
2151fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
216b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby                ENCODING_UNKNOWN, 0, 0);
2171fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq    }
2181fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq
2191fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq
2201fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq    /**
2211fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     * Get an SMS-SUBMIT PDU for a destination address and a message using the
2221fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     * specified encoding.
2231fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     *
2241fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     * @param scAddress Service Centre address.  Null means use default.
2251fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     * @param encoding Encoding defined by constants in android.telephony.SmsMessage.ENCODING_*
226b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby     * @param languageTable
227b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby     * @param languageShiftTable
2281fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     * @return a <code>SubmitPdu</code> containing the encoded SC
2291fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     *         address, if applicable, and the encoded message.
2301fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     *         Returns null on encode error.
2311fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     * @hide
2321fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     */
2331fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq    public static SubmitPdu getSubmitPdu(String scAddress,
2341fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            String destinationAddress, String message,
235b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby            boolean statusReportRequested, byte[] header, int encoding,
236b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby            int languageTable, int languageShiftTable) {
237767a662ecde33c3979bf02b793d392aca0403162Wink Saville
238767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // Perform null parameter checks.
239767a662ecde33c3979bf02b793d392aca0403162Wink Saville        if (message == null || destinationAddress == null) {
240767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return null;
241767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
242767a662ecde33c3979bf02b793d392aca0403162Wink Saville
24387d14a1756f2ff4abdc107de35d06739245a606eJake Hamby        if (encoding == ENCODING_UNKNOWN) {
24487d14a1756f2ff4abdc107de35d06739245a606eJake Hamby            // Find the best encoding to use
24587d14a1756f2ff4abdc107de35d06739245a606eJake Hamby            TextEncodingDetails ted = calculateLength(message, false);
24687d14a1756f2ff4abdc107de35d06739245a606eJake Hamby            encoding = ted.codeUnitSize;
24787d14a1756f2ff4abdc107de35d06739245a606eJake Hamby            languageTable = ted.languageTable;
24887d14a1756f2ff4abdc107de35d06739245a606eJake Hamby            languageShiftTable = ted.languageShiftTable;
24987d14a1756f2ff4abdc107de35d06739245a606eJake Hamby
25087d14a1756f2ff4abdc107de35d06739245a606eJake Hamby            if (encoding == ENCODING_7BIT && (languageTable != 0 || languageShiftTable != 0)) {
25187d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                if (header != null) {
25287d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                    SmsHeader smsHeader = SmsHeader.fromByteArray(header);
25387d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                    if (smsHeader.languageTable != languageTable
25487d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                            || smsHeader.languageShiftTable != languageShiftTable) {
25587d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                        Log.w(LOG_TAG, "Updating language table in SMS header: "
25687d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                                + smsHeader.languageTable + " -> " + languageTable + ", "
25787d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                                + smsHeader.languageShiftTable + " -> " + languageShiftTable);
25887d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                        smsHeader.languageTable = languageTable;
25987d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                        smsHeader.languageShiftTable = languageShiftTable;
26087d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                        header = SmsHeader.toByteArray(smsHeader);
26187d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                    }
26287d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                } else {
26387d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                    SmsHeader smsHeader = new SmsHeader();
26487d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                    smsHeader.languageTable = languageTable;
26587d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                    smsHeader.languageShiftTable = languageShiftTable;
26687d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                    header = SmsHeader.toByteArray(smsHeader);
26787d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                }
26887d14a1756f2ff4abdc107de35d06739245a606eJake Hamby            }
26987d14a1756f2ff4abdc107de35d06739245a606eJake Hamby        }
27087d14a1756f2ff4abdc107de35d06739245a606eJake Hamby
271767a662ecde33c3979bf02b793d392aca0403162Wink Saville        SubmitPdu ret = new SubmitPdu();
272767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // MTI = SMS-SUBMIT, UDHI = header != null
273767a662ecde33c3979bf02b793d392aca0403162Wink Saville        byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
274767a662ecde33c3979bf02b793d392aca0403162Wink Saville        ByteArrayOutputStream bo = getSubmitPduHead(
275767a662ecde33c3979bf02b793d392aca0403162Wink Saville                scAddress, destinationAddress, mtiByte,
276767a662ecde33c3979bf02b793d392aca0403162Wink Saville                statusReportRequested, ret);
27787d14a1756f2ff4abdc107de35d06739245a606eJake Hamby
2781fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        // User Data (and length)
2791fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        byte[] userData;
2801fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        try {
2811fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            if (encoding == ENCODING_7BIT) {
282b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby                userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
283b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby                        languageTable, languageShiftTable);
2841fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            } else { //assume UCS-2
2851fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                try {
2861fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                    userData = encodeUCS2(message, header);
2871fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                } catch(UnsupportedEncodingException uex) {
2881fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                    Log.e(LOG_TAG,
2891fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                            "Implausible UnsupportedEncodingException ",
2901fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                            uex);
2911fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                    return null;
2921fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                }
2931fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            }
2941fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        } catch (EncodeException ex) {
2951fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            // Encoding to the 7-bit alphabet failed. Let's see if we can
2961fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            // send it as a UCS-2 encoded message
2971fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            try {
2981fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                userData = encodeUCS2(message, header);
2990e200c3cbed83283c79703b552fe8e3d49040f10Bai Tao                encoding = ENCODING_16BIT;
3001fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            } catch(UnsupportedEncodingException uex) {
3011fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                Log.e(LOG_TAG,
3021fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                        "Implausible UnsupportedEncodingException ",
3031fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                        uex);
3041fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq                return null;
3051fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            }
3061fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        }
307767a662ecde33c3979bf02b793d392aca0403162Wink Saville
3081fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        if (encoding == ENCODING_7BIT) {
309767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
310767a662ecde33c3979bf02b793d392aca0403162Wink Saville                // Message too long
31187d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                Log.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
312767a662ecde33c3979bf02b793d392aca0403162Wink Saville                return null;
313767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
314767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // TP-Data-Coding-Scheme
315767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // Default encoding, uncompressed
316faf4413dffdc9079683b951736088ff2a01073a4jsh            // To test writing messages to the SIM card, change this value 0x00
317faf4413dffdc9079683b951736088ff2a01073a4jsh            // to 0x12, which means "bits 1 and 0 contain message class, and the
318faf4413dffdc9079683b951736088ff2a01073a4jsh            // class is 2". Note that this takes effect for the sender. In other
319faf4413dffdc9079683b951736088ff2a01073a4jsh            // words, messages sent by the phone with this change will end up on
320faf4413dffdc9079683b951736088ff2a01073a4jsh            // the receiver's SIM card. You can then send messages to yourself
321faf4413dffdc9079683b951736088ff2a01073a4jsh            // (on a phone with this change) and they'll end up on the SIM card.
322767a662ecde33c3979bf02b793d392aca0403162Wink Saville            bo.write(0x00);
323145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby        } else { // assume UCS-2
3241fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
325767a662ecde33c3979bf02b793d392aca0403162Wink Saville                // Message too long
32687d14a1756f2ff4abdc107de35d06739245a606eJake Hamby                Log.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
327767a662ecde33c3979bf02b793d392aca0403162Wink Saville                return null;
328767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
329767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // TP-Data-Coding-Scheme
330df0c2a94b51ead16a8b7c7b3159295bc978d9831sj            // UCS-2 encoding, uncompressed
331df0c2a94b51ead16a8b7c7b3159295bc978d9831sj            bo.write(0x08);
332767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
333767a662ecde33c3979bf02b793d392aca0403162Wink Saville
3341fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        // (no TP-Validity-Period)
3351fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        bo.write(userData, 0, userData.length);
336767a662ecde33c3979bf02b793d392aca0403162Wink Saville        ret.encodedMessage = bo.toByteArray();
337767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return ret;
338767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
339767a662ecde33c3979bf02b793d392aca0403162Wink Saville
340767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
3411fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary
3421fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     *
3431fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     * @return
3441fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     * @throws UnsupportedEncodingException
3451fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq     */
3461fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq    private static byte[] encodeUCS2(String message, byte[] header)
3471fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        throws UnsupportedEncodingException {
3481fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        byte[] userData, textPart;
3491fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        textPart = message.getBytes("utf-16be");
3501fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq
3511fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        if (header != null) {
3521fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            // Need 1 byte for UDHL
3531fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            userData = new byte[header.length + textPart.length + 1];
3541fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq
3551fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            userData[0] = (byte)header.length;
3561fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            System.arraycopy(header, 0, userData, 1, header.length);
3571fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
3581fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        }
3591fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        else {
3601fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq            userData = textPart;
3611fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        }
3621fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        byte[] ret = new byte[userData.length+1];
3631fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        ret[0] = (byte) (userData.length & 0xff );
3641fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        System.arraycopy(userData, 0, ret, 1, userData.length);
3651fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq        return ret;
3661fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq    }
3671fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq
3681fa7fae1399f735ea15242484fb5def187e07fdeGilles Duboscq    /**
369767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * Get an SMS-SUBMIT PDU for a destination address and a message
370767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *
371767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param scAddress Service Centre address.  Null means use default.
372767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @return a <code>SubmitPdu</code> containing the encoded SC
373767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *         address, if applicable, and the encoded message.
374767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *         Returns null on encode error.
375767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
376767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public static SubmitPdu getSubmitPdu(String scAddress,
377767a662ecde33c3979bf02b793d392aca0403162Wink Saville            String destinationAddress, String message,
378767a662ecde33c3979bf02b793d392aca0403162Wink Saville            boolean statusReportRequested) {
379767a662ecde33c3979bf02b793d392aca0403162Wink Saville
380767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
381767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
382767a662ecde33c3979bf02b793d392aca0403162Wink Saville
383767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
384767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
385767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *
386767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param scAddress Service Centre address. null == use default
387767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param destinationAddress the address of the destination for the message
388767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param destinationPort the port to deliver the message to at the
389767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *        destination
390145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby     * @param data the data for the message
391767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @return a <code>SubmitPdu</code> containing the encoded SC
392767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *         address, if applicable, and the encoded message.
393767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *         Returns null on encode error.
394767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
395767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public static SubmitPdu getSubmitPdu(String scAddress,
3961f952a178db86559ff4bab79c4a9b5fae18096bfTammo Spalink            String destinationAddress, int destinationPort, byte[] data,
397767a662ecde33c3979bf02b793d392aca0403162Wink Saville            boolean statusReportRequested) {
39864c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink
39964c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
40064c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        portAddrs.destPort = destinationPort;
40164c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        portAddrs.origPort = 0;
40264c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        portAddrs.areEightBits = false;
40364c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink
40464c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        SmsHeader smsHeader = new SmsHeader();
40564c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        smsHeader.portAddrs = portAddrs;
40664c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink
40764c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
40864c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink
40964c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
410767a662ecde33c3979bf02b793d392aca0403162Wink Saville            Log.e(LOG_TAG, "SMS data message may only contain "
41164c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink                    + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes");
412767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return null;
413767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
414767a662ecde33c3979bf02b793d392aca0403162Wink Saville
415767a662ecde33c3979bf02b793d392aca0403162Wink Saville        SubmitPdu ret = new SubmitPdu();
416767a662ecde33c3979bf02b793d392aca0403162Wink Saville        ByteArrayOutputStream bo = getSubmitPduHead(
417767a662ecde33c3979bf02b793d392aca0403162Wink Saville                scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT,
418767a662ecde33c3979bf02b793d392aca0403162Wink Saville                                                            // TP-UDHI = true
419767a662ecde33c3979bf02b793d392aca0403162Wink Saville                statusReportRequested, ret);
420767a662ecde33c3979bf02b793d392aca0403162Wink Saville
421767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Data-Coding-Scheme
422767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // No class, 8 bit data
423767a662ecde33c3979bf02b793d392aca0403162Wink Saville        bo.write(0x04);
424767a662ecde33c3979bf02b793d392aca0403162Wink Saville
425767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // (no TP-Validity-Period)
426767a662ecde33c3979bf02b793d392aca0403162Wink Saville
42764c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        // Total size
42864c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        bo.write(data.length + smsHeaderData.length + 1);
429767a662ecde33c3979bf02b793d392aca0403162Wink Saville
43064c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        // User data header
43164c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        bo.write(smsHeaderData.length);
43264c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink        bo.write(smsHeaderData, 0, smsHeaderData.length);
433767a662ecde33c3979bf02b793d392aca0403162Wink Saville
434767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // User data
435767a662ecde33c3979bf02b793d392aca0403162Wink Saville        bo.write(data, 0, data.length);
436767a662ecde33c3979bf02b793d392aca0403162Wink Saville
437767a662ecde33c3979bf02b793d392aca0403162Wink Saville        ret.encodedMessage = bo.toByteArray();
438767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return ret;
439767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
440767a662ecde33c3979bf02b793d392aca0403162Wink Saville
441767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
442767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * Create the beginning of a SUBMIT PDU.  This is the part of the
443767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu},
444767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * one of which takes a byte array and the other of which takes a
445767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * <code>String</code>.
446767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *
447767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param scAddress Service Centre address. null == use default
448767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param destinationAddress the address of the destination for the message
449767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param mtiByte
450767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param ret <code>SubmitPdu</code> containing the encoded SC
451767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *        address, if applicable, and the encoded message
452767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
453767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private static ByteArrayOutputStream getSubmitPduHead(
454767a662ecde33c3979bf02b793d392aca0403162Wink Saville            String scAddress, String destinationAddress, byte mtiByte,
455767a662ecde33c3979bf02b793d392aca0403162Wink Saville            boolean statusReportRequested, SubmitPdu ret) {
456767a662ecde33c3979bf02b793d392aca0403162Wink Saville        ByteArrayOutputStream bo = new ByteArrayOutputStream(
457767a662ecde33c3979bf02b793d392aca0403162Wink Saville                MAX_USER_DATA_BYTES + 40);
458767a662ecde33c3979bf02b793d392aca0403162Wink Saville
459767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // SMSC address with length octet, or 0
460767a662ecde33c3979bf02b793d392aca0403162Wink Saville        if (scAddress == null) {
461767a662ecde33c3979bf02b793d392aca0403162Wink Saville            ret.encodedScAddress = null;
462767a662ecde33c3979bf02b793d392aca0403162Wink Saville        } else {
463767a662ecde33c3979bf02b793d392aca0403162Wink Saville            ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
464767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    scAddress);
465767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
466767a662ecde33c3979bf02b793d392aca0403162Wink Saville
467767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Message-Type-Indicator (and friends)
468767a662ecde33c3979bf02b793d392aca0403162Wink Saville        if (statusReportRequested) {
469767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // Set TP-Status-Report-Request bit.
470767a662ecde33c3979bf02b793d392aca0403162Wink Saville            mtiByte |= 0x20;
47143a17654cf4bfe7f1ec22bd8b7b32daccdf27c09Joe Onorato            if (false) Log.d(LOG_TAG, "SMS status report requested");
472767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
473767a662ecde33c3979bf02b793d392aca0403162Wink Saville        bo.write(mtiByte);
474767a662ecde33c3979bf02b793d392aca0403162Wink Saville
475767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // space for TP-Message-Reference
476767a662ecde33c3979bf02b793d392aca0403162Wink Saville        bo.write(0);
477767a662ecde33c3979bf02b793d392aca0403162Wink Saville
478767a662ecde33c3979bf02b793d392aca0403162Wink Saville        byte[] daBytes;
479767a662ecde33c3979bf02b793d392aca0403162Wink Saville
480767a662ecde33c3979bf02b793d392aca0403162Wink Saville        daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
481767a662ecde33c3979bf02b793d392aca0403162Wink Saville
482767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // destination address length in BCD digits, ignoring TON byte and pad
483767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TODO Should be better.
484767a662ecde33c3979bf02b793d392aca0403162Wink Saville        bo.write((daBytes.length - 1) * 2
485767a662ecde33c3979bf02b793d392aca0403162Wink Saville                - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
486767a662ecde33c3979bf02b793d392aca0403162Wink Saville
487767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // destination address
488767a662ecde33c3979bf02b793d392aca0403162Wink Saville        bo.write(daBytes, 0, daBytes.length);
489767a662ecde33c3979bf02b793d392aca0403162Wink Saville
490767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Protocol-Identifier
491767a662ecde33c3979bf02b793d392aca0403162Wink Saville        bo.write(0);
492767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return bo;
493767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
494767a662ecde33c3979bf02b793d392aca0403162Wink Saville
495145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby    private static class PduParser {
496767a662ecde33c3979bf02b793d392aca0403162Wink Saville        byte pdu[];
497767a662ecde33c3979bf02b793d392aca0403162Wink Saville        int cur;
498767a662ecde33c3979bf02b793d392aca0403162Wink Saville        SmsHeader userDataHeader;
499767a662ecde33c3979bf02b793d392aca0403162Wink Saville        byte[] userData;
500767a662ecde33c3979bf02b793d392aca0403162Wink Saville        int mUserDataSeptetPadding;
501767a662ecde33c3979bf02b793d392aca0403162Wink Saville        int mUserDataSize;
502767a662ecde33c3979bf02b793d392aca0403162Wink Saville
503767a662ecde33c3979bf02b793d392aca0403162Wink Saville        PduParser(byte[] pdu) {
504767a662ecde33c3979bf02b793d392aca0403162Wink Saville            this.pdu = pdu;
505767a662ecde33c3979bf02b793d392aca0403162Wink Saville            cur = 0;
506767a662ecde33c3979bf02b793d392aca0403162Wink Saville            mUserDataSeptetPadding = 0;
507767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
508767a662ecde33c3979bf02b793d392aca0403162Wink Saville
509767a662ecde33c3979bf02b793d392aca0403162Wink Saville        /**
510767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * Parse and return the SC address prepended to SMS messages coming via
511767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * the TS 27.005 / AT interface.  Returns null on invalid address
512767a662ecde33c3979bf02b793d392aca0403162Wink Saville         */
513767a662ecde33c3979bf02b793d392aca0403162Wink Saville        String getSCAddress() {
514767a662ecde33c3979bf02b793d392aca0403162Wink Saville            int len;
515767a662ecde33c3979bf02b793d392aca0403162Wink Saville            String ret;
516767a662ecde33c3979bf02b793d392aca0403162Wink Saville
517767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // length of SC Address
518767a662ecde33c3979bf02b793d392aca0403162Wink Saville            len = getByte();
519767a662ecde33c3979bf02b793d392aca0403162Wink Saville
520767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if (len == 0) {
521767a662ecde33c3979bf02b793d392aca0403162Wink Saville                // no SC address
522767a662ecde33c3979bf02b793d392aca0403162Wink Saville                ret = null;
523767a662ecde33c3979bf02b793d392aca0403162Wink Saville            } else {
524767a662ecde33c3979bf02b793d392aca0403162Wink Saville                // SC address
525767a662ecde33c3979bf02b793d392aca0403162Wink Saville                try {
526767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    ret = PhoneNumberUtils
527767a662ecde33c3979bf02b793d392aca0403162Wink Saville                            .calledPartyBCDToString(pdu, cur, len);
528767a662ecde33c3979bf02b793d392aca0403162Wink Saville                } catch (RuntimeException tr) {
529767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    Log.d(LOG_TAG, "invalid SC address: ", tr);
530767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    ret = null;
531767a662ecde33c3979bf02b793d392aca0403162Wink Saville                }
532767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
533767a662ecde33c3979bf02b793d392aca0403162Wink Saville
534767a662ecde33c3979bf02b793d392aca0403162Wink Saville            cur += len;
535767a662ecde33c3979bf02b793d392aca0403162Wink Saville
536767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return ret;
537767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
538767a662ecde33c3979bf02b793d392aca0403162Wink Saville
539767a662ecde33c3979bf02b793d392aca0403162Wink Saville        /**
540767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * returns non-sign-extended byte value
541767a662ecde33c3979bf02b793d392aca0403162Wink Saville         */
542767a662ecde33c3979bf02b793d392aca0403162Wink Saville        int getByte() {
543767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return pdu[cur++] & 0xff;
544767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
545767a662ecde33c3979bf02b793d392aca0403162Wink Saville
546767a662ecde33c3979bf02b793d392aca0403162Wink Saville        /**
547767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * Any address except the SC address (eg, originating address) See TS
548767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * 23.040 9.1.2.5
549767a662ecde33c3979bf02b793d392aca0403162Wink Saville         */
550767a662ecde33c3979bf02b793d392aca0403162Wink Saville        GsmSmsAddress getAddress() {
551767a662ecde33c3979bf02b793d392aca0403162Wink Saville            GsmSmsAddress ret;
552767a662ecde33c3979bf02b793d392aca0403162Wink Saville
553767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // "The Address-Length field is an integer representation of
554145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby            // the number field, i.e. excludes any semi-octet containing only
555767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // fill bits."
556767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // The TOA field is not included as part of this
557767a662ecde33c3979bf02b793d392aca0403162Wink Saville            int addressLength = pdu[cur] & 0xff;
558767a662ecde33c3979bf02b793d392aca0403162Wink Saville            int lengthBytes = 2 + (addressLength + 1) / 2;
559767a662ecde33c3979bf02b793d392aca0403162Wink Saville
560767a662ecde33c3979bf02b793d392aca0403162Wink Saville            ret = new GsmSmsAddress(pdu, cur, lengthBytes);
561767a662ecde33c3979bf02b793d392aca0403162Wink Saville
562767a662ecde33c3979bf02b793d392aca0403162Wink Saville            cur += lengthBytes;
563767a662ecde33c3979bf02b793d392aca0403162Wink Saville
564767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return ret;
565767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
566767a662ecde33c3979bf02b793d392aca0403162Wink Saville
567767a662ecde33c3979bf02b793d392aca0403162Wink Saville        /**
568767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * Parses an SC timestamp and returns a currentTimeMillis()-style
569767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * timestamp
570767a662ecde33c3979bf02b793d392aca0403162Wink Saville         */
571767a662ecde33c3979bf02b793d392aca0403162Wink Saville
572767a662ecde33c3979bf02b793d392aca0403162Wink Saville        long getSCTimestampMillis() {
573767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // TP-Service-Centre-Time-Stamp
5749688c6046fdbf6a24e3541bd6342995b4605fd5dWink Saville            int year = IccUtils.gsmBcdByteToInt(pdu[cur++]);
5759688c6046fdbf6a24e3541bd6342995b4605fd5dWink Saville            int month = IccUtils.gsmBcdByteToInt(pdu[cur++]);
5769688c6046fdbf6a24e3541bd6342995b4605fd5dWink Saville            int day = IccUtils.gsmBcdByteToInt(pdu[cur++]);
5779688c6046fdbf6a24e3541bd6342995b4605fd5dWink Saville            int hour = IccUtils.gsmBcdByteToInt(pdu[cur++]);
5789688c6046fdbf6a24e3541bd6342995b4605fd5dWink Saville            int minute = IccUtils.gsmBcdByteToInt(pdu[cur++]);
5799688c6046fdbf6a24e3541bd6342995b4605fd5dWink Saville            int second = IccUtils.gsmBcdByteToInt(pdu[cur++]);
580767a662ecde33c3979bf02b793d392aca0403162Wink Saville
581767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // For the timezone, the most significant bit of the
582145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby            // least significant nibble is the sign byte
583767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // (meaning the max range of this field is 79 quarter-hours,
584767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // which is more than enough)
585767a662ecde33c3979bf02b793d392aca0403162Wink Saville
586767a662ecde33c3979bf02b793d392aca0403162Wink Saville            byte tzByte = pdu[cur++];
587767a662ecde33c3979bf02b793d392aca0403162Wink Saville
588767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // Mask out sign bit.
5899688c6046fdbf6a24e3541bd6342995b4605fd5dWink Saville            int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
590767a662ecde33c3979bf02b793d392aca0403162Wink Saville
5919688c6046fdbf6a24e3541bd6342995b4605fd5dWink Saville            timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
592767a662ecde33c3979bf02b793d392aca0403162Wink Saville
593767a662ecde33c3979bf02b793d392aca0403162Wink Saville            Time time = new Time(Time.TIMEZONE_UTC);
594767a662ecde33c3979bf02b793d392aca0403162Wink Saville
595767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // It's 2006.  Should I really support years < 2000?
596767a662ecde33c3979bf02b793d392aca0403162Wink Saville            time.year = year >= 90 ? year + 1900 : year + 2000;
597767a662ecde33c3979bf02b793d392aca0403162Wink Saville            time.month = month - 1;
598767a662ecde33c3979bf02b793d392aca0403162Wink Saville            time.monthDay = day;
599767a662ecde33c3979bf02b793d392aca0403162Wink Saville            time.hour = hour;
600767a662ecde33c3979bf02b793d392aca0403162Wink Saville            time.minute = minute;
601767a662ecde33c3979bf02b793d392aca0403162Wink Saville            time.second = second;
602767a662ecde33c3979bf02b793d392aca0403162Wink Saville
603767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // Timezone offset is in quarter hours.
604767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
605767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
606767a662ecde33c3979bf02b793d392aca0403162Wink Saville
607767a662ecde33c3979bf02b793d392aca0403162Wink Saville        /**
608767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * Pulls the user data out of the PDU, and separates the payload from
609767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * the header if there is one.
610767a662ecde33c3979bf02b793d392aca0403162Wink Saville         *
611767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * @param hasUserDataHeader true if there is a user data header
612767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * @param dataInSeptets true if the data payload is in septets instead
613767a662ecde33c3979bf02b793d392aca0403162Wink Saville         *  of octets
614767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * @return the number of septets or octets in the user data payload
615767a662ecde33c3979bf02b793d392aca0403162Wink Saville         */
616767a662ecde33c3979bf02b793d392aca0403162Wink Saville        int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
617767a662ecde33c3979bf02b793d392aca0403162Wink Saville            int offset = cur;
618767a662ecde33c3979bf02b793d392aca0403162Wink Saville            int userDataLength = pdu[offset++] & 0xff;
619767a662ecde33c3979bf02b793d392aca0403162Wink Saville            int headerSeptets = 0;
620faf4413dffdc9079683b951736088ff2a01073a4jsh            int userDataHeaderLength = 0;
621767a662ecde33c3979bf02b793d392aca0403162Wink Saville
622767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if (hasUserDataHeader) {
623faf4413dffdc9079683b951736088ff2a01073a4jsh                userDataHeaderLength = pdu[offset++] & 0xff;
624767a662ecde33c3979bf02b793d392aca0403162Wink Saville
625767a662ecde33c3979bf02b793d392aca0403162Wink Saville                byte[] udh = new byte[userDataHeaderLength];
626767a662ecde33c3979bf02b793d392aca0403162Wink Saville                System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
62764c499113a758cf80cddfd4d0183f944a1a6645aTammo Spalink                userDataHeader = SmsHeader.fromByteArray(udh);
628767a662ecde33c3979bf02b793d392aca0403162Wink Saville                offset += userDataHeaderLength;
629767a662ecde33c3979bf02b793d392aca0403162Wink Saville
630767a662ecde33c3979bf02b793d392aca0403162Wink Saville                int headerBits = (userDataHeaderLength + 1) * 8;
631767a662ecde33c3979bf02b793d392aca0403162Wink Saville                headerSeptets = headerBits / 7;
632767a662ecde33c3979bf02b793d392aca0403162Wink Saville                headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
633767a662ecde33c3979bf02b793d392aca0403162Wink Saville                mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
634767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
635767a662ecde33c3979bf02b793d392aca0403162Wink Saville
636faf4413dffdc9079683b951736088ff2a01073a4jsh            int bufferLen;
637faf4413dffdc9079683b951736088ff2a01073a4jsh            if (dataInSeptets) {
638faf4413dffdc9079683b951736088ff2a01073a4jsh                /*
639faf4413dffdc9079683b951736088ff2a01073a4jsh                 * Here we just create the user data length to be the remainder of
640faf4413dffdc9079683b951736088ff2a01073a4jsh                 * the pdu minus the user data header, since userDataLength means
641145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby                 * the number of uncompressed septets.
642faf4413dffdc9079683b951736088ff2a01073a4jsh                 */
643faf4413dffdc9079683b951736088ff2a01073a4jsh                bufferLen = pdu.length - offset;
644faf4413dffdc9079683b951736088ff2a01073a4jsh            } else {
645faf4413dffdc9079683b951736088ff2a01073a4jsh                /*
646faf4413dffdc9079683b951736088ff2a01073a4jsh                 * userDataLength is the count of octets, so just subtract the
647faf4413dffdc9079683b951736088ff2a01073a4jsh                 * user data header.
648faf4413dffdc9079683b951736088ff2a01073a4jsh                 */
649faf4413dffdc9079683b951736088ff2a01073a4jsh                bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
650faf4413dffdc9079683b951736088ff2a01073a4jsh                if (bufferLen < 0) {
651faf4413dffdc9079683b951736088ff2a01073a4jsh                    bufferLen = 0;
652faf4413dffdc9079683b951736088ff2a01073a4jsh                }
653faf4413dffdc9079683b951736088ff2a01073a4jsh            }
654faf4413dffdc9079683b951736088ff2a01073a4jsh
655faf4413dffdc9079683b951736088ff2a01073a4jsh            userData = new byte[bufferLen];
656767a662ecde33c3979bf02b793d392aca0403162Wink Saville            System.arraycopy(pdu, offset, userData, 0, userData.length);
657767a662ecde33c3979bf02b793d392aca0403162Wink Saville            cur = offset;
658767a662ecde33c3979bf02b793d392aca0403162Wink Saville
659767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if (dataInSeptets) {
660767a662ecde33c3979bf02b793d392aca0403162Wink Saville                // Return the number of septets
661faf4413dffdc9079683b951736088ff2a01073a4jsh                int count = userDataLength - headerSeptets;
662faf4413dffdc9079683b951736088ff2a01073a4jsh                // If count < 0, return 0 (means UDL was probably incorrect)
663faf4413dffdc9079683b951736088ff2a01073a4jsh                return count < 0 ? 0 : count;
664767a662ecde33c3979bf02b793d392aca0403162Wink Saville            } else {
665767a662ecde33c3979bf02b793d392aca0403162Wink Saville                // Return the number of octets
666767a662ecde33c3979bf02b793d392aca0403162Wink Saville                return userData.length;
667767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
668767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
669767a662ecde33c3979bf02b793d392aca0403162Wink Saville
670767a662ecde33c3979bf02b793d392aca0403162Wink Saville        /**
671767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * Returns the user data payload, not including the headers
672767a662ecde33c3979bf02b793d392aca0403162Wink Saville         *
673767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * @return the user data payload, not including the headers
674767a662ecde33c3979bf02b793d392aca0403162Wink Saville         */
675767a662ecde33c3979bf02b793d392aca0403162Wink Saville        byte[] getUserData() {
676767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return userData;
677767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
678767a662ecde33c3979bf02b793d392aca0403162Wink Saville
679767a662ecde33c3979bf02b793d392aca0403162Wink Saville        /**
680145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby         * Returns the number of padding bits at the beginning of the user data
681767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * array before the start of the septets.
682767a662ecde33c3979bf02b793d392aca0403162Wink Saville         *
683145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby         * @return the number of padding bits at the beginning of the user data
684767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * array before the start of the septets
685767a662ecde33c3979bf02b793d392aca0403162Wink Saville         */
686767a662ecde33c3979bf02b793d392aca0403162Wink Saville        int getUserDataSeptetPadding() {
687767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return mUserDataSeptetPadding;
688767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
689767a662ecde33c3979bf02b793d392aca0403162Wink Saville
690767a662ecde33c3979bf02b793d392aca0403162Wink Saville        /**
691767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * Returns an object representing the user data headers
692767a662ecde33c3979bf02b793d392aca0403162Wink Saville         *
693767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * {@hide}
694767a662ecde33c3979bf02b793d392aca0403162Wink Saville         */
695767a662ecde33c3979bf02b793d392aca0403162Wink Saville        SmsHeader getUserDataHeader() {
696767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return userDataHeader;
697767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
698767a662ecde33c3979bf02b793d392aca0403162Wink Saville
699767a662ecde33c3979bf02b793d392aca0403162Wink Saville        /**
700b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby         * Interprets the user data payload as packed GSM 7bit characters, and
701767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * decodes them into a String.
702767a662ecde33c3979bf02b793d392aca0403162Wink Saville         *
703767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * @param septetCount the number of septets in the user data payload
704767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * @return a String with the decoded characters
705767a662ecde33c3979bf02b793d392aca0403162Wink Saville         */
706b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby        String getUserDataGSM7Bit(int septetCount, int languageTable,
707b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby                int languageShiftTable) {
708767a662ecde33c3979bf02b793d392aca0403162Wink Saville            String ret;
709767a662ecde33c3979bf02b793d392aca0403162Wink Saville
710767a662ecde33c3979bf02b793d392aca0403162Wink Saville            ret = GsmAlphabet.gsm7BitPackedToString(pdu, cur, septetCount,
711b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby                    mUserDataSeptetPadding, languageTable, languageShiftTable);
712767a662ecde33c3979bf02b793d392aca0403162Wink Saville
713767a662ecde33c3979bf02b793d392aca0403162Wink Saville            cur += (septetCount * 7) / 8;
714767a662ecde33c3979bf02b793d392aca0403162Wink Saville
715767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return ret;
716767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
717767a662ecde33c3979bf02b793d392aca0403162Wink Saville
718767a662ecde33c3979bf02b793d392aca0403162Wink Saville        /**
719767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * Interprets the user data payload as UCS2 characters, and
720767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * decodes them into a String.
721767a662ecde33c3979bf02b793d392aca0403162Wink Saville         *
722767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * @param byteCount the number of bytes in the user data payload
723767a662ecde33c3979bf02b793d392aca0403162Wink Saville         * @return a String with the decoded characters
724767a662ecde33c3979bf02b793d392aca0403162Wink Saville         */
725767a662ecde33c3979bf02b793d392aca0403162Wink Saville        String getUserDataUCS2(int byteCount) {
726767a662ecde33c3979bf02b793d392aca0403162Wink Saville            String ret;
727767a662ecde33c3979bf02b793d392aca0403162Wink Saville
728767a662ecde33c3979bf02b793d392aca0403162Wink Saville            try {
729767a662ecde33c3979bf02b793d392aca0403162Wink Saville                ret = new String(pdu, cur, byteCount, "utf-16");
730767a662ecde33c3979bf02b793d392aca0403162Wink Saville            } catch (UnsupportedEncodingException ex) {
731767a662ecde33c3979bf02b793d392aca0403162Wink Saville                ret = "";
732767a662ecde33c3979bf02b793d392aca0403162Wink Saville                Log.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
733767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
734767a662ecde33c3979bf02b793d392aca0403162Wink Saville
735767a662ecde33c3979bf02b793d392aca0403162Wink Saville            cur += byteCount;
736767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return ret;
737767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
738767a662ecde33c3979bf02b793d392aca0403162Wink Saville
739b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee        /**
740b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee         * Interprets the user data payload as KSC-5601 characters, and
741b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee         * decodes them into a String.
742b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee         *
743b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee         * @param byteCount the number of bytes in the user data payload
744b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee         * @return a String with the decoded characters
745b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee         */
746b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee        String getUserDataKSC5601(int byteCount) {
747b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            String ret;
748b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee
749b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            try {
750b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee                ret = new String(pdu, cur, byteCount, "KSC5601");
751b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            } catch (UnsupportedEncodingException ex) {
752b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee                ret = "";
753b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee                Log.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
754b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            }
755b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee
756b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            cur += byteCount;
757b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            return ret;
758b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee        }
759b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee
760767a662ecde33c3979bf02b793d392aca0403162Wink Saville        boolean moreDataPresent() {
761767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return (pdu.length > cur);
762767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
763767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
764767a662ecde33c3979bf02b793d392aca0403162Wink Saville
765a94945d3a1cf23caf33759eb1de84195d3fcb37bTammo Spalink    /**
766a94945d3a1cf23caf33759eb1de84195d3fcb37bTammo Spalink     * Calculate the number of septets needed to encode the message.
767a94945d3a1cf23caf33759eb1de84195d3fcb37bTammo Spalink     *
768fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink     * @param msgBody the message to encode
769fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink     * @param use7bitOnly ignore (but still count) illegal characters if true
770fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink     * @return TextEncodingDetails
771a94945d3a1cf23caf33759eb1de84195d3fcb37bTammo Spalink     */
772fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink    public static TextEncodingDetails calculateLength(CharSequence msgBody,
773fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink            boolean use7bitOnly) {
774b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby        TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(msgBody, use7bitOnly);
775b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby        if (ted == null) {
776b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby            ted = new TextEncodingDetails();
777fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink            int octets = msgBody.length() * 2;
778fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink            ted.codeUnitCount = msgBody.length();
779fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink            if (octets > MAX_USER_DATA_BYTES) {
78017f616823a562ceb3a008f91e05d43bc56d37caeJake Hamby                ted.msgCount = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) /
78117f616823a562ceb3a008f91e05d43bc56d37caeJake Hamby                        MAX_USER_DATA_BYTES_WITH_HEADER;
78217f616823a562ceb3a008f91e05d43bc56d37caeJake Hamby                ted.codeUnitsRemaining = ((ted.msgCount *
78317f616823a562ceb3a008f91e05d43bc56d37caeJake Hamby                        MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2;
784fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink            } else {
785fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink                ted.msgCount = 1;
786fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink                ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2;
787fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink            }
788fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink            ted.codeUnitSize = ENCODING_16BIT;
789a94945d3a1cf23caf33759eb1de84195d3fcb37bTammo Spalink        }
790fc78f358cb1d1cee99758bcd6ef998a122ef27c9Tammo Spalink        return ted;
791a94945d3a1cf23caf33759eb1de84195d3fcb37bTammo Spalink    }
792a94945d3a1cf23caf33759eb1de84195d3fcb37bTammo Spalink
793767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** {@inheritDoc} */
794145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby    @Override
795767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public int getProtocolIdentifier() {
796767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return protocolIdentifier;
797767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
798767a662ecde33c3979bf02b793d392aca0403162Wink Saville
799ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby    /**
800ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby     * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages.
801ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby     * @return the TP-DCS field of the SMS header
802ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby     */
803ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby    int getDataCodingScheme() {
804ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby        return dataCodingScheme;
805ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby    }
806ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby
807767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** {@inheritDoc} */
808145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby    @Override
809767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public boolean isReplace() {
810767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return (protocolIdentifier & 0xc0) == 0x40
811767a662ecde33c3979bf02b793d392aca0403162Wink Saville                && (protocolIdentifier & 0x3f) > 0
812767a662ecde33c3979bf02b793d392aca0403162Wink Saville                && (protocolIdentifier & 0x3f) < 8;
813767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
814767a662ecde33c3979bf02b793d392aca0403162Wink Saville
815767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** {@inheritDoc} */
816145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby    @Override
817767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public boolean isCphsMwiMessage() {
818767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear()
819767a662ecde33c3979bf02b793d392aca0403162Wink Saville                || ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
820767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
821767a662ecde33c3979bf02b793d392aca0403162Wink Saville
822767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** {@inheritDoc} */
823145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby    @Override
824767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public boolean isMWIClearMessage() {
825b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby        if (isMwi && !mwiSense) {
826767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return true;
827767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
828767a662ecde33c3979bf02b793d392aca0403162Wink Saville
829767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return originatingAddress != null
830767a662ecde33c3979bf02b793d392aca0403162Wink Saville                && ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear();
831767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
832767a662ecde33c3979bf02b793d392aca0403162Wink Saville
833767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** {@inheritDoc} */
834145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby    @Override
835767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public boolean isMWISetMessage() {
836b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby        if (isMwi && mwiSense) {
837767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return true;
838767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
839767a662ecde33c3979bf02b793d392aca0403162Wink Saville
840767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return originatingAddress != null
841767a662ecde33c3979bf02b793d392aca0403162Wink Saville                && ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
842767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
843767a662ecde33c3979bf02b793d392aca0403162Wink Saville
844767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** {@inheritDoc} */
845145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby    @Override
846767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public boolean isMwiDontStore() {
847767a662ecde33c3979bf02b793d392aca0403162Wink Saville        if (isMwi && mwiDontStore) {
848767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return true;
849767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
850767a662ecde33c3979bf02b793d392aca0403162Wink Saville
851767a662ecde33c3979bf02b793d392aca0403162Wink Saville        if (isCphsMwiMessage()) {
852767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // See CPHS 4.2 Section B.4.2.1
853767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // If the user data is a single space char, do not store
854767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // the message. Otherwise, store and display as usual
855767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if (" ".equals(getMessageBody())) {
856767a662ecde33c3979bf02b793d392aca0403162Wink Saville                ;
857767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
858767a662ecde33c3979bf02b793d392aca0403162Wink Saville            return true;
859767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
860767a662ecde33c3979bf02b793d392aca0403162Wink Saville
861767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return false;
862767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
863767a662ecde33c3979bf02b793d392aca0403162Wink Saville
864767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** {@inheritDoc} */
865145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby    @Override
866767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public int getStatus() {
867767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return status;
868767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
869767a662ecde33c3979bf02b793d392aca0403162Wink Saville
870767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** {@inheritDoc} */
871145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby    @Override
872767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public boolean isStatusReportMessage() {
873767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return isStatusReportMessage;
874767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
875767a662ecde33c3979bf02b793d392aca0403162Wink Saville
876767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /** {@inheritDoc} */
877145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby    @Override
878767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public boolean isReplyPathPresent() {
879767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return replyPathPresent;
880767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
881767a662ecde33c3979bf02b793d392aca0403162Wink Saville
882767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
883145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby     * TS 27.005 3.1, &lt;pdu&gt; definition "In the case of SMS: 3GPP TS 24.011 [6]
884767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
885767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * ME/TA converts each octet of TP data unit into two IRA character long
886145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby     * hex number (e.g. octet with integer value 42 is presented to TE as two
887767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
888767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * something else...
889767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
890767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private void parsePdu(byte[] pdu) {
891767a662ecde33c3979bf02b793d392aca0403162Wink Saville        mPdu = pdu;
892145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby        // Log.d(LOG_TAG, "raw sms message:");
893767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // Log.d(LOG_TAG, s);
894767a662ecde33c3979bf02b793d392aca0403162Wink Saville
895767a662ecde33c3979bf02b793d392aca0403162Wink Saville        PduParser p = new PduParser(pdu);
896767a662ecde33c3979bf02b793d392aca0403162Wink Saville
897767a662ecde33c3979bf02b793d392aca0403162Wink Saville        scAddress = p.getSCAddress();
898767a662ecde33c3979bf02b793d392aca0403162Wink Saville
899767a662ecde33c3979bf02b793d392aca0403162Wink Saville        if (scAddress != null) {
90043a17654cf4bfe7f1ec22bd8b7b32daccdf27c09Joe Onorato            if (false) Log.d(LOG_TAG, "SMS SC address: " + scAddress);
901767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
902767a662ecde33c3979bf02b793d392aca0403162Wink Saville
903767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TODO(mkf) support reply path, user data header indicator
904767a662ecde33c3979bf02b793d392aca0403162Wink Saville
905767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Message-Type-Indicator
906767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // 9.2.3
907767a662ecde33c3979bf02b793d392aca0403162Wink Saville        int firstByte = p.getByte();
908767a662ecde33c3979bf02b793d392aca0403162Wink Saville
909767a662ecde33c3979bf02b793d392aca0403162Wink Saville        mti = firstByte & 0x3;
910767a662ecde33c3979bf02b793d392aca0403162Wink Saville        switch (mti) {
911767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Message-Type-Indicator
912767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // 9.2.3
913767a662ecde33c3979bf02b793d392aca0403162Wink Saville        case 0:
91418804b3fe4133e4e5c29118eac9dc1d426220db3Alex Yakavenka        case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
91518804b3fe4133e4e5c29118eac9dc1d426220db3Alex Yakavenka                //This should be processed in the same way as MTI == 0 (Deliver)
916767a662ecde33c3979bf02b793d392aca0403162Wink Saville            parseSmsDeliver(p, firstByte);
917767a662ecde33c3979bf02b793d392aca0403162Wink Saville            break;
918767a662ecde33c3979bf02b793d392aca0403162Wink Saville        case 2:
919767a662ecde33c3979bf02b793d392aca0403162Wink Saville            parseSmsStatusReport(p, firstByte);
920767a662ecde33c3979bf02b793d392aca0403162Wink Saville            break;
921767a662ecde33c3979bf02b793d392aca0403162Wink Saville        default:
922767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // TODO(mkf) the rest of these
923767a662ecde33c3979bf02b793d392aca0403162Wink Saville            throw new RuntimeException("Unsupported message type");
924767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
925767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
926767a662ecde33c3979bf02b793d392aca0403162Wink Saville
927767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
928767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * Parses a SMS-STATUS-REPORT message.
929767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *
930767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param p A PduParser, cued past the first byte.
931767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param firstByte The first byte of the PDU, which contains MTI, etc.
932767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
933767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private void parseSmsStatusReport(PduParser p, int firstByte) {
934767a662ecde33c3979bf02b793d392aca0403162Wink Saville        isStatusReportMessage = true;
935767a662ecde33c3979bf02b793d392aca0403162Wink Saville
936767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Status-Report-Qualifier bit == 0 for SUBMIT
937767a662ecde33c3979bf02b793d392aca0403162Wink Saville        forSubmit = (firstByte & 0x20) == 0x00;
938767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Message-Reference
939767a662ecde33c3979bf02b793d392aca0403162Wink Saville        messageRef = p.getByte();
940767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Recipient-Address
941767a662ecde33c3979bf02b793d392aca0403162Wink Saville        recipientAddress = p.getAddress();
942767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Service-Centre-Time-Stamp
943767a662ecde33c3979bf02b793d392aca0403162Wink Saville        scTimeMillis = p.getSCTimestampMillis();
944767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Discharge-Time
945767a662ecde33c3979bf02b793d392aca0403162Wink Saville        dischargeTimeMillis = p.getSCTimestampMillis();
946767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Status
947767a662ecde33c3979bf02b793d392aca0403162Wink Saville        status = p.getByte();
948767a662ecde33c3979bf02b793d392aca0403162Wink Saville
949767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // The following are optional fields that may or may not be present.
950767a662ecde33c3979bf02b793d392aca0403162Wink Saville        if (p.moreDataPresent()) {
951767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // TP-Parameter-Indicator
952767a662ecde33c3979bf02b793d392aca0403162Wink Saville            int extraParams = p.getByte();
953767a662ecde33c3979bf02b793d392aca0403162Wink Saville            int moreExtraParams = extraParams;
954767a662ecde33c3979bf02b793d392aca0403162Wink Saville            while ((moreExtraParams & 0x80) != 0) {
955767a662ecde33c3979bf02b793d392aca0403162Wink Saville                // We only know how to parse a few extra parameters, all
956767a662ecde33c3979bf02b793d392aca0403162Wink Saville                // indicated in the first TP-PI octet, so skip over any
957767a662ecde33c3979bf02b793d392aca0403162Wink Saville                // additional TP-PI octets.
958767a662ecde33c3979bf02b793d392aca0403162Wink Saville                moreExtraParams = p.getByte();
959767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
960767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // TP-Protocol-Identifier
961767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if ((extraParams & 0x01) != 0) {
962767a662ecde33c3979bf02b793d392aca0403162Wink Saville                protocolIdentifier = p.getByte();
963767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
964767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // TP-Data-Coding-Scheme
965767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if ((extraParams & 0x02) != 0) {
966767a662ecde33c3979bf02b793d392aca0403162Wink Saville                dataCodingScheme = p.getByte();
967767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
968767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // TP-User-Data-Length (implies existence of TP-User-Data)
969767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if ((extraParams & 0x04) != 0) {
970767a662ecde33c3979bf02b793d392aca0403162Wink Saville                boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
971767a662ecde33c3979bf02b793d392aca0403162Wink Saville                parseUserData(p, hasUserDataHeader);
972767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
973767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
974767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
975767a662ecde33c3979bf02b793d392aca0403162Wink Saville
976767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private void parseSmsDeliver(PduParser p, int firstByte) {
977767a662ecde33c3979bf02b793d392aca0403162Wink Saville        replyPathPresent = (firstByte & 0x80) == 0x80;
978767a662ecde33c3979bf02b793d392aca0403162Wink Saville
979767a662ecde33c3979bf02b793d392aca0403162Wink Saville        originatingAddress = p.getAddress();
980767a662ecde33c3979bf02b793d392aca0403162Wink Saville
981767a662ecde33c3979bf02b793d392aca0403162Wink Saville        if (originatingAddress != null) {
98243a17654cf4bfe7f1ec22bd8b7b32daccdf27c09Joe Onorato            if (false) Log.v(LOG_TAG, "SMS originating address: "
983767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    + originatingAddress.address);
984767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
985767a662ecde33c3979bf02b793d392aca0403162Wink Saville
986767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Protocol-Identifier (TP-PID)
987767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TS 23.040 9.2.3.9
988767a662ecde33c3979bf02b793d392aca0403162Wink Saville        protocolIdentifier = p.getByte();
989767a662ecde33c3979bf02b793d392aca0403162Wink Saville
990767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // TP-Data-Coding-Scheme
991767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // see TS 23.038
992767a662ecde33c3979bf02b793d392aca0403162Wink Saville        dataCodingScheme = p.getByte();
993767a662ecde33c3979bf02b793d392aca0403162Wink Saville
99443a17654cf4bfe7f1ec22bd8b7b32daccdf27c09Joe Onorato        if (false) {
995767a662ecde33c3979bf02b793d392aca0403162Wink Saville            Log.v(LOG_TAG, "SMS TP-PID:" + protocolIdentifier
996767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    + " data coding scheme: " + dataCodingScheme);
997767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
998767a662ecde33c3979bf02b793d392aca0403162Wink Saville
999767a662ecde33c3979bf02b793d392aca0403162Wink Saville        scTimeMillis = p.getSCTimestampMillis();
1000767a662ecde33c3979bf02b793d392aca0403162Wink Saville
100143a17654cf4bfe7f1ec22bd8b7b32daccdf27c09Joe Onorato        if (false) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
1002767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1003767a662ecde33c3979bf02b793d392aca0403162Wink Saville        boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
1004767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1005767a662ecde33c3979bf02b793d392aca0403162Wink Saville        parseUserData(p, hasUserDataHeader);
1006767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
1007767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1008767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
1009767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * Parses the User Data of an SMS.
1010767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *
1011767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param p The current PduParser.
1012767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * @param hasUserDataHeader Indicates whether a header is present in the
1013767a662ecde33c3979bf02b793d392aca0403162Wink Saville     *                          User Data.
1014767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
1015767a662ecde33c3979bf02b793d392aca0403162Wink Saville    private void parseUserData(PduParser p, boolean hasUserDataHeader) {
1016767a662ecde33c3979bf02b793d392aca0403162Wink Saville        boolean hasMessageClass = false;
1017767a662ecde33c3979bf02b793d392aca0403162Wink Saville        boolean userDataCompressed = false;
1018767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1019767a662ecde33c3979bf02b793d392aca0403162Wink Saville        int encodingType = ENCODING_UNKNOWN;
1020767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1021767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // Look up the data encoding scheme
1022767a662ecde33c3979bf02b793d392aca0403162Wink Saville        if ((dataCodingScheme & 0x80) == 0) {
1023767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // Bits 7..4 == 0xxx
1024767a662ecde33c3979bf02b793d392aca0403162Wink Saville            automaticDeletion = (0 != (dataCodingScheme & 0x40));
1025767a662ecde33c3979bf02b793d392aca0403162Wink Saville            userDataCompressed = (0 != (dataCodingScheme & 0x20));
1026767a662ecde33c3979bf02b793d392aca0403162Wink Saville            hasMessageClass = (0 != (dataCodingScheme & 0x10));
1027767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1028767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if (userDataCompressed) {
1029767a662ecde33c3979bf02b793d392aca0403162Wink Saville                Log.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
1030767a662ecde33c3979bf02b793d392aca0403162Wink Saville                        + "(compression) " + (dataCodingScheme & 0xff));
1031767a662ecde33c3979bf02b793d392aca0403162Wink Saville            } else {
1032767a662ecde33c3979bf02b793d392aca0403162Wink Saville                switch ((dataCodingScheme >> 2) & 0x3) {
1033767a662ecde33c3979bf02b793d392aca0403162Wink Saville                case 0: // GSM 7 bit default alphabet
1034767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    encodingType = ENCODING_7BIT;
1035767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    break;
1036767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1037767a662ecde33c3979bf02b793d392aca0403162Wink Saville                case 2: // UCS 2 (16bit)
1038767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    encodingType = ENCODING_16BIT;
1039767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    break;
1040767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1041767a662ecde33c3979bf02b793d392aca0403162Wink Saville                case 1: // 8 bit data
1042767a662ecde33c3979bf02b793d392aca0403162Wink Saville                case 3: // reserved
1043767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    Log.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
1044767a662ecde33c3979bf02b793d392aca0403162Wink Saville                            + (dataCodingScheme & 0xff));
1045767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    encodingType = ENCODING_8BIT;
1046767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    break;
1047767a662ecde33c3979bf02b793d392aca0403162Wink Saville                }
1048767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
1049767a662ecde33c3979bf02b793d392aca0403162Wink Saville        } else if ((dataCodingScheme & 0xf0) == 0xf0) {
1050767a662ecde33c3979bf02b793d392aca0403162Wink Saville            automaticDeletion = false;
1051767a662ecde33c3979bf02b793d392aca0403162Wink Saville            hasMessageClass = true;
1052767a662ecde33c3979bf02b793d392aca0403162Wink Saville            userDataCompressed = false;
1053767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1054767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if (0 == (dataCodingScheme & 0x04)) {
1055767a662ecde33c3979bf02b793d392aca0403162Wink Saville                // GSM 7 bit default alphabet
1056767a662ecde33c3979bf02b793d392aca0403162Wink Saville                encodingType = ENCODING_7BIT;
1057767a662ecde33c3979bf02b793d392aca0403162Wink Saville            } else {
1058767a662ecde33c3979bf02b793d392aca0403162Wink Saville                // 8 bit data
1059767a662ecde33c3979bf02b793d392aca0403162Wink Saville                encodingType = ENCODING_8BIT;
1060767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
1061767a662ecde33c3979bf02b793d392aca0403162Wink Saville        } else if ((dataCodingScheme & 0xF0) == 0xC0
1062767a662ecde33c3979bf02b793d392aca0403162Wink Saville                || (dataCodingScheme & 0xF0) == 0xD0
1063767a662ecde33c3979bf02b793d392aca0403162Wink Saville                || (dataCodingScheme & 0xF0) == 0xE0) {
1064767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
1065767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1066767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // 0xC0 == 7 bit, don't store
1067767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // 0xD0 == 7 bit, store
1068767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // 0xE0 == UCS-2, store
1069767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1070767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if ((dataCodingScheme & 0xF0) == 0xE0) {
1071767a662ecde33c3979bf02b793d392aca0403162Wink Saville                encodingType = ENCODING_16BIT;
1072767a662ecde33c3979bf02b793d392aca0403162Wink Saville            } else {
1073767a662ecde33c3979bf02b793d392aca0403162Wink Saville                encodingType = ENCODING_7BIT;
1074767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
1075767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1076767a662ecde33c3979bf02b793d392aca0403162Wink Saville            userDataCompressed = false;
1077767a662ecde33c3979bf02b793d392aca0403162Wink Saville            boolean active = ((dataCodingScheme & 0x08) == 0x08);
1078767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1079767a662ecde33c3979bf02b793d392aca0403162Wink Saville            // bit 0x04 reserved
1080767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1081767a662ecde33c3979bf02b793d392aca0403162Wink Saville            if ((dataCodingScheme & 0x03) == 0x00) {
1082767a662ecde33c3979bf02b793d392aca0403162Wink Saville                isMwi = true;
1083767a662ecde33c3979bf02b793d392aca0403162Wink Saville                mwiSense = active;
1084767a662ecde33c3979bf02b793d392aca0403162Wink Saville                mwiDontStore = ((dataCodingScheme & 0xF0) == 0xC0);
1085767a662ecde33c3979bf02b793d392aca0403162Wink Saville            } else {
1086767a662ecde33c3979bf02b793d392aca0403162Wink Saville                isMwi = false;
1087767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1088767a662ecde33c3979bf02b793d392aca0403162Wink Saville                Log.w(LOG_TAG, "MWI for fax, email, or other "
1089767a662ecde33c3979bf02b793d392aca0403162Wink Saville                        + (dataCodingScheme & 0xff));
1090767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
1091b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee        } else if ((dataCodingScheme & 0xC0) == 0x80) {
1092b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
1093b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            // 0x80..0xBF == Reserved coding groups
1094b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            if (dataCodingScheme == 0x84) {
1095b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee                // This value used for KSC5601 by carriers in Korea.
1096b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee                encodingType = ENCODING_KSC5601;
1097b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            } else {
1098b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee                Log.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
1099b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee                        + (dataCodingScheme & 0xff));
1100b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            }
1101767a662ecde33c3979bf02b793d392aca0403162Wink Saville        } else {
1102767a662ecde33c3979bf02b793d392aca0403162Wink Saville            Log.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
1103767a662ecde33c3979bf02b793d392aca0403162Wink Saville                    + (dataCodingScheme & 0xff));
1104767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
1105767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1106767a662ecde33c3979bf02b793d392aca0403162Wink Saville        // set both the user data and the user data header.
1107767a662ecde33c3979bf02b793d392aca0403162Wink Saville        int count = p.constructUserData(hasUserDataHeader,
1108767a662ecde33c3979bf02b793d392aca0403162Wink Saville                encodingType == ENCODING_7BIT);
1109767a662ecde33c3979bf02b793d392aca0403162Wink Saville        this.userData = p.getUserData();
1110767a662ecde33c3979bf02b793d392aca0403162Wink Saville        this.userDataHeader = p.getUserDataHeader();
1111767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1112767a662ecde33c3979bf02b793d392aca0403162Wink Saville        switch (encodingType) {
1113767a662ecde33c3979bf02b793d392aca0403162Wink Saville        case ENCODING_UNKNOWN:
1114767a662ecde33c3979bf02b793d392aca0403162Wink Saville        case ENCODING_8BIT:
1115767a662ecde33c3979bf02b793d392aca0403162Wink Saville            messageBody = null;
1116767a662ecde33c3979bf02b793d392aca0403162Wink Saville            break;
1117767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1118767a662ecde33c3979bf02b793d392aca0403162Wink Saville        case ENCODING_7BIT:
1119b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby            messageBody = p.getUserDataGSM7Bit(count,
1120b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby                    hasUserDataHeader ? userDataHeader.languageTable : 0,
1121b49a73dfc4c9817bba1f227e9330555acdf9b56fJake Hamby                    hasUserDataHeader ? userDataHeader.languageShiftTable : 0);
1122767a662ecde33c3979bf02b793d392aca0403162Wink Saville            break;
1123767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1124767a662ecde33c3979bf02b793d392aca0403162Wink Saville        case ENCODING_16BIT:
1125767a662ecde33c3979bf02b793d392aca0403162Wink Saville            messageBody = p.getUserDataUCS2(count);
1126767a662ecde33c3979bf02b793d392aca0403162Wink Saville            break;
1127b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee
1128b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee        case ENCODING_KSC5601:
1129b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            messageBody = p.getUserDataKSC5601(count);
1130b55df4471ed55a0e91dee79304f3b1209ffa4b35Sang-il, Lee            break;
1131767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
1132767a662ecde33c3979bf02b793d392aca0403162Wink Saville
113343a17654cf4bfe7f1ec22bd8b7b32daccdf27c09Joe Onorato        if (false) Log.v(LOG_TAG, "SMS message body (raw): '" + messageBody + "'");
1134767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1135767a662ecde33c3979bf02b793d392aca0403162Wink Saville        if (messageBody != null) {
1136767a662ecde33c3979bf02b793d392aca0403162Wink Saville            parseMessageBody();
1137767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
1138767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1139767a662ecde33c3979bf02b793d392aca0403162Wink Saville        if (!hasMessageClass) {
1140767a662ecde33c3979bf02b793d392aca0403162Wink Saville            messageClass = MessageClass.UNKNOWN;
1141767a662ecde33c3979bf02b793d392aca0403162Wink Saville        } else {
1142767a662ecde33c3979bf02b793d392aca0403162Wink Saville            switch (dataCodingScheme & 0x3) {
1143767a662ecde33c3979bf02b793d392aca0403162Wink Saville            case 0:
1144767a662ecde33c3979bf02b793d392aca0403162Wink Saville                messageClass = MessageClass.CLASS_0;
1145767a662ecde33c3979bf02b793d392aca0403162Wink Saville                break;
1146767a662ecde33c3979bf02b793d392aca0403162Wink Saville            case 1:
1147767a662ecde33c3979bf02b793d392aca0403162Wink Saville                messageClass = MessageClass.CLASS_1;
1148767a662ecde33c3979bf02b793d392aca0403162Wink Saville                break;
1149767a662ecde33c3979bf02b793d392aca0403162Wink Saville            case 2:
1150767a662ecde33c3979bf02b793d392aca0403162Wink Saville                messageClass = MessageClass.CLASS_2;
1151767a662ecde33c3979bf02b793d392aca0403162Wink Saville                break;
1152767a662ecde33c3979bf02b793d392aca0403162Wink Saville            case 3:
1153767a662ecde33c3979bf02b793d392aca0403162Wink Saville                messageClass = MessageClass.CLASS_3;
1154767a662ecde33c3979bf02b793d392aca0403162Wink Saville                break;
1155767a662ecde33c3979bf02b793d392aca0403162Wink Saville            }
1156767a662ecde33c3979bf02b793d392aca0403162Wink Saville        }
1157767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
1158767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1159767a662ecde33c3979bf02b793d392aca0403162Wink Saville    /**
1160767a662ecde33c3979bf02b793d392aca0403162Wink Saville     * {@inheritDoc}
1161767a662ecde33c3979bf02b793d392aca0403162Wink Saville     */
1162145ff609de3206b585819ef974fab20cdc2d9f5eJake Hamby    @Override
1163767a662ecde33c3979bf02b793d392aca0403162Wink Saville    public MessageClass getMessageClass() {
1164767a662ecde33c3979bf02b793d392aca0403162Wink Saville        return messageClass;
1165767a662ecde33c3979bf02b793d392aca0403162Wink Saville    }
1166767a662ecde33c3979bf02b793d392aca0403162Wink Saville
1167ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby    /**
1168ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby     * Returns true if this is a (U)SIM data download type SM.
1169ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby     * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9.
1170ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby     *
1171ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby     * @return true if this is a USIM data download message; false otherwise
1172ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby     */
1173ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby    boolean isUsimDataDownload() {
1174ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby        return messageClass == MessageClass.CLASS_2 &&
1175ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby                (protocolIdentifier == 0x7f || protocolIdentifier == 0x7c);
1176ac09d2af145b4d820a34f5e7628bc42e2e211bdbJake Hamby    }
1177767a662ecde33c3979bf02b793d392aca0403162Wink Saville}
1178