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