1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony;
18
19import android.content.ContentValues;
20import android.database.Cursor;
21
22import com.android.internal.util.HexDump;
23
24import java.util.Arrays;
25import java.util.Date;
26
27/**
28 * Tracker for an incoming SMS message ready to broadcast to listeners.
29 * This is similar to {@link com.android.internal.telephony.SMSDispatcher.SmsTracker} used for
30 * outgoing messages.
31 */
32public class InboundSmsTracker {
33
34    // Fields for single and multi-part messages
35    private final byte[] mPdu;
36    private final long mTimestamp;
37    private final int mDestPort;
38    private final boolean mIs3gpp2;
39    private final boolean mIs3gpp2WapPdu;
40    private final String mMessageBody;
41    // Copied from SmsMessageBase#getDisplayOriginatingAddress used for blocking messages.
42    private final String mAddress;
43
44    // Fields for concatenating multi-part SMS messages
45    private final int mReferenceNumber;
46    private final int mSequenceNumber;
47    private final int mMessageCount;
48
49    // Fields for deleting this message after delivery
50    private String mDeleteWhere;
51    private String[] mDeleteWhereArgs;
52
53    /** Destination port flag bit for no destination port. */
54    private static final int DEST_PORT_FLAG_NO_PORT = (1 << 16);
55
56    /** Destination port flag bit to indicate 3GPP format message. */
57    private static final int DEST_PORT_FLAG_3GPP = (1 << 17);
58
59    /** Destination port flag bit to indicate 3GPP2 format message. */
60    private static final int DEST_PORT_FLAG_3GPP2 = (1 << 18);
61
62    /** Destination port flag bit to indicate 3GPP2 format WAP message. */
63    private static final int DEST_PORT_FLAG_3GPP2_WAP_PDU = (1 << 19);
64
65    /** Destination port mask (16-bit unsigned value on GSM and CDMA). */
66    private static final int DEST_PORT_MASK = 0xffff;
67
68    /**
69     * Create a tracker for a single-part SMS.
70     *
71     * @param pdu the message PDU
72     * @param timestamp the message timestamp
73     * @param destPort the destination port
74     * @param is3gpp2 true for 3GPP2 format; false for 3GPP format
75     * @param is3gpp2WapPdu true for 3GPP2 format WAP PDU; false otherwise
76     * @param address originating address, or email if this message was from an email gateway
77     */
78    public InboundSmsTracker(byte[] pdu, long timestamp, int destPort, boolean is3gpp2,
79            boolean is3gpp2WapPdu, String address, String messageBody) {
80        mPdu = pdu;
81        mTimestamp = timestamp;
82        mDestPort = destPort;
83        mIs3gpp2 = is3gpp2;
84        mIs3gpp2WapPdu = is3gpp2WapPdu;
85        mMessageBody = messageBody;
86        mAddress = address;
87        // fields for multi-part SMS
88        mReferenceNumber = -1;
89        mSequenceNumber = getIndexOffset();     // 0 or 1, depending on type
90        mMessageCount = 1;
91    }
92
93    /**
94     * Create a tracker for a multi-part SMS. Sequence numbers start at 1 for 3GPP and regular
95     * concatenated 3GPP2 messages, but CDMA WAP push sequence numbers start at 0. The caller will
96     * subtract 1 if necessary so that the sequence number is always 0-based. When loading and
97     * saving to the raw table, the sequence number is adjusted if necessary for backwards
98     * compatibility.
99     *
100     * @param pdu the message PDU
101     * @param timestamp the message timestamp
102     * @param destPort the destination port
103     * @param is3gpp2 true for 3GPP2 format; false for 3GPP format
104     * @param address originating address, or email if this message was from an email gateway
105     * @param referenceNumber the concatenated reference number
106     * @param sequenceNumber the sequence number of this segment (0-based)
107     * @param messageCount the total number of segments
108     * @param is3gpp2WapPdu true for 3GPP2 format WAP PDU; false otherwise
109     */
110    public InboundSmsTracker(byte[] pdu, long timestamp, int destPort, boolean is3gpp2,
111            String address, int referenceNumber, int sequenceNumber, int messageCount,
112            boolean is3gpp2WapPdu, String messageBody) {
113        mPdu = pdu;
114        mTimestamp = timestamp;
115        mDestPort = destPort;
116        mIs3gpp2 = is3gpp2;
117        mIs3gpp2WapPdu = is3gpp2WapPdu;
118        mMessageBody = messageBody;
119        mAddress = address;
120        // fields for multi-part SMS
121        mReferenceNumber = referenceNumber;
122        mSequenceNumber = sequenceNumber;
123        mMessageCount = messageCount;
124    }
125
126    /**
127     * Create a new tracker from the row of the raw table pointed to by Cursor.
128     * Since this constructor is used only for recovery during startup, the Dispatcher is null.
129     * @param cursor a Cursor pointing to the row to construct this SmsTracker for
130     */
131    public InboundSmsTracker(Cursor cursor, boolean isCurrentFormat3gpp2) {
132        mPdu = HexDump.hexStringToByteArray(cursor.getString(InboundSmsHandler.PDU_COLUMN));
133
134        if (cursor.isNull(InboundSmsHandler.DESTINATION_PORT_COLUMN)) {
135            mDestPort = -1;
136            mIs3gpp2 = isCurrentFormat3gpp2;
137            mIs3gpp2WapPdu = false;
138        } else {
139            int destPort = cursor.getInt(InboundSmsHandler.DESTINATION_PORT_COLUMN);
140            if ((destPort & DEST_PORT_FLAG_3GPP) != 0) {
141                mIs3gpp2 = false;
142            } else if ((destPort & DEST_PORT_FLAG_3GPP2) != 0) {
143                mIs3gpp2 = true;
144            } else {
145                mIs3gpp2 = isCurrentFormat3gpp2;
146            }
147            mIs3gpp2WapPdu = ((destPort & DEST_PORT_FLAG_3GPP2_WAP_PDU) != 0);
148            mDestPort = getRealDestPort(destPort);
149        }
150
151        mTimestamp = cursor.getLong(InboundSmsHandler.DATE_COLUMN);
152        mAddress = cursor.getString(InboundSmsHandler.ADDRESS_COLUMN);
153
154        if (cursor.isNull(InboundSmsHandler.COUNT_COLUMN)) {
155            // single-part message
156            long rowId = cursor.getLong(InboundSmsHandler.ID_COLUMN);
157            mReferenceNumber = -1;
158            mSequenceNumber = getIndexOffset();     // 0 or 1, depending on type
159            mMessageCount = 1;
160            mDeleteWhere = InboundSmsHandler.SELECT_BY_ID;
161            mDeleteWhereArgs = new String[]{Long.toString(rowId)};
162        } else {
163            // multi-part message
164            mReferenceNumber = cursor.getInt(InboundSmsHandler.REFERENCE_NUMBER_COLUMN);
165            mMessageCount = cursor.getInt(InboundSmsHandler.COUNT_COLUMN);
166
167            // GSM sequence numbers start at 1; CDMA WDP datagram sequence numbers start at 0
168            mSequenceNumber = cursor.getInt(InboundSmsHandler.SEQUENCE_COLUMN);
169            int index = mSequenceNumber - getIndexOffset();
170
171            if (index < 0 || index >= mMessageCount) {
172                throw new IllegalArgumentException("invalid PDU sequence " + mSequenceNumber
173                        + " of " + mMessageCount);
174            }
175
176            mDeleteWhere = InboundSmsHandler.SELECT_BY_REFERENCE;
177            mDeleteWhereArgs = new String[]{mAddress,
178                    Integer.toString(mReferenceNumber), Integer.toString(mMessageCount)};
179        }
180        mMessageBody = cursor.getString(InboundSmsHandler.MESSAGE_BODY_COLUMN);
181    }
182
183    public ContentValues getContentValues() {
184        ContentValues values = new ContentValues();
185        values.put("pdu", HexDump.toHexString(mPdu));
186        values.put("date", mTimestamp);
187        // Always set the destination port, since it now contains message format flags.
188        // Port is a 16-bit value, or -1, so clear the upper bits before setting flags.
189        int destPort;
190        if (mDestPort == -1) {
191            destPort = DEST_PORT_FLAG_NO_PORT;
192        } else {
193            destPort = mDestPort & DEST_PORT_MASK;
194        }
195        if (mIs3gpp2) {
196            destPort |= DEST_PORT_FLAG_3GPP2;
197        } else {
198            destPort |= DEST_PORT_FLAG_3GPP;
199        }
200        if (mIs3gpp2WapPdu) {
201            destPort |= DEST_PORT_FLAG_3GPP2_WAP_PDU;
202        }
203        values.put("destination_port", destPort);
204        if (mAddress != null) {
205            values.put("address", mAddress);
206            values.put("reference_number", mReferenceNumber);
207            values.put("sequence", mSequenceNumber);
208            values.put("count", mMessageCount);
209        }
210        values.put("message_body", mMessageBody);
211        return values;
212    }
213
214    /**
215     * Get the port number, or -1 if there is no destination port.
216     * @param destPort the destination port value, with flags
217     * @return the real destination port, or -1 for no port
218     */
219    public static int getRealDestPort(int destPort) {
220        if ((destPort & DEST_PORT_FLAG_NO_PORT) != 0) {
221            return -1;
222        } else {
223           return destPort & DEST_PORT_MASK;
224        }
225    }
226
227    /**
228     * Update the values to delete all rows of the message from raw table.
229     * @param deleteWhere the selection to use
230     * @param deleteWhereArgs the selection args to use
231     */
232    public void setDeleteWhere(String deleteWhere, String[] deleteWhereArgs) {
233        mDeleteWhere = deleteWhere;
234        mDeleteWhereArgs = deleteWhereArgs;
235    }
236
237    public String toString() {
238        StringBuilder builder = new StringBuilder("SmsTracker{timestamp=");
239        builder.append(new Date(mTimestamp));
240        builder.append(" destPort=").append(mDestPort);
241        builder.append(" is3gpp2=").append(mIs3gpp2);
242        if (mAddress != null) {
243            builder.append(" address=").append(mAddress);
244            builder.append(" refNumber=").append(mReferenceNumber);
245            builder.append(" seqNumber=").append(mSequenceNumber);
246            builder.append(" msgCount=").append(mMessageCount);
247        }
248        if (mDeleteWhere != null) {
249            builder.append(" deleteWhere(").append(mDeleteWhere);
250            builder.append(") deleteArgs=(").append(Arrays.toString(mDeleteWhereArgs));
251            builder.append(')');
252        }
253        builder.append('}');
254        return builder.toString();
255    }
256
257    public byte[] getPdu() {
258        return mPdu;
259    }
260
261    public long getTimestamp() {
262        return mTimestamp;
263    }
264
265    public int getDestPort() {
266        return mDestPort;
267    }
268
269    public boolean is3gpp2() {
270        return mIs3gpp2;
271    }
272
273    public String getFormat() {
274        return mIs3gpp2 ? SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
275    }
276
277    /**
278     * Sequence numbers for concatenated messages start at 1. The exception is CDMA WAP PDU
279     * messages, which use a 0-based index.
280     * @return the offset to use to convert between mIndex and the sequence number
281     */
282    public int getIndexOffset() {
283        return (mIs3gpp2 && mIs3gpp2WapPdu) ? 0 : 1;
284    }
285
286    public String getAddress() {
287        return mAddress;
288    }
289
290    public String getMessageBody() {
291        return mMessageBody;
292    }
293
294    public int getReferenceNumber() {
295        return mReferenceNumber;
296    }
297
298    public int getSequenceNumber() {
299        return mSequenceNumber;
300    }
301
302    public int getMessageCount() {
303        return mMessageCount;
304    }
305
306    public String getDeleteWhere() {
307        return mDeleteWhere;
308    }
309
310    public String[] getDeleteWhereArgs() {
311        return mDeleteWhereArgs;
312    }
313}
314