Home | History | Annotate | Download | only in action
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.messaging.datamodel.action;
     18 
     19 import android.app.Activity;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.net.Uri;
     23 import android.os.Bundle;
     24 import android.os.Parcel;
     25 import android.os.Parcelable;
     26 import android.provider.Telephony.Mms;
     27 import android.telephony.SmsManager;
     28 import android.text.TextUtils;
     29 
     30 import com.android.messaging.Factory;
     31 import com.android.messaging.datamodel.BugleDatabaseOperations;
     32 import com.android.messaging.datamodel.BugleNotifications;
     33 import com.android.messaging.datamodel.DataModel;
     34 import com.android.messaging.datamodel.DataModelException;
     35 import com.android.messaging.datamodel.DatabaseWrapper;
     36 import com.android.messaging.datamodel.MessagingContentProvider;
     37 import com.android.messaging.datamodel.MmsFileProvider;
     38 import com.android.messaging.datamodel.SyncManager;
     39 import com.android.messaging.datamodel.data.MessageData;
     40 import com.android.messaging.datamodel.data.ParticipantData;
     41 import com.android.messaging.mmslib.SqliteWrapper;
     42 import com.android.messaging.mmslib.pdu.PduHeaders;
     43 import com.android.messaging.mmslib.pdu.RetrieveConf;
     44 import com.android.messaging.sms.DatabaseMessages;
     45 import com.android.messaging.sms.MmsSender;
     46 import com.android.messaging.sms.MmsUtils;
     47 import com.android.messaging.util.Assert;
     48 import com.android.messaging.util.LogUtil;
     49 import com.google.common.io.Files;
     50 
     51 import java.io.File;
     52 import java.io.FileNotFoundException;
     53 import java.io.IOException;
     54 import java.util.List;
     55 
     56 /**
     57  * Processes an MMS message after it has been downloaded.
     58  * NOTE: This action must queue a ProcessPendingMessagesAction when it is done (success or failure).
     59  */
     60 public class ProcessDownloadedMmsAction extends Action {
     61     private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
     62 
     63     // Always set when message downloaded
     64     private static final String KEY_DOWNLOADED_BY_PLATFORM = "downloaded_by_platform";
     65     private static final String KEY_MESSAGE_ID = "message_id";
     66     private static final String KEY_NOTIFICATION_URI = "notification_uri";
     67     private static final String KEY_CONVERSATION_ID = "conversation_id";
     68     private static final String KEY_PARTICIPANT_ID = "participant_id";
     69     private static final String KEY_STATUS_IF_FAILED = "status_if_failed";
     70 
     71     // Set when message downloaded by platform (L+)
     72     private static final String KEY_RESULT_CODE = "result_code";
     73     private static final String KEY_HTTP_STATUS_CODE = "http_status_code";
     74     private static final String KEY_CONTENT_URI = "content_uri";
     75     private static final String KEY_SUB_ID = "sub_id";
     76     private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number";
     77     private static final String KEY_TRANSACTION_ID = "transaction_id";
     78     private static final String KEY_CONTENT_LOCATION = "content_location";
     79     private static final String KEY_AUTO_DOWNLOAD = "auto_download";
     80     private static final String KEY_RECEIVED_TIMESTAMP = "received_timestamp";
     81 
     82     // Set when message downloaded by us (legacy)
     83     private static final String KEY_STATUS = "status";
     84     private static final String KEY_RAW_STATUS = "raw_status";
     85     private static final String KEY_MMS_URI =  "mms_uri";
     86 
     87     // Used to send a deferred response in response to auto-download failure
     88     private static final String KEY_SEND_DEFERRED_RESP_STATUS = "send_deferred_resp_status";
     89 
     90     // Results passed from background worker to processCompletion
     91     private static final String BUNDLE_REQUEST_STATUS = "request_status";
     92     private static final String BUNDLE_RAW_TELEPHONY_STATUS = "raw_status";
     93     private static final String BUNDLE_MMS_URI = "mms_uri";
     94 
     95     // This is called when MMS lib API returns via PendingIntent
     96     public static void processMessageDownloaded(final int resultCode, final Bundle extras) {
     97         final String messageId = extras.getString(DownloadMmsAction.EXTRA_MESSAGE_ID);
     98         final Uri contentUri = extras.getParcelable(DownloadMmsAction.EXTRA_CONTENT_URI);
     99         final Uri notificationUri = extras.getParcelable(DownloadMmsAction.EXTRA_NOTIFICATION_URI);
    100         final String conversationId = extras.getString(DownloadMmsAction.EXTRA_CONVERSATION_ID);
    101         final String participantId = extras.getString(DownloadMmsAction.EXTRA_PARTICIPANT_ID);
    102         Assert.notNull(messageId);
    103         Assert.notNull(contentUri);
    104         Assert.notNull(notificationUri);
    105         Assert.notNull(conversationId);
    106         Assert.notNull(participantId);
    107 
    108         final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
    109         final Bundle params = action.actionParameters;
    110         params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, true);
    111         params.putString(KEY_MESSAGE_ID, messageId);
    112         params.putInt(KEY_RESULT_CODE, resultCode);
    113         params.putInt(KEY_HTTP_STATUS_CODE,
    114                 extras.getInt(SmsManager.EXTRA_MMS_HTTP_STATUS, 0));
    115         params.putParcelable(KEY_CONTENT_URI, contentUri);
    116         params.putParcelable(KEY_NOTIFICATION_URI, notificationUri);
    117         params.putInt(KEY_SUB_ID,
    118                 extras.getInt(DownloadMmsAction.EXTRA_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID));
    119         params.putString(KEY_SUB_PHONE_NUMBER,
    120                 extras.getString(DownloadMmsAction.EXTRA_SUB_PHONE_NUMBER));
    121         params.putString(KEY_TRANSACTION_ID,
    122                 extras.getString(DownloadMmsAction.EXTRA_TRANSACTION_ID));
    123         params.putString(KEY_CONTENT_LOCATION,
    124                 extras.getString(DownloadMmsAction.EXTRA_CONTENT_LOCATION));
    125         params.putBoolean(KEY_AUTO_DOWNLOAD,
    126                 extras.getBoolean(DownloadMmsAction.EXTRA_AUTO_DOWNLOAD));
    127         params.putLong(KEY_RECEIVED_TIMESTAMP,
    128                 extras.getLong(DownloadMmsAction.EXTRA_RECEIVED_TIMESTAMP));
    129         params.putString(KEY_CONVERSATION_ID, conversationId);
    130         params.putString(KEY_PARTICIPANT_ID, participantId);
    131         params.putInt(KEY_STATUS_IF_FAILED,
    132                 extras.getInt(DownloadMmsAction.EXTRA_STATUS_IF_FAILED));
    133         action.start();
    134     }
    135 
    136     // This is called for fast failing downloading (due to airplane mode or mobile data )
    137     public static void processMessageDownloadFastFailed(final String messageId,
    138             final Uri notificationUri, final String conversationId, final String participantId,
    139             final String contentLocation, final int subId, final String subPhoneNumber,
    140             final int statusIfFailed, final boolean autoDownload, final String transactionId,
    141             final int resultCode) {
    142         Assert.notNull(messageId);
    143         Assert.notNull(notificationUri);
    144         Assert.notNull(conversationId);
    145         Assert.notNull(participantId);
    146 
    147         final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
    148         final Bundle params = action.actionParameters;
    149         params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, true);
    150         params.putString(KEY_MESSAGE_ID, messageId);
    151         params.putInt(KEY_RESULT_CODE, resultCode);
    152         params.putParcelable(KEY_NOTIFICATION_URI, notificationUri);
    153         params.putInt(KEY_SUB_ID, subId);
    154         params.putString(KEY_SUB_PHONE_NUMBER, subPhoneNumber);
    155         params.putString(KEY_CONTENT_LOCATION, contentLocation);
    156         params.putBoolean(KEY_AUTO_DOWNLOAD, autoDownload);
    157         params.putString(KEY_CONVERSATION_ID, conversationId);
    158         params.putString(KEY_PARTICIPANT_ID, participantId);
    159         params.putInt(KEY_STATUS_IF_FAILED, statusIfFailed);
    160         params.putString(KEY_TRANSACTION_ID, transactionId);
    161         action.start();
    162     }
    163 
    164     public static void processDownloadActionFailure(final String messageId, final int status,
    165             final int rawStatus, final String conversationId, final String participantId,
    166             final int statusIfFailed, final int subId, final String transactionId) {
    167         Assert.notNull(messageId);
    168         Assert.notNull(conversationId);
    169         Assert.notNull(participantId);
    170 
    171         final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
    172         final Bundle params = action.actionParameters;
    173         params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, false);
    174         params.putString(KEY_MESSAGE_ID, messageId);
    175         params.putInt(KEY_STATUS, status);
    176         params.putInt(KEY_RAW_STATUS, rawStatus);
    177         params.putString(KEY_CONVERSATION_ID, conversationId);
    178         params.putString(KEY_PARTICIPANT_ID, participantId);
    179         params.putInt(KEY_STATUS_IF_FAILED, statusIfFailed);
    180         params.putInt(KEY_SUB_ID, subId);
    181         params.putString(KEY_TRANSACTION_ID, transactionId);
    182         action.start();
    183     }
    184 
    185     public static void sendDeferredRespStatus(final String messageId, final String transactionId,
    186             final String contentLocation, final int subId) {
    187         final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
    188         final Bundle params = action.actionParameters;
    189         params.putString(KEY_MESSAGE_ID, messageId);
    190         params.putString(KEY_TRANSACTION_ID, transactionId);
    191         params.putString(KEY_CONTENT_LOCATION, contentLocation);
    192         params.putBoolean(KEY_SEND_DEFERRED_RESP_STATUS, true);
    193         params.putInt(KEY_SUB_ID, subId);
    194         action.start();
    195     }
    196 
    197     private ProcessDownloadedMmsAction() {
    198         // Callers must use one of the static methods above
    199     }
    200 
    201     @Override
    202     protected Object executeAction() {
    203         // Fire up the background worker
    204         requestBackgroundWork();
    205         return null;
    206     }
    207 
    208     @Override
    209     protected Bundle doBackgroundWork() throws DataModelException {
    210         final Context context = Factory.get().getApplicationContext();
    211         final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
    212         final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
    213         final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
    214         final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
    215         final boolean sendDeferredRespStatus =
    216                 actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS, false);
    217 
    218         // Send a response indicating that auto-download failed
    219         if (sendDeferredRespStatus) {
    220             if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
    221                 LogUtil.v(TAG, "DownloadMmsAction: Auto-download of message " + messageId
    222                         + " failed; sending DEFERRED NotifyRespInd");
    223             }
    224             MmsUtils.sendNotifyResponseForMmsDownload(
    225                     context,
    226                     subId,
    227                     MmsUtils.stringToBytes(transactionId, "UTF-8"),
    228                     contentLocation,
    229                     PduHeaders.STATUS_DEFERRED);
    230             return null;
    231         }
    232 
    233         // Processing a real MMS download
    234         final boolean downloadedByPlatform = actionParameters.getBoolean(
    235                 KEY_DOWNLOADED_BY_PLATFORM);
    236 
    237         final int status;
    238         int rawStatus = MmsUtils.PDU_HEADER_VALUE_UNDEFINED;
    239         Uri mmsUri = null;
    240 
    241         if (downloadedByPlatform) {
    242             final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
    243             if (resultCode == Activity.RESULT_OK) {
    244                 final Uri contentUri = actionParameters.getParcelable(KEY_CONTENT_URI);
    245                 final File downloadedFile = MmsFileProvider.getFile(contentUri);
    246                 byte[] downloadedData = null;
    247                 try {
    248                     downloadedData = Files.toByteArray(downloadedFile);
    249                 } catch (final FileNotFoundException e) {
    250                     LogUtil.e(TAG, "ProcessDownloadedMmsAction: MMS download file not found: "
    251                             + downloadedFile.getAbsolutePath());
    252                 } catch (final IOException e) {
    253                     LogUtil.e(TAG, "ProcessDownloadedMmsAction: Error reading MMS download file: "
    254                             + downloadedFile.getAbsolutePath(), e);
    255                 }
    256 
    257                 // Can delete the temp file now
    258                 if (downloadedFile.exists()) {
    259                     downloadedFile.delete();
    260                     if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
    261                         LogUtil.d(TAG, "ProcessDownloadedMmsAction: Deleted temp file with "
    262                                 + "downloaded MMS pdu: " + downloadedFile.getAbsolutePath());
    263                     }
    264                 }
    265 
    266                 if (downloadedData != null) {
    267                     final RetrieveConf retrieveConf =
    268                             MmsSender.parseRetrieveConf(downloadedData, subId);
    269                     if (MmsUtils.isDumpMmsEnabled()) {
    270                         MmsUtils.dumpPdu(downloadedData, retrieveConf);
    271                     }
    272                     if (retrieveConf != null) {
    273                         // Insert the downloaded MMS into telephony
    274                         final Uri notificationUri = actionParameters.getParcelable(
    275                                 KEY_NOTIFICATION_URI);
    276                         final String subPhoneNumber = actionParameters.getString(
    277                                 KEY_SUB_PHONE_NUMBER);
    278                         final boolean autoDownload = actionParameters.getBoolean(
    279                                 KEY_AUTO_DOWNLOAD);
    280                         final long receivedTimestampInSeconds =
    281                                 actionParameters.getLong(KEY_RECEIVED_TIMESTAMP);
    282 
    283                         // Inform sync we're adding a message to telephony
    284                         final SyncManager syncManager = DataModel.get().getSyncManager();
    285                         syncManager.onNewMessageInserted(receivedTimestampInSeconds * 1000L);
    286 
    287                         final MmsUtils.StatusPlusUri result =
    288                                 MmsUtils.insertDownloadedMessageAndSendResponse(context,
    289                                         notificationUri, subId, subPhoneNumber, transactionId,
    290                                         contentLocation, autoDownload, receivedTimestampInSeconds,
    291                                         retrieveConf);
    292                         status = result.status;
    293                         rawStatus = result.rawStatus;
    294                         mmsUri = result.uri;
    295                     } else {
    296                         // Invalid response PDU
    297                         status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
    298                     }
    299                 } else {
    300                     // Failed to read download file
    301                     status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
    302                 }
    303             } else {
    304                 LogUtil.w(TAG, "ProcessDownloadedMmsAction: Platform returned error resultCode: "
    305                         + resultCode);
    306                 final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
    307                 status = MmsSender.getErrorResultStatus(resultCode, httpStatusCode);
    308             }
    309         } else {
    310             // Message was already processed by the internal API, or the download action failed.
    311             // In either case, we just need to copy the status to the response bundle.
    312             status = actionParameters.getInt(KEY_STATUS);
    313             rawStatus = actionParameters.getInt(KEY_RAW_STATUS);
    314             mmsUri = actionParameters.getParcelable(KEY_MMS_URI);
    315         }
    316 
    317         final Bundle response = new Bundle();
    318         response.putInt(BUNDLE_REQUEST_STATUS, status);
    319         response.putInt(BUNDLE_RAW_TELEPHONY_STATUS, rawStatus);
    320         response.putParcelable(BUNDLE_MMS_URI, mmsUri);
    321         return response;
    322     }
    323 
    324     @Override
    325     protected Object processBackgroundResponse(final Bundle response) {
    326         if (response == null) {
    327             // No message download to process; doBackgroundWork sent a notify deferred response
    328             Assert.isTrue(actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS));
    329             return null;
    330         }
    331 
    332         final int status = response.getInt(BUNDLE_REQUEST_STATUS);
    333         final int rawStatus = response.getInt(BUNDLE_RAW_TELEPHONY_STATUS);
    334         final Uri messageUri = response.getParcelable(BUNDLE_MMS_URI);
    335         final boolean autoDownload = actionParameters.getBoolean(KEY_AUTO_DOWNLOAD);
    336         final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
    337 
    338         // Do post-processing on downloaded message
    339         final MessageData message = processResult(status, rawStatus, messageUri);
    340 
    341         final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
    342         // If we were trying to auto-download but have failed need to send the deferred response
    343         if (autoDownload && message == null && status == MmsUtils.MMS_REQUEST_MANUAL_RETRY) {
    344             final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
    345             final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
    346             sendDeferredRespStatus(messageId, transactionId, contentLocation, subId);
    347         }
    348 
    349         if (autoDownload) {
    350             final DatabaseWrapper db = DataModel.get().getDatabase();
    351             MessageData toastMessage = message;
    352             if (toastMessage == null) {
    353                 // If the downloaded failed (message is null), then we should announce the
    354                 // receiving of the wap push message. Load the wap push message here instead.
    355                 toastMessage = BugleDatabaseOperations.readMessageData(db, messageId);
    356             }
    357             if (toastMessage != null) {
    358                 final ParticipantData sender = ParticipantData.getFromId(
    359                         db, toastMessage.getParticipantId());
    360                 BugleActionToasts.onMessageReceived(
    361                         toastMessage.getConversationId(), sender, toastMessage);
    362             }
    363         } else {
    364             final boolean success = message != null && status == MmsUtils.MMS_REQUEST_SUCCEEDED;
    365             BugleActionToasts.onSendMessageOrManualDownloadActionCompleted(
    366                     // If download failed, use the wap push message's conversation instead
    367                     success ? message.getConversationId()
    368                             : actionParameters.getString(KEY_CONVERSATION_ID),
    369                     success, status, false/*isSms*/, subId, false /*isSend*/);
    370         }
    371 
    372         final boolean failed = (messageUri == null);
    373         ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(failed, this);
    374         if (failed) {
    375             BugleNotifications.update(false, BugleNotifications.UPDATE_ERRORS);
    376         }
    377 
    378         return message;
    379     }
    380 
    381     @Override
    382     protected Object processBackgroundFailure() {
    383         if (actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS)) {
    384             // We can early-out for these failures. processResult is only designed to handle
    385             // post-processing of MMS downloads (whether successful or not).
    386             LogUtil.w(TAG,
    387                     "ProcessDownloadedMmsAction: Exception while sending deferred NotifyRespInd");
    388             return null;
    389         }
    390 
    391         // Background worker threw an exception; require manual retry
    392         processResult(MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
    393                 null /* mmsUri */);
    394 
    395         ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(true /* failed */,
    396                 this);
    397 
    398         return null;
    399     }
    400 
    401     private MessageData processResult(final int status, final int rawStatus, final Uri mmsUri) {
    402         final Context context = Factory.get().getApplicationContext();
    403         final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
    404         final Uri mmsNotificationUri = actionParameters.getParcelable(KEY_NOTIFICATION_URI);
    405         final String notificationConversationId = actionParameters.getString(KEY_CONVERSATION_ID);
    406         final String notificationParticipantId = actionParameters.getString(KEY_PARTICIPANT_ID);
    407         final int statusIfFailed = actionParameters.getInt(KEY_STATUS_IF_FAILED);
    408         final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
    409 
    410         Assert.notNull(messageId);
    411 
    412         LogUtil.i(TAG, "ProcessDownloadedMmsAction: Processed MMS download of message " + messageId
    413                 + "; status is " + MmsUtils.getRequestStatusDescription(status));
    414 
    415         DatabaseMessages.MmsMessage mms = null;
    416         if (status == MmsUtils.MMS_REQUEST_SUCCEEDED && mmsUri != null) {
    417             // Delete the initial M-Notification.ind from telephony
    418             SqliteWrapper.delete(context, context.getContentResolver(),
    419                     mmsNotificationUri, null, null);
    420 
    421             // Read the sent MMS from the telephony provider
    422             mms = MmsUtils.loadMms(mmsUri);
    423         }
    424 
    425         boolean messageInFocusedConversation = false;
    426         boolean messageInObservableConversation = false;
    427         String conversationId = null;
    428         MessageData message = null;
    429         final DatabaseWrapper db = DataModel.get().getDatabase();
    430         db.beginTransaction();
    431         try {
    432             if (mms != null) {
    433                 final ParticipantData self = ParticipantData.getSelfParticipant(mms.getSubId());
    434                 final String selfId =
    435                         BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
    436 
    437                 final List<String> recipients = MmsUtils.getRecipientsByThread(mms.mThreadId);
    438                 String from = MmsUtils.getMmsSender(recipients, mms.getUri());
    439                 if (from == null) {
    440                     LogUtil.w(TAG,
    441                             "Downloaded an MMS without sender address; using unknown sender.");
    442                     from = ParticipantData.getUnknownSenderDestination();
    443                 }
    444                 final ParticipantData sender = ParticipantData.getFromRawPhoneBySimLocale(from,
    445                         subId);
    446                 final String senderParticipantId =
    447                         BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender);
    448                 if (!senderParticipantId.equals(notificationParticipantId)) {
    449                     LogUtil.e(TAG, "ProcessDownloadedMmsAction: Downloaded MMS message "
    450                             + messageId + " has different sender (participantId = "
    451                             + senderParticipantId + ") than notification ("
    452                             + notificationParticipantId + ")");
    453                 }
    454                 final boolean blockedSender = BugleDatabaseOperations.isBlockedDestination(
    455                         db, sender.getNormalizedDestination());
    456                 conversationId = BugleDatabaseOperations.getOrCreateConversationFromThreadId(db,
    457                         mms.mThreadId, blockedSender, subId);
    458 
    459                 messageInFocusedConversation =
    460                         DataModel.get().isFocusedConversation(conversationId);
    461                 messageInObservableConversation =
    462                         DataModel.get().isNewMessageObservable(conversationId);
    463 
    464                 // TODO: Also write these values to the telephony provider
    465                 mms.mRead = messageInFocusedConversation;
    466                 mms.mSeen = messageInObservableConversation;
    467 
    468                 // Translate to our format
    469                 message = MmsUtils.createMmsMessage(mms, conversationId, senderParticipantId,
    470                         selfId, MessageData.BUGLE_STATUS_INCOMING_COMPLETE);
    471                 // Update image sizes.
    472                 message.updateSizesForImageParts();
    473                 // Inform sync that message has been added at local received timestamp
    474                 final SyncManager syncManager = DataModel.get().getSyncManager();
    475                 syncManager.onNewMessageInserted(message.getReceivedTimeStamp());
    476                 final MessageData current = BugleDatabaseOperations.readMessageData(db, messageId);
    477                 if (current == null) {
    478                     LogUtil.w(TAG, "Message deleted prior to update");
    479                     BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
    480                 } else {
    481                     // Overwrite existing notification message
    482                     message.updateMessageId(messageId);
    483                     // Write message
    484                     BugleDatabaseOperations.updateMessageInTransaction(db, message);
    485                 }
    486 
    487                 if (!TextUtils.equals(notificationConversationId, conversationId)) {
    488                     // If this is a group conversation, the message is moved. So the original
    489                     // 1v1 conversation (as referenced by notificationConversationId) could
    490                     // be left with no non-draft message. Delete the conversation if that
    491                     // happens. See the comment for the method below for why we need to do this.
    492                     if (!BugleDatabaseOperations.deleteConversationIfEmptyInTransaction(
    493                             db, notificationConversationId)) {
    494                         BugleDatabaseOperations.maybeRefreshConversationMetadataInTransaction(
    495                                 db, notificationConversationId, messageId,
    496                                 true /*shouldAutoSwitchSelfId*/, blockedSender /*keepArchived*/);
    497                     }
    498                 }
    499 
    500                 BugleDatabaseOperations.refreshConversationMetadataInTransaction(db, conversationId,
    501                         true /*shouldAutoSwitchSelfId*/, blockedSender /*keepArchived*/);
    502             } else {
    503                 messageInFocusedConversation =
    504                         DataModel.get().isFocusedConversation(notificationConversationId);
    505 
    506                 // Default to retry status unless status indicates otherwise
    507                 int bugleStatus = statusIfFailed;
    508                 if (status == MmsUtils.MMS_REQUEST_MANUAL_RETRY) {
    509                     bugleStatus = MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED;
    510                 } else if (status == MmsUtils.MMS_REQUEST_NO_RETRY) {
    511                     bugleStatus = MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE;
    512                 }
    513                 DownloadMmsAction.updateMessageStatus(mmsNotificationUri, messageId,
    514                         notificationConversationId, bugleStatus, rawStatus);
    515 
    516                 // Log MMS download failed
    517                 final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
    518                 final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
    519 
    520                 // Just in case this was the latest message update the summary data
    521                 BugleDatabaseOperations.refreshConversationMetadataInTransaction(db,
    522                         notificationConversationId, true /*shouldAutoSwitchSelfId*/,
    523                         false /*keepArchived*/);
    524             }
    525 
    526             db.setTransactionSuccessful();
    527         } finally {
    528             db.endTransaction();
    529         }
    530 
    531         if (mmsUri != null) {
    532             // Update mms table with read status now we know the conversation id
    533             final ContentValues values = new ContentValues(1);
    534             values.put(Mms.READ, messageInFocusedConversation);
    535             SqliteWrapper.update(context, context.getContentResolver(), mmsUri, values,
    536                     null, null);
    537         }
    538 
    539         // Show a notification to let the user know a new message has arrived
    540         BugleNotifications.update(false /*silent*/, conversationId, BugleNotifications.UPDATE_ALL);
    541 
    542         // Messages may have changed in two conversations
    543         if (conversationId != null) {
    544             MessagingContentProvider.notifyMessagesChanged(conversationId);
    545         }
    546         MessagingContentProvider.notifyMessagesChanged(notificationConversationId);
    547         MessagingContentProvider.notifyPartsChanged();
    548 
    549         return message;
    550     }
    551 
    552     private ProcessDownloadedMmsAction(final Parcel in) {
    553         super(in);
    554     }
    555 
    556     public static final Parcelable.Creator<ProcessDownloadedMmsAction> CREATOR
    557             = new Parcelable.Creator<ProcessDownloadedMmsAction>() {
    558         @Override
    559         public ProcessDownloadedMmsAction createFromParcel(final Parcel in) {
    560             return new ProcessDownloadedMmsAction(in);
    561         }
    562 
    563         @Override
    564         public ProcessDownloadedMmsAction[] newArray(final int size) {
    565             return new ProcessDownloadedMmsAction[size];
    566         }
    567     };
    568 
    569     @Override
    570     public void writeToParcel(final Parcel parcel, final int flags) {
    571         writeActionToParcel(parcel, flags);
    572     }
    573 }
    574