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