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