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