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 getMmsExpiry()543 public final long getMmsExpiry() { 544 return mMmsExpiry; 545 } 546 547 /** 548 * For incoming MMS messages this returns the retrieve-status value 549 * For sent MMS messages this returns the response-status value 550 * See PduHeaders.java for possible values 551 * Otherwise (SMS etc) this is RAW_TELEPHONY_STATUS_UNDEFINED 552 */ getRawTelephonyStatus()553 public final int getRawTelephonyStatus() { 554 return mRawStatus; 555 } 556 setMessageSeen(final boolean hasSeen)557 public final void setMessageSeen(final boolean hasSeen) { 558 mSeen = hasSeen; 559 } 560 getInResendWindow(final long now)561 public final boolean getInResendWindow(final long now) { 562 final long maxAgeToResend = BugleGservices.get().getLong( 563 BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS, 564 BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS_DEFAULT); 565 final long age = now - mRetryStartTimestamp; 566 return age < maxAgeToResend; 567 } 568 getInDownloadWindow(final long now)569 public final boolean getInDownloadWindow(final long now) { 570 final long maxAgeToRedownload = BugleGservices.get().getLong( 571 BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS, 572 BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS_DEFAULT); 573 final long age = now - mRetryStartTimestamp; 574 return age < maxAgeToRedownload; 575 } 576 getShowDownloadMessage(final int status)577 static boolean getShowDownloadMessage(final int status) { 578 if (OsUtil.isSecondaryUser()) { 579 // Secondary users can't download mms's. Mms's are downloaded by bugle running as the 580 // primary user. 581 return false; 582 } 583 // Should show option for manual download iff status is manual download or failed 584 return (status == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED || 585 status == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD || 586 // If debug is enabled, allow to download an expired or unavailable message. 587 (DebugUtils.isDebugEnabled() 588 && status == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE)); 589 } 590 canDownloadMessage()591 public boolean canDownloadMessage() { 592 if (OsUtil.isSecondaryUser()) { 593 // Secondary users can't download mms's. Mms's are downloaded by bugle running as the 594 // primary user. 595 return false; 596 } 597 // Can download iff status is retrying auto/manual downloading 598 return (mStatus == BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD || 599 mStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD); 600 } 601 canRedownloadMessage()602 public boolean canRedownloadMessage() { 603 if (OsUtil.isSecondaryUser()) { 604 // Secondary users can't download mms's. Mms's are downloaded by bugle running as the 605 // primary user. 606 return false; 607 } 608 // Can redownload iff status is manual download not started or download failed 609 return (mStatus == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED || 610 mStatus == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD || 611 // If debug is enabled, allow to download an expired or unavailable message. 612 (DebugUtils.isDebugEnabled() 613 && mStatus == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE)); 614 } 615 getShowResendMessage(final int status)616 static boolean getShowResendMessage(final int status) { 617 // Should show option to resend iff status is failed 618 return (status == BUGLE_STATUS_OUTGOING_FAILED); 619 } 620 getOneClickResendMessage(final int status, final int rawStatus)621 static boolean getOneClickResendMessage(final int status, final int rawStatus) { 622 // Should show option to resend iff status is failed 623 return (status == BUGLE_STATUS_OUTGOING_FAILED 624 && rawStatus == RAW_TELEPHONY_STATUS_UNDEFINED); 625 } 626 canResendMessage()627 public boolean canResendMessage() { 628 // Manual retry allowed only from failed 629 return (mStatus == BUGLE_STATUS_OUTGOING_FAILED); 630 } 631 canSendMessage()632 public boolean canSendMessage() { 633 // Sending messages must be in yet_to_send or awaiting_retry state 634 return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND || 635 mStatus == BUGLE_STATUS_OUTGOING_AWAITING_RETRY); 636 } 637 getYetToSend()638 public final boolean getYetToSend() { 639 return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND); 640 } 641 getIsMms()642 public final boolean getIsMms() { 643 return mProtocol == MessageData.PROTOCOL_MMS 644 || mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION; 645 } 646 getIsMmsNotification(final int protocol)647 public static final boolean getIsMmsNotification(final int protocol) { 648 return (protocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION); 649 } 650 getIsMmsNotification()651 public final boolean getIsMmsNotification() { 652 return getIsMmsNotification(mProtocol); 653 } 654 getIsSms(final int protocol)655 public static final boolean getIsSms(final int protocol) { 656 return protocol == (MessageData.PROTOCOL_SMS); 657 } 658 getIsSms()659 public final boolean getIsSms() { 660 return getIsSms(mProtocol); 661 } 662 getIsIncoming(final int status)663 public static boolean getIsIncoming(final int status) { 664 return (status >= MessageData.BUGLE_STATUS_FIRST_INCOMING); 665 } 666 getIsIncoming()667 public boolean getIsIncoming() { 668 return getIsIncoming(mStatus); 669 } 670 getRetryStartTimestamp()671 public long getRetryStartTimestamp() { 672 return mRetryStartTimestamp; 673 } 674 getMessageText()675 public final String getMessageText() { 676 final String separator = System.getProperty("line.separator"); 677 final StringBuilder text = new StringBuilder(); 678 for (final MessagePartData part : mParts) { 679 if (!part.isAttachment() && !TextUtils.isEmpty(part.getText())) { 680 if (text.length() > 0) { 681 text.append(separator); 682 } 683 text.append(part.getText()); 684 } 685 } 686 return text.toString(); 687 } 688 689 /** 690 * Takes all captions from attachments and adds them as a prefix to the first text part or 691 * appends a text part 692 */ consolidateText()693 public final void consolidateText() { 694 final String separator = System.getProperty("line.separator"); 695 final StringBuilder captionText = new StringBuilder(); 696 MessagePartData firstTextPart = null; 697 int firstTextPartIndex = -1; 698 for (int i = 0; i < mParts.size(); i++) { 699 final MessagePartData part = mParts.get(i); 700 if (firstTextPart == null && !part.isAttachment()) { 701 firstTextPart = part; 702 firstTextPartIndex = i; 703 } 704 if (part.isAttachment() && !TextUtils.isEmpty(part.getText())) { 705 if (captionText.length() > 0) { 706 captionText.append(separator); 707 } 708 captionText.append(part.getText()); 709 } 710 } 711 712 if (captionText.length() == 0) { 713 // Nothing to consolidate 714 return; 715 } 716 717 if (firstTextPart == null) { 718 addPart(MessagePartData.createTextMessagePart(captionText.toString())); 719 } else { 720 final String partText = firstTextPart.getText(); 721 if (partText.length() > 0) { 722 captionText.append(separator); 723 captionText.append(partText); 724 } 725 mParts.set(firstTextPartIndex, 726 MessagePartData.createTextMessagePart(captionText.toString())); 727 } 728 } 729 getFirstAttachment()730 public final MessagePartData getFirstAttachment() { 731 for (final MessagePartData part : mParts) { 732 if (part.isAttachment()) { 733 return part; 734 } 735 } 736 return null; 737 } 738 739 /** 740 * Updates the messageId for this message. 741 * Can be used to reset the messageId prior to persisting (which will assign a new messageId) 742 * or can be called on a message that does not yet have a valid messageId to set it. 743 */ updateMessageId(final String messageId)744 public void updateMessageId(final String messageId) { 745 Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId)); 746 mMessageId = messageId; 747 748 // TODO : This should probably also call updateMessageId on the message parts. We 749 // may also want to make messages effectively immutable once they have a valid message id. 750 } 751 updateSendingMessage(final String conversationId, final Uri messageUri, final long timestamp)752 public final void updateSendingMessage(final String conversationId, final Uri messageUri, 753 final long timestamp) { 754 mConversationId = conversationId; 755 mSmsMessageUri = messageUri; 756 mRead = true; 757 mSeen = true; 758 mReceivedTimestamp = timestamp; 759 mSentTimestamp = timestamp; 760 mStatus = BUGLE_STATUS_OUTGOING_YET_TO_SEND; 761 mRetryStartTimestamp = timestamp; 762 } 763 markMessageManualResend(final long timestamp)764 public final void markMessageManualResend(final long timestamp) { 765 // Manual send updates timestamp and transitions back to initial sending status. 766 mReceivedTimestamp = timestamp; 767 mSentTimestamp = timestamp; 768 mStatus = BUGLE_STATUS_OUTGOING_SENDING; 769 } 770 markMessageSending(final long timestamp)771 public final void markMessageSending(final long timestamp) { 772 // Initial send 773 mStatus = BUGLE_STATUS_OUTGOING_SENDING; 774 mSentTimestamp = timestamp; 775 } 776 markMessageResending(final long timestamp)777 public final void markMessageResending(final long timestamp) { 778 // Auto resend of message 779 mStatus = BUGLE_STATUS_OUTGOING_RESENDING; 780 mSentTimestamp = timestamp; 781 } 782 markMessageSent(final long timestamp)783 public final void markMessageSent(final long timestamp) { 784 mSentTimestamp = timestamp; 785 mStatus = BUGLE_STATUS_OUTGOING_COMPLETE; 786 } 787 markMessageFailed(final long timestamp)788 public final void markMessageFailed(final long timestamp) { 789 mSentTimestamp = timestamp; 790 mStatus = BUGLE_STATUS_OUTGOING_FAILED; 791 } 792 markMessageFailedEmergencyNumber(final long timestamp)793 public final void markMessageFailedEmergencyNumber(final long timestamp) { 794 mSentTimestamp = timestamp; 795 mStatus = BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER; 796 } 797 markMessageNotSent(final long timestamp)798 public final void markMessageNotSent(final long timestamp) { 799 mSentTimestamp = timestamp; 800 mStatus = BUGLE_STATUS_OUTGOING_AWAITING_RETRY; 801 } 802 updateSizesForImageParts()803 public final void updateSizesForImageParts() { 804 for (final MessagePartData part : getParts()) { 805 part.decodeAndSaveSizeIfImage(false /* saveToStorage */); 806 } 807 } 808 setRetryStartTimestamp(final long timestamp)809 public final void setRetryStartTimestamp(final long timestamp) { 810 mRetryStartTimestamp = timestamp; 811 } 812 setRawTelephonyStatus(final int rawStatus)813 public final void setRawTelephonyStatus(final int rawStatus) { 814 mRawStatus = rawStatus; 815 } 816 hasContent()817 public boolean hasContent() { 818 return !TextUtils.isEmpty(mMmsSubject) || 819 getFirstAttachment() != null || 820 !TextUtils.isEmpty(getMessageText()); 821 } 822 bindSelfId(final String selfId)823 public final void bindSelfId(final String selfId) { 824 mSelfId = selfId; 825 } 826 bindParticipantId(final String participantId)827 public final void bindParticipantId(final String participantId) { 828 mParticipantId = participantId; 829 } 830 MessageData(final Parcel in)831 protected MessageData(final Parcel in) { 832 mMessageId = in.readString(); 833 mConversationId = in.readString(); 834 mParticipantId = in.readString(); 835 mSelfId = in.readString(); 836 mSentTimestamp = in.readLong(); 837 mReceivedTimestamp = in.readLong(); 838 mSeen = (in.readInt() != 0); 839 mRead = (in.readInt() != 0); 840 mProtocol = in.readInt(); 841 mStatus = in.readInt(); 842 final String smsMessageUri = in.readString(); 843 mSmsMessageUri = (smsMessageUri == null ? null : Uri.parse(smsMessageUri)); 844 mSmsPriority = in.readInt(); 845 mSmsMessageSize = in.readLong(); 846 mMmsExpiry = in.readLong(); 847 mMmsSubject = in.readString(); 848 mMmsTransactionId = in.readString(); 849 mMmsContentLocation = in.readString(); 850 mRawStatus = in.readInt(); 851 mRetryStartTimestamp = in.readLong(); 852 853 // Read parts 854 mParts = new ArrayList<MessagePartData>(); 855 final int partCount = in.readInt(); 856 for (int i = 0; i < partCount; i++) { 857 mParts.add((MessagePartData) in.readParcelable(MessagePartData.class.getClassLoader())); 858 } 859 } 860 861 @Override describeContents()862 public int describeContents() { 863 return 0; 864 } 865 866 @Override writeToParcel(final Parcel dest, final int flags)867 public void writeToParcel(final Parcel dest, final int flags) { 868 dest.writeString(mMessageId); 869 dest.writeString(mConversationId); 870 dest.writeString(mParticipantId); 871 dest.writeString(mSelfId); 872 dest.writeLong(mSentTimestamp); 873 dest.writeLong(mReceivedTimestamp); 874 dest.writeInt(mRead ? 1 : 0); 875 dest.writeInt(mSeen ? 1 : 0); 876 dest.writeInt(mProtocol); 877 dest.writeInt(mStatus); 878 final String smsMessageUri = (mSmsMessageUri == null) ? null : mSmsMessageUri.toString(); 879 dest.writeString(smsMessageUri); 880 dest.writeInt(mSmsPriority); 881 dest.writeLong(mSmsMessageSize); 882 dest.writeLong(mMmsExpiry); 883 dest.writeString(mMmsSubject); 884 dest.writeString(mMmsTransactionId); 885 dest.writeString(mMmsContentLocation); 886 dest.writeInt(mRawStatus); 887 dest.writeLong(mRetryStartTimestamp); 888 889 // Write parts 890 dest.writeInt(mParts.size()); 891 for (final MessagePartData messagePartData : mParts) { 892 dest.writeParcelable(messagePartData, flags); 893 } 894 } 895 896 public static final Parcelable.Creator<MessageData> CREATOR 897 = new Parcelable.Creator<MessageData>() { 898 @Override 899 public MessageData createFromParcel(final Parcel in) { 900 return new MessageData(in); 901 } 902 903 @Override 904 public MessageData[] newArray(final int size) { 905 return new MessageData[size]; 906 } 907 }; 908 909 @Override toString()910 public String toString() { 911 return toString(mMessageId, mParts); 912 } 913 toString(String messageId, List<MessagePartData> parts)914 public static String toString(String messageId, List<MessagePartData> parts) { 915 StringBuilder sb = new StringBuilder(); 916 if (messageId != null) { 917 sb.append(messageId); 918 sb.append(": "); 919 } 920 for (MessagePartData part : parts) { 921 sb.append(part.toString()); 922 sb.append(" "); 923 } 924 return sb.toString(); 925 } 926 } 927