Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2015 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.messaging.datamodel.data;
     18 
     19 import android.content.ContentValues;
     20 import android.database.Cursor;
     21 import android.database.sqlite.SQLiteStatement;
     22 import android.net.Uri;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 import android.text.TextUtils;
     26 
     27 import com.android.messaging.datamodel.DatabaseHelper;
     28 import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
     29 import com.android.messaging.datamodel.DatabaseWrapper;
     30 import com.android.messaging.sms.MmsUtils;
     31 import com.android.messaging.util.Assert;
     32 import com.android.messaging.util.BugleGservices;
     33 import com.android.messaging.util.BugleGservicesKeys;
     34 import com.android.messaging.util.Dates;
     35 import com.android.messaging.util.DebugUtils;
     36 import com.android.messaging.util.OsUtil;
     37 
     38 import java.util.ArrayList;
     39 import java.util.Arrays;
     40 import java.util.List;
     41 
     42 public class MessageData implements Parcelable {
     43     private static final String[] sProjection = {
     44         MessageColumns._ID,
     45         MessageColumns.CONVERSATION_ID,
     46         MessageColumns.SENDER_PARTICIPANT_ID,
     47         MessageColumns.SELF_PARTICIPANT_ID,
     48         MessageColumns.SENT_TIMESTAMP,
     49         MessageColumns.RECEIVED_TIMESTAMP,
     50         MessageColumns.SEEN,
     51         MessageColumns.READ,
     52         MessageColumns.PROTOCOL,
     53         MessageColumns.STATUS,
     54         MessageColumns.SMS_MESSAGE_URI,
     55         MessageColumns.SMS_PRIORITY,
     56         MessageColumns.SMS_MESSAGE_SIZE,
     57         MessageColumns.MMS_SUBJECT,
     58         MessageColumns.MMS_TRANSACTION_ID,
     59         MessageColumns.MMS_CONTENT_LOCATION,
     60         MessageColumns.MMS_EXPIRY,
     61         MessageColumns.RAW_TELEPHONY_STATUS,
     62         MessageColumns.RETRY_START_TIMESTAMP,
     63     };
     64 
     65     private static final int INDEX_ID = 0;
     66     private static final int INDEX_CONVERSATION_ID = 1;
     67     private static final int INDEX_PARTICIPANT_ID = 2;
     68     private static final int INDEX_SELF_ID = 3;
     69     private static final int INDEX_SENT_TIMESTAMP = 4;
     70     private static final int INDEX_RECEIVED_TIMESTAMP = 5;
     71     private static final int INDEX_SEEN = 6;
     72     private static final int INDEX_READ = 7;
     73     private static final int INDEX_PROTOCOL = 8;
     74     private static final int INDEX_BUGLE_STATUS = 9;
     75     private static final int INDEX_SMS_MESSAGE_URI = 10;
     76     private static final int INDEX_SMS_PRIORITY = 11;
     77     private static final int INDEX_SMS_MESSAGE_SIZE = 12;
     78     private static final int INDEX_MMS_SUBJECT = 13;
     79     private static final int INDEX_MMS_TRANSACTION_ID = 14;
     80     private static final int INDEX_MMS_CONTENT_LOCATION = 15;
     81     private static final int INDEX_MMS_EXPIRY = 16;
     82     private static final int INDEX_RAW_TELEPHONY_STATUS = 17;
     83     private static final int INDEX_RETRY_START_TIMESTAMP = 18;
     84 
     85     // SQL statement to insert a "complete" message row (columns based on the projection above).
     86     private static final String INSERT_MESSAGE_SQL =
     87             "INSERT INTO " + DatabaseHelper.MESSAGES_TABLE + " ( "
     88                     + TextUtils.join(", ", Arrays.copyOfRange(sProjection, 1,
     89                             INDEX_RETRY_START_TIMESTAMP + 1))
     90                     + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
     91 
     92     private String mMessageId;
     93     private String mConversationId;
     94     private String mParticipantId;
     95     private String mSelfId;
     96     private long mSentTimestamp;
     97     private long mReceivedTimestamp;
     98     private boolean mSeen;
     99     private boolean mRead;
    100     private int mProtocol;
    101     private Uri mSmsMessageUri;
    102     private int mSmsPriority;
    103     private long mSmsMessageSize;
    104     private String mMmsSubject;
    105     private String mMmsTransactionId;
    106     private String mMmsContentLocation;
    107     private long mMmsExpiry;
    108     private int mRawStatus;
    109     private int mStatus;
    110     private final ArrayList<MessagePartData> mParts;
    111     private long mRetryStartTimestamp;
    112 
    113     // PROTOCOL Values
    114     public static final int PROTOCOL_UNKNOWN = -1;              // Unknown type
    115     public static final int PROTOCOL_SMS = 0;                   // SMS message
    116     public static final int PROTOCOL_MMS = 1;                   // MMS message
    117     public static final int PROTOCOL_MMS_PUSH_NOTIFICATION = 2; // MMS WAP push notification
    118 
    119     // Bugle STATUS Values
    120     public static final int BUGLE_STATUS_UNKNOWN = 0;
    121 
    122     // Outgoing
    123     public static final int BUGLE_STATUS_OUTGOING_COMPLETE                = 1;
    124     public static final int BUGLE_STATUS_OUTGOING_DELIVERED               = 2;
    125     // Transitions to either YET_TO_SEND or SEND_AFTER_PROCESSING depending attachments.
    126     public static final int BUGLE_STATUS_OUTGOING_DRAFT                   = 3;
    127     public static final int BUGLE_STATUS_OUTGOING_YET_TO_SEND             = 4;
    128     public static final int BUGLE_STATUS_OUTGOING_SENDING                 = 5;
    129     public static final int BUGLE_STATUS_OUTGOING_RESENDING               = 6;
    130     public static final int BUGLE_STATUS_OUTGOING_AWAITING_RETRY          = 7;
    131     public static final int BUGLE_STATUS_OUTGOING_FAILED                  = 8;
    132     public static final int BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER = 9;
    133 
    134     // Incoming
    135     public static final int BUGLE_STATUS_INCOMING_COMPLETE                   = 100;
    136     public static final int BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD     = 101;
    137     public static final int BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD   = 102;
    138     public static final int BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING         = 103;
    139     public static final int BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD     = 104;
    140     public static final int BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING           = 105;
    141     public static final int BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED            = 106;
    142     public static final int BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE   = 107;
    143 
    144     public static final String getStatusDescription(int status) {
    145         switch (status) {
    146             case BUGLE_STATUS_UNKNOWN:
    147                 return "UNKNOWN";
    148             case BUGLE_STATUS_OUTGOING_COMPLETE:
    149                 return "OUTGOING_COMPLETE";
    150             case BUGLE_STATUS_OUTGOING_DELIVERED:
    151                 return "OUTGOING_DELIVERED";
    152             case BUGLE_STATUS_OUTGOING_DRAFT:
    153                 return "OUTGOING_DRAFT";
    154             case BUGLE_STATUS_OUTGOING_YET_TO_SEND:
    155                 return "OUTGOING_YET_TO_SEND";
    156             case BUGLE_STATUS_OUTGOING_SENDING:
    157                 return "OUTGOING_SENDING";
    158             case BUGLE_STATUS_OUTGOING_RESENDING:
    159                 return "OUTGOING_RESENDING";
    160             case BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
    161                 return "OUTGOING_AWAITING_RETRY";
    162             case BUGLE_STATUS_OUTGOING_FAILED:
    163                 return "OUTGOING_FAILED";
    164             case BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
    165                 return "OUTGOING_FAILED_EMERGENCY_NUMBER";
    166             case BUGLE_STATUS_INCOMING_COMPLETE:
    167                 return "INCOMING_COMPLETE";
    168             case BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
    169                 return "INCOMING_YET_TO_MANUAL_DOWNLOAD";
    170             case BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
    171                 return "INCOMING_RETRYING_MANUAL_DOWNLOAD";
    172             case BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
    173                 return "INCOMING_MANUAL_DOWNLOADING";
    174             case BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
    175                 return "INCOMING_RETRYING_AUTO_DOWNLOAD";
    176             case BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
    177                 return "INCOMING_AUTO_DOWNLOADING";
    178             case BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
    179                 return "INCOMING_DOWNLOAD_FAILED";
    180             case BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
    181                 return "INCOMING_EXPIRED_OR_NOT_AVAILABLE";
    182             default:
    183                 return String.valueOf(status) + " (check MessageData)";
    184         }
    185     }
    186 
    187     // All incoming messages expect to have status >= BUGLE_STATUS_FIRST_INCOMING
    188     public static final int BUGLE_STATUS_FIRST_INCOMING = BUGLE_STATUS_INCOMING_COMPLETE;
    189 
    190     // Detailed MMS failures. Most of the values are defined in PduHeaders. However, a few are
    191     // defined here instead. These are never returned in the MMS HTTP response, but are used
    192     // internally. The values here must not conflict with any of the existing PduHeader values.
    193     public static final int RAW_TELEPHONY_STATUS_UNDEFINED = MmsUtils.PDU_HEADER_VALUE_UNDEFINED;
    194     public static final int RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG = 10000;
    195 
    196     // Unknown result code for MMS sending/downloading. This is used as the default value
    197     // for result code returned from platform MMS API.
    198     public static final int UNKNOWN_RESULT_CODE = 0;
    199 
    200     /**
    201      * Create an "empty" message
    202      */
    203     public MessageData() {
    204         mParts = new ArrayList<MessagePartData>();
    205     }
    206 
    207     public static String[] getProjection() {
    208         return sProjection;
    209     }
    210 
    211     /**
    212      * Create a draft message for a particular conversation based on supplied content
    213      */
    214     public static MessageData createDraftMessage(final String conversationId,
    215             final String selfId, final MessageData content) {
    216         final MessageData message = new MessageData();
    217         message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
    218         message.mProtocol = PROTOCOL_UNKNOWN;
    219         message.mConversationId = conversationId;
    220         message.mParticipantId = selfId;
    221         message.mReceivedTimestamp = System.currentTimeMillis();
    222         if (content == null) {
    223             message.mParts.add(MessagePartData.createTextMessagePart(""));
    224         } else {
    225             if (!TextUtils.isEmpty(content.mParticipantId)) {
    226                 message.mParticipantId = content.mParticipantId;
    227             }
    228             if (!TextUtils.isEmpty(content.mMmsSubject)) {
    229                 message.mMmsSubject = content.mMmsSubject;
    230             }
    231             for (final MessagePartData part : content.getParts()) {
    232                 message.mParts.add(part);
    233             }
    234         }
    235         message.mSelfId = selfId;
    236         return message;
    237     }
    238 
    239     /**
    240      * Create a draft sms message for a particular conversation
    241      */
    242     public static MessageData createDraftSmsMessage(final String conversationId,
    243             final String selfId, final String messageText) {
    244         final MessageData message = new MessageData();
    245         message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
    246         message.mProtocol = PROTOCOL_SMS;
    247         message.mConversationId = conversationId;
    248         message.mParticipantId = selfId;
    249         message.mSelfId = selfId;
    250         message.mParts.add(MessagePartData.createTextMessagePart(messageText));
    251         message.mReceivedTimestamp = System.currentTimeMillis();
    252         return message;
    253     }
    254 
    255     /**
    256      * Create a draft mms message for a particular conversation
    257      */
    258     public static MessageData createDraftMmsMessage(final String conversationId,
    259             final String selfId, final String messageText, final String subjectText) {
    260         final MessageData message = new MessageData();
    261         message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
    262         message.mProtocol = PROTOCOL_MMS;
    263         message.mConversationId = conversationId;
    264         message.mParticipantId = selfId;
    265         message.mSelfId = selfId;
    266         message.mMmsSubject = subjectText;
    267         message.mReceivedTimestamp = System.currentTimeMillis();
    268         if (!TextUtils.isEmpty(messageText)) {
    269             message.mParts.add(MessagePartData.createTextMessagePart(messageText));
    270         }
    271         return message;
    272     }
    273 
    274     /**
    275      * Create a message received from a particular number in a particular conversation
    276      */
    277     public static MessageData createReceivedSmsMessage(final Uri uri, final String conversationId,
    278             final String participantId, final String selfId, final String messageText,
    279             final String subject, final long sent, final long recieved,
    280             final boolean seen, final boolean read) {
    281         final MessageData message = new MessageData();
    282         message.mSmsMessageUri = uri;
    283         message.mConversationId = conversationId;
    284         message.mParticipantId = participantId;
    285         message.mSelfId = selfId;
    286         message.mProtocol = PROTOCOL_SMS;
    287         message.mStatus = BUGLE_STATUS_INCOMING_COMPLETE;
    288         message.mMmsSubject = subject;
    289         message.mReceivedTimestamp = recieved;
    290         message.mSentTimestamp = sent;
    291         message.mParts.add(MessagePartData.createTextMessagePart(messageText));
    292         message.mSeen = seen;
    293         message.mRead = read;
    294         return message;
    295     }
    296 
    297     /**
    298      * Create a message not yet associated with a particular conversation
    299      */
    300     public static MessageData createSharedMessage(final String messageText) {
    301         final MessageData message = new MessageData();
    302         message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
    303         if (!TextUtils.isEmpty(messageText)) {
    304             message.mParts.add(MessagePartData.createTextMessagePart(messageText));
    305         }
    306         return message;
    307     }
    308 
    309     /**
    310      * Create a message from Sms table fields
    311      */
    312     public static MessageData createSmsMessage(final String messageUri, final String participantId,
    313             final String selfId, final String conversationId, final int bugleStatus,
    314             final boolean seen, final boolean read, final long sent,
    315             final long recieved, final String messageText) {
    316         final MessageData message = new MessageData();
    317         message.mParticipantId = participantId;
    318         message.mSelfId = selfId;
    319         message.mConversationId = conversationId;
    320         message.mSentTimestamp = sent;
    321         message.mReceivedTimestamp = recieved;
    322         message.mSeen = seen;
    323         message.mRead = read;
    324         message.mProtocol = PROTOCOL_SMS;
    325         message.mStatus = bugleStatus;
    326         message.mSmsMessageUri = Uri.parse(messageUri);
    327         message.mParts.add(MessagePartData.createTextMessagePart(messageText));
    328         return message;
    329     }
    330 
    331     /**
    332      * Create a message from Mms table fields
    333      */
    334     public static MessageData createMmsMessage(final String messageUri, final String participantId,
    335             final String selfId, final String conversationId, final boolean isNotification,
    336             final int bugleStatus, final String contentLocation, final String transactionId,
    337             final int smsPriority, final String subject, final boolean seen, final boolean read,
    338             final long size, final int rawStatus, final long expiry, final long sent,
    339             final long received) {
    340         final MessageData message = new MessageData();
    341         message.mParticipantId = participantId;
    342         message.mSelfId = selfId;
    343         message.mConversationId = conversationId;
    344         message.mSentTimestamp = sent;
    345         message.mReceivedTimestamp = received;
    346         message.mMmsContentLocation = contentLocation;
    347         message.mMmsTransactionId = transactionId;
    348         message.mSeen = seen;
    349         message.mRead = read;
    350         message.mStatus = bugleStatus;
    351         message.mProtocol = (isNotification ? PROTOCOL_MMS_PUSH_NOTIFICATION : PROTOCOL_MMS);
    352         message.mSmsMessageUri = Uri.parse(messageUri);
    353         message.mSmsPriority = smsPriority;
    354         message.mSmsMessageSize = size;
    355         message.mMmsSubject = subject;
    356         message.mMmsExpiry = expiry;
    357         message.mRawStatus = rawStatus;
    358         if (bugleStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD ||
    359                 bugleStatus == BUGLE_STATUS_OUTGOING_RESENDING) {
    360             // Set the retry start timestamp if this message is already in process of retrying
    361             // Either as autodownload is starting or sending already in progress (MMS update)
    362             message.mRetryStartTimestamp = received;
    363         }
    364         return message;
    365     }
    366 
    367     public void addPart(final MessagePartData part) {
    368         if (part instanceof PendingAttachmentData) {
    369             // Pending attachments may only be added to shared message data that's not associated
    370             // with any particular conversation, in order to store shared images.
    371             Assert.isTrue(mConversationId == null);
    372         }
    373         mParts.add(part);
    374     }
    375 
    376     public Iterable<MessagePartData> getParts() {
    377         return mParts;
    378     }
    379 
    380     public void bind(final Cursor cursor) {
    381         mMessageId = cursor.getString(INDEX_ID);
    382         mConversationId = cursor.getString(INDEX_CONVERSATION_ID);
    383         mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
    384         mSelfId = cursor.getString(INDEX_SELF_ID);
    385         mSentTimestamp = cursor.getLong(INDEX_SENT_TIMESTAMP);
    386         mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
    387         mSeen = (cursor.getInt(INDEX_SEEN) != 0);
    388         mRead = (cursor.getInt(INDEX_READ) != 0);
    389         mProtocol = cursor.getInt(INDEX_PROTOCOL);
    390         mStatus = cursor.getInt(INDEX_BUGLE_STATUS);
    391         final String smsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI);
    392         mSmsMessageUri = (smsMessageUri == null) ? null : Uri.parse(smsMessageUri);
    393         mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY);
    394         mSmsMessageSize = cursor.getLong(INDEX_SMS_MESSAGE_SIZE);
    395         mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY);
    396         mRawStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS);
    397         mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT);
    398         mMmsTransactionId = cursor.getString(INDEX_MMS_TRANSACTION_ID);
    399         mMmsContentLocation = cursor.getString(INDEX_MMS_CONTENT_LOCATION);
    400         mRetryStartTimestamp = cursor.getLong(INDEX_RETRY_START_TIMESTAMP);
    401     }
    402 
    403     /**
    404      * Bind to the draft message data for a conversation. The conversation's self id is used as
    405      * the draft's self id.
    406      */
    407     public void bindDraft(final Cursor cursor, final String conversationSelfId) {
    408         bind(cursor);
    409         mSelfId = conversationSelfId;
    410     }
    411 
    412     protected static String getParticipantId(final Cursor cursor) {
    413         return cursor.getString(INDEX_PARTICIPANT_ID);
    414     }
    415 
    416     public void populate(final ContentValues values) {
    417         values.put(MessageColumns.CONVERSATION_ID, mConversationId);
    418         values.put(MessageColumns.SENDER_PARTICIPANT_ID, mParticipantId);
    419         values.put(MessageColumns.SELF_PARTICIPANT_ID, mSelfId);
    420         values.put(MessageColumns.SENT_TIMESTAMP, mSentTimestamp);
    421         values.put(MessageColumns.RECEIVED_TIMESTAMP, mReceivedTimestamp);
    422         values.put(MessageColumns.SEEN, mSeen ? 1 : 0);
    423         values.put(MessageColumns.READ, mRead ? 1 : 0);
    424         values.put(MessageColumns.PROTOCOL, mProtocol);
    425         values.put(MessageColumns.STATUS, mStatus);
    426         final String smsMessageUri = ((mSmsMessageUri == null) ? null : mSmsMessageUri.toString());
    427         values.put(MessageColumns.SMS_MESSAGE_URI, smsMessageUri);
    428         values.put(MessageColumns.SMS_PRIORITY, mSmsPriority);
    429         values.put(MessageColumns.SMS_MESSAGE_SIZE, mSmsMessageSize);
    430         values.put(MessageColumns.MMS_EXPIRY, mMmsExpiry);
    431         values.put(MessageColumns.MMS_SUBJECT, mMmsSubject);
    432         values.put(MessageColumns.MMS_TRANSACTION_ID, mMmsTransactionId);
    433         values.put(MessageColumns.MMS_CONTENT_LOCATION, mMmsContentLocation);
    434         values.put(MessageColumns.RAW_TELEPHONY_STATUS, mRawStatus);
    435         values.put(MessageColumns.RETRY_START_TIMESTAMP, mRetryStartTimestamp);
    436     }
    437 
    438     /**
    439      * Note this is not thread safe so callers need to make sure they own the wrapper + statements
    440      * while they call this and use the returned value.
    441      */
    442     public SQLiteStatement getInsertStatement(final DatabaseWrapper db) {
    443         final SQLiteStatement insert = db.getStatementInTransaction(
    444                 DatabaseWrapper.INDEX_INSERT_MESSAGE, INSERT_MESSAGE_SQL);
    445         insert.clearBindings();
    446         insert.bindString(INDEX_CONVERSATION_ID, mConversationId);
    447         insert.bindString(INDEX_PARTICIPANT_ID, mParticipantId);
    448         insert.bindString(INDEX_SELF_ID, mSelfId);
    449         insert.bindLong(INDEX_SENT_TIMESTAMP, mSentTimestamp);
    450         insert.bindLong(INDEX_RECEIVED_TIMESTAMP, mReceivedTimestamp);
    451         insert.bindLong(INDEX_SEEN, mSeen ? 1 : 0);
    452         insert.bindLong(INDEX_READ, mRead ? 1 : 0);
    453         insert.bindLong(INDEX_PROTOCOL, mProtocol);
    454         insert.bindLong(INDEX_BUGLE_STATUS, mStatus);
    455         if (mSmsMessageUri != null) {
    456             insert.bindString(INDEX_SMS_MESSAGE_URI, mSmsMessageUri.toString());
    457         }
    458         insert.bindLong(INDEX_SMS_PRIORITY, mSmsPriority);
    459         insert.bindLong(INDEX_SMS_MESSAGE_SIZE, mSmsMessageSize);
    460         insert.bindLong(INDEX_MMS_EXPIRY, mMmsExpiry);
    461         if (mMmsSubject != null) {
    462             insert.bindString(INDEX_MMS_SUBJECT, mMmsSubject);
    463         }
    464         if (mMmsTransactionId != null) {
    465             insert.bindString(INDEX_MMS_TRANSACTION_ID, mMmsTransactionId);
    466         }
    467         if (mMmsContentLocation != null) {
    468             insert.bindString(INDEX_MMS_CONTENT_LOCATION, mMmsContentLocation);
    469         }
    470         insert.bindLong(INDEX_RAW_TELEPHONY_STATUS, mRawStatus);
    471         insert.bindLong(INDEX_RETRY_START_TIMESTAMP, mRetryStartTimestamp);
    472         return insert;
    473     }
    474 
    475     public final String getMessageId() {
    476         return mMessageId;
    477     }
    478 
    479     public final String getConversationId() {
    480         return mConversationId;
    481     }
    482 
    483     public final String getParticipantId() {
    484         return mParticipantId;
    485     }
    486 
    487     public final String getSelfId() {
    488         return mSelfId;
    489     }
    490 
    491     public final long getSentTimeStamp() {
    492         return mSentTimestamp;
    493     }
    494 
    495     public final long getReceivedTimeStamp() {
    496         return mReceivedTimestamp;
    497     }
    498 
    499     public final String getFormattedReceivedTimeStamp() {
    500         return Dates.getMessageTimeString(mReceivedTimestamp).toString();
    501     }
    502 
    503     public final int getProtocol() {
    504         return mProtocol;
    505     }
    506 
    507     public final int getStatus() {
    508         return mStatus;
    509     }
    510 
    511     public final Uri getSmsMessageUri() {
    512         return mSmsMessageUri;
    513     }
    514 
    515     public final int getSmsPriority() {
    516         return mSmsPriority;
    517     }
    518 
    519     public final long getSmsMessageSize() {
    520         return mSmsMessageSize;
    521     }
    522 
    523     public final String getMmsSubject() {
    524         return mMmsSubject;
    525     }
    526 
    527     public final void setMmsSubject(final String subject) {
    528         mMmsSubject = subject;
    529     }
    530 
    531     public final String getMmsContentLocation() {
    532         return mMmsContentLocation;
    533     }
    534 
    535     public final String getMmsTransactionId() {
    536         return mMmsTransactionId;
    537     }
    538 
    539     public final boolean getMessageSeen() {
    540         return mSeen;
    541     }
    542 
    543     /**
    544      * For incoming MMS messages this returns the retrieve-status value
    545      * For sent MMS messages this returns the response-status value
    546      * See PduHeaders.java for possible values
    547      * Otherwise (SMS etc) this is RAW_TELEPHONY_STATUS_UNDEFINED
    548      */
    549     public final int getRawTelephonyStatus() {
    550         return mRawStatus;
    551     }
    552 
    553     public final void setMessageSeen(final boolean hasSeen) {
    554         mSeen = hasSeen;
    555     }
    556 
    557     public final boolean getInResendWindow(final long now) {
    558         final long maxAgeToResend = BugleGservices.get().getLong(
    559                 BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS,
    560                 BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS_DEFAULT);
    561         final long age = now - mRetryStartTimestamp;
    562         return age < maxAgeToResend;
    563     }
    564 
    565     public final boolean getInDownloadWindow(final long now) {
    566         final long maxAgeToRedownload = BugleGservices.get().getLong(
    567                 BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS,
    568                 BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS_DEFAULT);
    569         final long age = now - mRetryStartTimestamp;
    570         return age < maxAgeToRedownload;
    571     }
    572 
    573     static boolean getShowDownloadMessage(final int status) {
    574         if (OsUtil.isSecondaryUser()) {
    575             // Secondary users can't download mms's. Mms's are downloaded by bugle running as the
    576             // primary user.
    577             return false;
    578         }
    579         // Should show option for manual download iff status is manual download or failed
    580         return (status == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
    581                 status == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD ||
    582                 // If debug is enabled, allow to download an expired or unavailable message.
    583                 (DebugUtils.isDebugEnabled()
    584                         && status == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE));
    585     }
    586 
    587     public boolean canDownloadMessage() {
    588         if (OsUtil.isSecondaryUser()) {
    589             // Secondary users can't download mms's. Mms's are downloaded by bugle running as the
    590             // primary user.
    591             return false;
    592         }
    593         // Can download iff status is retrying auto/manual downloading
    594         return (mStatus == BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD ||
    595                 mStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD);
    596     }
    597 
    598     public boolean canRedownloadMessage() {
    599         if (OsUtil.isSecondaryUser()) {
    600             // Secondary users can't download mms's. Mms's are downloaded by bugle running as the
    601             // primary user.
    602             return false;
    603         }
    604         // Can redownload iff status is manual download not started or download failed
    605         return (mStatus == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
    606                 mStatus == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD ||
    607                 // If debug is enabled, allow to download an expired or unavailable message.
    608                 (DebugUtils.isDebugEnabled()
    609                         && mStatus == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE));
    610     }
    611 
    612     static boolean getShowResendMessage(final int status) {
    613         // Should show option to resend iff status is failed
    614         return (status == BUGLE_STATUS_OUTGOING_FAILED);
    615     }
    616 
    617     static boolean getOneClickResendMessage(final int status, final int rawStatus) {
    618         // Should show option to resend iff status is failed
    619         return (status == BUGLE_STATUS_OUTGOING_FAILED
    620                 && rawStatus == RAW_TELEPHONY_STATUS_UNDEFINED);
    621     }
    622 
    623     public boolean canResendMessage() {
    624         // Manual retry allowed only from failed
    625         return (mStatus == BUGLE_STATUS_OUTGOING_FAILED);
    626     }
    627 
    628     public boolean canSendMessage() {
    629         // Sending messages must be in yet_to_send or awaiting_retry state
    630         return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND ||
    631                 mStatus == BUGLE_STATUS_OUTGOING_AWAITING_RETRY);
    632     }
    633 
    634     public final boolean getYetToSend() {
    635         return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND);
    636     }
    637 
    638     public final boolean getIsMms() {
    639         return mProtocol == MessageData.PROTOCOL_MMS
    640                 || mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION;
    641     }
    642 
    643     public static final boolean getIsMmsNotification(final int protocol) {
    644         return (protocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
    645     }
    646 
    647     public final boolean getIsMmsNotification() {
    648         return getIsMmsNotification(mProtocol);
    649     }
    650 
    651     public static final boolean getIsSms(final int protocol) {
    652         return protocol == (MessageData.PROTOCOL_SMS);
    653     }
    654 
    655     public final boolean getIsSms() {
    656         return getIsSms(mProtocol);
    657     }
    658 
    659     public static boolean getIsIncoming(final int status) {
    660         return (status >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
    661     }
    662 
    663     public boolean getIsIncoming() {
    664         return getIsIncoming(mStatus);
    665     }
    666 
    667     public long getRetryStartTimestamp() {
    668         return mRetryStartTimestamp;
    669     }
    670 
    671     public final String getMessageText() {
    672         final String separator = System.getProperty("line.separator");
    673         final StringBuilder text = new StringBuilder();
    674         for (final MessagePartData part : mParts) {
    675             if (!part.isAttachment() && !TextUtils.isEmpty(part.getText())) {
    676                 if (text.length() > 0) {
    677                     text.append(separator);
    678                 }
    679                 text.append(part.getText());
    680             }
    681         }
    682         return text.toString();
    683     }
    684 
    685     /**
    686      * Takes all captions from attachments and adds them as a prefix to the first text part or
    687      * appends a text part
    688      */
    689     public final void consolidateText() {
    690         final String separator = System.getProperty("line.separator");
    691         final StringBuilder captionText = new StringBuilder();
    692         MessagePartData firstTextPart = null;
    693         int firstTextPartIndex = -1;
    694         for (int i = 0; i < mParts.size(); i++) {
    695             final MessagePartData part = mParts.get(i);
    696             if (firstTextPart == null && !part.isAttachment()) {
    697                 firstTextPart = part;
    698                 firstTextPartIndex = i;
    699             }
    700             if (part.isAttachment() && !TextUtils.isEmpty(part.getText())) {
    701                 if (captionText.length() > 0) {
    702                     captionText.append(separator);
    703                 }
    704                 captionText.append(part.getText());
    705             }
    706         }
    707 
    708         if (captionText.length() == 0) {
    709             // Nothing to consolidate
    710             return;
    711         }
    712 
    713         if (firstTextPart == null) {
    714             addPart(MessagePartData.createTextMessagePart(captionText.toString()));
    715         } else {
    716             final String partText = firstTextPart.getText();
    717             if (partText.length() > 0) {
    718                 captionText.append(separator);
    719                 captionText.append(partText);
    720             }
    721             mParts.set(firstTextPartIndex,
    722                     MessagePartData.createTextMessagePart(captionText.toString()));
    723         }
    724     }
    725 
    726     public final MessagePartData getFirstAttachment() {
    727         for (final MessagePartData part : mParts) {
    728             if (part.isAttachment()) {
    729                 return part;
    730             }
    731         }
    732         return null;
    733     }
    734 
    735     /**
    736      * Updates the messageId for this message.
    737      * Can be used to reset the messageId prior to persisting (which will assign a new messageId)
    738      *  or can be called on a message that does not yet have a valid messageId to set it.
    739      */
    740     public void updateMessageId(final String messageId) {
    741         Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId));
    742         mMessageId = messageId;
    743 
    744         // TODO : This should probably also call updateMessageId on the message parts. We
    745         // may also want to make messages effectively immutable once they have a valid message id.
    746     }
    747 
    748     public final void updateSendingMessage(final String conversationId, final Uri messageUri,
    749             final long timestamp) {
    750         mConversationId = conversationId;
    751         mSmsMessageUri = messageUri;
    752         mRead = true;
    753         mSeen = true;
    754         mReceivedTimestamp = timestamp;
    755         mSentTimestamp = timestamp;
    756         mStatus = BUGLE_STATUS_OUTGOING_YET_TO_SEND;
    757         mRetryStartTimestamp = timestamp;
    758     }
    759 
    760     public final void markMessageManualResend(final long timestamp) {
    761         // Manual send updates timestamp and transitions back to initial sending status.
    762         mReceivedTimestamp = timestamp;
    763         mSentTimestamp = timestamp;
    764         mStatus = BUGLE_STATUS_OUTGOING_SENDING;
    765     }
    766 
    767     public final void markMessageSending(final long timestamp) {
    768         // Initial send
    769         mStatus = BUGLE_STATUS_OUTGOING_SENDING;
    770         mSentTimestamp = timestamp;
    771     }
    772 
    773     public final void markMessageResending(final long timestamp) {
    774         // Auto resend of message
    775         mStatus = BUGLE_STATUS_OUTGOING_RESENDING;
    776         mSentTimestamp = timestamp;
    777     }
    778 
    779     public final void markMessageSent(final long timestamp) {
    780         mSentTimestamp = timestamp;
    781         mStatus = BUGLE_STATUS_OUTGOING_COMPLETE;
    782     }
    783 
    784     public final void markMessageFailed(final long timestamp) {
    785         mSentTimestamp = timestamp;
    786         mStatus = BUGLE_STATUS_OUTGOING_FAILED;
    787     }
    788 
    789     public final void markMessageFailedEmergencyNumber(final long timestamp) {
    790         mSentTimestamp = timestamp;
    791         mStatus = BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER;
    792     }
    793 
    794     public final void markMessageNotSent(final long timestamp) {
    795         mSentTimestamp = timestamp;
    796         mStatus = BUGLE_STATUS_OUTGOING_AWAITING_RETRY;
    797     }
    798 
    799     public final void updateSizesForImageParts() {
    800         for (final MessagePartData part : getParts()) {
    801             part.decodeAndSaveSizeIfImage(false /* saveToStorage */);
    802         }
    803     }
    804 
    805     public final void setRetryStartTimestamp(final long timestamp) {
    806         mRetryStartTimestamp = timestamp;
    807     }
    808 
    809     public final void setRawTelephonyStatus(final int rawStatus) {
    810         mRawStatus = rawStatus;
    811     }
    812 
    813     public boolean hasContent() {
    814         return !TextUtils.isEmpty(mMmsSubject) ||
    815                 getFirstAttachment() != null ||
    816                 !TextUtils.isEmpty(getMessageText());
    817     }
    818 
    819     public final void bindSelfId(final String selfId) {
    820         mSelfId = selfId;
    821     }
    822 
    823     public final void bindParticipantId(final String participantId) {
    824         mParticipantId = participantId;
    825     }
    826 
    827     protected MessageData(final Parcel in) {
    828         mMessageId = in.readString();
    829         mConversationId = in.readString();
    830         mParticipantId = in.readString();
    831         mSelfId = in.readString();
    832         mSentTimestamp = in.readLong();
    833         mReceivedTimestamp = in.readLong();
    834         mSeen = (in.readInt() != 0);
    835         mRead = (in.readInt() != 0);
    836         mProtocol = in.readInt();
    837         mStatus = in.readInt();
    838         final String smsMessageUri = in.readString();
    839         mSmsMessageUri = (smsMessageUri == null ? null : Uri.parse(smsMessageUri));
    840         mSmsPriority = in.readInt();
    841         mSmsMessageSize = in.readLong();
    842         mMmsExpiry = in.readLong();
    843         mMmsSubject = in.readString();
    844         mMmsTransactionId = in.readString();
    845         mMmsContentLocation = in.readString();
    846         mRawStatus = in.readInt();
    847         mRetryStartTimestamp = in.readLong();
    848 
    849         // Read parts
    850         mParts = new ArrayList<MessagePartData>();
    851         final int partCount = in.readInt();
    852         for (int i = 0; i < partCount; i++) {
    853             mParts.add((MessagePartData) in.readParcelable(MessagePartData.class.getClassLoader()));
    854         }
    855     }
    856 
    857     @Override
    858     public int describeContents() {
    859         return 0;
    860     }
    861 
    862     @Override
    863     public void writeToParcel(final Parcel dest, final int flags) {
    864         dest.writeString(mMessageId);
    865         dest.writeString(mConversationId);
    866         dest.writeString(mParticipantId);
    867         dest.writeString(mSelfId);
    868         dest.writeLong(mSentTimestamp);
    869         dest.writeLong(mReceivedTimestamp);
    870         dest.writeInt(mRead ? 1 : 0);
    871         dest.writeInt(mSeen ? 1 : 0);
    872         dest.writeInt(mProtocol);
    873         dest.writeInt(mStatus);
    874         final String smsMessageUri = (mSmsMessageUri == null) ? null : mSmsMessageUri.toString();
    875         dest.writeString(smsMessageUri);
    876         dest.writeInt(mSmsPriority);
    877         dest.writeLong(mSmsMessageSize);
    878         dest.writeLong(mMmsExpiry);
    879         dest.writeString(mMmsSubject);
    880         dest.writeString(mMmsTransactionId);
    881         dest.writeString(mMmsContentLocation);
    882         dest.writeInt(mRawStatus);
    883         dest.writeLong(mRetryStartTimestamp);
    884 
    885         // Write parts
    886         dest.writeInt(mParts.size());
    887         for (final MessagePartData messagePartData : mParts) {
    888             dest.writeParcelable(messagePartData, flags);
    889         }
    890     }
    891 
    892     public static final Parcelable.Creator<MessageData> CREATOR
    893             = new Parcelable.Creator<MessageData>() {
    894         @Override
    895         public MessageData createFromParcel(final Parcel in) {
    896             return new MessageData(in);
    897         }
    898 
    899         @Override
    900         public MessageData[] newArray(final int size) {
    901             return new MessageData[size];
    902         }
    903     };
    904 
    905     @Override
    906     public String toString() {
    907         return toString(mMessageId, mParts);
    908     }
    909 
    910     public static String toString(String messageId, List<MessagePartData> parts) {
    911         StringBuilder sb = new StringBuilder();
    912         if (messageId != null) {
    913             sb.append(messageId);
    914             sb.append(": ");
    915         }
    916         for (MessagePartData part : parts) {
    917             sb.append(part.toString());
    918             sb.append(" ");
    919         }
    920         return sb.toString();
    921     }
    922 }
    923