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