blob: 2a852775d832817df6ed803b9c60f64e33df29d4 [file] [log] [blame]
/*
* 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.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.text.format.DateUtils;
import com.android.messaging.datamodel.DatabaseHelper;
import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
import com.android.messaging.util.Assert;
import com.android.messaging.util.BugleGservices;
import com.android.messaging.util.BugleGservicesKeys;
import com.android.messaging.util.ContentType;
import com.android.messaging.util.Dates;
import com.android.messaging.util.LogUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* Class representing a message within a conversation sequence. The message parts
* are available via the getParts() method.
*
* TODO: See if we can delegate to MessageData for the logic that this class duplicates
* (e.g. getIsMms).
*/
public class ConversationMessageData {
private static final String TAG = LogUtil.BUGLE_TAG;
private String mMessageId;
private String mConversationId;
private String mParticipantId;
private int mPartsCount;
private List<MessagePartData> mParts;
private long mSentTimestamp;
private long mReceivedTimestamp;
private boolean mSeen;
private boolean mRead;
private int mProtocol;
private int mStatus;
private String mSmsMessageUri;
private int mSmsPriority;
private int mSmsMessageSize;
private String mMmsSubject;
private long mMmsExpiry;
private int mRawTelephonyStatus;
private String mSenderFullName;
private String mSenderFirstName;
private String mSenderDisplayDestination;
private String mSenderNormalizedDestination;
private String mSenderProfilePhotoUri;
private long mSenderContactId;
private String mSenderContactLookupKey;
private String mSelfParticipantId;
/** Are we similar enough to the previous/next messages that we can cluster them? */
private boolean mCanClusterWithPreviousMessage;
private boolean mCanClusterWithNextMessage;
public ConversationMessageData() {
}
public void bind(final Cursor cursor) {
mMessageId = cursor.getString(INDEX_MESSAGE_ID);
mConversationId = cursor.getString(INDEX_CONVERSATION_ID);
mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
mPartsCount = cursor.getInt(INDEX_PARTS_COUNT);
mParts = makeParts(
cursor.getString(INDEX_PARTS_IDS),
cursor.getString(INDEX_PARTS_CONTENT_TYPES),
cursor.getString(INDEX_PARTS_CONTENT_URIS),
cursor.getString(INDEX_PARTS_WIDTHS),
cursor.getString(INDEX_PARTS_HEIGHTS),
cursor.getString(INDEX_PARTS_TEXTS),
mPartsCount,
mMessageId);
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_STATUS);
mSmsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI);
mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY);
mSmsMessageSize = cursor.getInt(INDEX_SMS_MESSAGE_SIZE);
mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT);
mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY);
mRawTelephonyStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS);
mSenderFullName = cursor.getString(INDEX_SENDER_FULL_NAME);
mSenderFirstName = cursor.getString(INDEX_SENDER_FIRST_NAME);
mSenderDisplayDestination = cursor.getString(INDEX_SENDER_DISPLAY_DESTINATION);
mSenderNormalizedDestination = cursor.getString(INDEX_SENDER_NORMALIZED_DESTINATION);
mSenderProfilePhotoUri = cursor.getString(INDEX_SENDER_PROFILE_PHOTO_URI);
mSenderContactId = cursor.getLong(INDEX_SENDER_CONTACT_ID);
mSenderContactLookupKey = cursor.getString(INDEX_SENDER_CONTACT_LOOKUP_KEY);
mSelfParticipantId = cursor.getString(INDEX_SELF_PARTICIPIANT_ID);
if (!cursor.isFirst() && cursor.moveToPrevious()) {
mCanClusterWithPreviousMessage = canClusterWithMessage(cursor);
cursor.moveToNext();
} else {
mCanClusterWithPreviousMessage = false;
}
if (!cursor.isLast() && cursor.moveToNext()) {
mCanClusterWithNextMessage = canClusterWithMessage(cursor);
cursor.moveToPrevious();
} else {
mCanClusterWithNextMessage = false;
}
}
private boolean canClusterWithMessage(final Cursor cursor) {
final String otherParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
if (!TextUtils.equals(getParticipantId(), otherParticipantId)) {
return false;
}
final int otherStatus = cursor.getInt(INDEX_STATUS);
final boolean otherIsIncoming = (otherStatus >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
if (getIsIncoming() != otherIsIncoming) {
return false;
}
final long otherReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
final long timestampDeltaMillis = Math.abs(mReceivedTimestamp - otherReceivedTimestamp);
if (timestampDeltaMillis > DateUtils.MINUTE_IN_MILLIS) {
return false;
}
final String otherSelfId = cursor.getString(INDEX_SELF_PARTICIPIANT_ID);
if (!TextUtils.equals(getSelfParticipantId(), otherSelfId)) {
return false;
}
return true;
}
private static final Character QUOTE_CHAR = '\'';
private static final char DIVIDER = '|';
// statics to avoid unnecessary object allocation
private static final StringBuilder sUnquoteStringBuilder = new StringBuilder();
private static final ArrayList<String> sUnquoteResults = new ArrayList<String>();
// this lock is used to guard access to the above statics
private static final Object sUnquoteLock = new Object();
private static void addResult(final ArrayList<String> results, final StringBuilder value) {
if (value.length() > 0) {
results.add(value.toString());
} else {
results.add(EMPTY_STRING);
}
}
@VisibleForTesting
static String[] splitUnquotedString(final String inputString) {
if (TextUtils.isEmpty(inputString)) {
return new String[0];
}
return inputString.split("\\" + DIVIDER);
}
/**
* Takes a group-concated and quoted string and decomposes it into its constituent
* parts. A quoted string starts and ends with a single quote. Actual single quotes
* within the string are escaped using a second single quote. So, for example, an
* input string with 3 constituent parts might look like this:
*
* 'now is the time'|'I can''t do it'|'foo'
*
* This would be returned as an array of 3 strings as follows:
* now is the time
* I can't do it
* foo
*
* This is achieved by walking through the inputString, character by character,
* ignoring the outer quotes and the divider and replacing any pair of consecutive
* single quotes with a single single quote.
*
* @param inputString
* @return array of constituent strings
*/
@VisibleForTesting
static String[] splitQuotedString(final String inputString) {
if (TextUtils.isEmpty(inputString)) {
return new String[0];
}
// this method can be called from multiple threads but it uses a static
// string builder
synchronized (sUnquoteLock) {
final int length = inputString.length();
final ArrayList<String> results = sUnquoteResults;
results.clear();
int characterPos = -1;
while (++characterPos < length) {
final char mustBeQuote = inputString.charAt(characterPos);
Assert.isTrue(QUOTE_CHAR == mustBeQuote);
while (++characterPos < length) {
final char currentChar = inputString.charAt(characterPos);
if (currentChar == QUOTE_CHAR) {
final char peekAhead = characterPos < length - 1
? inputString.charAt(characterPos + 1) : 0;
if (peekAhead == QUOTE_CHAR) {
characterPos += 1; // skip the second quote
} else {
addResult(results, sUnquoteStringBuilder);
sUnquoteStringBuilder.setLength(0);
Assert.isTrue((peekAhead == DIVIDER) || (peekAhead == (char) 0));
characterPos += 1; // skip the divider
break;
}
}
sUnquoteStringBuilder.append(currentChar);
}
}
return results.toArray(new String[results.size()]);
}
}
static MessagePartData makePartData(
final String partId,
final String contentType,
final String contentUriString,
final String contentWidth,
final String contentHeight,
final String text,
final String messageId) {
if (ContentType.isTextType(contentType)) {
final MessagePartData textPart = MessagePartData.createTextMessagePart(text);
textPart.updatePartId(partId);
textPart.updateMessageId(messageId);
return textPart;
} else {
final Uri contentUri = Uri.parse(contentUriString);
final int width = Integer.parseInt(contentWidth);
final int height = Integer.parseInt(contentHeight);
final MessagePartData attachmentPart = MessagePartData.createMediaMessagePart(
contentType, contentUri, width, height);
attachmentPart.updatePartId(partId);
attachmentPart.updateMessageId(messageId);
return attachmentPart;
}
}
@VisibleForTesting
static List<MessagePartData> makeParts(
final String rawIds,
final String rawContentTypes,
final String rawContentUris,
final String rawWidths,
final String rawHeights,
final String rawTexts,
final int partsCount,
final String messageId) {
final List<MessagePartData> parts = new LinkedList<MessagePartData>();
if (partsCount == 1) {
parts.add(makePartData(
rawIds,
rawContentTypes,
rawContentUris,
rawWidths,
rawHeights,
rawTexts,
messageId));
} else {
unpackMessageParts(
parts,
splitUnquotedString(rawIds),
splitQuotedString(rawContentTypes),
splitQuotedString(rawContentUris),
splitUnquotedString(rawWidths),
splitUnquotedString(rawHeights),
splitQuotedString(rawTexts),
partsCount,
messageId);
}
return parts;
}
@VisibleForTesting
static void unpackMessageParts(
final List<MessagePartData> parts,
final String[] ids,
final String[] contentTypes,
final String[] contentUris,
final String[] contentWidths,
final String[] contentHeights,
final String[] texts,
final int partsCount,
final String messageId) {
Assert.equals(partsCount, ids.length);
Assert.equals(partsCount, contentTypes.length);
Assert.equals(partsCount, contentUris.length);
Assert.equals(partsCount, contentWidths.length);
Assert.equals(partsCount, contentHeights.length);
Assert.equals(partsCount, texts.length);
for (int i = 0; i < partsCount; i++) {
parts.add(makePartData(
ids[i],
contentTypes[i],
contentUris[i],
contentWidths[i],
contentHeights[i],
texts[i],
messageId));
}
if (parts.size() != partsCount) {
LogUtil.wtf(TAG, "Only unpacked " + parts.size() + " parts from message (id="
+ messageId + "), expected " + partsCount + " parts");
}
}
public final String getMessageId() {
return mMessageId;
}
public final String getConversationId() {
return mConversationId;
}
public final String getParticipantId() {
return mParticipantId;
}
public List<MessagePartData> getParts() {
return mParts;
}
public boolean hasText() {
for (final MessagePartData part : mParts) {
if (part.isText()) {
return true;
}
}
return false;
}
/**
* Get a concatenation of all text parts
*
* @return the text that is a concatenation of all text parts
*/
public String getText() {
// This is optimized for single text part case, which is the majority
// For single text part, we just return the part without creating the StringBuilder
String firstTextPart = null;
boolean foundText = false;
// For multiple text parts, we need the StringBuilder and the separator for concatenation
StringBuilder sb = null;
String separator = null;
for (final MessagePartData part : mParts) {
if (part.isText()) {
if (!foundText) {
// First text part
firstTextPart = part.getText();
foundText = true;
} else {
// Second and beyond
if (sb == null) {
// Need the StringBuilder and the separator starting from 2nd text part
sb = new StringBuilder();
if (!TextUtils.isEmpty(firstTextPart)) {
sb.append(firstTextPart);
}
separator = BugleGservices.get().getString(
BugleGservicesKeys.MMS_TEXT_CONCAT_SEPARATOR,
BugleGservicesKeys.MMS_TEXT_CONCAT_SEPARATOR_DEFAULT);
}
final String partText = part.getText();
if (!TextUtils.isEmpty(partText)) {
if (!TextUtils.isEmpty(separator) && sb.length() > 0) {
sb.append(separator);
}
sb.append(partText);
}
}
}
}
if (sb == null) {
// Only one text part
return firstTextPart;
} else {
// More than one
return sb.toString();
}
}
public boolean hasAttachments() {
for (final MessagePartData part : mParts) {
if (part.isAttachment()) {
return true;
}
}
return false;
}
public List<MessagePartData> getAttachments() {
return getAttachments(null);
}
public List<MessagePartData> getAttachments(final Predicate<MessagePartData> filter) {
if (mParts.isEmpty()) {
return Collections.emptyList();
}
final List<MessagePartData> attachmentParts = new LinkedList<>();
for (final MessagePartData part : mParts) {
if (part.isAttachment()) {
if (filter == null || filter.apply(part)) {
attachmentParts.add(part);
}
}
}
return attachmentParts;
}
public final long getSentTimeStamp() {
return mSentTimestamp;
}
public final long getReceivedTimeStamp() {
return mReceivedTimestamp;
}
public final String getFormattedReceivedTimeStamp() {
return Dates.getMessageTimeString(mReceivedTimestamp).toString();
}
public final boolean getIsSeen() {
return mSeen;
}
public final boolean getIsRead() {
return mRead;
}
public final boolean getIsMms() {
return (mProtocol == MessageData.PROTOCOL_MMS ||
mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
}
public final boolean getIsMmsNotification() {
return (mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
}
public final boolean getIsSms() {
return mProtocol == (MessageData.PROTOCOL_SMS);
}
final int getProtocol() {
return mProtocol;
}
public final int getStatus() {
return mStatus;
}
public final String getSmsMessageUri() {
return mSmsMessageUri;
}
public final int getSmsPriority() {
return mSmsPriority;
}
public final int getSmsMessageSize() {
return mSmsMessageSize;
}
public final String getMmsSubject() {
return mMmsSubject;
}
public final long getMmsExpiry() {
return mMmsExpiry;
}
public final int getRawTelephonyStatus() {
return mRawTelephonyStatus;
}
public final String getSelfParticipantId() {
return mSelfParticipantId;
}
public boolean getIsIncoming() {
return (mStatus >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
}
public boolean hasIncomingErrorStatus() {
return (mStatus == MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE ||
mStatus == MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED);
}
public boolean getIsSendComplete() {
return (mStatus == MessageData.BUGLE_STATUS_OUTGOING_COMPLETE
|| mStatus == MessageData.BUGLE_STATUS_OUTGOING_DELIVERED);
}
public String getSenderFullName() {
return mSenderFullName;
}
public String getSenderFirstName() {
return mSenderFirstName;
}
public String getSenderDisplayDestination() {
return mSenderDisplayDestination;
}
public String getSenderNormalizedDestination() {
return mSenderNormalizedDestination;
}
public Uri getSenderProfilePhotoUri() {
return mSenderProfilePhotoUri == null ? null : Uri.parse(mSenderProfilePhotoUri);
}
public long getSenderContactId() {
return mSenderContactId;
}
public String getSenderDisplayName() {
if (!TextUtils.isEmpty(mSenderFullName)) {
return mSenderFullName;
}
if (!TextUtils.isEmpty(mSenderFirstName)) {
return mSenderFirstName;
}
return mSenderDisplayDestination;
}
public String getSenderContactLookupKey() {
return mSenderContactLookupKey;
}
public boolean getShowDownloadMessage() {
return MessageData.getShowDownloadMessage(mStatus);
}
public boolean getShowResendMessage() {
return MessageData.getShowResendMessage(mStatus);
}
public boolean getCanForwardMessage() {
// Even for outgoing messages, we only allow forwarding if the message has finished sending
// as media often has issues when send isn't complete
return (mStatus == MessageData.BUGLE_STATUS_OUTGOING_COMPLETE
|| mStatus == MessageData.BUGLE_STATUS_OUTGOING_DELIVERED
|| mStatus == MessageData.BUGLE_STATUS_INCOMING_COMPLETE);
}
public boolean getCanCopyMessageToClipboard() {
return (hasText() &&
(!getIsIncoming() || mStatus == MessageData.BUGLE_STATUS_INCOMING_COMPLETE));
}
public boolean getOneClickResendMessage() {
return MessageData.getOneClickResendMessage(mStatus, mRawTelephonyStatus);
}
/**
* Get sender's lookup uri.
* This method doesn't support corp contacts.
*
* @return Lookup uri of sender's contact
*/
public Uri getSenderContactLookupUri() {
if (mSenderContactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED
&& !TextUtils.isEmpty(mSenderContactLookupKey)) {
return ContactsContract.Contacts.getLookupUri(mSenderContactId,
mSenderContactLookupKey);
}
return null;
}
public boolean getCanClusterWithPreviousMessage() {
return mCanClusterWithPreviousMessage;
}
public boolean getCanClusterWithNextMessage() {
return mCanClusterWithNextMessage;
}
@Override
public String toString() {
return MessageData.toString(mMessageId, mParts);
}
// Data definitions
public static final String getConversationMessagesQuerySql() {
return CONVERSATION_MESSAGES_QUERY_SQL
+ " AND "
// Inject the conversation id
+ DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?)"
+ CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY;
}
static final String getConversationMessageIdsQuerySql() {
return CONVERSATION_MESSAGES_IDS_QUERY_SQL
+ " AND "
// Inject the conversation id
+ DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?)"
+ CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY;
}
public static final String getNotificationQuerySql() {
return CONVERSATION_MESSAGES_QUERY_SQL
+ " AND "
+ "(" + DatabaseHelper.MessageColumns.STATUS + " in ("
+ MessageData.BUGLE_STATUS_INCOMING_COMPLETE + ", "
+ MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD + ")"
+ " AND "
+ DatabaseHelper.MessageColumns.SEEN + " = 0)"
+ ")"
+ NOTIFICATION_QUERY_SQL_GROUP_BY;
}
public static final String getWearableQuerySql() {
return CONVERSATION_MESSAGES_QUERY_SQL
+ " AND "
+ DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?"
+ " AND "
+ DatabaseHelper.MessageColumns.STATUS + " IN ("
+ MessageData.BUGLE_STATUS_OUTGOING_DELIVERED + ", "
+ MessageData.BUGLE_STATUS_OUTGOING_COMPLETE + ", "
+ MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND + ", "
+ MessageData.BUGLE_STATUS_OUTGOING_SENDING + ", "
+ MessageData.BUGLE_STATUS_OUTGOING_RESENDING + ", "
+ MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ", "
+ MessageData.BUGLE_STATUS_INCOMING_COMPLETE + ", "
+ MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD + ")"
+ ")"
+ NOTIFICATION_QUERY_SQL_GROUP_BY;
}
/*
* Generate a sqlite snippet to call the quote function on the columnName argument.
* The columnName doesn't strictly have to be a column name (e.g. it could be an
* expression).
*/
private static String quote(final String columnName) {
return "quote(" + columnName + ")";
}
private static String makeGroupConcatString(final String column) {
return "group_concat(" + column + ", '" + DIVIDER + "')";
}
private static String makeIfNullString(final String column) {
return "ifnull(" + column + "," + "''" + ")";
}
private static String makePartsTableColumnString(final String column) {
return DatabaseHelper.PARTS_TABLE + '.' + column;
}
private static String makeCaseWhenString(final String column,
final boolean quote,
final String asColumn) {
final String fullColumn = makeIfNullString(makePartsTableColumnString(column));
final String groupConcatTerm = quote
? makeGroupConcatString(quote(fullColumn))
: makeGroupConcatString(fullColumn);
return "CASE WHEN (" + CONVERSATION_MESSAGE_VIEW_PARTS_COUNT + ">1) THEN " + groupConcatTerm
+ " ELSE " + makePartsTableColumnString(column) + " END AS " + asColumn;
}
private static final String CONVERSATION_MESSAGE_VIEW_PARTS_COUNT =
"count(" + DatabaseHelper.PARTS_TABLE + '.' + PartColumns._ID + ")";
private static final String EMPTY_STRING = "";
private static final String CONVERSATION_MESSAGES_QUERY_PROJECTION_SQL =
DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID
+ " as " + ConversationMessageViewColumns._ID + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.CONVERSATION_ID
+ " as " + ConversationMessageViewColumns.CONVERSATION_ID + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
+ " as " + ConversationMessageViewColumns.PARTICIPANT_ID + ", "
+ makeCaseWhenString(PartColumns._ID, false,
ConversationMessageViewColumns.PARTS_IDS) + ", "
+ makeCaseWhenString(PartColumns.CONTENT_TYPE, true,
ConversationMessageViewColumns.PARTS_CONTENT_TYPES) + ", "
+ makeCaseWhenString(PartColumns.CONTENT_URI, true,
ConversationMessageViewColumns.PARTS_CONTENT_URIS) + ", "
+ makeCaseWhenString(PartColumns.WIDTH, false,
ConversationMessageViewColumns.PARTS_WIDTHS) + ", "
+ makeCaseWhenString(PartColumns.HEIGHT, false,
ConversationMessageViewColumns.PARTS_HEIGHTS) + ", "
+ makeCaseWhenString(PartColumns.TEXT, true,
ConversationMessageViewColumns.PARTS_TEXTS) + ", "
+ CONVERSATION_MESSAGE_VIEW_PARTS_COUNT
+ " as " + ConversationMessageViewColumns.PARTS_COUNT + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENT_TIMESTAMP
+ " as " + ConversationMessageViewColumns.SENT_TIMESTAMP + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP
+ " as " + ConversationMessageViewColumns.RECEIVED_TIMESTAMP + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SEEN
+ " as " + ConversationMessageViewColumns.SEEN + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.READ
+ " as " + ConversationMessageViewColumns.READ + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.PROTOCOL
+ " as " + ConversationMessageViewColumns.PROTOCOL + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS
+ " as " + ConversationMessageViewColumns.STATUS + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_MESSAGE_URI
+ " as " + ConversationMessageViewColumns.SMS_MESSAGE_URI + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_PRIORITY
+ " as " + ConversationMessageViewColumns.SMS_PRIORITY + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_MESSAGE_SIZE
+ " as " + ConversationMessageViewColumns.SMS_MESSAGE_SIZE + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.MMS_SUBJECT
+ " as " + ConversationMessageViewColumns.MMS_SUBJECT + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.MMS_EXPIRY
+ " as " + ConversationMessageViewColumns.MMS_EXPIRY + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RAW_TELEPHONY_STATUS
+ " as " + ConversationMessageViewColumns.RAW_TELEPHONY_STATUS + ", "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SELF_PARTICIPANT_ID
+ " as " + ConversationMessageViewColumns.SELF_PARTICIPANT_ID + ", "
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FULL_NAME
+ " as " + ConversationMessageViewColumns.SENDER_FULL_NAME + ", "
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FIRST_NAME
+ " as " + ConversationMessageViewColumns.SENDER_FIRST_NAME + ", "
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION
+ " as " + ConversationMessageViewColumns.SENDER_DISPLAY_DESTINATION + ", "
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.NORMALIZED_DESTINATION
+ " as " + ConversationMessageViewColumns.SENDER_NORMALIZED_DESTINATION + ", "
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.PROFILE_PHOTO_URI
+ " as " + ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI + ", "
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.CONTACT_ID
+ " as " + ConversationMessageViewColumns.SENDER_CONTACT_ID + ", "
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.LOOKUP_KEY
+ " as " + ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY + " ";
private static final String CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL =
" FROM " + DatabaseHelper.MESSAGES_TABLE
+ " LEFT JOIN " + DatabaseHelper.PARTS_TABLE
+ " ON (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns._ID
+ "=" + DatabaseHelper.PARTS_TABLE + "." + PartColumns.MESSAGE_ID + ") "
+ " LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE
+ " ON (" + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
+ '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns._ID + ")"
// Exclude draft messages from main view
+ " WHERE (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.STATUS
+ " <> " + MessageData.BUGLE_STATUS_OUTGOING_DRAFT;
// This query is mostly static, except for the injection of conversation id. This is for
// performance reasons, to ensure that the query uses indices and does not trigger full scans
// of the messages table. See b/17160946 for more details.
private static final String CONVERSATION_MESSAGES_QUERY_SQL = "SELECT "
+ CONVERSATION_MESSAGES_QUERY_PROJECTION_SQL
+ CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL;
private static final String CONVERSATION_MESSAGE_IDS_PROJECTION_SQL =
DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID
+ " as " + ConversationMessageViewColumns._ID + " ";
private static final String CONVERSATION_MESSAGES_IDS_QUERY_SQL = "SELECT "
+ CONVERSATION_MESSAGE_IDS_PROJECTION_SQL
+ CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL;
// Note that we sort DESC and ConversationData reverses the cursor. This is a performance
// issue (improvement) for large cursors.
private static final String CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY =
" GROUP BY " + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.MESSAGE_ID
+ " ORDER BY "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " DESC";
private static final String NOTIFICATION_QUERY_SQL_GROUP_BY =
" GROUP BY " + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.MESSAGE_ID
+ " ORDER BY "
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " DESC";
interface ConversationMessageViewColumns extends BaseColumns {
static final String _ID = MessageColumns._ID;
static final String CONVERSATION_ID = MessageColumns.CONVERSATION_ID;
static final String PARTICIPANT_ID = MessageColumns.SENDER_PARTICIPANT_ID;
static final String PARTS_COUNT = "parts_count";
static final String SENT_TIMESTAMP = MessageColumns.SENT_TIMESTAMP;
static final String RECEIVED_TIMESTAMP = MessageColumns.RECEIVED_TIMESTAMP;
static final String SEEN = MessageColumns.SEEN;
static final String READ = MessageColumns.READ;
static final String PROTOCOL = MessageColumns.PROTOCOL;
static final String STATUS = MessageColumns.STATUS;
static final String SMS_MESSAGE_URI = MessageColumns.SMS_MESSAGE_URI;
static final String SMS_PRIORITY = MessageColumns.SMS_PRIORITY;
static final String SMS_MESSAGE_SIZE = MessageColumns.SMS_MESSAGE_SIZE;
static final String MMS_SUBJECT = MessageColumns.MMS_SUBJECT;
static final String MMS_EXPIRY = MessageColumns.MMS_EXPIRY;
static final String RAW_TELEPHONY_STATUS = MessageColumns.RAW_TELEPHONY_STATUS;
static final String SELF_PARTICIPANT_ID = MessageColumns.SELF_PARTICIPANT_ID;
static final String SENDER_FULL_NAME = ParticipantColumns.FULL_NAME;
static final String SENDER_FIRST_NAME = ParticipantColumns.FIRST_NAME;
static final String SENDER_DISPLAY_DESTINATION = ParticipantColumns.DISPLAY_DESTINATION;
static final String SENDER_NORMALIZED_DESTINATION =
ParticipantColumns.NORMALIZED_DESTINATION;
static final String SENDER_PROFILE_PHOTO_URI = ParticipantColumns.PROFILE_PHOTO_URI;
static final String SENDER_CONTACT_ID = ParticipantColumns.CONTACT_ID;
static final String SENDER_CONTACT_LOOKUP_KEY = ParticipantColumns.LOOKUP_KEY;
static final String PARTS_IDS = "parts_ids";
static final String PARTS_CONTENT_TYPES = "parts_content_types";
static final String PARTS_CONTENT_URIS = "parts_content_uris";
static final String PARTS_WIDTHS = "parts_widths";
static final String PARTS_HEIGHTS = "parts_heights";
static final String PARTS_TEXTS = "parts_texts";
}
private static int sIndexIncrementer = 0;
private static final int INDEX_MESSAGE_ID = sIndexIncrementer++;
private static final int INDEX_CONVERSATION_ID = sIndexIncrementer++;
private static final int INDEX_PARTICIPANT_ID = sIndexIncrementer++;
private static final int INDEX_PARTS_IDS = sIndexIncrementer++;
private static final int INDEX_PARTS_CONTENT_TYPES = sIndexIncrementer++;
private static final int INDEX_PARTS_CONTENT_URIS = sIndexIncrementer++;
private static final int INDEX_PARTS_WIDTHS = sIndexIncrementer++;
private static final int INDEX_PARTS_HEIGHTS = sIndexIncrementer++;
private static final int INDEX_PARTS_TEXTS = sIndexIncrementer++;
private static final int INDEX_PARTS_COUNT = sIndexIncrementer++;
private static final int INDEX_SENT_TIMESTAMP = sIndexIncrementer++;
private static final int INDEX_RECEIVED_TIMESTAMP = sIndexIncrementer++;
private static final int INDEX_SEEN = sIndexIncrementer++;
private static final int INDEX_READ = sIndexIncrementer++;
private static final int INDEX_PROTOCOL = sIndexIncrementer++;
private static final int INDEX_STATUS = sIndexIncrementer++;
private static final int INDEX_SMS_MESSAGE_URI = sIndexIncrementer++;
private static final int INDEX_SMS_PRIORITY = sIndexIncrementer++;
private static final int INDEX_SMS_MESSAGE_SIZE = sIndexIncrementer++;
private static final int INDEX_MMS_SUBJECT = sIndexIncrementer++;
private static final int INDEX_MMS_EXPIRY = sIndexIncrementer++;
private static final int INDEX_RAW_TELEPHONY_STATUS = sIndexIncrementer++;
private static final int INDEX_SELF_PARTICIPIANT_ID = sIndexIncrementer++;
private static final int INDEX_SENDER_FULL_NAME = sIndexIncrementer++;
private static final int INDEX_SENDER_FIRST_NAME = sIndexIncrementer++;
private static final int INDEX_SENDER_DISPLAY_DESTINATION = sIndexIncrementer++;
private static final int INDEX_SENDER_NORMALIZED_DESTINATION = sIndexIncrementer++;
private static final int INDEX_SENDER_PROFILE_PHOTO_URI = sIndexIncrementer++;
private static final int INDEX_SENDER_CONTACT_ID = sIndexIncrementer++;
private static final int INDEX_SENDER_CONTACT_LOOKUP_KEY = sIndexIncrementer++;
private static String[] sProjection = {
ConversationMessageViewColumns._ID,
ConversationMessageViewColumns.CONVERSATION_ID,
ConversationMessageViewColumns.PARTICIPANT_ID,
ConversationMessageViewColumns.PARTS_IDS,
ConversationMessageViewColumns.PARTS_CONTENT_TYPES,
ConversationMessageViewColumns.PARTS_CONTENT_URIS,
ConversationMessageViewColumns.PARTS_WIDTHS,
ConversationMessageViewColumns.PARTS_HEIGHTS,
ConversationMessageViewColumns.PARTS_TEXTS,
ConversationMessageViewColumns.PARTS_COUNT,
ConversationMessageViewColumns.SENT_TIMESTAMP,
ConversationMessageViewColumns.RECEIVED_TIMESTAMP,
ConversationMessageViewColumns.SEEN,
ConversationMessageViewColumns.READ,
ConversationMessageViewColumns.PROTOCOL,
ConversationMessageViewColumns.STATUS,
ConversationMessageViewColumns.SMS_MESSAGE_URI,
ConversationMessageViewColumns.SMS_PRIORITY,
ConversationMessageViewColumns.SMS_MESSAGE_SIZE,
ConversationMessageViewColumns.MMS_SUBJECT,
ConversationMessageViewColumns.MMS_EXPIRY,
ConversationMessageViewColumns.RAW_TELEPHONY_STATUS,
ConversationMessageViewColumns.SELF_PARTICIPANT_ID,
ConversationMessageViewColumns.SENDER_FULL_NAME,
ConversationMessageViewColumns.SENDER_FIRST_NAME,
ConversationMessageViewColumns.SENDER_DISPLAY_DESTINATION,
ConversationMessageViewColumns.SENDER_NORMALIZED_DESTINATION,
ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI,
ConversationMessageViewColumns.SENDER_CONTACT_ID,
ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY,
};
public static String[] getProjection() {
return sProjection;
}
}