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