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
17package com.android.messaging.datamodel.action;
18
19import android.app.Activity;
20import android.content.ContentValues;
21import android.content.Context;
22import android.net.Uri;
23import android.os.Bundle;
24import android.os.Parcel;
25import android.os.Parcelable;
26import android.provider.Telephony.Mms;
27import android.telephony.SmsManager;
28import android.text.TextUtils;
29
30import com.android.messaging.Factory;
31import com.android.messaging.datamodel.BugleDatabaseOperations;
32import com.android.messaging.datamodel.BugleNotifications;
33import com.android.messaging.datamodel.DataModel;
34import com.android.messaging.datamodel.DataModelException;
35import com.android.messaging.datamodel.DatabaseWrapper;
36import com.android.messaging.datamodel.MessagingContentProvider;
37import com.android.messaging.datamodel.MmsFileProvider;
38import com.android.messaging.datamodel.SyncManager;
39import com.android.messaging.datamodel.data.MessageData;
40import com.android.messaging.datamodel.data.ParticipantData;
41import com.android.messaging.mmslib.SqliteWrapper;
42import com.android.messaging.mmslib.pdu.PduHeaders;
43import com.android.messaging.mmslib.pdu.RetrieveConf;
44import com.android.messaging.sms.DatabaseMessages;
45import com.android.messaging.sms.MmsSender;
46import com.android.messaging.sms.MmsUtils;
47import com.android.messaging.util.Assert;
48import com.android.messaging.util.LogUtil;
49import com.google.common.io.Files;
50
51import java.io.File;
52import java.io.FileNotFoundException;
53import java.io.IOException;
54import 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 */
60public 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