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