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 android.support.v7.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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 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 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