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