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
processMessageDownloaded(final int resultCode, final Bundle extras)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 )
processMessageDownloadFastFailed(final String messageId, final Uri notificationUri, final String conversationId, final String participantId, final String contentLocation, final int subId, final String subPhoneNumber, final int statusIfFailed, final boolean autoDownload, final String transactionId, final int resultCode)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 
processDownloadActionFailure(final String messageId, final int status, final int rawStatus, final String conversationId, final String participantId, final int statusIfFailed, final int subId, final String transactionId)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 
sendDeferredRespStatus(final String messageId, final String transactionId, final String contentLocation, final int subId)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 
ProcessDownloadedMmsAction()197     private ProcessDownloadedMmsAction() {
198         // Callers must use one of the static methods above
199     }
200 
201     @Override
executeAction()202     protected Object executeAction() {
203         // Fire up the background worker
204         requestBackgroundWork();
205         return null;
206     }
207 
208     @Override
doBackgroundWork()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
processBackgroundResponse(final Bundle response)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
processBackgroundFailure()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 
processResult(final int status, final int rawStatus, final Uri mmsUri)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 
ProcessDownloadedMmsAction(final Parcel in)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
writeToParcel(final Parcel parcel, final int flags)570     public void writeToParcel(final Parcel parcel, final int flags) {
571         writeActionToParcel(parcel, flags);
572     }
573 }
574