| /* |
| * 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); |
| } |
| } |