blob: f408e470bfd8e202c9346ee49f4479442b625cbb [file] [log] [blame]
/*
* 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);
}
}