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, final String subjectText)300 public static MessageData createSharedMessage(final String messageText, 301 final String subjectText) { 302 final MessageData message = new MessageData(); 303 message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT; 304 message.mMmsSubject = subjectText; 305 if (!TextUtils.isEmpty(messageText)) { 306 message.mParts.add(MessagePartData.createTextMessagePart(messageText)); 307 } 308 return message; 309 } 310 311 /** 312 * Create a message from Sms table fields 313 */ 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)314 public static MessageData createSmsMessage(final String messageUri, final String participantId, 315 final String selfId, final String conversationId, final int bugleStatus, 316 final boolean seen, final boolean read, final long sent, 317 final long recieved, final String messageText) { 318 final MessageData message = new MessageData(); 319 message.mParticipantId = participantId; 320 message.mSelfId = selfId; 321 message.mConversationId = conversationId; 322 message.mSentTimestamp = sent; 323 message.mReceivedTimestamp = recieved; 324 message.mSeen = seen; 325 message.mRead = read; 326 message.mProtocol = PROTOCOL_SMS; 327 message.mStatus = bugleStatus; 328 message.mSmsMessageUri = Uri.parse(messageUri); 329 message.mParts.add(MessagePartData.createTextMessagePart(messageText)); 330 return message; 331 } 332 333 /** 334 * Create a message from Mms table fields 335 */ 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)336 public static MessageData createMmsMessage(final String messageUri, final String participantId, 337 final String selfId, final String conversationId, final boolean isNotification, 338 final int bugleStatus, final String contentLocation, final String transactionId, 339 final int smsPriority, final String subject, final boolean seen, final boolean read, 340 final long size, final int rawStatus, final long expiry, final long sent, 341 final long received) { 342 final MessageData message = new MessageData(); 343 message.mParticipantId = participantId; 344 message.mSelfId = selfId; 345 message.mConversationId = conversationId; 346 message.mSentTimestamp = sent; 347 message.mReceivedTimestamp = received; 348 message.mMmsContentLocation = contentLocation; 349 message.mMmsTransactionId = transactionId; 350 message.mSeen = seen; 351 message.mRead = read; 352 message.mStatus = bugleStatus; 353 message.mProtocol = (isNotification ? PROTOCOL_MMS_PUSH_NOTIFICATION : PROTOCOL_MMS); 354 message.mSmsMessageUri = Uri.parse(messageUri); 355 message.mSmsPriority = smsPriority; 356 message.mSmsMessageSize = size; 357 message.mMmsSubject = subject; 358 message.mMmsExpiry = expiry; 359 message.mRawStatus = rawStatus; 360 if (bugleStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD || 361 bugleStatus == BUGLE_STATUS_OUTGOING_RESENDING) { 362 // Set the retry start timestamp if this message is already in process of retrying 363 // Either as autodownload is starting or sending already in progress (MMS update) 364 message.mRetryStartTimestamp = received; 365 } 366 return message; 367 } 368 addPart(final MessagePartData part)369 public void addPart(final MessagePartData part) { 370 if (part instanceof PendingAttachmentData) { 371 // Pending attachments may only be added to shared message data that's not associated 372 // with any particular conversation, in order to store shared images. 373 Assert.isTrue(mConversationId == null); 374 } 375 mParts.add(part); 376 } 377 getParts()378 public Iterable<MessagePartData> getParts() { 379 return mParts; 380 } 381 bind(final Cursor cursor)382 public void bind(final Cursor cursor) { 383 mMessageId = cursor.getString(INDEX_ID); 384 mConversationId = cursor.getString(INDEX_CONVERSATION_ID); 385 mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID); 386 mSelfId = cursor.getString(INDEX_SELF_ID); 387 mSentTimestamp = cursor.getLong(INDEX_SENT_TIMESTAMP); 388 mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP); 389 mSeen = (cursor.getInt(INDEX_SEEN) != 0); 390 mRead = (cursor.getInt(INDEX_READ) != 0); 391 mProtocol = cursor.getInt(INDEX_PROTOCOL); 392 mStatus = cursor.getInt(INDEX_BUGLE_STATUS); 393 final String smsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI); 394 mSmsMessageUri = (smsMessageUri == null) ? null : Uri.parse(smsMessageUri); 395 mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY); 396 mSmsMessageSize = cursor.getLong(INDEX_SMS_MESSAGE_SIZE); 397 mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY); 398 mRawStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS); 399 mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT); 400 mMmsTransactionId = cursor.getString(INDEX_MMS_TRANSACTION_ID); 401 mMmsContentLocation = cursor.getString(INDEX_MMS_CONTENT_LOCATION); 402 mRetryStartTimestamp = cursor.getLong(INDEX_RETRY_START_TIMESTAMP); 403 } 404 405 /** 406 * Bind to the draft message data for a conversation. The conversation's self id is used as 407 * the draft's self id. 408 */ bindDraft(final Cursor cursor, final String conversationSelfId)409 public void bindDraft(final Cursor cursor, final String conversationSelfId) { 410 bind(cursor); 411 mSelfId = conversationSelfId; 412 } 413 getParticipantId(final Cursor cursor)414 protected static String getParticipantId(final Cursor cursor) { 415 return cursor.getString(INDEX_PARTICIPANT_ID); 416 } 417 populate(final ContentValues values)418 public void populate(final ContentValues values) { 419 values.put(MessageColumns.CONVERSATION_ID, mConversationId); 420 values.put(MessageColumns.SENDER_PARTICIPANT_ID, mParticipantId); 421 values.put(MessageColumns.SELF_PARTICIPANT_ID, mSelfId); 422 values.put(MessageColumns.SENT_TIMESTAMP, mSentTimestamp); 423 values.put(MessageColumns.RECEIVED_TIMESTAMP, mReceivedTimestamp); 424 values.put(MessageColumns.SEEN, mSeen ? 1 : 0); 425 values.put(MessageColumns.READ, mRead ? 1 : 0); 426 values.put(MessageColumns.PROTOCOL, mProtocol); 427 values.put(MessageColumns.STATUS, mStatus); 428 final String smsMessageUri = ((mSmsMessageUri == null) ? null : mSmsMessageUri.toString()); 429 values.put(MessageColumns.SMS_MESSAGE_URI, smsMessageUri); 430 values.put(MessageColumns.SMS_PRIORITY, mSmsPriority); 431 values.put(MessageColumns.SMS_MESSAGE_SIZE, mSmsMessageSize); 432 values.put(MessageColumns.MMS_EXPIRY, mMmsExpiry); 433 values.put(MessageColumns.MMS_SUBJECT, mMmsSubject); 434 values.put(MessageColumns.MMS_TRANSACTION_ID, mMmsTransactionId); 435 values.put(MessageColumns.MMS_CONTENT_LOCATION, mMmsContentLocation); 436 values.put(MessageColumns.RAW_TELEPHONY_STATUS, mRawStatus); 437 values.put(MessageColumns.RETRY_START_TIMESTAMP, mRetryStartTimestamp); 438 } 439 440 /** 441 * Note this is not thread safe so callers need to make sure they own the wrapper + statements 442 * while they call this and use the returned value. 443 */ getInsertStatement(final DatabaseWrapper db)444 public SQLiteStatement getInsertStatement(final DatabaseWrapper db) { 445 final SQLiteStatement insert = db.getStatementInTransaction( 446 DatabaseWrapper.INDEX_INSERT_MESSAGE, INSERT_MESSAGE_SQL); 447 insert.clearBindings(); 448 insert.bindString(INDEX_CONVERSATION_ID, mConversationId); 449 insert.bindString(INDEX_PARTICIPANT_ID, mParticipantId); 450 insert.bindString(INDEX_SELF_ID, mSelfId); 451 insert.bindLong(INDEX_SENT_TIMESTAMP, mSentTimestamp); 452 insert.bindLong(INDEX_RECEIVED_TIMESTAMP, mReceivedTimestamp); 453 insert.bindLong(INDEX_SEEN, mSeen ? 1 : 0); 454 insert.bindLong(INDEX_READ, mRead ? 1 : 0); 455 insert.bindLong(INDEX_PROTOCOL, mProtocol); 456 insert.bindLong(INDEX_BUGLE_STATUS, mStatus); 457 if (mSmsMessageUri != null) { 458 insert.bindString(INDEX_SMS_MESSAGE_URI, mSmsMessageUri.toString()); 459 } 460 insert.bindLong(INDEX_SMS_PRIORITY, mSmsPriority); 461 insert.bindLong(INDEX_SMS_MESSAGE_SIZE, mSmsMessageSize); 462 insert.bindLong(INDEX_MMS_EXPIRY, mMmsExpiry); 463 if (mMmsSubject != null) { 464 insert.bindString(INDEX_MMS_SUBJECT, mMmsSubject); 465 } 466 if (mMmsTransactionId != null) { 467 insert.bindString(INDEX_MMS_TRANSACTION_ID, mMmsTransactionId); 468 } 469 if (mMmsContentLocation != null) { 470 insert.bindString(INDEX_MMS_CONTENT_LOCATION, mMmsContentLocation); 471 } 472 insert.bindLong(INDEX_RAW_TELEPHONY_STATUS, mRawStatus); 473 insert.bindLong(INDEX_RETRY_START_TIMESTAMP, mRetryStartTimestamp); 474 return insert; 475 } 476 getMessageId()477 public final String getMessageId() { 478 return mMessageId; 479 } 480 getConversationId()481 public final String getConversationId() { 482 return mConversationId; 483 } 484 getParticipantId()485 public final String getParticipantId() { 486 return mParticipantId; 487 } 488 getSelfId()489 public final String getSelfId() { 490 return mSelfId; 491 } 492 getSentTimeStamp()493 public final long getSentTimeStamp() { 494 return mSentTimestamp; 495 } 496 getReceivedTimeStamp()497 public final long getReceivedTimeStamp() { 498 return mReceivedTimestamp; 499 } 500 getFormattedReceivedTimeStamp()501 public final String getFormattedReceivedTimeStamp() { 502 return Dates.getMessageTimeString(mReceivedTimestamp).toString(); 503 } 504 getProtocol()505 public final int getProtocol() { 506 return mProtocol; 507 } 508 getStatus()509 public final int getStatus() { 510 return mStatus; 511 } 512 getSmsMessageUri()513 public final Uri getSmsMessageUri() { 514 return mSmsMessageUri; 515 } 516 getSmsPriority()517 public final int getSmsPriority() { 518 return mSmsPriority; 519 } 520 getSmsMessageSize()521 public final long getSmsMessageSize() { 522 return mSmsMessageSize; 523 } 524 getMmsSubject()525 public final String getMmsSubject() { 526 return mMmsSubject; 527 } 528 setMmsSubject(final String subject)529 public final void setMmsSubject(final String subject) { 530 mMmsSubject = subject; 531 } 532 getMmsContentLocation()533 public final String getMmsContentLocation() { 534 return mMmsContentLocation; 535 } 536 getMmsTransactionId()537 public final String getMmsTransactionId() { 538 return mMmsTransactionId; 539 } 540 getMessageSeen()541 public final boolean getMessageSeen() { 542 return mSeen; 543 } 544 getMmsExpiry()545 public final long getMmsExpiry() { 546 return mMmsExpiry; 547 } 548 549 /** 550 * For incoming MMS messages this returns the retrieve-status value 551 * For sent MMS messages this returns the response-status value 552 * See PduHeaders.java for possible values 553 * Otherwise (SMS etc) this is RAW_TELEPHONY_STATUS_UNDEFINED 554 */ getRawTelephonyStatus()555 public final int getRawTelephonyStatus() { 556 return mRawStatus; 557 } 558 setMessageSeen(final boolean hasSeen)559 public final void setMessageSeen(final boolean hasSeen) { 560 mSeen = hasSeen; 561 } 562 getInResendWindow(final long now)563 public final boolean getInResendWindow(final long now) { 564 final long maxAgeToResend = BugleGservices.get().getLong( 565 BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS, 566 BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS_DEFAULT); 567 final long age = now - mRetryStartTimestamp; 568 return age < maxAgeToResend; 569 } 570 getInDownloadWindow(final long now)571 public final boolean getInDownloadWindow(final long now) { 572 final long maxAgeToRedownload = BugleGservices.get().getLong( 573 BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS, 574 BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS_DEFAULT); 575 final long age = now - mRetryStartTimestamp; 576 return age < maxAgeToRedownload; 577 } 578 getShowDownloadMessage(final int status)579 static boolean getShowDownloadMessage(final int status) { 580 if (OsUtil.isSecondaryUser()) { 581 // Secondary users can't download mms's. Mms's are downloaded by bugle running as the 582 // primary user. 583 return false; 584 } 585 // Should show option for manual download if status is manual download or failed 586 return (status == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED || 587 status == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD || 588 // If debug is enabled, allow to download an expired or unavailable message. 589 (DebugUtils.isDebugEnabled() 590 && status == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE)); 591 } 592 canDownloadMessage()593 public boolean canDownloadMessage() { 594 if (OsUtil.isSecondaryUser()) { 595 // Secondary users can't download mms's. Mms's are downloaded by bugle running as the 596 // primary user. 597 return false; 598 } 599 // Can download if status is retrying auto/manual downloading 600 return (mStatus == BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD || 601 mStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD); 602 } 603 canRedownloadMessage()604 public boolean canRedownloadMessage() { 605 if (OsUtil.isSecondaryUser()) { 606 // Secondary users can't download mms's. Mms's are downloaded by bugle running as the 607 // primary user. 608 return false; 609 } 610 // Can redownload if status is manual download not started or download failed 611 return (mStatus == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED || 612 mStatus == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD || 613 // If debug is enabled, allow to download an expired or unavailable message. 614 (DebugUtils.isDebugEnabled() 615 && mStatus == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE)); 616 } 617 getShowResendMessage(final int status)618 static boolean getShowResendMessage(final int status) { 619 // Should show option to resend if status is failed 620 return (status == BUGLE_STATUS_OUTGOING_FAILED); 621 } 622 getOneClickResendMessage(final int status, final int rawStatus)623 static boolean getOneClickResendMessage(final int status, final int rawStatus) { 624 // Should show option to resend if status is failed 625 return (status == BUGLE_STATUS_OUTGOING_FAILED 626 && rawStatus == RAW_TELEPHONY_STATUS_UNDEFINED); 627 } 628 canResendMessage()629 public boolean canResendMessage() { 630 // Manual retry allowed only from failed 631 return (mStatus == BUGLE_STATUS_OUTGOING_FAILED); 632 } 633 canSendMessage()634 public boolean canSendMessage() { 635 // Sending messages must be in yet_to_send or awaiting_retry state 636 return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND || 637 mStatus == BUGLE_STATUS_OUTGOING_AWAITING_RETRY); 638 } 639 getYetToSend()640 public final boolean getYetToSend() { 641 return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND); 642 } 643 getIsMms()644 public final boolean getIsMms() { 645 return mProtocol == MessageData.PROTOCOL_MMS 646 || mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION; 647 } 648 getIsMmsNotification(final int protocol)649 public static final boolean getIsMmsNotification(final int protocol) { 650 return (protocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION); 651 } 652 getIsMmsNotification()653 public final boolean getIsMmsNotification() { 654 return getIsMmsNotification(mProtocol); 655 } 656 getIsSms(final int protocol)657 public static final boolean getIsSms(final int protocol) { 658 return protocol == (MessageData.PROTOCOL_SMS); 659 } 660 getIsSms()661 public final boolean getIsSms() { 662 return getIsSms(mProtocol); 663 } 664 getIsIncoming(final int status)665 public static boolean getIsIncoming(final int status) { 666 return (status >= MessageData.BUGLE_STATUS_FIRST_INCOMING); 667 } 668 getIsIncoming()669 public boolean getIsIncoming() { 670 return getIsIncoming(mStatus); 671 } 672 getRetryStartTimestamp()673 public long getRetryStartTimestamp() { 674 return mRetryStartTimestamp; 675 } 676 getMessageText()677 public final String getMessageText() { 678 final String separator = System.getProperty("line.separator"); 679 final StringBuilder text = new StringBuilder(); 680 for (final MessagePartData part : mParts) { 681 if (!part.isAttachment() && !TextUtils.isEmpty(part.getText())) { 682 if (text.length() > 0) { 683 text.append(separator); 684 } 685 text.append(part.getText()); 686 } 687 } 688 return text.toString(); 689 } 690 691 /** 692 * Takes all captions from attachments and adds them as a prefix to the first text part or 693 * appends a text part 694 */ consolidateText()695 public final void consolidateText() { 696 final String separator = System.getProperty("line.separator"); 697 final StringBuilder captionText = new StringBuilder(); 698 MessagePartData firstTextPart = null; 699 int firstTextPartIndex = -1; 700 for (int i = 0; i < mParts.size(); i++) { 701 final MessagePartData part = mParts.get(i); 702 if (firstTextPart == null && !part.isAttachment()) { 703 firstTextPart = part; 704 firstTextPartIndex = i; 705 } 706 if (part.isAttachment() && !TextUtils.isEmpty(part.getText())) { 707 if (captionText.length() > 0) { 708 captionText.append(separator); 709 } 710 captionText.append(part.getText()); 711 } 712 } 713 714 if (captionText.length() == 0) { 715 // Nothing to consolidate 716 return; 717 } 718 719 if (firstTextPart == null) { 720 addPart(MessagePartData.createTextMessagePart(captionText.toString())); 721 } else { 722 final String partText = firstTextPart.getText(); 723 if (partText.length() > 0) { 724 captionText.append(separator); 725 captionText.append(partText); 726 } 727 mParts.set(firstTextPartIndex, 728 MessagePartData.createTextMessagePart(captionText.toString())); 729 } 730 } 731 getFirstAttachment()732 public final MessagePartData getFirstAttachment() { 733 for (final MessagePartData part : mParts) { 734 if (part.isAttachment()) { 735 return part; 736 } 737 } 738 return null; 739 } 740 741 /** 742 * Updates the messageId for this message. 743 * Can be used to reset the messageId prior to persisting (which will assign a new messageId) 744 * or can be called on a message that does not yet have a valid messageId to set it. 745 */ updateMessageId(final String messageId)746 public void updateMessageId(final String messageId) { 747 Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId)); 748 mMessageId = messageId; 749 750 // TODO : This should probably also call updateMessageId on the message parts. We 751 // may also want to make messages effectively immutable once they have a valid message id. 752 } 753 updateSendingMessage(final String conversationId, final Uri messageUri, final long timestamp)754 public final void updateSendingMessage(final String conversationId, final Uri messageUri, 755 final long timestamp) { 756 mConversationId = conversationId; 757 mSmsMessageUri = messageUri; 758 mRead = true; 759 mSeen = true; 760 mReceivedTimestamp = timestamp; 761 mSentTimestamp = timestamp; 762 mStatus = BUGLE_STATUS_OUTGOING_YET_TO_SEND; 763 mRetryStartTimestamp = timestamp; 764 } 765 markMessageManualResend(final long timestamp)766 public final void markMessageManualResend(final long timestamp) { 767 // Manual send updates timestamp and transitions back to initial sending status. 768 mReceivedTimestamp = timestamp; 769 mSentTimestamp = timestamp; 770 mStatus = BUGLE_STATUS_OUTGOING_SENDING; 771 } 772 markMessageSending(final long timestamp)773 public final void markMessageSending(final long timestamp) { 774 // Initial send 775 mStatus = BUGLE_STATUS_OUTGOING_SENDING; 776 mSentTimestamp = timestamp; 777 } 778 markMessageResending(final long timestamp)779 public final void markMessageResending(final long timestamp) { 780 // Auto resend of message 781 mStatus = BUGLE_STATUS_OUTGOING_RESENDING; 782 mSentTimestamp = timestamp; 783 } 784 markMessageSent(final long timestamp)785 public final void markMessageSent(final long timestamp) { 786 mSentTimestamp = timestamp; 787 mStatus = BUGLE_STATUS_OUTGOING_COMPLETE; 788 } 789 markMessageFailed(final long timestamp)790 public final void markMessageFailed(final long timestamp) { 791 mSentTimestamp = timestamp; 792 mStatus = BUGLE_STATUS_OUTGOING_FAILED; 793 } 794 markMessageFailedEmergencyNumber(final long timestamp)795 public final void markMessageFailedEmergencyNumber(final long timestamp) { 796 mSentTimestamp = timestamp; 797 mStatus = BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER; 798 } 799 markMessageNotSent(final long timestamp)800 public final void markMessageNotSent(final long timestamp) { 801 mSentTimestamp = timestamp; 802 mStatus = BUGLE_STATUS_OUTGOING_AWAITING_RETRY; 803 } 804 updateSizesForImageParts()805 public final void updateSizesForImageParts() { 806 for (final MessagePartData part : getParts()) { 807 part.decodeAndSaveSizeIfImage(false /* saveToStorage */); 808 } 809 } 810 setRetryStartTimestamp(final long timestamp)811 public final void setRetryStartTimestamp(final long timestamp) { 812 mRetryStartTimestamp = timestamp; 813 } 814 setRawTelephonyStatus(final int rawStatus)815 public final void setRawTelephonyStatus(final int rawStatus) { 816 mRawStatus = rawStatus; 817 } 818 hasContent()819 public boolean hasContent() { 820 return !TextUtils.isEmpty(mMmsSubject) || 821 getFirstAttachment() != null || 822 !TextUtils.isEmpty(getMessageText()); 823 } 824 bindSelfId(final String selfId)825 public final void bindSelfId(final String selfId) { 826 mSelfId = selfId; 827 } 828 bindParticipantId(final String participantId)829 public final void bindParticipantId(final String participantId) { 830 mParticipantId = participantId; 831 } 832 MessageData(final Parcel in)833 protected MessageData(final Parcel in) { 834 mMessageId = in.readString(); 835 mConversationId = in.readString(); 836 mParticipantId = in.readString(); 837 mSelfId = in.readString(); 838 mSentTimestamp = in.readLong(); 839 mReceivedTimestamp = in.readLong(); 840 mSeen = (in.readInt() != 0); 841 mRead = (in.readInt() != 0); 842 mProtocol = in.readInt(); 843 mStatus = in.readInt(); 844 final String smsMessageUri = in.readString(); 845 mSmsMessageUri = (smsMessageUri == null ? null : Uri.parse(smsMessageUri)); 846 mSmsPriority = in.readInt(); 847 mSmsMessageSize = in.readLong(); 848 mMmsExpiry = in.readLong(); 849 mMmsSubject = in.readString(); 850 mMmsTransactionId = in.readString(); 851 mMmsContentLocation = in.readString(); 852 mRawStatus = in.readInt(); 853 mRetryStartTimestamp = in.readLong(); 854 855 // Read parts 856 mParts = new ArrayList<MessagePartData>(); 857 final int partCount = in.readInt(); 858 for (int i = 0; i < partCount; i++) { 859 mParts.add((MessagePartData) in.readParcelable(MessagePartData.class.getClassLoader())); 860 } 861 } 862 863 @Override describeContents()864 public int describeContents() { 865 return 0; 866 } 867 868 @Override writeToParcel(final Parcel dest, final int flags)869 public void writeToParcel(final Parcel dest, final int flags) { 870 dest.writeString(mMessageId); 871 dest.writeString(mConversationId); 872 dest.writeString(mParticipantId); 873 dest.writeString(mSelfId); 874 dest.writeLong(mSentTimestamp); 875 dest.writeLong(mReceivedTimestamp); 876 dest.writeInt(mRead ? 1 : 0); 877 dest.writeInt(mSeen ? 1 : 0); 878 dest.writeInt(mProtocol); 879 dest.writeInt(mStatus); 880 final String smsMessageUri = (mSmsMessageUri == null) ? null : mSmsMessageUri.toString(); 881 dest.writeString(smsMessageUri); 882 dest.writeInt(mSmsPriority); 883 dest.writeLong(mSmsMessageSize); 884 dest.writeLong(mMmsExpiry); 885 dest.writeString(mMmsSubject); 886 dest.writeString(mMmsTransactionId); 887 dest.writeString(mMmsContentLocation); 888 dest.writeInt(mRawStatus); 889 dest.writeLong(mRetryStartTimestamp); 890 891 // Write parts 892 dest.writeInt(mParts.size()); 893 for (final MessagePartData messagePartData : mParts) { 894 dest.writeParcelable(messagePartData, flags); 895 } 896 } 897 898 public static final Parcelable.Creator<MessageData> CREATOR 899 = new Parcelable.Creator<MessageData>() { 900 @Override 901 public MessageData createFromParcel(final Parcel in) { 902 return new MessageData(in); 903 } 904 905 @Override 906 public MessageData[] newArray(final int size) { 907 return new MessageData[size]; 908 } 909 }; 910 911 @Override toString()912 public String toString() { 913 return toString(mMessageId, mParts); 914 } 915 toString(String messageId, List<MessagePartData> parts)916 public static String toString(String messageId, List<MessagePartData> parts) { 917 StringBuilder sb = new StringBuilder(); 918 if (messageId != null) { 919 sb.append(messageId); 920 sb.append(": "); 921 } 922 for (MessagePartData part : parts) { 923 sb.append(part.toString()); 924 sb.append(" "); 925 } 926 return sb.toString(); 927 } 928 } 929