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.annotations.VisibleForTesting;
23import com.android.internal.util.HexDump;
24
25import java.util.Arrays;
26import java.util.Date;
27
28/**
29 * Tracker for an incoming SMS message ready to broadcast to listeners.
30 * This is similar to {@link com.android.internal.telephony.SMSDispatcher.SmsTracker} used for
31 * outgoing messages.
32 */
33public class InboundSmsTracker {
34
35    // Fields for single and multi-part messages
36    private final byte[] mPdu;
37    private final long mTimestamp;
38    private final int mDestPort;
39    private final boolean mIs3gpp2;
40    private final boolean mIs3gpp2WapPdu;
41    private final String mMessageBody;
42
43    // Fields for concatenating multi-part SMS messages
44    private final String mAddress;
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    /**
54     * Copied from SmsMessageBase#getDisplayOriginatingAddress used for blocking messages.
55     * DisplayAddress could be email address if this message was from an email gateway, otherwise
56     * same as mAddress. Email gateway might set a generic gateway address as the mAddress which
57     * could not be used for blocking check and append the display email address at the beginning
58     * of the message body. In that case, display email address is only available for the first SMS
59     * in the Multi-part SMS.
60     */
61    private final String mDisplayAddress;
62
63    @VisibleForTesting
64    /** Destination port flag bit for no destination port. */
65    public static final int DEST_PORT_FLAG_NO_PORT = (1 << 16);
66
67    /** Destination port flag bit to indicate 3GPP format message. */
68    private static final int DEST_PORT_FLAG_3GPP = (1 << 17);
69
70    @VisibleForTesting
71    /** Destination port flag bit to indicate 3GPP2 format message. */
72    public static final int DEST_PORT_FLAG_3GPP2 = (1 << 18);
73
74    @VisibleForTesting
75    /** Destination port flag bit to indicate 3GPP2 format WAP message. */
76    public static final int DEST_PORT_FLAG_3GPP2_WAP_PDU = (1 << 19);
77
78    /** Destination port mask (16-bit unsigned value on GSM and CDMA). */
79    private static final int DEST_PORT_MASK = 0xffff;
80
81    @VisibleForTesting
82    public static final String SELECT_BY_REFERENCE = "address=? AND reference_number=? AND "
83            + "count=? AND (destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU
84            + "=0) AND deleted=0";
85
86    @VisibleForTesting
87    public static final String SELECT_BY_REFERENCE_3GPP2WAP = "address=? AND reference_number=? "
88            + "AND count=? AND (destination_port & "
89            + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=" + DEST_PORT_FLAG_3GPP2_WAP_PDU + ") AND deleted=0";
90
91    @VisibleForTesting
92    public static final String SELECT_BY_DUPLICATE_REFERENCE = "address=? AND "
93            + "reference_number=? AND count=? AND sequence=? AND "
94            + "((date=? AND message_body=?) OR deleted=0) AND (destination_port & "
95            + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=0)";
96
97    @VisibleForTesting
98    public static final String SELECT_BY_DUPLICATE_REFERENCE_3GPP2WAP = "address=? AND "
99            + "reference_number=? " + "AND count=? AND sequence=? AND "
100            + "((date=? AND message_body=?) OR deleted=0) AND "
101            + "(destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU + "="
102            + DEST_PORT_FLAG_3GPP2_WAP_PDU + ")";
103
104    /**
105     * Create a tracker for a single-part SMS.
106     *
107     * @param pdu the message PDU
108     * @param timestamp the message timestamp
109     * @param destPort the destination port
110     * @param is3gpp2 true for 3GPP2 format; false for 3GPP format
111     * @param is3gpp2WapPdu true for 3GPP2 format WAP PDU; false otherwise
112     * @param address originating address
113     * @param displayAddress email address if this message was from an email gateway, otherwise same
114     *                       as originating address
115     */
116    public InboundSmsTracker(byte[] pdu, long timestamp, int destPort, boolean is3gpp2,
117            boolean is3gpp2WapPdu, String address, String displayAddress, String messageBody) {
118        mPdu = pdu;
119        mTimestamp = timestamp;
120        mDestPort = destPort;
121        mIs3gpp2 = is3gpp2;
122        mIs3gpp2WapPdu = is3gpp2WapPdu;
123        mMessageBody = messageBody;
124        mAddress = address;
125        mDisplayAddress = displayAddress;
126        // fields for multi-part SMS
127        mReferenceNumber = -1;
128        mSequenceNumber = getIndexOffset();     // 0 or 1, depending on type
129        mMessageCount = 1;
130    }
131
132    /**
133     * Create a tracker for a multi-part SMS. Sequence numbers start at 1 for 3GPP and regular
134     * concatenated 3GPP2 messages, but CDMA WAP push sequence numbers start at 0. The caller will
135     * subtract 1 if necessary so that the sequence number is always 0-based. When loading and
136     * saving to the raw table, the sequence number is adjusted if necessary for backwards
137     * compatibility.
138     *
139     * @param pdu the message PDU
140     * @param timestamp the message timestamp
141     * @param destPort the destination port
142     * @param is3gpp2 true for 3GPP2 format; false for 3GPP format
143     * @param address originating address, or email if this message was from an email gateway
144     * @param displayAddress email address if this message was from an email gateway, otherwise same
145     *                       as originating address
146     * @param referenceNumber the concatenated reference number
147     * @param sequenceNumber the sequence number of this segment (0-based)
148     * @param messageCount the total number of segments
149     * @param is3gpp2WapPdu true for 3GPP2 format WAP PDU; false otherwise
150     */
151    public InboundSmsTracker(byte[] pdu, long timestamp, int destPort, boolean is3gpp2,
152            String address, String displayAddress, int referenceNumber, int sequenceNumber,
153            int messageCount, boolean is3gpp2WapPdu, String messageBody) {
154        mPdu = pdu;
155        mTimestamp = timestamp;
156        mDestPort = destPort;
157        mIs3gpp2 = is3gpp2;
158        mIs3gpp2WapPdu = is3gpp2WapPdu;
159        mMessageBody = messageBody;
160        // fields used for check blocking message
161        mDisplayAddress = displayAddress;
162        // fields for multi-part SMS
163        mAddress = address;
164        mReferenceNumber = referenceNumber;
165        mSequenceNumber = sequenceNumber;
166        mMessageCount = messageCount;
167    }
168
169    /**
170     * Create a new tracker from the row of the raw table pointed to by Cursor.
171     * Since this constructor is used only for recovery during startup, the Dispatcher is null.
172     * @param cursor a Cursor pointing to the row to construct this SmsTracker for
173     */
174    public InboundSmsTracker(Cursor cursor, boolean isCurrentFormat3gpp2) {
175        mPdu = HexDump.hexStringToByteArray(cursor.getString(InboundSmsHandler.PDU_COLUMN));
176
177        if (cursor.isNull(InboundSmsHandler.DESTINATION_PORT_COLUMN)) {
178            mDestPort = -1;
179            mIs3gpp2 = isCurrentFormat3gpp2;
180            mIs3gpp2WapPdu = false;
181        } else {
182            int destPort = cursor.getInt(InboundSmsHandler.DESTINATION_PORT_COLUMN);
183            if ((destPort & DEST_PORT_FLAG_3GPP) != 0) {
184                mIs3gpp2 = false;
185            } else if ((destPort & DEST_PORT_FLAG_3GPP2) != 0) {
186                mIs3gpp2 = true;
187            } else {
188                mIs3gpp2 = isCurrentFormat3gpp2;
189            }
190            mIs3gpp2WapPdu = ((destPort & DEST_PORT_FLAG_3GPP2_WAP_PDU) != 0);
191            mDestPort = getRealDestPort(destPort);
192        }
193
194        mTimestamp = cursor.getLong(InboundSmsHandler.DATE_COLUMN);
195        mAddress = cursor.getString(InboundSmsHandler.ADDRESS_COLUMN);
196        mDisplayAddress = cursor.getString(InboundSmsHandler.DISPLAY_ADDRESS_COLUMN);
197
198        if (cursor.isNull(InboundSmsHandler.COUNT_COLUMN)) {
199            // single-part message
200            long rowId = cursor.getLong(InboundSmsHandler.ID_COLUMN);
201            mReferenceNumber = -1;
202            mSequenceNumber = getIndexOffset();     // 0 or 1, depending on type
203            mMessageCount = 1;
204            mDeleteWhere = InboundSmsHandler.SELECT_BY_ID;
205            mDeleteWhereArgs = new String[]{Long.toString(rowId)};
206        } else {
207            // multi-part message
208            mReferenceNumber = cursor.getInt(InboundSmsHandler.REFERENCE_NUMBER_COLUMN);
209            mMessageCount = cursor.getInt(InboundSmsHandler.COUNT_COLUMN);
210
211            // GSM sequence numbers start at 1; CDMA WDP datagram sequence numbers start at 0
212            mSequenceNumber = cursor.getInt(InboundSmsHandler.SEQUENCE_COLUMN);
213            int index = mSequenceNumber - getIndexOffset();
214
215            if (index < 0 || index >= mMessageCount) {
216                throw new IllegalArgumentException("invalid PDU sequence " + mSequenceNumber
217                        + " of " + mMessageCount);
218            }
219
220            mDeleteWhere = getQueryForSegments();
221            mDeleteWhereArgs = new String[]{mAddress,
222                    Integer.toString(mReferenceNumber), Integer.toString(mMessageCount)};
223        }
224        mMessageBody = cursor.getString(InboundSmsHandler.MESSAGE_BODY_COLUMN);
225    }
226
227    public ContentValues getContentValues() {
228        ContentValues values = new ContentValues();
229        values.put("pdu", HexDump.toHexString(mPdu));
230        values.put("date", mTimestamp);
231        // Always set the destination port, since it now contains message format flags.
232        // Port is a 16-bit value, or -1, so clear the upper bits before setting flags.
233        int destPort;
234        if (mDestPort == -1) {
235            destPort = DEST_PORT_FLAG_NO_PORT;
236        } else {
237            destPort = mDestPort & DEST_PORT_MASK;
238        }
239        if (mIs3gpp2) {
240            destPort |= DEST_PORT_FLAG_3GPP2;
241        } else {
242            destPort |= DEST_PORT_FLAG_3GPP;
243        }
244        if (mIs3gpp2WapPdu) {
245            destPort |= DEST_PORT_FLAG_3GPP2_WAP_PDU;
246        }
247        values.put("destination_port", destPort);
248        if (mAddress != null) {
249            values.put("address", mAddress);
250            values.put("display_originating_addr", mDisplayAddress);
251            values.put("reference_number", mReferenceNumber);
252            values.put("sequence", mSequenceNumber);
253            values.put("count", mMessageCount);
254        }
255        values.put("message_body", mMessageBody);
256        return values;
257    }
258
259    /**
260     * Get the port number, or -1 if there is no destination port.
261     * @param destPort the destination port value, with flags
262     * @return the real destination port, or -1 for no port
263     */
264    public static int getRealDestPort(int destPort) {
265        if ((destPort & DEST_PORT_FLAG_NO_PORT) != 0) {
266            return -1;
267        } else {
268           return destPort & DEST_PORT_MASK;
269        }
270    }
271
272    /**
273     * Update the values to delete all rows of the message from raw table.
274     * @param deleteWhere the selection to use
275     * @param deleteWhereArgs the selection args to use
276     */
277    public void setDeleteWhere(String deleteWhere, String[] deleteWhereArgs) {
278        mDeleteWhere = deleteWhere;
279        mDeleteWhereArgs = deleteWhereArgs;
280    }
281
282    public String toString() {
283        StringBuilder builder = new StringBuilder("SmsTracker{timestamp=");
284        builder.append(new Date(mTimestamp));
285        builder.append(" destPort=").append(mDestPort);
286        builder.append(" is3gpp2=").append(mIs3gpp2);
287        if (mAddress != null) {
288            builder.append(" address=").append(mAddress);
289            builder.append(" display_originating_addr=").append(mDisplayAddress);
290            builder.append(" refNumber=").append(mReferenceNumber);
291            builder.append(" seqNumber=").append(mSequenceNumber);
292            builder.append(" msgCount=").append(mMessageCount);
293        }
294        if (mDeleteWhere != null) {
295            builder.append(" deleteWhere(").append(mDeleteWhere);
296            builder.append(") deleteArgs=(").append(Arrays.toString(mDeleteWhereArgs));
297            builder.append(')');
298        }
299        builder.append('}');
300        return builder.toString();
301    }
302
303    public byte[] getPdu() {
304        return mPdu;
305    }
306
307    public long getTimestamp() {
308        return mTimestamp;
309    }
310
311    public int getDestPort() {
312        return mDestPort;
313    }
314
315    public boolean is3gpp2() {
316        return mIs3gpp2;
317    }
318
319    public String getFormat() {
320        return mIs3gpp2 ? SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
321    }
322
323    public String getQueryForSegments() {
324        return mIs3gpp2WapPdu ? SELECT_BY_REFERENCE_3GPP2WAP : SELECT_BY_REFERENCE;
325    }
326
327    public String getQueryForMultiPartDuplicates() {
328        return mIs3gpp2WapPdu ? SELECT_BY_DUPLICATE_REFERENCE_3GPP2WAP :
329                SELECT_BY_DUPLICATE_REFERENCE;
330    }
331
332    /**
333     * Sequence numbers for concatenated messages start at 1. The exception is CDMA WAP PDU
334     * messages, which use a 0-based index.
335     * @return the offset to use to convert between mIndex and the sequence number
336     */
337    public int getIndexOffset() {
338        return (mIs3gpp2 && mIs3gpp2WapPdu) ? 0 : 1;
339    }
340
341    public String getAddress() {
342        return mAddress;
343    }
344
345    public String getDisplayAddress() {
346        return mDisplayAddress;
347    }
348
349    public String getMessageBody() {
350        return mMessageBody;
351    }
352
353    public int getReferenceNumber() {
354        return mReferenceNumber;
355    }
356
357    public int getSequenceNumber() {
358        return mSequenceNumber;
359    }
360
361    public int getMessageCount() {
362        return mMessageCount;
363    }
364
365    public String getDeleteWhere() {
366        return mDeleteWhere;
367    }
368
369    public String[] getDeleteWhereArgs() {
370        return mDeleteWhereArgs;
371    }
372}
373