Initial checkin of AOSP Messaging app.
b/23110861 Change-Id: I11db999bd10656801e618f78ab2b2ef74136fff1
This commit is contained in:
@@ -0,0 +1,917 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
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_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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user