929 lines
36 KiB
Java
929 lines
36 KiB
Java
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.messaging.datamodel.data;
|
|
|
|
import android.content.ContentValues;
|
|
import android.database.Cursor;
|
|
import android.database.sqlite.SQLiteStatement;
|
|
import android.net.Uri;
|
|
import android.os.Parcel;
|
|
import android.os.Parcelable;
|
|
import android.text.TextUtils;
|
|
|
|
import com.android.messaging.datamodel.DatabaseHelper;
|
|
import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
|
|
import com.android.messaging.datamodel.DatabaseWrapper;
|
|
import com.android.messaging.sms.MmsUtils;
|
|
import com.android.messaging.util.Assert;
|
|
import com.android.messaging.util.BugleGservices;
|
|
import com.android.messaging.util.BugleGservicesKeys;
|
|
import com.android.messaging.util.Dates;
|
|
import com.android.messaging.util.DebugUtils;
|
|
import com.android.messaging.util.OsUtil;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
public class MessageData implements Parcelable {
|
|
private static final String[] sProjection = {
|
|
MessageColumns._ID,
|
|
MessageColumns.CONVERSATION_ID,
|
|
MessageColumns.SENDER_PARTICIPANT_ID,
|
|
MessageColumns.SELF_PARTICIPANT_ID,
|
|
MessageColumns.SENT_TIMESTAMP,
|
|
MessageColumns.RECEIVED_TIMESTAMP,
|
|
MessageColumns.SEEN,
|
|
MessageColumns.READ,
|
|
MessageColumns.PROTOCOL,
|
|
MessageColumns.STATUS,
|
|
MessageColumns.SMS_MESSAGE_URI,
|
|
MessageColumns.SMS_PRIORITY,
|
|
MessageColumns.SMS_MESSAGE_SIZE,
|
|
MessageColumns.MMS_SUBJECT,
|
|
MessageColumns.MMS_TRANSACTION_ID,
|
|
MessageColumns.MMS_CONTENT_LOCATION,
|
|
MessageColumns.MMS_EXPIRY,
|
|
MessageColumns.RAW_TELEPHONY_STATUS,
|
|
MessageColumns.RETRY_START_TIMESTAMP,
|
|
};
|
|
|
|
private static final int INDEX_ID = 0;
|
|
private static final int INDEX_CONVERSATION_ID = 1;
|
|
private static final int INDEX_PARTICIPANT_ID = 2;
|
|
private static final int INDEX_SELF_ID = 3;
|
|
private static final int INDEX_SENT_TIMESTAMP = 4;
|
|
private static final int INDEX_RECEIVED_TIMESTAMP = 5;
|
|
private static final int INDEX_SEEN = 6;
|
|
private static final int INDEX_READ = 7;
|
|
private static final int INDEX_PROTOCOL = 8;
|
|
private static final int INDEX_BUGLE_STATUS = 9;
|
|
private static final int INDEX_SMS_MESSAGE_URI = 10;
|
|
private static final int INDEX_SMS_PRIORITY = 11;
|
|
private static final int INDEX_SMS_MESSAGE_SIZE = 12;
|
|
private static final int INDEX_MMS_SUBJECT = 13;
|
|
private static final int INDEX_MMS_TRANSACTION_ID = 14;
|
|
private static final int INDEX_MMS_CONTENT_LOCATION = 15;
|
|
private static final int INDEX_MMS_EXPIRY = 16;
|
|
private static final int INDEX_RAW_TELEPHONY_STATUS = 17;
|
|
private static final int INDEX_RETRY_START_TIMESTAMP = 18;
|
|
|
|
// SQL statement to insert a "complete" message row (columns based on the projection above).
|
|
private static final String INSERT_MESSAGE_SQL =
|
|
"INSERT INTO " + DatabaseHelper.MESSAGES_TABLE + " ( "
|
|
+ TextUtils.join(", ", Arrays.copyOfRange(sProjection, 1,
|
|
INDEX_RETRY_START_TIMESTAMP + 1))
|
|
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
|
|
|
private String mMessageId;
|
|
private String mConversationId;
|
|
private String mParticipantId;
|
|
private String mSelfId;
|
|
private long mSentTimestamp;
|
|
private long mReceivedTimestamp;
|
|
private boolean mSeen;
|
|
private boolean mRead;
|
|
private int mProtocol;
|
|
private Uri mSmsMessageUri;
|
|
private int mSmsPriority;
|
|
private long mSmsMessageSize;
|
|
private String mMmsSubject;
|
|
private String mMmsTransactionId;
|
|
private String mMmsContentLocation;
|
|
private long mMmsExpiry;
|
|
private int mRawStatus;
|
|
private int mStatus;
|
|
private final ArrayList<MessagePartData> mParts;
|
|
private long mRetryStartTimestamp;
|
|
|
|
// PROTOCOL Values
|
|
public static final int PROTOCOL_UNKNOWN = -1; // Unknown type
|
|
public static final int PROTOCOL_SMS = 0; // SMS message
|
|
public static final int PROTOCOL_MMS = 1; // MMS message
|
|
public static final int PROTOCOL_MMS_PUSH_NOTIFICATION = 2; // MMS WAP push notification
|
|
|
|
// Bugle STATUS Values
|
|
public static final int BUGLE_STATUS_UNKNOWN = 0;
|
|
|
|
// Outgoing
|
|
public static final int BUGLE_STATUS_OUTGOING_COMPLETE = 1;
|
|
public static final int BUGLE_STATUS_OUTGOING_DELIVERED = 2;
|
|
// Transitions to either YET_TO_SEND or SEND_AFTER_PROCESSING depending attachments.
|
|
public static final int BUGLE_STATUS_OUTGOING_DRAFT = 3;
|
|
public static final int BUGLE_STATUS_OUTGOING_YET_TO_SEND = 4;
|
|
public static final int BUGLE_STATUS_OUTGOING_SENDING = 5;
|
|
public static final int BUGLE_STATUS_OUTGOING_RESENDING = 6;
|
|
public static final int BUGLE_STATUS_OUTGOING_AWAITING_RETRY = 7;
|
|
public static final int BUGLE_STATUS_OUTGOING_FAILED = 8;
|
|
public static final int BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER = 9;
|
|
|
|
// Incoming
|
|
public static final int BUGLE_STATUS_INCOMING_COMPLETE = 100;
|
|
public static final int BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD = 101;
|
|
public static final int BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD = 102;
|
|
public static final int BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING = 103;
|
|
public static final int BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD = 104;
|
|
public static final int BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING = 105;
|
|
public static final int BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED = 106;
|
|
public static final int BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE = 107;
|
|
|
|
public static final String getStatusDescription(int status) {
|
|
switch (status) {
|
|
case BUGLE_STATUS_UNKNOWN:
|
|
return "UNKNOWN";
|
|
case BUGLE_STATUS_OUTGOING_COMPLETE:
|
|
return "OUTGOING_COMPLETE";
|
|
case BUGLE_STATUS_OUTGOING_DELIVERED:
|
|
return "OUTGOING_DELIVERED";
|
|
case BUGLE_STATUS_OUTGOING_DRAFT:
|
|
return "OUTGOING_DRAFT";
|
|
case BUGLE_STATUS_OUTGOING_YET_TO_SEND:
|
|
return "OUTGOING_YET_TO_SEND";
|
|
case BUGLE_STATUS_OUTGOING_SENDING:
|
|
return "OUTGOING_SENDING";
|
|
case BUGLE_STATUS_OUTGOING_RESENDING:
|
|
return "OUTGOING_RESENDING";
|
|
case BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
|
|
return "OUTGOING_AWAITING_RETRY";
|
|
case BUGLE_STATUS_OUTGOING_FAILED:
|
|
return "OUTGOING_FAILED";
|
|
case BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
|
|
return "OUTGOING_FAILED_EMERGENCY_NUMBER";
|
|
case BUGLE_STATUS_INCOMING_COMPLETE:
|
|
return "INCOMING_COMPLETE";
|
|
case BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
|
|
return "INCOMING_YET_TO_MANUAL_DOWNLOAD";
|
|
case BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
|
|
return "INCOMING_RETRYING_MANUAL_DOWNLOAD";
|
|
case BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
|
|
return "INCOMING_MANUAL_DOWNLOADING";
|
|
case BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
|
|
return "INCOMING_RETRYING_AUTO_DOWNLOAD";
|
|
case BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
|
|
return "INCOMING_AUTO_DOWNLOADING";
|
|
case BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
|
|
return "INCOMING_DOWNLOAD_FAILED";
|
|
case BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
|
|
return "INCOMING_EXPIRED_OR_NOT_AVAILABLE";
|
|
default:
|
|
return String.valueOf(status) + " (check MessageData)";
|
|
}
|
|
}
|
|
|
|
// All incoming messages expect to have status >= BUGLE_STATUS_FIRST_INCOMING
|
|
public static final int BUGLE_STATUS_FIRST_INCOMING = BUGLE_STATUS_INCOMING_COMPLETE;
|
|
|
|
// Detailed MMS failures. Most of the values are defined in PduHeaders. However, a few are
|
|
// defined here instead. These are never returned in the MMS HTTP response, but are used
|
|
// internally. The values here must not conflict with any of the existing PduHeader values.
|
|
public static final int RAW_TELEPHONY_STATUS_UNDEFINED = MmsUtils.PDU_HEADER_VALUE_UNDEFINED;
|
|
public static final int RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG = 10000;
|
|
|
|
// Unknown result code for MMS sending/downloading. This is used as the default value
|
|
// for result code returned from platform MMS API.
|
|
public static final int UNKNOWN_RESULT_CODE = 0;
|
|
|
|
/**
|
|
* Create an "empty" message
|
|
*/
|
|
public MessageData() {
|
|
mParts = new ArrayList<MessagePartData>();
|
|
}
|
|
|
|
public static String[] getProjection() {
|
|
return sProjection;
|
|
}
|
|
|
|
/**
|
|
* Create a draft message for a particular conversation based on supplied content
|
|
*/
|
|
public static MessageData createDraftMessage(final String conversationId,
|
|
final String selfId, final MessageData content) {
|
|
final MessageData message = new MessageData();
|
|
message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
|
|
message.mProtocol = PROTOCOL_UNKNOWN;
|
|
message.mConversationId = conversationId;
|
|
message.mParticipantId = selfId;
|
|
message.mReceivedTimestamp = System.currentTimeMillis();
|
|
if (content == null) {
|
|
message.mParts.add(MessagePartData.createTextMessagePart(""));
|
|
} else {
|
|
if (!TextUtils.isEmpty(content.mParticipantId)) {
|
|
message.mParticipantId = content.mParticipantId;
|
|
}
|
|
if (!TextUtils.isEmpty(content.mMmsSubject)) {
|
|
message.mMmsSubject = content.mMmsSubject;
|
|
}
|
|
for (final MessagePartData part : content.getParts()) {
|
|
message.mParts.add(part);
|
|
}
|
|
}
|
|
message.mSelfId = selfId;
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Create a draft sms message for a particular conversation
|
|
*/
|
|
public static MessageData createDraftSmsMessage(final String conversationId,
|
|
final String selfId, final String messageText) {
|
|
final MessageData message = new MessageData();
|
|
message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
|
|
message.mProtocol = PROTOCOL_SMS;
|
|
message.mConversationId = conversationId;
|
|
message.mParticipantId = selfId;
|
|
message.mSelfId = selfId;
|
|
message.mParts.add(MessagePartData.createTextMessagePart(messageText));
|
|
message.mReceivedTimestamp = System.currentTimeMillis();
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Create a draft mms message for a particular conversation
|
|
*/
|
|
public static MessageData createDraftMmsMessage(final String conversationId,
|
|
final String selfId, final String messageText, final String subjectText) {
|
|
final MessageData message = new MessageData();
|
|
message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
|
|
message.mProtocol = PROTOCOL_MMS;
|
|
message.mConversationId = conversationId;
|
|
message.mParticipantId = selfId;
|
|
message.mSelfId = selfId;
|
|
message.mMmsSubject = subjectText;
|
|
message.mReceivedTimestamp = System.currentTimeMillis();
|
|
if (!TextUtils.isEmpty(messageText)) {
|
|
message.mParts.add(MessagePartData.createTextMessagePart(messageText));
|
|
}
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Create a message received from a particular number in a particular conversation
|
|
*/
|
|
public static MessageData 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) {
|
|
final MessageData message = new MessageData();
|
|
message.mSmsMessageUri = uri;
|
|
message.mConversationId = conversationId;
|
|
message.mParticipantId = participantId;
|
|
message.mSelfId = selfId;
|
|
message.mProtocol = PROTOCOL_SMS;
|
|
message.mStatus = BUGLE_STATUS_INCOMING_COMPLETE;
|
|
message.mMmsSubject = subject;
|
|
message.mReceivedTimestamp = recieved;
|
|
message.mSentTimestamp = sent;
|
|
message.mParts.add(MessagePartData.createTextMessagePart(messageText));
|
|
message.mSeen = seen;
|
|
message.mRead = read;
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Create a message not yet associated with a particular conversation
|
|
*/
|
|
public static MessageData createSharedMessage(final String messageText,
|
|
final String subjectText) {
|
|
final MessageData message = new MessageData();
|
|
message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
|
|
message.mMmsSubject = subjectText;
|
|
if (!TextUtils.isEmpty(messageText)) {
|
|
message.mParts.add(MessagePartData.createTextMessagePart(messageText));
|
|
}
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Create a message from Sms table fields
|
|
*/
|
|
public static MessageData 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) {
|
|
final MessageData message = new MessageData();
|
|
message.mParticipantId = participantId;
|
|
message.mSelfId = selfId;
|
|
message.mConversationId = conversationId;
|
|
message.mSentTimestamp = sent;
|
|
message.mReceivedTimestamp = recieved;
|
|
message.mSeen = seen;
|
|
message.mRead = read;
|
|
message.mProtocol = PROTOCOL_SMS;
|
|
message.mStatus = bugleStatus;
|
|
message.mSmsMessageUri = Uri.parse(messageUri);
|
|
message.mParts.add(MessagePartData.createTextMessagePart(messageText));
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Create a message from Mms table fields
|
|
*/
|
|
public static MessageData 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) {
|
|
final MessageData message = new MessageData();
|
|
message.mParticipantId = participantId;
|
|
message.mSelfId = selfId;
|
|
message.mConversationId = conversationId;
|
|
message.mSentTimestamp = sent;
|
|
message.mReceivedTimestamp = received;
|
|
message.mMmsContentLocation = contentLocation;
|
|
message.mMmsTransactionId = transactionId;
|
|
message.mSeen = seen;
|
|
message.mRead = read;
|
|
message.mStatus = bugleStatus;
|
|
message.mProtocol = (isNotification ? PROTOCOL_MMS_PUSH_NOTIFICATION : PROTOCOL_MMS);
|
|
message.mSmsMessageUri = Uri.parse(messageUri);
|
|
message.mSmsPriority = smsPriority;
|
|
message.mSmsMessageSize = size;
|
|
message.mMmsSubject = subject;
|
|
message.mMmsExpiry = expiry;
|
|
message.mRawStatus = rawStatus;
|
|
if (bugleStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD ||
|
|
bugleStatus == BUGLE_STATUS_OUTGOING_RESENDING) {
|
|
// Set the retry start timestamp if this message is already in process of retrying
|
|
// Either as autodownload is starting or sending already in progress (MMS update)
|
|
message.mRetryStartTimestamp = received;
|
|
}
|
|
return message;
|
|
}
|
|
|
|
public void addPart(final MessagePartData part) {
|
|
if (part instanceof PendingAttachmentData) {
|
|
// Pending attachments may only be added to shared message data that's not associated
|
|
// with any particular conversation, in order to store shared images.
|
|
Assert.isTrue(mConversationId == null);
|
|
}
|
|
mParts.add(part);
|
|
}
|
|
|
|
public Iterable<MessagePartData> getParts() {
|
|
return mParts;
|
|
}
|
|
|
|
public void bind(final Cursor cursor) {
|
|
mMessageId = cursor.getString(INDEX_ID);
|
|
mConversationId = cursor.getString(INDEX_CONVERSATION_ID);
|
|
mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
|
|
mSelfId = cursor.getString(INDEX_SELF_ID);
|
|
mSentTimestamp = cursor.getLong(INDEX_SENT_TIMESTAMP);
|
|
mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
|
|
mSeen = (cursor.getInt(INDEX_SEEN) != 0);
|
|
mRead = (cursor.getInt(INDEX_READ) != 0);
|
|
mProtocol = cursor.getInt(INDEX_PROTOCOL);
|
|
mStatus = cursor.getInt(INDEX_BUGLE_STATUS);
|
|
final String smsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI);
|
|
mSmsMessageUri = (smsMessageUri == null) ? null : Uri.parse(smsMessageUri);
|
|
mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY);
|
|
mSmsMessageSize = cursor.getLong(INDEX_SMS_MESSAGE_SIZE);
|
|
mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY);
|
|
mRawStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS);
|
|
mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT);
|
|
mMmsTransactionId = cursor.getString(INDEX_MMS_TRANSACTION_ID);
|
|
mMmsContentLocation = cursor.getString(INDEX_MMS_CONTENT_LOCATION);
|
|
mRetryStartTimestamp = cursor.getLong(INDEX_RETRY_START_TIMESTAMP);
|
|
}
|
|
|
|
/**
|
|
* Bind to the draft message data for a conversation. The conversation's self id is used as
|
|
* the draft's self id.
|
|
*/
|
|
public void bindDraft(final Cursor cursor, final String conversationSelfId) {
|
|
bind(cursor);
|
|
mSelfId = conversationSelfId;
|
|
}
|
|
|
|
protected static String getParticipantId(final Cursor cursor) {
|
|
return cursor.getString(INDEX_PARTICIPANT_ID);
|
|
}
|
|
|
|
public void populate(final ContentValues values) {
|
|
values.put(MessageColumns.CONVERSATION_ID, mConversationId);
|
|
values.put(MessageColumns.SENDER_PARTICIPANT_ID, mParticipantId);
|
|
values.put(MessageColumns.SELF_PARTICIPANT_ID, mSelfId);
|
|
values.put(MessageColumns.SENT_TIMESTAMP, mSentTimestamp);
|
|
values.put(MessageColumns.RECEIVED_TIMESTAMP, mReceivedTimestamp);
|
|
values.put(MessageColumns.SEEN, mSeen ? 1 : 0);
|
|
values.put(MessageColumns.READ, mRead ? 1 : 0);
|
|
values.put(MessageColumns.PROTOCOL, mProtocol);
|
|
values.put(MessageColumns.STATUS, mStatus);
|
|
final String smsMessageUri = ((mSmsMessageUri == null) ? null : mSmsMessageUri.toString());
|
|
values.put(MessageColumns.SMS_MESSAGE_URI, smsMessageUri);
|
|
values.put(MessageColumns.SMS_PRIORITY, mSmsPriority);
|
|
values.put(MessageColumns.SMS_MESSAGE_SIZE, mSmsMessageSize);
|
|
values.put(MessageColumns.MMS_EXPIRY, mMmsExpiry);
|
|
values.put(MessageColumns.MMS_SUBJECT, mMmsSubject);
|
|
values.put(MessageColumns.MMS_TRANSACTION_ID, mMmsTransactionId);
|
|
values.put(MessageColumns.MMS_CONTENT_LOCATION, mMmsContentLocation);
|
|
values.put(MessageColumns.RAW_TELEPHONY_STATUS, mRawStatus);
|
|
values.put(MessageColumns.RETRY_START_TIMESTAMP, mRetryStartTimestamp);
|
|
}
|
|
|
|
/**
|
|
* Note this is not thread safe so callers need to make sure they own the wrapper + statements
|
|
* while they call this and use the returned value.
|
|
*/
|
|
public SQLiteStatement getInsertStatement(final DatabaseWrapper db) {
|
|
final SQLiteStatement insert = db.getStatementInTransaction(
|
|
DatabaseWrapper.INDEX_INSERT_MESSAGE, INSERT_MESSAGE_SQL);
|
|
insert.clearBindings();
|
|
insert.bindString(INDEX_CONVERSATION_ID, mConversationId);
|
|
insert.bindString(INDEX_PARTICIPANT_ID, mParticipantId);
|
|
insert.bindString(INDEX_SELF_ID, mSelfId);
|
|
insert.bindLong(INDEX_SENT_TIMESTAMP, mSentTimestamp);
|
|
insert.bindLong(INDEX_RECEIVED_TIMESTAMP, mReceivedTimestamp);
|
|
insert.bindLong(INDEX_SEEN, mSeen ? 1 : 0);
|
|
insert.bindLong(INDEX_READ, mRead ? 1 : 0);
|
|
insert.bindLong(INDEX_PROTOCOL, mProtocol);
|
|
insert.bindLong(INDEX_BUGLE_STATUS, mStatus);
|
|
if (mSmsMessageUri != null) {
|
|
insert.bindString(INDEX_SMS_MESSAGE_URI, mSmsMessageUri.toString());
|
|
}
|
|
insert.bindLong(INDEX_SMS_PRIORITY, mSmsPriority);
|
|
insert.bindLong(INDEX_SMS_MESSAGE_SIZE, mSmsMessageSize);
|
|
insert.bindLong(INDEX_MMS_EXPIRY, mMmsExpiry);
|
|
if (mMmsSubject != null) {
|
|
insert.bindString(INDEX_MMS_SUBJECT, mMmsSubject);
|
|
}
|
|
if (mMmsTransactionId != null) {
|
|
insert.bindString(INDEX_MMS_TRANSACTION_ID, mMmsTransactionId);
|
|
}
|
|
if (mMmsContentLocation != null) {
|
|
insert.bindString(INDEX_MMS_CONTENT_LOCATION, mMmsContentLocation);
|
|
}
|
|
insert.bindLong(INDEX_RAW_TELEPHONY_STATUS, mRawStatus);
|
|
insert.bindLong(INDEX_RETRY_START_TIMESTAMP, mRetryStartTimestamp);
|
|
return insert;
|
|
}
|
|
|
|
public final String getMessageId() {
|
|
return mMessageId;
|
|
}
|
|
|
|
public final String getConversationId() {
|
|
return mConversationId;
|
|
}
|
|
|
|
public final String getParticipantId() {
|
|
return mParticipantId;
|
|
}
|
|
|
|
public final String getSelfId() {
|
|
return mSelfId;
|
|
}
|
|
|
|
public final long getSentTimeStamp() {
|
|
return mSentTimestamp;
|
|
}
|
|
|
|
public final long getReceivedTimeStamp() {
|
|
return mReceivedTimestamp;
|
|
}
|
|
|
|
public final String getFormattedReceivedTimeStamp() {
|
|
return Dates.getMessageTimeString(mReceivedTimestamp).toString();
|
|
}
|
|
|
|
public final int getProtocol() {
|
|
return mProtocol;
|
|
}
|
|
|
|
public final int getStatus() {
|
|
return mStatus;
|
|
}
|
|
|
|
public final Uri getSmsMessageUri() {
|
|
return mSmsMessageUri;
|
|
}
|
|
|
|
public final int getSmsPriority() {
|
|
return mSmsPriority;
|
|
}
|
|
|
|
public final long getSmsMessageSize() {
|
|
return mSmsMessageSize;
|
|
}
|
|
|
|
public final String getMmsSubject() {
|
|
return mMmsSubject;
|
|
}
|
|
|
|
public final void setMmsSubject(final String subject) {
|
|
mMmsSubject = subject;
|
|
}
|
|
|
|
public final String getMmsContentLocation() {
|
|
return mMmsContentLocation;
|
|
}
|
|
|
|
public final String getMmsTransactionId() {
|
|
return mMmsTransactionId;
|
|
}
|
|
|
|
public final boolean getMessageSeen() {
|
|
return mSeen;
|
|
}
|
|
|
|
public final long getMmsExpiry() {
|
|
return mMmsExpiry;
|
|
}
|
|
|
|
/**
|
|
* For incoming MMS messages this returns the retrieve-status value
|
|
* For sent MMS messages this returns the response-status value
|
|
* See PduHeaders.java for possible values
|
|
* Otherwise (SMS etc) this is RAW_TELEPHONY_STATUS_UNDEFINED
|
|
*/
|
|
public final int getRawTelephonyStatus() {
|
|
return mRawStatus;
|
|
}
|
|
|
|
public final void setMessageSeen(final boolean hasSeen) {
|
|
mSeen = hasSeen;
|
|
}
|
|
|
|
public final boolean getInResendWindow(final long now) {
|
|
final long maxAgeToResend = BugleGservices.get().getLong(
|
|
BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS,
|
|
BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS_DEFAULT);
|
|
final long age = now - mRetryStartTimestamp;
|
|
return age < maxAgeToResend;
|
|
}
|
|
|
|
public final boolean getInDownloadWindow(final long now) {
|
|
final long maxAgeToRedownload = BugleGservices.get().getLong(
|
|
BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS,
|
|
BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS_DEFAULT);
|
|
final long age = now - mRetryStartTimestamp;
|
|
return age < maxAgeToRedownload;
|
|
}
|
|
|
|
static boolean getShowDownloadMessage(final int status) {
|
|
if (OsUtil.isSecondaryUser()) {
|
|
// Secondary users can't download mms's. Mms's are downloaded by bugle running as the
|
|
// primary user.
|
|
return false;
|
|
}
|
|
// Should show option for manual download iff status is manual download or failed
|
|
return (status == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
|
|
status == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD ||
|
|
// If debug is enabled, allow to download an expired or unavailable message.
|
|
(DebugUtils.isDebugEnabled()
|
|
&& status == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE));
|
|
}
|
|
|
|
public boolean canDownloadMessage() {
|
|
if (OsUtil.isSecondaryUser()) {
|
|
// Secondary users can't download mms's. Mms's are downloaded by bugle running as the
|
|
// primary user.
|
|
return false;
|
|
}
|
|
// Can download iff status is retrying auto/manual downloading
|
|
return (mStatus == BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD ||
|
|
mStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD);
|
|
}
|
|
|
|
public boolean canRedownloadMessage() {
|
|
if (OsUtil.isSecondaryUser()) {
|
|
// Secondary users can't download mms's. Mms's are downloaded by bugle running as the
|
|
// primary user.
|
|
return false;
|
|
}
|
|
// Can redownload iff status is manual download not started or download failed
|
|
return (mStatus == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
|
|
mStatus == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD ||
|
|
// If debug is enabled, allow to download an expired or unavailable message.
|
|
(DebugUtils.isDebugEnabled()
|
|
&& mStatus == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE));
|
|
}
|
|
|
|
static boolean getShowResendMessage(final int status) {
|
|
// Should show option to resend iff status is failed
|
|
return (status == BUGLE_STATUS_OUTGOING_FAILED);
|
|
}
|
|
|
|
static boolean getOneClickResendMessage(final int status, final int rawStatus) {
|
|
// Should show option to resend iff status is failed
|
|
return (status == BUGLE_STATUS_OUTGOING_FAILED
|
|
&& rawStatus == RAW_TELEPHONY_STATUS_UNDEFINED);
|
|
}
|
|
|
|
public boolean canResendMessage() {
|
|
// Manual retry allowed only from failed
|
|
return (mStatus == BUGLE_STATUS_OUTGOING_FAILED);
|
|
}
|
|
|
|
public boolean canSendMessage() {
|
|
// Sending messages must be in yet_to_send or awaiting_retry state
|
|
return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND ||
|
|
mStatus == BUGLE_STATUS_OUTGOING_AWAITING_RETRY);
|
|
}
|
|
|
|
public final boolean getYetToSend() {
|
|
return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND);
|
|
}
|
|
|
|
public final boolean getIsMms() {
|
|
return mProtocol == MessageData.PROTOCOL_MMS
|
|
|| mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION;
|
|
}
|
|
|
|
public static final boolean getIsMmsNotification(final int protocol) {
|
|
return (protocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
|
|
}
|
|
|
|
public final boolean getIsMmsNotification() {
|
|
return getIsMmsNotification(mProtocol);
|
|
}
|
|
|
|
public static final boolean getIsSms(final int protocol) {
|
|
return protocol == (MessageData.PROTOCOL_SMS);
|
|
}
|
|
|
|
public final boolean getIsSms() {
|
|
return getIsSms(mProtocol);
|
|
}
|
|
|
|
public static boolean getIsIncoming(final int status) {
|
|
return (status >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
|
|
}
|
|
|
|
public boolean getIsIncoming() {
|
|
return getIsIncoming(mStatus);
|
|
}
|
|
|
|
public long getRetryStartTimestamp() {
|
|
return mRetryStartTimestamp;
|
|
}
|
|
|
|
public final String getMessageText() {
|
|
final String separator = System.getProperty("line.separator");
|
|
final StringBuilder text = new StringBuilder();
|
|
for (final MessagePartData part : mParts) {
|
|
if (!part.isAttachment() && !TextUtils.isEmpty(part.getText())) {
|
|
if (text.length() > 0) {
|
|
text.append(separator);
|
|
}
|
|
text.append(part.getText());
|
|
}
|
|
}
|
|
return text.toString();
|
|
}
|
|
|
|
/**
|
|
* Takes all captions from attachments and adds them as a prefix to the first text part or
|
|
* appends a text part
|
|
*/
|
|
public final void consolidateText() {
|
|
final String separator = System.getProperty("line.separator");
|
|
final StringBuilder captionText = new StringBuilder();
|
|
MessagePartData firstTextPart = null;
|
|
int firstTextPartIndex = -1;
|
|
for (int i = 0; i < mParts.size(); i++) {
|
|
final MessagePartData part = mParts.get(i);
|
|
if (firstTextPart == null && !part.isAttachment()) {
|
|
firstTextPart = part;
|
|
firstTextPartIndex = i;
|
|
}
|
|
if (part.isAttachment() && !TextUtils.isEmpty(part.getText())) {
|
|
if (captionText.length() > 0) {
|
|
captionText.append(separator);
|
|
}
|
|
captionText.append(part.getText());
|
|
}
|
|
}
|
|
|
|
if (captionText.length() == 0) {
|
|
// Nothing to consolidate
|
|
return;
|
|
}
|
|
|
|
if (firstTextPart == null) {
|
|
addPart(MessagePartData.createTextMessagePart(captionText.toString()));
|
|
} else {
|
|
final String partText = firstTextPart.getText();
|
|
if (partText.length() > 0) {
|
|
captionText.append(separator);
|
|
captionText.append(partText);
|
|
}
|
|
mParts.set(firstTextPartIndex,
|
|
MessagePartData.createTextMessagePart(captionText.toString()));
|
|
}
|
|
}
|
|
|
|
public final MessagePartData getFirstAttachment() {
|
|
for (final MessagePartData part : mParts) {
|
|
if (part.isAttachment()) {
|
|
return part;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Updates the messageId for this message.
|
|
* Can be used to reset the messageId prior to persisting (which will assign a new messageId)
|
|
* or can be called on a message that does not yet have a valid messageId to set it.
|
|
*/
|
|
public void updateMessageId(final String messageId) {
|
|
Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId));
|
|
mMessageId = messageId;
|
|
|
|
// TODO : This should probably also call updateMessageId on the message parts. We
|
|
// may also want to make messages effectively immutable once they have a valid message id.
|
|
}
|
|
|
|
public final void updateSendingMessage(final String conversationId, final Uri messageUri,
|
|
final long timestamp) {
|
|
mConversationId = conversationId;
|
|
mSmsMessageUri = messageUri;
|
|
mRead = true;
|
|
mSeen = true;
|
|
mReceivedTimestamp = timestamp;
|
|
mSentTimestamp = timestamp;
|
|
mStatus = BUGLE_STATUS_OUTGOING_YET_TO_SEND;
|
|
mRetryStartTimestamp = timestamp;
|
|
}
|
|
|
|
public final void markMessageManualResend(final long timestamp) {
|
|
// Manual send updates timestamp and transitions back to initial sending status.
|
|
mReceivedTimestamp = timestamp;
|
|
mSentTimestamp = timestamp;
|
|
mStatus = BUGLE_STATUS_OUTGOING_SENDING;
|
|
}
|
|
|
|
public final void markMessageSending(final long timestamp) {
|
|
// Initial send
|
|
mStatus = BUGLE_STATUS_OUTGOING_SENDING;
|
|
mSentTimestamp = timestamp;
|
|
}
|
|
|
|
public final void markMessageResending(final long timestamp) {
|
|
// Auto resend of message
|
|
mStatus = BUGLE_STATUS_OUTGOING_RESENDING;
|
|
mSentTimestamp = timestamp;
|
|
}
|
|
|
|
public final void markMessageSent(final long timestamp) {
|
|
mSentTimestamp = timestamp;
|
|
mStatus = BUGLE_STATUS_OUTGOING_COMPLETE;
|
|
}
|
|
|
|
public final void markMessageFailed(final long timestamp) {
|
|
mSentTimestamp = timestamp;
|
|
mStatus = BUGLE_STATUS_OUTGOING_FAILED;
|
|
}
|
|
|
|
public final void markMessageFailedEmergencyNumber(final long timestamp) {
|
|
mSentTimestamp = timestamp;
|
|
mStatus = BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER;
|
|
}
|
|
|
|
public final void markMessageNotSent(final long timestamp) {
|
|
mSentTimestamp = timestamp;
|
|
mStatus = BUGLE_STATUS_OUTGOING_AWAITING_RETRY;
|
|
}
|
|
|
|
public final void updateSizesForImageParts() {
|
|
for (final MessagePartData part : getParts()) {
|
|
part.decodeAndSaveSizeIfImage(false /* saveToStorage */);
|
|
}
|
|
}
|
|
|
|
public final void setRetryStartTimestamp(final long timestamp) {
|
|
mRetryStartTimestamp = timestamp;
|
|
}
|
|
|
|
public final void setRawTelephonyStatus(final int rawStatus) {
|
|
mRawStatus = rawStatus;
|
|
}
|
|
|
|
public boolean hasContent() {
|
|
return !TextUtils.isEmpty(mMmsSubject) ||
|
|
getFirstAttachment() != null ||
|
|
!TextUtils.isEmpty(getMessageText());
|
|
}
|
|
|
|
public final void bindSelfId(final String selfId) {
|
|
mSelfId = selfId;
|
|
}
|
|
|
|
public final void bindParticipantId(final String participantId) {
|
|
mParticipantId = participantId;
|
|
}
|
|
|
|
protected MessageData(final Parcel in) {
|
|
mMessageId = in.readString();
|
|
mConversationId = in.readString();
|
|
mParticipantId = in.readString();
|
|
mSelfId = in.readString();
|
|
mSentTimestamp = in.readLong();
|
|
mReceivedTimestamp = in.readLong();
|
|
mSeen = (in.readInt() != 0);
|
|
mRead = (in.readInt() != 0);
|
|
mProtocol = in.readInt();
|
|
mStatus = in.readInt();
|
|
final String smsMessageUri = in.readString();
|
|
mSmsMessageUri = (smsMessageUri == null ? null : Uri.parse(smsMessageUri));
|
|
mSmsPriority = in.readInt();
|
|
mSmsMessageSize = in.readLong();
|
|
mMmsExpiry = in.readLong();
|
|
mMmsSubject = in.readString();
|
|
mMmsTransactionId = in.readString();
|
|
mMmsContentLocation = in.readString();
|
|
mRawStatus = in.readInt();
|
|
mRetryStartTimestamp = in.readLong();
|
|
|
|
// Read parts
|
|
mParts = new ArrayList<MessagePartData>();
|
|
final int partCount = in.readInt();
|
|
for (int i = 0; i < partCount; i++) {
|
|
mParts.add((MessagePartData) in.readParcelable(MessagePartData.class.getClassLoader()));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int describeContents() {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public void writeToParcel(final Parcel dest, final int flags) {
|
|
dest.writeString(mMessageId);
|
|
dest.writeString(mConversationId);
|
|
dest.writeString(mParticipantId);
|
|
dest.writeString(mSelfId);
|
|
dest.writeLong(mSentTimestamp);
|
|
dest.writeLong(mReceivedTimestamp);
|
|
dest.writeInt(mRead ? 1 : 0);
|
|
dest.writeInt(mSeen ? 1 : 0);
|
|
dest.writeInt(mProtocol);
|
|
dest.writeInt(mStatus);
|
|
final String smsMessageUri = (mSmsMessageUri == null) ? null : mSmsMessageUri.toString();
|
|
dest.writeString(smsMessageUri);
|
|
dest.writeInt(mSmsPriority);
|
|
dest.writeLong(mSmsMessageSize);
|
|
dest.writeLong(mMmsExpiry);
|
|
dest.writeString(mMmsSubject);
|
|
dest.writeString(mMmsTransactionId);
|
|
dest.writeString(mMmsContentLocation);
|
|
dest.writeInt(mRawStatus);
|
|
dest.writeLong(mRetryStartTimestamp);
|
|
|
|
// Write parts
|
|
dest.writeInt(mParts.size());
|
|
for (final MessagePartData messagePartData : mParts) {
|
|
dest.writeParcelable(messagePartData, flags);
|
|
}
|
|
}
|
|
|
|
public static final Parcelable.Creator<MessageData> CREATOR
|
|
= new Parcelable.Creator<MessageData>() {
|
|
@Override
|
|
public MessageData createFromParcel(final Parcel in) {
|
|
return new MessageData(in);
|
|
}
|
|
|
|
@Override
|
|
public MessageData[] newArray(final int size) {
|
|
return new MessageData[size];
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public String toString() {
|
|
return toString(mMessageId, mParts);
|
|
}
|
|
|
|
public static String toString(String messageId, List<MessagePartData> parts) {
|
|
StringBuilder sb = new StringBuilder();
|
|
if (messageId != null) {
|
|
sb.append(messageId);
|
|
sb.append(": ");
|
|
}
|
|
for (MessagePartData part : parts) {
|
|
sb.append(part.toString());
|
|
sb.append(" ");
|
|
}
|
|
return sb.toString();
|
|
}
|
|
}
|