Home | History | Annotate | Download | only in action
      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.Context;
     21 import android.net.Uri;
     22 import android.os.Bundle;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 import android.telephony.PhoneNumberUtils;
     26 import android.telephony.SmsManager;
     27 
     28 import com.android.messaging.Factory;
     29 import com.android.messaging.datamodel.BugleDatabaseOperations;
     30 import com.android.messaging.datamodel.BugleNotifications;
     31 import com.android.messaging.datamodel.DataModel;
     32 import com.android.messaging.datamodel.DatabaseWrapper;
     33 import com.android.messaging.datamodel.MmsFileProvider;
     34 import com.android.messaging.datamodel.data.MessageData;
     35 import com.android.messaging.datamodel.data.MessagePartData;
     36 import com.android.messaging.datamodel.data.ParticipantData;
     37 import com.android.messaging.mmslib.pdu.SendConf;
     38 import com.android.messaging.sms.MmsConfig;
     39 import com.android.messaging.sms.MmsSender;
     40 import com.android.messaging.sms.MmsUtils;
     41 import com.android.messaging.util.Assert;
     42 import com.android.messaging.util.LogUtil;
     43 
     44 import java.io.File;
     45 import java.util.ArrayList;
     46 
     47 /**
     48 * Update message status to reflect success or failure
     49 * Can also update the message itself if a "final" message is now available from telephony db
     50 */
     51 public class ProcessSentMessageAction extends Action {
     52     private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
     53 
     54     // These are always set
     55     private static final String KEY_SMS = "is_sms";
     56     private static final String KEY_SENT_BY_PLATFORM = "sent_by_platform";
     57 
     58     // These are set when we're processing a message sent by the user. They are null for messages
     59     // sent automatically (e.g. a NotifyRespInd/AcknowledgeInd sent in response to a download).
     60     private static final String KEY_MESSAGE_ID = "message_id";
     61     private static final String KEY_MESSAGE_URI = "message_uri";
     62     private static final String KEY_UPDATED_MESSAGE_URI = "updated_message_uri";
     63     private static final String KEY_SUB_ID = "sub_id";
     64 
     65     // These are set for messages sent by the platform (L+)
     66     public static final String KEY_RESULT_CODE = "result_code";
     67     public static final String KEY_HTTP_STATUS_CODE = "http_status_code";
     68     private static final String KEY_CONTENT_URI = "content_uri";
     69     private static final String KEY_RESPONSE = "response";
     70     private static final String KEY_RESPONSE_IMPORTANT = "response_important";
     71 
     72     // These are set for messages we sent ourself (legacy), or which we fast-failed before sending.
     73     private static final String KEY_STATUS = "status";
     74     private static final String KEY_RAW_STATUS = "raw_status";
     75 
     76     // This is called when MMS lib API returns via PendingIntent
     77     public static void processMmsSent(final int resultCode, final Uri messageUri,
     78             final Bundle extras) {
     79         final ProcessSentMessageAction action = new ProcessSentMessageAction();
     80         final Bundle params = action.actionParameters;
     81         params.putBoolean(KEY_SMS, false);
     82         params.putBoolean(KEY_SENT_BY_PLATFORM, true);
     83         params.putString(KEY_MESSAGE_ID, extras.getString(SendMessageAction.EXTRA_MESSAGE_ID));
     84         params.putParcelable(KEY_MESSAGE_URI, messageUri);
     85         params.putParcelable(KEY_UPDATED_MESSAGE_URI,
     86                 extras.getParcelable(SendMessageAction.EXTRA_UPDATED_MESSAGE_URI));
     87         params.putInt(KEY_SUB_ID,
     88                 extras.getInt(SendMessageAction.KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID));
     89         params.putInt(KEY_RESULT_CODE, resultCode);
     90         params.putInt(KEY_HTTP_STATUS_CODE, extras.getInt(SmsManager.EXTRA_MMS_HTTP_STATUS, 0));
     91         params.putParcelable(KEY_CONTENT_URI,
     92                 extras.getParcelable(SendMessageAction.EXTRA_CONTENT_URI));
     93         params.putByteArray(KEY_RESPONSE, extras.getByteArray(SmsManager.EXTRA_MMS_DATA));
     94         params.putBoolean(KEY_RESPONSE_IMPORTANT,
     95                 extras.getBoolean(SendMessageAction.EXTRA_RESPONSE_IMPORTANT));
     96         action.start();
     97     }
     98 
     99     public static void processMessageSentFastFailed(final String messageId,
    100             final Uri messageUri, final Uri updatedMessageUri, final int subId, final boolean isSms,
    101             final int status, final int rawStatus, final int resultCode) {
    102         final ProcessSentMessageAction action = new ProcessSentMessageAction();
    103         final Bundle params = action.actionParameters;
    104         params.putBoolean(KEY_SMS, isSms);
    105         params.putBoolean(KEY_SENT_BY_PLATFORM, false);
    106         params.putString(KEY_MESSAGE_ID, messageId);
    107         params.putParcelable(KEY_MESSAGE_URI, messageUri);
    108         params.putParcelable(KEY_UPDATED_MESSAGE_URI, updatedMessageUri);
    109         params.putInt(KEY_SUB_ID, subId);
    110         params.putInt(KEY_STATUS, status);
    111         params.putInt(KEY_RAW_STATUS, rawStatus);
    112         params.putInt(KEY_RESULT_CODE, resultCode);
    113         action.start();
    114     }
    115 
    116     private ProcessSentMessageAction() {
    117         // Callers must use one of the static methods above
    118     }
    119 
    120     /**
    121     * Update message status to reflect success or failure
    122     * Can also update the message itself if a "final" message is now available from telephony db
    123     */
    124     @Override
    125     protected Object executeAction() {
    126         final Context context = Factory.get().getApplicationContext();
    127         final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
    128         final Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI);
    129         final Uri updatedMessageUri = actionParameters.getParcelable(KEY_UPDATED_MESSAGE_URI);
    130         final boolean isSms = actionParameters.getBoolean(KEY_SMS);
    131         final boolean sentByPlatform = actionParameters.getBoolean(KEY_SENT_BY_PLATFORM);
    132 
    133         int status = actionParameters.getInt(KEY_STATUS, MmsUtils.MMS_REQUEST_MANUAL_RETRY);
    134         int rawStatus = actionParameters.getInt(KEY_RAW_STATUS,
    135                 MmsUtils.PDU_HEADER_VALUE_UNDEFINED);
    136         final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
    137 
    138         if (sentByPlatform) {
    139             // Delete temporary file backing the contentUri passed to MMS service
    140             final Uri contentUri = actionParameters.getParcelable(KEY_CONTENT_URI);
    141             Assert.isTrue(contentUri != null);
    142             final File tempFile = MmsFileProvider.getFile(contentUri);
    143             long messageSize = 0;
    144             if (tempFile.exists()) {
    145                 messageSize = tempFile.length();
    146                 tempFile.delete();
    147                 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
    148                     LogUtil.v(TAG, "ProcessSentMessageAction: Deleted temp file with outgoing "
    149                             + "MMS pdu: " + contentUri);
    150                 }
    151             }
    152 
    153             final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
    154             final boolean responseImportant = actionParameters.getBoolean(KEY_RESPONSE_IMPORTANT);
    155             if (resultCode == Activity.RESULT_OK) {
    156                 if (responseImportant) {
    157                     // Get the status from the response PDU and update telephony
    158                     final byte[] response = actionParameters.getByteArray(KEY_RESPONSE);
    159                     final SendConf sendConf = MmsSender.parseSendConf(response, subId);
    160                     if (sendConf != null) {
    161                         final MmsUtils.StatusPlusUri result =
    162                                 MmsUtils.updateSentMmsMessageStatus(context, messageUri, sendConf);
    163                         status = result.status;
    164                         rawStatus = result.rawStatus;
    165                     }
    166                 }
    167             } else {
    168                 String errorMsg = "ProcessSentMessageAction: Platform returned error resultCode: "
    169                         + resultCode;
    170                 final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
    171                 if (httpStatusCode != 0) {
    172                     errorMsg += (", HTTP status code: " + httpStatusCode);
    173                 }
    174                 LogUtil.w(TAG, errorMsg);
    175                 status = MmsSender.getErrorResultStatus(resultCode, httpStatusCode);
    176 
    177                 // Check for MMS messages that failed because they exceeded the maximum size,
    178                 // indicated by an I/O error from the platform.
    179                 if (resultCode == SmsManager.MMS_ERROR_IO_ERROR) {
    180                     if (messageSize > MmsConfig.get(subId).getMaxMessageSize()) {
    181                         rawStatus = MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG;
    182                     }
    183                 }
    184             }
    185         }
    186         if (messageId != null) {
    187             final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
    188             final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
    189             processResult(
    190                     messageId, updatedMessageUri, status, rawStatus, isSms, this, subId,
    191                     resultCode, httpStatusCode);
    192         } else {
    193             if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
    194                 LogUtil.v(TAG, "ProcessSentMessageAction: No sent message to process (it was "
    195                         + "probably a notify response for an MMS download)");
    196             }
    197         }
    198         return null;
    199     }
    200 
    201     static void processResult(final String messageId, Uri updatedMessageUri, int status,
    202             final int rawStatus, final boolean isSms, final Action processingAction,
    203             final int subId, final int resultCode, final int httpStatusCode) {
    204         final DatabaseWrapper db = DataModel.get().getDatabase();
    205         MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
    206         final MessageData originalMessage = message;
    207         if (message == null) {
    208             LogUtil.w(TAG, "ProcessSentMessageAction: Sent message " + messageId
    209                     + " missing from local database");
    210             return;
    211         }
    212         final String conversationId = message.getConversationId();
    213         if (updatedMessageUri != null) {
    214             // Update message if we have newly written final message in the telephony db
    215             final MessageData update = MmsUtils.readSendingMmsMessage(updatedMessageUri,
    216                     conversationId, message.getParticipantId(), message.getSelfId());
    217             if (update != null) {
    218                 // Set message Id of final message to that of the existing place holder.
    219                 update.updateMessageId(message.getMessageId());
    220                 // Update image sizes.
    221                 update.updateSizesForImageParts();
    222                 // Temp attachments are no longer needed
    223                 for (final MessagePartData part : message.getParts()) {
    224                     part.destroySync();
    225                 }
    226                 message = update;
    227                 // processResult will rewrite the complete message as part of update
    228             } else {
    229                 updatedMessageUri = null;
    230                 status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
    231                 LogUtil.e(TAG, "ProcessSentMessageAction: Unable to read sending message");
    232             }
    233         }
    234 
    235         final long timestamp = System.currentTimeMillis();
    236         boolean failed;
    237         if (status == MmsUtils.MMS_REQUEST_SUCCEEDED) {
    238             message.markMessageSent(timestamp);
    239             failed = false;
    240         } else if (status == MmsUtils.MMS_REQUEST_AUTO_RETRY
    241                 && message.getInResendWindow(timestamp)) {
    242             message.markMessageNotSent(timestamp);
    243             message.setRawTelephonyStatus(rawStatus);
    244             failed = false;
    245         } else {
    246             message.markMessageFailed(timestamp);
    247             message.setRawTelephonyStatus(rawStatus);
    248             message.setMessageSeen(false);
    249             failed = true;
    250         }
    251 
    252         // We have special handling for when a message to an emergency number fails. In this case,
    253         // we notify immediately of any failure (even if we auto-retry), and instruct the user to
    254         // try calling the emergency number instead.
    255         if (status != MmsUtils.MMS_REQUEST_SUCCEEDED) {
    256             final ArrayList<String> recipients =
    257                     BugleDatabaseOperations.getRecipientsForConversation(db, conversationId);
    258             for (final String recipient : recipients) {
    259                 if (PhoneNumberUtils.isEmergencyNumber(recipient)) {
    260                     BugleNotifications.notifyEmergencySmsFailed(recipient, conversationId);
    261                     message.markMessageFailedEmergencyNumber(timestamp);
    262                     failed = true;
    263                     break;
    264                 }
    265             }
    266         }
    267 
    268         // Update the message status and optionally refresh the message with final parts/values.
    269         if (SendMessageAction.updateMessageAndStatus(isSms, message, updatedMessageUri, failed)) {
    270             // We shouldn't show any notifications if we're not allowed to modify Telephony for
    271             // this message.
    272             if (failed) {
    273                 BugleNotifications.update(false, BugleNotifications.UPDATE_ERRORS);
    274             }
    275             BugleActionToasts.onSendMessageOrManualDownloadActionCompleted(
    276                     conversationId, !failed, status, isSms, subId, true/*isSend*/);
    277         }
    278 
    279         LogUtil.i(TAG, "ProcessSentMessageAction: Done sending " + (isSms ? "SMS" : "MMS")
    280                 + " message " + message.getMessageId()
    281                 + " in conversation " + conversationId
    282                 + "; status is " + MmsUtils.getRequestStatusDescription(status));
    283 
    284         // Whether we succeeded or failed we will check and maybe schedule some more work
    285         ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(
    286                 status != MmsUtils.MMS_REQUEST_SUCCEEDED, processingAction);
    287     }
    288 
    289     private ProcessSentMessageAction(final Parcel in) {
    290         super(in);
    291     }
    292 
    293     public static final Parcelable.Creator<ProcessSentMessageAction> CREATOR
    294             = new Parcelable.Creator<ProcessSentMessageAction>() {
    295         @Override
    296         public ProcessSentMessageAction createFromParcel(final Parcel in) {
    297             return new ProcessSentMessageAction(in);
    298         }
    299 
    300         @Override
    301         public ProcessSentMessageAction[] newArray(final int size) {
    302             return new ProcessSentMessageAction[size];
    303         }
    304     };
    305 
    306     @Override
    307     public void writeToParcel(final Parcel parcel, final int flags) {
    308         writeActionToParcel(parcel, flags);
    309     }
    310 }
    311