• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getStatusDescription(int status)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      */
MessageData()203     public MessageData() {
204         mParts = new ArrayList<MessagePartData>();
205     }
206 
getProjection()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      */
createDraftMessage(final String conversationId, final String selfId, final MessageData content)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      */
createDraftSmsMessage(final String conversationId, final String selfId, final String messageText)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      */
createDraftMmsMessage(final String conversationId, final String selfId, final String messageText, final String subjectText)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      */
createReceivedSmsMessage(final Uri uri, final String conversationId, final String participantId, final String selfId, final String messageText, final String subject, final long sent, final long recieved, final boolean seen, final boolean read)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      */
createSharedMessage(final String messageText)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      */
createSmsMessage(final String messageUri, final String participantId, final String selfId, final String conversationId, final int bugleStatus, final boolean seen, final boolean read, final long sent, final long recieved, final String messageText)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      */
createMmsMessage(final String messageUri, final String participantId, final String selfId, final String conversationId, final boolean isNotification, final int bugleStatus, final String contentLocation, final String transactionId, final int smsPriority, final String subject, final boolean seen, final boolean read, final long size, final int rawStatus, final long expiry, final long sent, final long received)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 
addPart(final MessagePartData part)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 
getParts()376     public Iterable<MessagePartData> getParts() {
377         return mParts;
378     }
379 
bind(final Cursor cursor)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      */
bindDraft(final Cursor cursor, final String conversationSelfId)407     public void bindDraft(final Cursor cursor, final String conversationSelfId) {
408         bind(cursor);
409         mSelfId = conversationSelfId;
410     }
411 
getParticipantId(final Cursor cursor)412     protected static String getParticipantId(final Cursor cursor) {
413         return cursor.getString(INDEX_PARTICIPANT_ID);
414     }
415 
populate(final ContentValues values)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      */
getInsertStatement(final DatabaseWrapper db)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 
getMessageId()475     public final String getMessageId() {
476         return mMessageId;
477     }
478 
getConversationId()479     public final String getConversationId() {
480         return mConversationId;
481     }
482 
getParticipantId()483     public final String getParticipantId() {
484         return mParticipantId;
485     }
486 
getSelfId()487     public final String getSelfId() {
488         return mSelfId;
489     }
490 
getSentTimeStamp()491     public final long getSentTimeStamp() {
492         return mSentTimestamp;
493     }
494 
getReceivedTimeStamp()495     public final long getReceivedTimeStamp() {
496         return mReceivedTimestamp;
497     }
498 
getFormattedReceivedTimeStamp()499     public final String getFormattedReceivedTimeStamp() {
500         return Dates.getMessageTimeString(mReceivedTimestamp).toString();
501     }
502 
getProtocol()503     public final int getProtocol() {
504         return mProtocol;
505     }
506 
getStatus()507     public final int getStatus() {
508         return mStatus;
509     }
510 
getSmsMessageUri()511     public final Uri getSmsMessageUri() {
512         return mSmsMessageUri;
513     }
514 
getSmsPriority()515     public final int getSmsPriority() {
516         return mSmsPriority;
517     }
518 
getSmsMessageSize()519     public final long getSmsMessageSize() {
520         return mSmsMessageSize;
521     }
522 
getMmsSubject()523     public final String getMmsSubject() {
524         return mMmsSubject;
525     }
526 
setMmsSubject(final String subject)527     public final void setMmsSubject(final String subject) {
528         mMmsSubject = subject;
529     }
530 
getMmsContentLocation()531     public final String getMmsContentLocation() {
532         return mMmsContentLocation;
533     }
534 
getMmsTransactionId()535     public final String getMmsTransactionId() {
536         return mMmsTransactionId;
537     }
538 
getMessageSeen()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      */
getRawTelephonyStatus()549     public final int getRawTelephonyStatus() {
550         return mRawStatus;
551     }
552 
setMessageSeen(final boolean hasSeen)553     public final void setMessageSeen(final boolean hasSeen) {
554         mSeen = hasSeen;
555     }
556 
getInResendWindow(final long now)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 
getInDownloadWindow(final long now)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 
getShowDownloadMessage(final int status)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 
canDownloadMessage()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 
canRedownloadMessage()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 
getShowResendMessage(final int status)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 
getOneClickResendMessage(final int status, final int rawStatus)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 
canResendMessage()623     public boolean canResendMessage() {
624         // Manual retry allowed only from failed
625         return (mStatus == BUGLE_STATUS_OUTGOING_FAILED);
626     }
627 
canSendMessage()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 
getYetToSend()634     public final boolean getYetToSend() {
635         return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND);
636     }
637 
getIsMms()638     public final boolean getIsMms() {
639         return mProtocol == MessageData.PROTOCOL_MMS
640                 || mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION;
641     }
642 
getIsMmsNotification(final int protocol)643     public static final boolean getIsMmsNotification(final int protocol) {
644         return (protocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
645     }
646 
getIsMmsNotification()647     public final boolean getIsMmsNotification() {
648         return getIsMmsNotification(mProtocol);
649     }
650 
getIsSms(final int protocol)651     public static final boolean getIsSms(final int protocol) {
652         return protocol == (MessageData.PROTOCOL_SMS);
653     }
654 
getIsSms()655     public final boolean getIsSms() {
656         return getIsSms(mProtocol);
657     }
658 
getIsIncoming(final int status)659     public static boolean getIsIncoming(final int status) {
660         return (status >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
661     }
662 
getIsIncoming()663     public boolean getIsIncoming() {
664         return getIsIncoming(mStatus);
665     }
666 
getRetryStartTimestamp()667     public long getRetryStartTimestamp() {
668         return mRetryStartTimestamp;
669     }
670 
getMessageText()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      */
consolidateText()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 
getFirstAttachment()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      */
updateMessageId(final String messageId)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 
updateSendingMessage(final String conversationId, final Uri messageUri, final long timestamp)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 
markMessageManualResend(final long timestamp)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 
markMessageSending(final long timestamp)767     public final void markMessageSending(final long timestamp) {
768         // Initial send
769         mStatus = BUGLE_STATUS_OUTGOING_SENDING;
770         mSentTimestamp = timestamp;
771     }
772 
markMessageResending(final long timestamp)773     public final void markMessageResending(final long timestamp) {
774         // Auto resend of message
775         mStatus = BUGLE_STATUS_OUTGOING_RESENDING;
776         mSentTimestamp = timestamp;
777     }
778 
markMessageSent(final long timestamp)779     public final void markMessageSent(final long timestamp) {
780         mSentTimestamp = timestamp;
781         mStatus = BUGLE_STATUS_OUTGOING_COMPLETE;
782     }
783 
markMessageFailed(final long timestamp)784     public final void markMessageFailed(final long timestamp) {
785         mSentTimestamp = timestamp;
786         mStatus = BUGLE_STATUS_OUTGOING_FAILED;
787     }
788 
markMessageFailedEmergencyNumber(final long timestamp)789     public final void markMessageFailedEmergencyNumber(final long timestamp) {
790         mSentTimestamp = timestamp;
791         mStatus = BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER;
792     }
793 
markMessageNotSent(final long timestamp)794     public final void markMessageNotSent(final long timestamp) {
795         mSentTimestamp = timestamp;
796         mStatus = BUGLE_STATUS_OUTGOING_AWAITING_RETRY;
797     }
798 
updateSizesForImageParts()799     public final void updateSizesForImageParts() {
800         for (final MessagePartData part : getParts()) {
801             part.decodeAndSaveSizeIfImage(false /* saveToStorage */);
802         }
803     }
804 
setRetryStartTimestamp(final long timestamp)805     public final void setRetryStartTimestamp(final long timestamp) {
806         mRetryStartTimestamp = timestamp;
807     }
808 
setRawTelephonyStatus(final int rawStatus)809     public final void setRawTelephonyStatus(final int rawStatus) {
810         mRawStatus = rawStatus;
811     }
812 
hasContent()813     public boolean hasContent() {
814         return !TextUtils.isEmpty(mMmsSubject) ||
815                 getFirstAttachment() != null ||
816                 !TextUtils.isEmpty(getMessageText());
817     }
818 
bindSelfId(final String selfId)819     public final void bindSelfId(final String selfId) {
820         mSelfId = selfId;
821     }
822 
bindParticipantId(final String participantId)823     public final void bindParticipantId(final String participantId) {
824         mParticipantId = participantId;
825     }
826 
MessageData(final Parcel in)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
describeContents()858     public int describeContents() {
859         return 0;
860     }
861 
862     @Override
writeToParcel(final Parcel dest, final int flags)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
toString()906     public String toString() {
907         return toString(mMessageId, mParts);
908     }
909 
toString(String messageId, List<MessagePartData> parts)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