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