1 /* 2 * Copyright (C) 2014 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.mms.service; 18 19 import android.app.Activity; 20 import android.app.PendingIntent; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.net.Uri; 25 import android.os.Binder; 26 import android.os.Bundle; 27 import android.os.RemoteException; 28 import android.provider.Telephony; 29 import android.service.carrier.CarrierMessagingService; 30 import android.service.carrier.ICarrierMessagingService; 31 import android.telephony.CarrierMessagingServiceManager; 32 import android.telephony.PhoneNumberUtils; 33 import android.telephony.SmsManager; 34 import android.text.TextUtils; 35 36 import com.android.internal.telephony.AsyncEmergencyContactNotifier; 37 import com.android.internal.telephony.Phone; 38 import com.android.internal.telephony.PhoneFactory; 39 import com.android.internal.telephony.SmsApplication; 40 import com.android.internal.telephony.SmsNumberUtils; 41 import com.android.mms.service.exception.MmsHttpException; 42 import com.google.android.mms.MmsException; 43 import com.google.android.mms.pdu.EncodedStringValue; 44 import com.google.android.mms.pdu.GenericPdu; 45 import com.google.android.mms.pdu.PduComposer; 46 import com.google.android.mms.pdu.PduHeaders; 47 import com.google.android.mms.pdu.PduParser; 48 import com.google.android.mms.pdu.PduPersister; 49 import com.google.android.mms.pdu.SendConf; 50 import com.google.android.mms.pdu.SendReq; 51 import com.google.android.mms.util.SqliteWrapper; 52 53 /** 54 * Request to send an MMS 55 */ 56 public class SendRequest extends MmsRequest { 57 private final Uri mPduUri; 58 private byte[] mPduData; 59 private final String mLocationUrl; 60 private final PendingIntent mSentIntent; 61 SendRequest(RequestManager manager, int subId, Uri contentUri, String locationUrl, PendingIntent sentIntent, String creator, Bundle configOverrides, Context context)62 public SendRequest(RequestManager manager, int subId, Uri contentUri, String locationUrl, 63 PendingIntent sentIntent, String creator, Bundle configOverrides, Context context) { 64 super(manager, subId, creator, configOverrides, context); 65 mPduUri = contentUri; 66 mPduData = null; 67 mLocationUrl = locationUrl; 68 mSentIntent = sentIntent; 69 } 70 71 @Override doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)72 protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn) 73 throws MmsHttpException { 74 final String requestId = getRequestId(); 75 final MmsHttpClient mmsHttpClient = netMgr.getOrCreateHttpClient(); 76 if (mmsHttpClient == null) { 77 LogUtil.e(requestId, "MMS network is not ready!"); 78 throw new MmsHttpException(0/*statusCode*/, "MMS network is not ready"); 79 } 80 final GenericPdu parsedPdu = parsePdu(); 81 notifyIfEmergencyContactNoThrow(parsedPdu); 82 updateDestinationAddress(parsedPdu); 83 return mmsHttpClient.execute( 84 mLocationUrl != null ? mLocationUrl : apn.getMmscUrl(), 85 mPduData, 86 MmsHttpClient.METHOD_POST, 87 apn.isProxySet(), 88 apn.getProxyAddress(), 89 apn.getProxyPort(), 90 mMmsConfig, 91 mSubId, 92 requestId); 93 } 94 parsePdu()95 private GenericPdu parsePdu() { 96 final String requestId = getRequestId(); 97 try { 98 if (mPduData == null) { 99 LogUtil.w(requestId, "Empty PDU raw data"); 100 return null; 101 } 102 final boolean supportContentDisposition = 103 mMmsConfig.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION); 104 return new PduParser(mPduData, supportContentDisposition).parse(); 105 } catch (final Exception e) { 106 LogUtil.w(requestId, "Failed to parse PDU raw data"); 107 } 108 return null; 109 } 110 111 /** 112 * If the MMS is being sent to an emergency number, the blocked number provider is notified 113 * so that it can disable number blocking. 114 */ notifyIfEmergencyContactNoThrow(final GenericPdu parsedPdu)115 private void notifyIfEmergencyContactNoThrow(final GenericPdu parsedPdu) { 116 try { 117 notifyIfEmergencyContact(parsedPdu); 118 } catch (Exception e) { 119 LogUtil.w(getRequestId(), "Error in notifyIfEmergencyContact", e); 120 } 121 } 122 notifyIfEmergencyContact(final GenericPdu parsedPdu)123 private void notifyIfEmergencyContact(final GenericPdu parsedPdu) { 124 if (parsedPdu != null && parsedPdu.getMessageType() == PduHeaders.MESSAGE_TYPE_SEND_REQ) { 125 SendReq sendReq = (SendReq) parsedPdu; 126 for (EncodedStringValue encodedStringValue : sendReq.getTo()) { 127 if (isEmergencyNumber(encodedStringValue.getString())) { 128 LogUtil.i(getRequestId(), "Notifying emergency contact"); 129 new AsyncEmergencyContactNotifier(mContext).execute(); 130 return; 131 } 132 } 133 } 134 } 135 isEmergencyNumber(String address)136 private boolean isEmergencyNumber(String address) { 137 return !TextUtils.isEmpty(address) && PhoneNumberUtils.isEmergencyNumber(mSubId, address); 138 } 139 140 @Override getPendingIntent()141 protected PendingIntent getPendingIntent() { 142 return mSentIntent; 143 } 144 145 @Override getQueueType()146 protected int getQueueType() { 147 return MmsService.QUEUE_INDEX_SEND; 148 } 149 150 @Override persistIfRequired(Context context, int result, byte[] response)151 protected Uri persistIfRequired(Context context, int result, byte[] response) { 152 final String requestId = getRequestId(); 153 if (!SmsApplication.shouldWriteMessageForPackage(mCreator, context)) { 154 // Not required to persist 155 return null; 156 } 157 LogUtil.d(requestId, "persistIfRequired"); 158 if (mPduData == null) { 159 LogUtil.e(requestId, "persistIfRequired: empty PDU"); 160 return null; 161 } 162 final long identity = Binder.clearCallingIdentity(); 163 try { 164 final boolean supportContentDisposition = 165 mMmsConfig.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION); 166 // Persist the request PDU first 167 GenericPdu pdu = (new PduParser(mPduData, supportContentDisposition)).parse(); 168 if (pdu == null) { 169 LogUtil.e(requestId, "persistIfRequired: can't parse input PDU"); 170 return null; 171 } 172 if (!(pdu instanceof SendReq)) { 173 LogUtil.d(requestId, "persistIfRequired: not SendReq"); 174 return null; 175 } 176 final PduPersister persister = PduPersister.getPduPersister(context); 177 final Uri messageUri = persister.persist( 178 pdu, 179 Telephony.Mms.Sent.CONTENT_URI, 180 true/*createThreadId*/, 181 true/*groupMmsEnabled*/, 182 null/*preOpenedFiles*/); 183 if (messageUri == null) { 184 LogUtil.e(requestId, "persistIfRequired: can not persist message"); 185 return null; 186 } 187 // Update the additional columns based on the send result 188 final ContentValues values = new ContentValues(); 189 SendConf sendConf = null; 190 if (response != null && response.length > 0) { 191 pdu = (new PduParser(response, supportContentDisposition)).parse(); 192 if (pdu != null && pdu instanceof SendConf) { 193 sendConf = (SendConf) pdu; 194 } 195 } 196 if (result != Activity.RESULT_OK 197 || sendConf == null 198 || sendConf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) { 199 // Since we can't persist a message directly into FAILED box, 200 // we have to update the column after we persist it into SENT box. 201 // The gap between the state change is tiny so I would not expect 202 // it to cause any serious problem 203 // TODO: we should add a "failed" URI for this in MmsProvider? 204 values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_FAILED); 205 } 206 if (sendConf != null) { 207 values.put(Telephony.Mms.RESPONSE_STATUS, sendConf.getResponseStatus()); 208 values.put(Telephony.Mms.MESSAGE_ID, 209 PduPersister.toIsoString(sendConf.getMessageId())); 210 } 211 values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L); 212 values.put(Telephony.Mms.READ, 1); 213 values.put(Telephony.Mms.SEEN, 1); 214 if (!TextUtils.isEmpty(mCreator)) { 215 values.put(Telephony.Mms.CREATOR, mCreator); 216 } 217 values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId); 218 if (SqliteWrapper.update(context, context.getContentResolver(), messageUri, values, 219 null/*where*/, null/*selectionArg*/) != 1) { 220 LogUtil.e(requestId, "persistIfRequired: failed to update message"); 221 } 222 return messageUri; 223 } catch (MmsException e) { 224 LogUtil.e(requestId, "persistIfRequired: can not persist message", e); 225 } catch (RuntimeException e) { 226 LogUtil.e(requestId, "persistIfRequired: unexpected parsing failure", e); 227 } finally { 228 Binder.restoreCallingIdentity(identity); 229 } 230 return null; 231 } 232 233 /** 234 * Update the destination Address of MO MMS before sending. 235 * This is special for VZW requirement. Follow the specificaitons of assisted dialing 236 * of MO MMS while traveling on VZW CDMA, international CDMA or GSM markets. 237 */ updateDestinationAddress(final GenericPdu pdu)238 private void updateDestinationAddress(final GenericPdu pdu) { 239 final String requestId = getRequestId(); 240 if (pdu == null) { 241 LogUtil.e(requestId, "updateDestinationAddress: can't parse input PDU"); 242 return ; 243 } 244 if (!(pdu instanceof SendReq)) { 245 LogUtil.i(requestId, "updateDestinationAddress: not SendReq"); 246 return; 247 } 248 249 boolean isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.TO); 250 isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.CC) || isUpdated; 251 isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.BCC) || isUpdated; 252 253 if (isUpdated) { 254 mPduData = new PduComposer(mContext, (SendReq)pdu).make(); 255 } 256 } 257 updateDestinationAddressPerType(SendReq pdu, int type)258 private boolean updateDestinationAddressPerType(SendReq pdu, int type) { 259 boolean isUpdated = false; 260 EncodedStringValue[] recipientNumbers = null; 261 262 switch (type) { 263 case PduHeaders.TO: 264 recipientNumbers = pdu.getTo(); 265 break; 266 case PduHeaders.CC: 267 recipientNumbers = pdu.getCc(); 268 break; 269 case PduHeaders.BCC: 270 recipientNumbers = pdu.getBcc(); 271 break; 272 default: 273 return false; 274 } 275 276 if (recipientNumbers != null) { 277 int nNumberCount = recipientNumbers.length; 278 if (nNumberCount > 0) { 279 Phone phone = PhoneFactory.getDefaultPhone(); 280 EncodedStringValue[] newNumbers = new EncodedStringValue[nNumberCount]; 281 String toNumber; 282 String newToNumber; 283 for (int i = 0; i < nNumberCount; i++) { 284 toNumber = recipientNumbers[i].getString(); 285 newToNumber = SmsNumberUtils.filterDestAddr(phone, toNumber); 286 if (!TextUtils.equals(toNumber, newToNumber)) { 287 isUpdated = true; 288 newNumbers[i] = new EncodedStringValue(newToNumber); 289 } else { 290 newNumbers[i] = recipientNumbers[i]; 291 } 292 } 293 switch (type) { 294 case PduHeaders.TO: 295 pdu.setTo(newNumbers); 296 break; 297 case PduHeaders.CC: 298 pdu.setCc(newNumbers); 299 break; 300 case PduHeaders.BCC: 301 pdu.setBcc(newNumbers); 302 break; 303 } 304 } 305 } 306 307 return isUpdated; 308 } 309 310 /** 311 * Read the pdu from the file descriptor and cache pdu bytes in request 312 * @return true if pdu read successfully 313 */ readPduFromContentUri()314 private boolean readPduFromContentUri() { 315 if (mPduData != null) { 316 return true; 317 } 318 final int bytesTobeRead = mMmsConfig.getInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE); 319 mPduData = mRequestManager.readPduFromContentUri(mPduUri, bytesTobeRead); 320 return (mPduData != null); 321 } 322 323 /** 324 * Transfer the received response to the caller (for send requests the pdu is small and can 325 * just include bytes as extra in the "returned" intent). 326 * 327 * @param fillIn the intent that will be returned to the caller 328 * @param response the pdu to transfer 329 */ 330 @Override transferResponse(Intent fillIn, byte[] response)331 protected boolean transferResponse(Intent fillIn, byte[] response) { 332 // SendConf pdus are always small and can be included in the intent 333 if (response != null) { 334 fillIn.putExtra(SmsManager.EXTRA_MMS_DATA, response); 335 } 336 return true; 337 } 338 339 /** 340 * Read the data from the file descriptor if not yet done 341 * @return whether data successfully read 342 */ 343 @Override prepareForHttpRequest()344 protected boolean prepareForHttpRequest() { 345 return readPduFromContentUri(); 346 } 347 348 /** 349 * Try sending via the carrier app 350 * 351 * @param context the context 352 * @param carrierMessagingServicePackage the carrier messaging service sending the MMS 353 */ trySendingByCarrierApp(Context context, String carrierMessagingServicePackage)354 public void trySendingByCarrierApp(Context context, String carrierMessagingServicePackage) { 355 final CarrierSendManager carrierSendManger = new CarrierSendManager(); 356 final CarrierSendCompleteCallback sendCallback = new CarrierSendCompleteCallback( 357 context, carrierSendManger); 358 carrierSendManger.sendMms(context, carrierMessagingServicePackage, sendCallback); 359 } 360 361 @Override revokeUriPermission(Context context)362 protected void revokeUriPermission(Context context) { 363 if (mPduUri != null) { 364 context.revokeUriPermission(mPduUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); 365 } 366 } 367 368 /** 369 * Sends the MMS through through the carrier app. 370 */ 371 private final class CarrierSendManager extends CarrierMessagingServiceManager { 372 // Initialized in sendMms 373 private volatile CarrierSendCompleteCallback mCarrierSendCompleteCallback; 374 sendMms(Context context, String carrierMessagingServicePackage, CarrierSendCompleteCallback carrierSendCompleteCallback)375 void sendMms(Context context, String carrierMessagingServicePackage, 376 CarrierSendCompleteCallback carrierSendCompleteCallback) { 377 mCarrierSendCompleteCallback = carrierSendCompleteCallback; 378 if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) { 379 LogUtil.v("bindService() for carrier messaging service succeeded"); 380 } else { 381 LogUtil.e("bindService() for carrier messaging service failed"); 382 carrierSendCompleteCallback.onSendMmsComplete( 383 CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 384 null /* no sendConfPdu */); 385 } 386 } 387 388 @Override onServiceReady(ICarrierMessagingService carrierMessagingService)389 protected void onServiceReady(ICarrierMessagingService carrierMessagingService) { 390 try { 391 Uri locationUri = null; 392 if (mLocationUrl != null) { 393 locationUri = Uri.parse(mLocationUrl); 394 } 395 carrierMessagingService.sendMms(mPduUri, mSubId, locationUri, 396 mCarrierSendCompleteCallback); 397 } catch (RemoteException e) { 398 LogUtil.e("Exception sending MMS using the carrier messaging service: " + e, e); 399 mCarrierSendCompleteCallback.onSendMmsComplete( 400 CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 401 null /* no sendConfPdu */); 402 } 403 } 404 } 405 406 /** 407 * A callback which notifies carrier messaging app send result. Once the result is ready, the 408 * carrier messaging service connection is disposed. 409 */ 410 private final class CarrierSendCompleteCallback extends 411 MmsRequest.CarrierMmsActionCallback { 412 private final Context mContext; 413 private final CarrierSendManager mCarrierSendManager; 414 CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager)415 public CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager) { 416 mContext = context; 417 mCarrierSendManager = carrierSendManager; 418 } 419 420 @Override onSendMmsComplete(int result, byte[] sendConfPdu)421 public void onSendMmsComplete(int result, byte[] sendConfPdu) { 422 LogUtil.d("Carrier app result for send: " + result); 423 mCarrierSendManager.disposeConnection(mContext); 424 425 if (!maybeFallbackToRegularDelivery(result)) { 426 processResult(mContext, toSmsManagerResult(result), sendConfPdu, 427 0/* httpStatusCode */); 428 } 429 } 430 431 @Override onDownloadMmsComplete(int result)432 public void onDownloadMmsComplete(int result) { 433 LogUtil.e("Unexpected onDownloadMmsComplete call with result: " + result); 434 } 435 } 436 } 437