Home | History | Annotate | Download | only in telephony
      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 
     17 package com.android.internal.telephony;
     18 
     19 import android.content.ContentValues;
     20 import android.database.Cursor;
     21 
     22 import com.android.internal.annotations.VisibleForTesting;
     23 import com.android.internal.util.HexDump;
     24 
     25 import java.util.Arrays;
     26 import 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  */
     33 public 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.getInt(InboundSmsHandler.COUNT_COLUMN) == 1) {
    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         }
    254         values.put("count", mMessageCount);
    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