• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             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 
ProcessSentMessageAction(final Parcel in)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
writeToParcel(final Parcel parcel, final int flags)307     public void writeToParcel(final Parcel parcel, final int flags) {
308         writeActionToParcel(parcel, flags);
309     }
310 }
311