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