diff options
Diffstat (limited to 'src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java')
-rw-r--r-- | src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java b/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java new file mode 100644 index 0000000..f408e47 --- /dev/null +++ b/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.datamodel.action; + +import android.app.Activity; +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.PhoneNumberUtils; +import android.telephony.SmsManager; + +import com.android.messaging.Factory; +import com.android.messaging.datamodel.BugleDatabaseOperations; +import com.android.messaging.datamodel.BugleNotifications; +import com.android.messaging.datamodel.DataModel; +import com.android.messaging.datamodel.DatabaseWrapper; +import com.android.messaging.datamodel.MmsFileProvider; +import com.android.messaging.datamodel.data.MessageData; +import com.android.messaging.datamodel.data.MessagePartData; +import com.android.messaging.datamodel.data.ParticipantData; +import com.android.messaging.mmslib.pdu.SendConf; +import com.android.messaging.sms.MmsConfig; +import com.android.messaging.sms.MmsSender; +import com.android.messaging.sms.MmsUtils; +import com.android.messaging.util.Assert; +import com.android.messaging.util.LogUtil; + +import java.io.File; +import java.util.ArrayList; + +/** +* Update message status to reflect success or failure +* Can also update the message itself if a "final" message is now available from telephony db +*/ +public class ProcessSentMessageAction extends Action { + private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; + + // These are always set + private static final String KEY_SMS = "is_sms"; + private static final String KEY_SENT_BY_PLATFORM = "sent_by_platform"; + + // These are set when we're processing a message sent by the user. They are null for messages + // sent automatically (e.g. a NotifyRespInd/AcknowledgeInd sent in response to a download). + private static final String KEY_MESSAGE_ID = "message_id"; + private static final String KEY_MESSAGE_URI = "message_uri"; + private static final String KEY_UPDATED_MESSAGE_URI = "updated_message_uri"; + private static final String KEY_SUB_ID = "sub_id"; + + // These are set for messages sent by the platform (L+) + public static final String KEY_RESULT_CODE = "result_code"; + public static final String KEY_HTTP_STATUS_CODE = "http_status_code"; + private static final String KEY_CONTENT_URI = "content_uri"; + private static final String KEY_RESPONSE = "response"; + private static final String KEY_RESPONSE_IMPORTANT = "response_important"; + + // These are set for messages we sent ourself (legacy), or which we fast-failed before sending. + private static final String KEY_STATUS = "status"; + private static final String KEY_RAW_STATUS = "raw_status"; + + // This is called when MMS lib API returns via PendingIntent + public static void processMmsSent(final int resultCode, final Uri messageUri, + final Bundle extras) { + final ProcessSentMessageAction action = new ProcessSentMessageAction(); + final Bundle params = action.actionParameters; + params.putBoolean(KEY_SMS, false); + params.putBoolean(KEY_SENT_BY_PLATFORM, true); + params.putString(KEY_MESSAGE_ID, extras.getString(SendMessageAction.EXTRA_MESSAGE_ID)); + params.putParcelable(KEY_MESSAGE_URI, messageUri); + params.putParcelable(KEY_UPDATED_MESSAGE_URI, + extras.getParcelable(SendMessageAction.EXTRA_UPDATED_MESSAGE_URI)); + params.putInt(KEY_SUB_ID, + extras.getInt(SendMessageAction.KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID)); + params.putInt(KEY_RESULT_CODE, resultCode); + params.putInt(KEY_HTTP_STATUS_CODE, extras.getInt(SmsManager.EXTRA_MMS_HTTP_STATUS, 0)); + params.putParcelable(KEY_CONTENT_URI, + extras.getParcelable(SendMessageAction.EXTRA_CONTENT_URI)); + params.putByteArray(KEY_RESPONSE, extras.getByteArray(SmsManager.EXTRA_MMS_DATA)); + params.putBoolean(KEY_RESPONSE_IMPORTANT, + extras.getBoolean(SendMessageAction.EXTRA_RESPONSE_IMPORTANT)); + action.start(); + } + + public static void processMessageSentFastFailed(final String messageId, + final Uri messageUri, final Uri updatedMessageUri, final int subId, final boolean isSms, + final int status, final int rawStatus, final int resultCode) { + final ProcessSentMessageAction action = new ProcessSentMessageAction(); + final Bundle params = action.actionParameters; + params.putBoolean(KEY_SMS, isSms); + params.putBoolean(KEY_SENT_BY_PLATFORM, false); + params.putString(KEY_MESSAGE_ID, messageId); + params.putParcelable(KEY_MESSAGE_URI, messageUri); + params.putParcelable(KEY_UPDATED_MESSAGE_URI, updatedMessageUri); + params.putInt(KEY_SUB_ID, subId); + params.putInt(KEY_STATUS, status); + params.putInt(KEY_RAW_STATUS, rawStatus); + params.putInt(KEY_RESULT_CODE, resultCode); + action.start(); + } + + private ProcessSentMessageAction() { + // Callers must use one of the static methods above + } + + /** + * Update message status to reflect success or failure + * Can also update the message itself if a "final" message is now available from telephony db + */ + @Override + protected Object executeAction() { + final Context context = Factory.get().getApplicationContext(); + final String messageId = actionParameters.getString(KEY_MESSAGE_ID); + final Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI); + final Uri updatedMessageUri = actionParameters.getParcelable(KEY_UPDATED_MESSAGE_URI); + final boolean isSms = actionParameters.getBoolean(KEY_SMS); + final boolean sentByPlatform = actionParameters.getBoolean(KEY_SENT_BY_PLATFORM); + + int status = actionParameters.getInt(KEY_STATUS, MmsUtils.MMS_REQUEST_MANUAL_RETRY); + int rawStatus = actionParameters.getInt(KEY_RAW_STATUS, + MmsUtils.PDU_HEADER_VALUE_UNDEFINED); + final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID); + + if (sentByPlatform) { + // Delete temporary file backing the contentUri passed to MMS service + final Uri contentUri = actionParameters.getParcelable(KEY_CONTENT_URI); + Assert.isTrue(contentUri != null); + final File tempFile = MmsFileProvider.getFile(contentUri); + long messageSize = 0; + if (tempFile.exists()) { + messageSize = tempFile.length(); + tempFile.delete(); + if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { + LogUtil.v(TAG, "ProcessSentMessageAction: Deleted temp file with outgoing " + + "MMS pdu: " + contentUri); + } + } + + final int resultCode = actionParameters.getInt(KEY_RESULT_CODE); + final boolean responseImportant = actionParameters.getBoolean(KEY_RESPONSE_IMPORTANT); + if (resultCode == Activity.RESULT_OK) { + if (responseImportant) { + // Get the status from the response PDU and update telephony + final byte[] response = actionParameters.getByteArray(KEY_RESPONSE); + final SendConf sendConf = MmsSender.parseSendConf(response, subId); + if (sendConf != null) { + final MmsUtils.StatusPlusUri result = + MmsUtils.updateSentMmsMessageStatus(context, messageUri, sendConf); + status = result.status; + rawStatus = result.rawStatus; + } + } + } else { + String errorMsg = "ProcessSentMessageAction: Platform returned error resultCode: " + + resultCode; + final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE); + if (httpStatusCode != 0) { + errorMsg += (", HTTP status code: " + httpStatusCode); + } + LogUtil.w(TAG, errorMsg); + status = MmsSender.getErrorResultStatus(resultCode, httpStatusCode); + + // Check for MMS messages that failed because they exceeded the maximum size, + // indicated by an I/O error from the platform. + if (resultCode == SmsManager.MMS_ERROR_IO_ERROR) { + if (messageSize > MmsConfig.get(subId).getMaxMessageSize()) { + rawStatus = MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG; + } + } + } + } + if (messageId != null) { + final int resultCode = actionParameters.getInt(KEY_RESULT_CODE); + final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE); + processResult( + messageId, updatedMessageUri, status, rawStatus, isSms, this, subId, + resultCode, httpStatusCode); + } else { + if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { + LogUtil.v(TAG, "ProcessSentMessageAction: No sent message to process (it was " + + "probably a notify response for an MMS download)"); + } + } + return null; + } + + static void processResult(final String messageId, Uri updatedMessageUri, int status, + final int rawStatus, final boolean isSms, final Action processingAction, + final int subId, final int resultCode, final int httpStatusCode) { + final DatabaseWrapper db = DataModel.get().getDatabase(); + MessageData message = BugleDatabaseOperations.readMessage(db, messageId); + final MessageData originalMessage = message; + if (message == null) { + LogUtil.w(TAG, "ProcessSentMessageAction: Sent message " + messageId + + " missing from local database"); + return; + } + final String conversationId = message.getConversationId(); + if (updatedMessageUri != null) { + // Update message if we have newly written final message in the telephony db + final MessageData update = MmsUtils.readSendingMmsMessage(updatedMessageUri, + conversationId, message.getParticipantId(), message.getSelfId()); + if (update != null) { + // Set message Id of final message to that of the existing place holder. + update.updateMessageId(message.getMessageId()); + // Update image sizes. + update.updateSizesForImageParts(); + // Temp attachments are no longer needed + for (final MessagePartData part : message.getParts()) { + part.destroySync(); + } + message = update; + // processResult will rewrite the complete message as part of update + } else { + updatedMessageUri = null; + status = MmsUtils.MMS_REQUEST_MANUAL_RETRY; + LogUtil.e(TAG, "ProcessSentMessageAction: Unable to read sending message"); + } + } + + final long timestamp = System.currentTimeMillis(); + boolean failed; + if (status == MmsUtils.MMS_REQUEST_SUCCEEDED) { + message.markMessageSent(timestamp); + failed = false; + } else if (status == MmsUtils.MMS_REQUEST_AUTO_RETRY + && message.getInResendWindow(timestamp)) { + message.markMessageNotSent(timestamp); + message.setRawTelephonyStatus(rawStatus); + failed = false; + } else { + message.markMessageFailed(timestamp); + message.setRawTelephonyStatus(rawStatus); + message.setMessageSeen(false); + failed = true; + } + + // We have special handling for when a message to an emergency number fails. In this case, + // we notify immediately of any failure (even if we auto-retry), and instruct the user to + // try calling the emergency number instead. + if (status != MmsUtils.MMS_REQUEST_SUCCEEDED) { + final ArrayList<String> recipients = + BugleDatabaseOperations.getRecipientsForConversation(db, conversationId); + for (final String recipient : recipients) { + if (PhoneNumberUtils.isEmergencyNumber(recipient)) { + BugleNotifications.notifyEmergencySmsFailed(recipient, conversationId); + message.markMessageFailedEmergencyNumber(timestamp); + failed = true; + break; + } + } + } + + // Update the message status and optionally refresh the message with final parts/values. + if (SendMessageAction.updateMessageAndStatus(isSms, message, updatedMessageUri, failed)) { + // We shouldn't show any notifications if we're not allowed to modify Telephony for + // this message. + if (failed) { + BugleNotifications.update(false, BugleNotifications.UPDATE_ERRORS); + } + BugleActionToasts.onSendMessageOrManualDownloadActionCompleted( + conversationId, !failed, status, isSms, subId, true/*isSend*/); + } + + LogUtil.i(TAG, "ProcessSentMessageAction: Done sending " + (isSms ? "SMS" : "MMS") + + " message " + message.getMessageId() + + " in conversation " + conversationId + + "; status is " + MmsUtils.getRequestStatusDescription(status)); + + // Whether we succeeded or failed we will check and maybe schedule some more work + ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction( + status != MmsUtils.MMS_REQUEST_SUCCEEDED, processingAction); + } + + private ProcessSentMessageAction(final Parcel in) { + super(in); + } + + public static final Parcelable.Creator<ProcessSentMessageAction> CREATOR + = new Parcelable.Creator<ProcessSentMessageAction>() { + @Override + public ProcessSentMessageAction createFromParcel(final Parcel in) { + return new ProcessSentMessageAction(in); + } + + @Override + public ProcessSentMessageAction[] newArray(final int size) { + return new ProcessSentMessageAction[size]; + } + }; + + @Override + public void writeToParcel(final Parcel parcel, final int flags) { + writeActionToParcel(parcel, flags); + } +} |