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