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
processMmsSent(final int resultCode, final Uri messageUri, final Bundle extras)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 
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)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 
ProcessSentMessageAction()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
executeAction()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 
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)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             ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(
211                     true /* failed */, processingAction);
212             return;
213         }
214         final String conversationId = message.getConversationId();
215         if (updatedMessageUri != null) {
216             // Update message if we have newly written final message in the telephony db
217             final MessageData update = MmsUtils.readSendingMmsMessage(updatedMessageUri,
218                     conversationId, message.getParticipantId(), message.getSelfId());
219             if (update != null) {
220                 // Set message Id of final message to that of the existing place holder.
221                 update.updateMessageId(message.getMessageId());
222                 // Update image sizes.
223                 update.updateSizesForImageParts();
224                 // Temp attachments are no longer needed
225                 for (final MessagePartData part : message.getParts()) {
226                     part.destroySync();
227                 }
228                 message = update;
229                 // processResult will rewrite the complete message as part of update
230             } else {
231                 updatedMessageUri = null;
232                 status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
233                 LogUtil.e(TAG, "ProcessSentMessageAction: Unable to read sending message");
234             }
235         }
236 
237         final long timestamp = System.currentTimeMillis();
238         boolean failed;
239         if (status == MmsUtils.MMS_REQUEST_SUCCEEDED) {
240             message.markMessageSent(timestamp);
241             failed = false;
242         } else if (status == MmsUtils.MMS_REQUEST_AUTO_RETRY
243                 && message.getInResendWindow(timestamp)) {
244             message.markMessageNotSent(timestamp);
245             message.setRawTelephonyStatus(rawStatus);
246             failed = false;
247         } else {
248             message.markMessageFailed(timestamp);
249             message.setRawTelephonyStatus(rawStatus);
250             message.setMessageSeen(false);
251             failed = true;
252         }
253 
254         // We have special handling for when a message to an emergency number fails. In this case,
255         // we notify immediately of any failure (even if we auto-retry), and instruct the user to
256         // try calling the emergency number instead.
257         if (status != MmsUtils.MMS_REQUEST_SUCCEEDED) {
258             final ArrayList<String> recipients =
259                     BugleDatabaseOperations.getRecipientsForConversation(db, conversationId);
260             for (final String recipient : recipients) {
261                 if (PhoneNumberUtils.isEmergencyNumber(recipient)) {
262                     BugleNotifications.notifyEmergencySmsFailed(recipient, conversationId);
263                     message.markMessageFailedEmergencyNumber(timestamp);
264                     failed = true;
265                     break;
266                 }
267             }
268         }
269 
270         // Update the message status and optionally refresh the message with final parts/values.
271         if (SendMessageAction.updateMessageAndStatus(isSms, message, updatedMessageUri, failed)) {
272             // We shouldn't show any notifications if we're not allowed to modify Telephony for
273             // this message.
274             if (failed) {
275                 BugleNotifications.update(false, BugleNotifications.UPDATE_ERRORS);
276             }
277             BugleActionToasts.onSendMessageOrManualDownloadActionCompleted(
278                     conversationId, !failed, status, isSms, subId, true/*isSend*/);
279         }
280 
281         LogUtil.i(TAG, "ProcessSentMessageAction: Done sending " + (isSms ? "SMS" : "MMS")
282                 + " message " + message.getMessageId()
283                 + " in conversation " + conversationId
284                 + "; status is " + MmsUtils.getRequestStatusDescription(status));
285 
286         // Whether we succeeded or failed we will check and maybe schedule some more work
287         ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(
288                 status != MmsUtils.MMS_REQUEST_SUCCEEDED, processingAction);
289     }
290 
ProcessSentMessageAction(final Parcel in)291     private ProcessSentMessageAction(final Parcel in) {
292         super(in);
293     }
294 
295     public static final Parcelable.Creator<ProcessSentMessageAction> CREATOR
296             = new Parcelable.Creator<ProcessSentMessageAction>() {
297         @Override
298         public ProcessSentMessageAction createFromParcel(final Parcel in) {
299             return new ProcessSentMessageAction(in);
300         }
301 
302         @Override
303         public ProcessSentMessageAction[] newArray(final int size) {
304             return new ProcessSentMessageAction[size];
305         }
306     };
307 
308     @Override
writeToParcel(final Parcel parcel, final int flags)309     public void writeToParcel(final Parcel parcel, final int flags) {
310         writeActionToParcel(parcel, flags);
311     }
312 }
313