diff options
Diffstat (limited to 'src/com/android/messaging/datamodel/action/SyncMessageBatch.java')
-rw-r--r-- | src/com/android/messaging/datamodel/action/SyncMessageBatch.java | 383 |
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; - } -} |