blob: 36c699610dfbb7d9c9494776dde22ea0bb29f48e [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony;
import android.content.ContentValues;
import android.database.Cursor;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
import java.util.Arrays;
import java.util.Date;
/**
* Tracker for an incoming SMS message ready to broadcast to listeners.
* This is similar to {@link com.android.internal.telephony.SMSDispatcher.SmsTracker} used for
* outgoing messages.
*/
public class InboundSmsTracker {
// Fields for single and multi-part messages
private final byte[] mPdu;
private final long mTimestamp;
private final int mDestPort;
private final boolean mIs3gpp2;
private final boolean mIs3gpp2WapPdu;
private final String mMessageBody;
// Fields for concatenating multi-part SMS messages
private final String mAddress;
private final int mReferenceNumber;
private final int mSequenceNumber;
private final int mMessageCount;
// Fields for deleting this message after delivery
private String mDeleteWhere;
private String[] mDeleteWhereArgs;
/**
* Copied from SmsMessageBase#getDisplayOriginatingAddress used for blocking messages.
* DisplayAddress could be email address if this message was from an email gateway, otherwise
* same as mAddress. Email gateway might set a generic gateway address as the mAddress which
* could not be used for blocking check and append the display email address at the beginning
* of the message body. In that case, display email address is only available for the first SMS
* in the Multi-part SMS.
*/
private final String mDisplayAddress;
@VisibleForTesting
/** Destination port flag bit for no destination port. */
public static final int DEST_PORT_FLAG_NO_PORT = (1 << 16);
/** Destination port flag bit to indicate 3GPP format message. */
private static final int DEST_PORT_FLAG_3GPP = (1 << 17);
@VisibleForTesting
/** Destination port flag bit to indicate 3GPP2 format message. */
public static final int DEST_PORT_FLAG_3GPP2 = (1 << 18);
@VisibleForTesting
/** Destination port flag bit to indicate 3GPP2 format WAP message. */
public static final int DEST_PORT_FLAG_3GPP2_WAP_PDU = (1 << 19);
/** Destination port mask (16-bit unsigned value on GSM and CDMA). */
private static final int DEST_PORT_MASK = 0xffff;
@VisibleForTesting
public static final String SELECT_BY_REFERENCE = "address=? AND reference_number=? AND "
+ "count=? AND (destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU
+ "=0) AND deleted=0";
@VisibleForTesting
public static final String SELECT_BY_REFERENCE_3GPP2WAP = "address=? AND reference_number=? "
+ "AND count=? AND (destination_port & "
+ DEST_PORT_FLAG_3GPP2_WAP_PDU + "=" + DEST_PORT_FLAG_3GPP2_WAP_PDU + ") AND deleted=0";
@VisibleForTesting
public static final String SELECT_BY_DUPLICATE_REFERENCE = "address=? AND "
+ "reference_number=? AND count=? AND sequence=? AND "
+ "((date=? AND message_body=?) OR deleted=0) AND (destination_port & "
+ DEST_PORT_FLAG_3GPP2_WAP_PDU + "=0)";
@VisibleForTesting
public static final String SELECT_BY_DUPLICATE_REFERENCE_3GPP2WAP = "address=? AND "
+ "reference_number=? " + "AND count=? AND sequence=? AND "
+ "((date=? AND message_body=?) OR deleted=0) AND "
+ "(destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU + "="
+ DEST_PORT_FLAG_3GPP2_WAP_PDU + ")";
/**
* Create a tracker for a single-part SMS.
*
* @param pdu the message PDU
* @param timestamp the message timestamp
* @param destPort the destination port
* @param is3gpp2 true for 3GPP2 format; false for 3GPP format
* @param is3gpp2WapPdu true for 3GPP2 format WAP PDU; false otherwise
* @param address originating address
* @param displayAddress email address if this message was from an email gateway, otherwise same
* as originating address
*/
public InboundSmsTracker(byte[] pdu, long timestamp, int destPort, boolean is3gpp2,
boolean is3gpp2WapPdu, String address, String displayAddress, String messageBody) {
mPdu = pdu;
mTimestamp = timestamp;
mDestPort = destPort;
mIs3gpp2 = is3gpp2;
mIs3gpp2WapPdu = is3gpp2WapPdu;
mMessageBody = messageBody;
mAddress = address;
mDisplayAddress = displayAddress;
// fields for multi-part SMS
mReferenceNumber = -1;
mSequenceNumber = getIndexOffset(); // 0 or 1, depending on type
mMessageCount = 1;
}
/**
* Create a tracker for a multi-part SMS. Sequence numbers start at 1 for 3GPP and regular
* concatenated 3GPP2 messages, but CDMA WAP push sequence numbers start at 0. The caller will
* subtract 1 if necessary so that the sequence number is always 0-based. When loading and
* saving to the raw table, the sequence number is adjusted if necessary for backwards
* compatibility.
*
* @param pdu the message PDU
* @param timestamp the message timestamp
* @param destPort the destination port
* @param is3gpp2 true for 3GPP2 format; false for 3GPP format
* @param address originating address, or email if this message was from an email gateway
* @param displayAddress email address if this message was from an email gateway, otherwise same
* as originating address
* @param referenceNumber the concatenated reference number
* @param sequenceNumber the sequence number of this segment (0-based)
* @param messageCount the total number of segments
* @param is3gpp2WapPdu true for 3GPP2 format WAP PDU; false otherwise
*/
public InboundSmsTracker(byte[] pdu, long timestamp, int destPort, boolean is3gpp2,
String address, String displayAddress, int referenceNumber, int sequenceNumber,
int messageCount, boolean is3gpp2WapPdu, String messageBody) {
mPdu = pdu;
mTimestamp = timestamp;
mDestPort = destPort;
mIs3gpp2 = is3gpp2;
mIs3gpp2WapPdu = is3gpp2WapPdu;
mMessageBody = messageBody;
// fields used for check blocking message
mDisplayAddress = displayAddress;
// fields for multi-part SMS
mAddress = address;
mReferenceNumber = referenceNumber;
mSequenceNumber = sequenceNumber;
mMessageCount = messageCount;
}
/**
* Create a new tracker from the row of the raw table pointed to by Cursor.
* Since this constructor is used only for recovery during startup, the Dispatcher is null.
* @param cursor a Cursor pointing to the row to construct this SmsTracker for
*/
public InboundSmsTracker(Cursor cursor, boolean isCurrentFormat3gpp2) {
mPdu = HexDump.hexStringToByteArray(cursor.getString(InboundSmsHandler.PDU_COLUMN));
if (cursor.isNull(InboundSmsHandler.DESTINATION_PORT_COLUMN)) {
mDestPort = -1;
mIs3gpp2 = isCurrentFormat3gpp2;
mIs3gpp2WapPdu = false;
} else {
int destPort = cursor.getInt(InboundSmsHandler.DESTINATION_PORT_COLUMN);
if ((destPort & DEST_PORT_FLAG_3GPP) != 0) {
mIs3gpp2 = false;
} else if ((destPort & DEST_PORT_FLAG_3GPP2) != 0) {
mIs3gpp2 = true;
} else {
mIs3gpp2 = isCurrentFormat3gpp2;
}
mIs3gpp2WapPdu = ((destPort & DEST_PORT_FLAG_3GPP2_WAP_PDU) != 0);
mDestPort = getRealDestPort(destPort);
}
mTimestamp = cursor.getLong(InboundSmsHandler.DATE_COLUMN);
mAddress = cursor.getString(InboundSmsHandler.ADDRESS_COLUMN);
mDisplayAddress = cursor.getString(InboundSmsHandler.DISPLAY_ADDRESS_COLUMN);
if (cursor.getInt(InboundSmsHandler.COUNT_COLUMN) == 1) {
// single-part message
long rowId = cursor.getLong(InboundSmsHandler.ID_COLUMN);
mReferenceNumber = -1;
mSequenceNumber = getIndexOffset(); // 0 or 1, depending on type
mMessageCount = 1;
mDeleteWhere = InboundSmsHandler.SELECT_BY_ID;
mDeleteWhereArgs = new String[]{Long.toString(rowId)};
} else {
// multi-part message
mReferenceNumber = cursor.getInt(InboundSmsHandler.REFERENCE_NUMBER_COLUMN);
mMessageCount = cursor.getInt(InboundSmsHandler.COUNT_COLUMN);
// GSM sequence numbers start at 1; CDMA WDP datagram sequence numbers start at 0
mSequenceNumber = cursor.getInt(InboundSmsHandler.SEQUENCE_COLUMN);
int index = mSequenceNumber - getIndexOffset();
if (index < 0 || index >= mMessageCount) {
throw new IllegalArgumentException("invalid PDU sequence " + mSequenceNumber
+ " of " + mMessageCount);
}
mDeleteWhere = getQueryForSegments();
mDeleteWhereArgs = new String[]{mAddress,
Integer.toString(mReferenceNumber), Integer.toString(mMessageCount)};
}
mMessageBody = cursor.getString(InboundSmsHandler.MESSAGE_BODY_COLUMN);
}
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put("pdu", HexDump.toHexString(mPdu));
values.put("date", mTimestamp);
// Always set the destination port, since it now contains message format flags.
// Port is a 16-bit value, or -1, so clear the upper bits before setting flags.
int destPort;
if (mDestPort == -1) {
destPort = DEST_PORT_FLAG_NO_PORT;
} else {
destPort = mDestPort & DEST_PORT_MASK;
}
if (mIs3gpp2) {
destPort |= DEST_PORT_FLAG_3GPP2;
} else {
destPort |= DEST_PORT_FLAG_3GPP;
}
if (mIs3gpp2WapPdu) {
destPort |= DEST_PORT_FLAG_3GPP2_WAP_PDU;
}
values.put("destination_port", destPort);
if (mAddress != null) {
values.put("address", mAddress);
values.put("display_originating_addr", mDisplayAddress);
values.put("reference_number", mReferenceNumber);
values.put("sequence", mSequenceNumber);
}
values.put("count", mMessageCount);
values.put("message_body", mMessageBody);
return values;
}
/**
* Get the port number, or -1 if there is no destination port.
* @param destPort the destination port value, with flags
* @return the real destination port, or -1 for no port
*/
public static int getRealDestPort(int destPort) {
if ((destPort & DEST_PORT_FLAG_NO_PORT) != 0) {
return -1;
} else {
return destPort & DEST_PORT_MASK;
}
}
/**
* Update the values to delete all rows of the message from raw table.
* @param deleteWhere the selection to use
* @param deleteWhereArgs the selection args to use
*/
public void setDeleteWhere(String deleteWhere, String[] deleteWhereArgs) {
mDeleteWhere = deleteWhere;
mDeleteWhereArgs = deleteWhereArgs;
}
public String toString() {
StringBuilder builder = new StringBuilder("SmsTracker{timestamp=");
builder.append(new Date(mTimestamp));
builder.append(" destPort=").append(mDestPort);
builder.append(" is3gpp2=").append(mIs3gpp2);
if (mAddress != null) {
builder.append(" address=").append(mAddress);
builder.append(" display_originating_addr=").append(mDisplayAddress);
builder.append(" refNumber=").append(mReferenceNumber);
builder.append(" seqNumber=").append(mSequenceNumber);
builder.append(" msgCount=").append(mMessageCount);
}
if (mDeleteWhere != null) {
builder.append(" deleteWhere(").append(mDeleteWhere);
builder.append(") deleteArgs=(").append(Arrays.toString(mDeleteWhereArgs));
builder.append(')');
}
builder.append('}');
return builder.toString();
}
public byte[] getPdu() {
return mPdu;
}
public long getTimestamp() {
return mTimestamp;
}
public int getDestPort() {
return mDestPort;
}
public boolean is3gpp2() {
return mIs3gpp2;
}
public String getFormat() {
return mIs3gpp2 ? SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
}
public String getQueryForSegments() {
return mIs3gpp2WapPdu ? SELECT_BY_REFERENCE_3GPP2WAP : SELECT_BY_REFERENCE;
}
public String getQueryForMultiPartDuplicates() {
return mIs3gpp2WapPdu ? SELECT_BY_DUPLICATE_REFERENCE_3GPP2WAP :
SELECT_BY_DUPLICATE_REFERENCE;
}
/**
* Sequence numbers for concatenated messages start at 1. The exception is CDMA WAP PDU
* messages, which use a 0-based index.
* @return the offset to use to convert between mIndex and the sequence number
*/
public int getIndexOffset() {
return (mIs3gpp2 && mIs3gpp2WapPdu) ? 0 : 1;
}
public String getAddress() {
return mAddress;
}
public String getDisplayAddress() {
return mDisplayAddress;
}
public String getMessageBody() {
return mMessageBody;
}
public int getReferenceNumber() {
return mReferenceNumber;
}
public int getSequenceNumber() {
return mSequenceNumber;
}
public int getMessageCount() {
return mMessageCount;
}
public String getDeleteWhere() {
return mDeleteWhere;
}
public String[] getDeleteWhereArgs() {
return mDeleteWhereArgs;
}
}