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