summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/datamodel/action/SyncMessageBatch.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging/datamodel/action/SyncMessageBatch.java')
-rw-r--r--src/com/android/messaging/datamodel/action/SyncMessageBatch.java383
1 files changed, 0 insertions, 383 deletions
diff --git a/src/com/android/messaging/datamodel/action/SyncMessageBatch.java b/src/com/android/messaging/datamodel/action/SyncMessageBatch.java
deleted file mode 100644
index 972d691..0000000
--- a/src/com/android/messaging/datamodel/action/SyncMessageBatch.java
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- * 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.action;
-
-import android.database.Cursor;
-import android.database.sqlite.SQLiteConstraintException;
-import android.provider.Telephony;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Sms;
-import android.text.TextUtils;
-
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.SyncManager.ThreadInfoCache;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.pdu.PduHeaders;
-import com.android.messaging.sms.DatabaseMessages.LocalDatabaseMessage;
-import com.android.messaging.sms.DatabaseMessages.MmsMessage;
-import com.android.messaging.sms.DatabaseMessages.SmsMessage;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Update local database with a batch of messages to add/delete in one transaction
- */
-class SyncMessageBatch {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- // Variables used during executeAction
- private final HashSet<String> mConversationsToUpdate;
- // Cache of thread->conversationId map
- private final ThreadInfoCache mCache;
-
- // Set of SMS messages to add
- private final ArrayList<SmsMessage> mSmsToAdd;
- // Set of MMS messages to add
- private final ArrayList<MmsMessage> mMmsToAdd;
- // Set of local messages to delete
- private final ArrayList<LocalDatabaseMessage> mMessagesToDelete;
-
- SyncMessageBatch(final ArrayList<SmsMessage> smsToAdd,
- final ArrayList<MmsMessage> mmsToAdd,
- final ArrayList<LocalDatabaseMessage> messagesToDelete,
- final ThreadInfoCache cache) {
- mSmsToAdd = smsToAdd;
- mMmsToAdd = mmsToAdd;
- mMessagesToDelete = messagesToDelete;
- mCache = cache;
- mConversationsToUpdate = new HashSet<String>();
- }
-
- void updateLocalDatabase() {
- // Perform local database changes in one transaction
- final DatabaseWrapper db = DataModel.get().getDatabase();
- db.beginTransaction();
- try {
- // Store all the SMS messages
- for (final SmsMessage sms : mSmsToAdd) {
- storeSms(db, sms);
- }
- // Store all the MMS messages
- for (final MmsMessage mms : mMmsToAdd) {
- storeMms(db, mms);
- }
- // Keep track of conversations with messages deleted
- for (final LocalDatabaseMessage message : mMessagesToDelete) {
- mConversationsToUpdate.add(message.getConversationId());
- }
- // Batch delete local messages
- batchDelete(db, DatabaseHelper.MESSAGES_TABLE, MessageColumns._ID,
- messageListToIds(mMessagesToDelete));
-
- for (final LocalDatabaseMessage message : mMessagesToDelete) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncMessageBatch: Deleted message " + message.getLocalId()
- + " for SMS/MMS " + message.getUri() + " with timestamp "
- + message.getTimestampInMillis());
- }
- }
-
- // Update conversation state for imported messages, like snippet,
- updateConversations(db);
-
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- private static String[] messageListToIds(final List<LocalDatabaseMessage> messagesToDelete) {
- final String[] ids = new String[messagesToDelete.size()];
- for (int i = 0; i < ids.length; i++) {
- ids[i] = Long.toString(messagesToDelete.get(i).getLocalId());
- }
- return ids;
- }
-
- /**
- * Store the SMS message into local database.
- *
- * @param sms
- */
- private void storeSms(final DatabaseWrapper db, final SmsMessage sms) {
- if (sms.mBody == null) {
- LogUtil.w(TAG, "SyncMessageBatch: SMS " + sms.mUri + " has no body; adding empty one");
- // try to fix it
- sms.mBody = "";
- }
-
- if (TextUtils.isEmpty(sms.mAddress)) {
- LogUtil.e(TAG, "SyncMessageBatch: SMS has no address; using unknown sender");
- // try to fix it
- sms.mAddress = ParticipantData.getUnknownSenderDestination();
- }
-
- // TODO : We need to also deal with messages in a failed/retry state
- final boolean isOutgoing = sms.mType != Sms.MESSAGE_TYPE_INBOX;
-
- final String otherPhoneNumber = sms.mAddress;
-
- // A forced resync of all messages should still keep the archived states.
- // The database upgrade code notifies sync manager of this. We need to
- // honor the original customization to this conversation if created.
- final String conversationId = mCache.getOrCreateConversation(db, sms.mThreadId, sms.mSubId,
- DataModel.get().getSyncManager().getCustomizationForThread(sms.mThreadId));
- if (conversationId == null) {
- // Cannot create conversation for this message? This should not happen.
- LogUtil.e(TAG, "SyncMessageBatch: Failed to create conversation for SMS thread "
- + sms.mThreadId);
- return;
- }
- final ParticipantData self = ParticipantData.getSelfParticipant(sms.getSubId());
- final String selfId =
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
- final ParticipantData sender = isOutgoing ?
- self :
- ParticipantData.getFromRawPhoneBySimLocale(otherPhoneNumber, sms.getSubId());
- final String participantId = (isOutgoing ? selfId :
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender));
-
- final int bugleStatus = bugleStatusForSms(isOutgoing, sms.mType, sms.mStatus);
-
- final MessageData message = MessageData.createSmsMessage(
- sms.mUri,
- participantId,
- selfId,
- conversationId,
- bugleStatus,
- sms.mSeen,
- sms.mRead,
- sms.mTimestampSentInMillis,
- sms.mTimestampInMillis,
- sms.mBody);
-
- // Inserting sms content into messages table
- try {
- BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
- } catch (SQLiteConstraintException e) {
- rethrowSQLiteConstraintExceptionWithDetails(e, db, sms.mUri, sms.mThreadId,
- conversationId, selfId, participantId);
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncMessageBatch: Inserted new message " + message.getMessageId()
- + " for SMS " + message.getSmsMessageUri() + " received at "
- + message.getReceivedTimeStamp());
- }
-
- // Keep track of updated conversation for later updating the conversation snippet, etc.
- mConversationsToUpdate.add(conversationId);
- }
-
- public static int bugleStatusForSms(final boolean isOutgoing, final int type,
- final int status) {
- int bugleStatus = MessageData.BUGLE_STATUS_UNKNOWN;
- // For a message we sync either
- if (isOutgoing) {
- // Outgoing message not yet been sent
- if (type == Telephony.Sms.MESSAGE_TYPE_FAILED ||
- type == Telephony.Sms.MESSAGE_TYPE_OUTBOX ||
- type == Telephony.Sms.MESSAGE_TYPE_QUEUED ||
- (type == Telephony.Sms.MESSAGE_TYPE_SENT &&
- status == Telephony.Sms.STATUS_FAILED)) {
- // Not sent counts as failed and available for manual resend
- bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_FAILED;
- } else if (status == Sms.STATUS_COMPLETE) {
- bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_DELIVERED;
- } else {
- // Otherwise outgoing message is complete
- bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_COMPLETE;
- }
- } else {
- // All incoming SMS messages are complete
- bugleStatus = MessageData.BUGLE_STATUS_INCOMING_COMPLETE;
- }
- return bugleStatus;
- }
-
- /**
- * Store the MMS message into local database
- *
- * @param mms
- */
- private void storeMms(final DatabaseWrapper db, final MmsMessage mms) {
- if (mms.mParts.size() < 1) {
- LogUtil.w(TAG, "SyncMessageBatch: MMS " + mms.mUri + " has no parts");
- }
-
- // TODO : We need to also deal with messages in a failed/retry state
- final boolean isOutgoing = mms.mType != Mms.MESSAGE_BOX_INBOX;
- final boolean isNotification = (mms.mMmsMessageType ==
- PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
-
- final String senderId = mms.mSender;
-
- // A forced resync of all messages should still keep the archived states.
- // The database upgrade code notifies sync manager of this. We need to
- // honor the original customization to this conversation if created.
- final String conversationId = mCache.getOrCreateConversation(db, mms.mThreadId, mms.mSubId,
- DataModel.get().getSyncManager().getCustomizationForThread(mms.mThreadId));
- if (conversationId == null) {
- LogUtil.e(TAG, "SyncMessageBatch: Failed to create conversation for MMS thread "
- + mms.mThreadId);
- return;
- }
- final ParticipantData self = ParticipantData.getSelfParticipant(mms.getSubId());
- final String selfId =
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
- final ParticipantData sender = isOutgoing ?
- self : ParticipantData.getFromRawPhoneBySimLocale(senderId, mms.getSubId());
- final String participantId = (isOutgoing ? selfId :
- BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender));
-
- final int bugleStatus = MmsUtils.bugleStatusForMms(isOutgoing, isNotification, mms.mType);
-
- // Import message and all of the parts.
- // TODO : For now we are importing these in the order we found them in the MMS
- // database. Ideally we would load and parse the SMIL which describes how the parts relate
- // to one another.
-
- // TODO: Need to set correct status on message
- final MessageData message = MmsUtils.createMmsMessage(mms, conversationId, participantId,
- selfId, bugleStatus);
-
- // Inserting mms content into messages table
- try {
- BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
- } catch (SQLiteConstraintException e) {
- rethrowSQLiteConstraintExceptionWithDetails(e, db, mms.mUri, mms.mThreadId,
- conversationId, selfId, participantId);
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncMessageBatch: Inserted new message " + message.getMessageId()
- + " for MMS " + message.getSmsMessageUri() + " received at "
- + message.getReceivedTimeStamp());
- }
-
- // Keep track of updated conversation for later updating the conversation snippet, etc.
- mConversationsToUpdate.add(conversationId);
- }
-
- // TODO: Remove this after we no longer see this crash (b/18375758)
- private static void rethrowSQLiteConstraintExceptionWithDetails(SQLiteConstraintException e,
- DatabaseWrapper db, String messageUri, long threadId, String conversationId,
- String selfId, String senderId) {
- // Add some extra debug information to the exception for tracking down b/18375758.
- // The default detail message for SQLiteConstraintException tells us that a foreign
- // key constraint failed, but not which one! Messages have foreign keys to 3 tables:
- // conversations, participants (self), participants (sender). We'll query each one
- // to determine which one(s) violated the constraint, and then throw a new exception
- // with those details.
-
- String foundConversationId = null;
- Cursor cursor = null;
- try {
- // Look for an existing conversation in the db with the conversation id
- cursor = db.rawQuery("SELECT " + ConversationColumns._ID
- + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
- + " WHERE " + ConversationColumns._ID + "=" + conversationId,
- null);
- if (cursor != null && cursor.moveToFirst()) {
- Assert.isTrue(cursor.getCount() == 1);
- foundConversationId = cursor.getString(0);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- ParticipantData foundSelfParticipant =
- BugleDatabaseOperations.getExistingParticipant(db, selfId);
- ParticipantData foundSenderParticipant =
- BugleDatabaseOperations.getExistingParticipant(db, senderId);
-
- String errorMsg = "SQLiteConstraintException while inserting message for " + messageUri
- + "; conversation id from getOrCreateConversation = " + conversationId
- + " (lookup thread = " + threadId + "), found conversation id = "
- + foundConversationId + ", found self participant = "
- + LogUtil.sanitizePII(foundSelfParticipant.getNormalizedDestination())
- + " (lookup id = " + selfId + "), found sender participant = "
- + LogUtil.sanitizePII(foundSenderParticipant.getNormalizedDestination())
- + " (lookup id = " + senderId + ")";
- throw new RuntimeException(errorMsg, e);
- }
-
- /**
- * Use the tracked latest message info to update conversations, including
- * latest chat message and sort timestamp.
- */
- private void updateConversations(final DatabaseWrapper db) {
- for (final String conversationId : mConversationsToUpdate) {
- if (BugleDatabaseOperations.deleteConversationIfEmptyInTransaction(db,
- conversationId)) {
- continue;
- }
-
- final boolean archived = mCache.isArchived(conversationId);
- // Always attempt to auto-switch conversation self id for sync/import case.
- BugleDatabaseOperations.maybeRefreshConversationMetadataInTransaction(db,
- conversationId, true /*shouldAutoSwitchSelfId*/, archived /*keepArchived*/);
- }
- }
-
-
- /**
- * Batch delete database rows by matching a column with a list of values, usually some
- * kind of IDs.
- *
- * @param table
- * @param column
- * @param ids
- * @return Total number of deleted messages
- */
- private static int batchDelete(final DatabaseWrapper db, final String table,
- final String column, final String[] ids) {
- int totalDeleted = 0;
- final int totalIds = ids.length;
- for (int start = 0; start < totalIds; start += MmsUtils.MAX_IDS_PER_QUERY) {
- final int end = Math.min(start + MmsUtils.MAX_IDS_PER_QUERY, totalIds); //excluding
- final int count = end - start;
- final String batchSelection = String.format(
- Locale.US,
- "%s IN %s",
- column,
- MmsUtils.getSqlInOperand(count));
- final String[] batchSelectionArgs = Arrays.copyOfRange(ids, start, end);
- final int deleted = db.delete(
- table,
- batchSelection,
- batchSelectionArgs);
- totalDeleted += deleted;
- }
- return totalDeleted;
- }
-}