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.sms;
18 
19 import android.app.Activity;
20 import android.app.PendingIntent;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import androidx.appcompat.mms.MmsManager;
26 import android.telephony.SmsManager;
27 
28 import com.android.messaging.datamodel.MmsFileProvider;
29 import com.android.messaging.datamodel.action.SendMessageAction;
30 import com.android.messaging.datamodel.data.MessageData;
31 import com.android.messaging.mmslib.InvalidHeaderValueException;
32 import com.android.messaging.mmslib.pdu.AcknowledgeInd;
33 import com.android.messaging.mmslib.pdu.EncodedStringValue;
34 import com.android.messaging.mmslib.pdu.GenericPdu;
35 import com.android.messaging.mmslib.pdu.NotifyRespInd;
36 import com.android.messaging.mmslib.pdu.PduComposer;
37 import com.android.messaging.mmslib.pdu.PduHeaders;
38 import com.android.messaging.mmslib.pdu.PduParser;
39 import com.android.messaging.mmslib.pdu.RetrieveConf;
40 import com.android.messaging.mmslib.pdu.SendConf;
41 import com.android.messaging.mmslib.pdu.SendReq;
42 import com.android.messaging.receiver.SendStatusReceiver;
43 import com.android.messaging.util.Assert;
44 import com.android.messaging.util.LogUtil;
45 import com.android.messaging.util.PhoneUtils;
46 
47 import java.io.File;
48 import java.io.FileOutputStream;
49 import java.io.IOException;
50 
51 /**
52  * Class that sends chat message via MMS.
53  *
54  * The interface emulates a blocking send similar to making an HTTP request.
55  */
56 public class MmsSender {
57     private static final String TAG = LogUtil.BUGLE_TAG;
58 
59     /**
60      * Send an MMS message.
61      *
62      * @param context Context
63      * @param messageUri The unique URI of the message for identifying it during sending
64      * @param sendReq The SendReq PDU of the message
65      * @throws MmsFailureException
66      */
sendMms(final Context context, final int subId, final Uri messageUri, final SendReq sendReq, final Bundle sentIntentExras)67     public static void sendMms(final Context context, final int subId, final Uri messageUri,
68             final SendReq sendReq, final Bundle sentIntentExras) throws MmsFailureException {
69         sendMms(context,
70                 subId,
71                 messageUri,
72                 null /* locationUrl */,
73                 sendReq,
74                 true /* responseImportant */,
75                 sentIntentExras);
76     }
77 
78     /**
79      * Send NotifyRespInd (response to mms auto download).
80      *
81      * @param context Context
82      * @param subId subscription to use to send the response
83      * @param transactionId The transaction id of the MMS message
84      * @param contentLocation The url of the MMS message
85      * @param status The status to send with the NotifyRespInd
86      * @throws MmsFailureException
87      * @throws InvalidHeaderValueException
88      */
sendNotifyResponseForMmsDownload(final Context context, final int subId, final byte[] transactionId, final String contentLocation, final int status)89     public static void sendNotifyResponseForMmsDownload(final Context context, final int subId,
90             final byte[] transactionId, final String contentLocation, final int status)
91             throws MmsFailureException, InvalidHeaderValueException {
92         // Create the M-NotifyResp.ind
93         final NotifyRespInd notifyRespInd = new NotifyRespInd(
94                 PduHeaders.CURRENT_MMS_VERSION, transactionId, status);
95         final Uri messageUri = Uri.parse(contentLocation);
96         // Pack M-NotifyResp.ind and send it
97         sendMms(context,
98                 subId,
99                 messageUri,
100                 MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
101                 notifyRespInd,
102                 false /* responseImportant */,
103                 null /* sentIntentExtras */);
104     }
105 
106     /**
107      * Send AcknowledgeInd (response to mms manual download). Ignore failures.
108      *
109      * @param context Context
110      * @param subId The SIM's subId we are currently using
111      * @param transactionId The transaction id of the MMS message
112      * @param contentLocation The url of the MMS message
113      * @throws MmsFailureException
114      * @throws InvalidHeaderValueException
115      */
sendAcknowledgeForMmsDownload(final Context context, final int subId, final byte[] transactionId, final String contentLocation)116     public static void sendAcknowledgeForMmsDownload(final Context context, final int subId,
117             final byte[] transactionId, final String contentLocation)
118             throws MmsFailureException, InvalidHeaderValueException {
119         final String selfNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/);
120         // Create the M-Acknowledge.ind
121         final AcknowledgeInd acknowledgeInd = new AcknowledgeInd(PduHeaders.CURRENT_MMS_VERSION,
122                 transactionId);
123         acknowledgeInd.setFrom(new EncodedStringValue(selfNumber));
124         final Uri messageUri = Uri.parse(contentLocation);
125         // Sending
126         sendMms(context,
127                 subId,
128                 messageUri,
129                 MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
130                 acknowledgeInd,
131                 false /*responseImportant*/,
132                 null /* sentIntentExtras */);
133     }
134 
135     /**
136      * Send a generic PDU.
137      *
138      * @param context Context
139      * @param messageUri The unique URI of the message for identifying it during sending
140      * @param locationUrl The optional URL to send to
141      * @param pdu The PDU to send
142      * @param responseImportant If the sending response is important. Responses to the
143      * Sending of AcknowledgeInd and NotifyRespInd are not important.
144      * @throws MmsFailureException
145      */
sendMms(final Context context, final int subId, final Uri messageUri, final String locationUrl, final GenericPdu pdu, final boolean responseImportant, final Bundle sentIntentExtras)146     private static void sendMms(final Context context, final int subId, final Uri messageUri,
147             final String locationUrl, final GenericPdu pdu, final boolean responseImportant,
148             final Bundle sentIntentExtras) throws MmsFailureException {
149         // Write PDU to temporary file to send to platform
150         final Uri contentUri = writePduToTempFile(context, pdu, subId);
151 
152         // Construct PendingIntent that will notify us when message sending is complete
153         final Intent sentIntent = new Intent(SendStatusReceiver.MMS_SENT_ACTION,
154                 messageUri,
155                 context,
156                 SendStatusReceiver.class);
157         sentIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
158         sentIntent.putExtra(SendMessageAction.EXTRA_RESPONSE_IMPORTANT, responseImportant);
159         if (sentIntentExtras != null) {
160             sentIntent.putExtras(sentIntentExtras);
161         }
162         final PendingIntent sentPendingIntent = PendingIntent.getBroadcast(
163                 context,
164                 0 /*request code*/,
165                 sentIntent,
166                 PendingIntent.FLAG_UPDATE_CURRENT);
167 
168         // Send the message
169         MmsManager.sendMultimediaMessage(subId, context, contentUri, locationUrl,
170                 sentPendingIntent);
171     }
172 
writePduToTempFile(final Context context, final GenericPdu pdu, int subId)173     private static Uri writePduToTempFile(final Context context, final GenericPdu pdu, int subId)
174             throws MmsFailureException {
175         final Uri contentUri = MmsFileProvider.buildRawMmsUri();
176         final File tempFile = MmsFileProvider.getFile(contentUri);
177         FileOutputStream writer = null;
178         try {
179             // Ensure rawmms directory exists
180             tempFile.getParentFile().mkdirs();
181             writer = new FileOutputStream(tempFile);
182             final byte[] pduBytes = new PduComposer(context, pdu).make();
183             if (pduBytes == null) {
184                 throw new MmsFailureException(
185                         MmsUtils.MMS_REQUEST_NO_RETRY, "Failed to compose PDU");
186             }
187             if (pduBytes.length > MmsConfig.get(subId).getMaxMessageSize()) {
188                 throw new MmsFailureException(
189                         MmsUtils.MMS_REQUEST_NO_RETRY,
190                         MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
191             }
192             writer.write(pduBytes);
193         } catch (final IOException e) {
194             if (tempFile != null) {
195                 tempFile.delete();
196             }
197             LogUtil.e(TAG, "Cannot create temporary file " + tempFile.getAbsolutePath(), e);
198             throw new MmsFailureException(
199                     MmsUtils.MMS_REQUEST_AUTO_RETRY, "Cannot create raw mms file");
200         } catch (final OutOfMemoryError e) {
201             if (tempFile != null) {
202                 tempFile.delete();
203             }
204             LogUtil.e(TAG, "Out of memory in composing PDU", e);
205             throw new MmsFailureException(
206                     MmsUtils.MMS_REQUEST_MANUAL_RETRY,
207                     MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
208         } finally {
209             if (writer != null) {
210                 try {
211                     writer.close();
212                 } catch (final IOException e) {
213                     // no action we can take here
214                 }
215             }
216         }
217         return contentUri;
218     }
219 
parseSendConf(byte[] response, int subId)220     public static SendConf parseSendConf(byte[] response, int subId) {
221         if (response != null) {
222             final GenericPdu respPdu = new PduParser(
223                     response, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
224             if (respPdu != null) {
225                 if (respPdu instanceof SendConf) {
226                     return (SendConf) respPdu;
227                 } else {
228                     LogUtil.e(TAG, "MmsSender: send response not SendConf");
229                 }
230             } else {
231                 // Invalid PDU
232                 LogUtil.e(TAG, "MmsSender: send invalid response");
233             }
234         }
235         // Empty or invalid response
236         return null;
237     }
238 
239     /**
240      * Download an MMS message.
241      *
242      * @param context Context
243      * @param contentLocation The url of the MMS message
244      * @throws MmsFailureException
245      * @throws InvalidHeaderValueException
246      */
downloadMms(final Context context, final int subId, final String contentLocation, Bundle extras)247     public static void downloadMms(final Context context, final int subId,
248             final String contentLocation, Bundle extras) throws MmsFailureException,
249             InvalidHeaderValueException {
250         final Uri requestUri = Uri.parse(contentLocation);
251         final Uri contentUri = MmsFileProvider.buildRawMmsUri();
252 
253         final Intent downloadedIntent = new Intent(SendStatusReceiver.MMS_DOWNLOADED_ACTION,
254                 requestUri,
255                 context,
256                 SendStatusReceiver.class);
257         downloadedIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
258         if (extras != null) {
259             downloadedIntent.putExtras(extras);
260         }
261         final PendingIntent downloadedPendingIntent = PendingIntent.getBroadcast(
262                 context,
263                 0 /*request code*/,
264                 downloadedIntent,
265                 PendingIntent.FLAG_UPDATE_CURRENT);
266 
267         MmsManager.downloadMultimediaMessage(subId, context, contentLocation, contentUri,
268                 downloadedPendingIntent);
269     }
270 
parseRetrieveConf(byte[] data, int subId)271     public static RetrieveConf parseRetrieveConf(byte[] data, int subId) {
272         if (data != null) {
273             final GenericPdu pdu = new PduParser(
274                     data, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
275             if (pdu != null) {
276                 if (pdu instanceof RetrieveConf) {
277                     return (RetrieveConf) pdu;
278                 } else {
279                     LogUtil.e(TAG, "MmsSender: downloaded pdu not RetrieveConf: "
280                             + pdu.getClass().getName());
281                 }
282             } else {
283                 LogUtil.e(TAG, "MmsSender: downloaded pdu could not be parsed (invalid)");
284             }
285         }
286         LogUtil.e(TAG, "MmsSender: downloaded pdu is empty");
287         return null;
288     }
289 
290     // Process different result code from platform MMS service
getErrorResultStatus(int resultCode, int httpStatusCode)291     public static int getErrorResultStatus(int resultCode, int httpStatusCode) {
292         Assert.isFalse(resultCode == Activity.RESULT_OK);
293         switch (resultCode) {
294             case SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS:
295             case SmsManager.MMS_ERROR_IO_ERROR:
296                 return MmsUtils.MMS_REQUEST_AUTO_RETRY;
297             case SmsManager.MMS_ERROR_INVALID_APN:
298             case SmsManager.MMS_ERROR_CONFIGURATION_ERROR:
299             case SmsManager.MMS_ERROR_NO_DATA_NETWORK:
300             case SmsManager.MMS_ERROR_UNSPECIFIED:
301                 return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
302             case SmsManager.MMS_ERROR_HTTP_FAILURE:
303                 if (httpStatusCode == 404) {
304                     return MmsUtils.MMS_REQUEST_NO_RETRY;
305                 } else {
306                     return MmsUtils.MMS_REQUEST_AUTO_RETRY;
307                 }
308             default:
309                 return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
310         }
311     }
312 }
313