/* * 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.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 final 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; // 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; /** Destination port flag bit for no destination port. */ private 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); /** Destination port flag bit to indicate 3GPP2 format message. */ private static final int DEST_PORT_FLAG_3GPP2 = (1 << 18); /** Destination port flag bit to indicate 3GPP2 format WAP message. */ private 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; /** * 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 */ InboundSmsTracker(byte[] pdu, long timestamp, int destPort, boolean is3gpp2, boolean is3gpp2WapPdu) { mPdu = pdu; mTimestamp = timestamp; mDestPort = destPort; mIs3gpp2 = is3gpp2; mIs3gpp2WapPdu = is3gpp2WapPdu; // fields for multi-part SMS mAddress = null; 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 the 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, int referenceNumber, int sequenceNumber, int messageCount, boolean is3gpp2WapPdu) { mPdu = pdu; mTimestamp = timestamp; mDestPort = destPort; mIs3gpp2 = is3gpp2; mIs3gpp2WapPdu = is3gpp2WapPdu; // 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 */ 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); if (cursor.isNull(InboundSmsHandler.COUNT_COLUMN)) { // single-part message long rowId = cursor.getLong(InboundSmsHandler.ID_COLUMN); mAddress = null; 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 mAddress = cursor.getString(InboundSmsHandler.ADDRESS_COLUMN); 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 = InboundSmsHandler.SELECT_BY_REFERENCE; mDeleteWhereArgs = new String[]{mAddress, Integer.toString(mReferenceNumber), Integer.toString(mMessageCount)}; } } 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("reference_number", mReferenceNumber); values.put("sequence", mSequenceNumber); values.put("count", mMessageCount); } 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 */ 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 */ 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(" 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(); } byte[] getPdu() { return mPdu; } long getTimestamp() { return mTimestamp; } int getDestPort() { return mDestPort; } boolean is3gpp2() { return mIs3gpp2; } String getFormat() { return mIs3gpp2 ? SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP; } /** * 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 */ int getIndexOffset() { return (mIs3gpp2 && mIs3gpp2WapPdu) ? 0 : 1; } String getAddress() { return mAddress; } int getReferenceNumber() { return mReferenceNumber; } int getSequenceNumber() { return mSequenceNumber; } int getMessageCount() { return mMessageCount; } String getDeleteWhere() { return mDeleteWhere; } String[] getDeleteWhereArgs() { return mDeleteWhereArgs; } }