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 byte[] messageId = sendConf.getMessageId(); 209 if (messageId != null) { 210 values.put(Telephony.Mms.MESSAGE_ID, PduPersister.toIsoString(messageId)); 211 } 212 } 213 values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L); 214 values.put(Telephony.Mms.READ, 1); 215 values.put(Telephony.Mms.SEEN, 1); 216 if (!TextUtils.isEmpty(mCreator)) { 217 values.put(Telephony.Mms.CREATOR, mCreator); 218 } 219 values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId); 220 if (SqliteWrapper.update(context, context.getContentResolver(), messageUri, values, 221 null/*where*/, null/*selectionArg*/) != 1) { 222 LogUtil.e(requestId, "persistIfRequired: failed to update message"); 223 } 224 return messageUri; 225 } catch (MmsException e) { 226 LogUtil.e(requestId, "persistIfRequired: can not persist message", e); 227 } catch (RuntimeException e) { 228 LogUtil.e(requestId, "persistIfRequired: unexpected parsing failure", e); 229 } finally { 230 Binder.restoreCallingIdentity(identity); 231 } 232 return null; 233 } 234 235 /** 236 * Update the destination Address of MO MMS before sending. 237 * This is special for VZW requirement. Follow the specificaitons of assisted dialing 238 * of MO MMS while traveling on VZW CDMA, international CDMA or GSM markets. 239 */ updateDestinationAddress(final GenericPdu pdu)240 private void updateDestinationAddress(final GenericPdu pdu) { 241 final String requestId = getRequestId(); 242 if (pdu == null) { 243 LogUtil.e(requestId, "updateDestinationAddress: can't parse input PDU"); 244 return ; 245 } 246 if (!(pdu instanceof SendReq)) { 247 LogUtil.i(requestId, "updateDestinationAddress: not SendReq"); 248 return; 249 } 250 251 boolean isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.TO); 252 isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.CC) || isUpdated; 253 isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.BCC) || isUpdated; 254 255 if (isUpdated) { 256 mPduData = new PduComposer(mContext, (SendReq)pdu).make(); 257 } 258 } 259 updateDestinationAddressPerType(SendReq pdu, int type)260 private boolean updateDestinationAddressPerType(SendReq pdu, int type) { 261 boolean isUpdated = false; 262 EncodedStringValue[] recipientNumbers = null; 263 264 switch (type) { 265 case PduHeaders.TO: 266 recipientNumbers = pdu.getTo(); 267 break; 268 case PduHeaders.CC: 269 recipientNumbers = pdu.getCc(); 270 break; 271 case PduHeaders.BCC: 272 recipientNumbers = pdu.getBcc(); 273 break; 274 default: 275 return false; 276 } 277 278 if (recipientNumbers != null) { 279 int nNumberCount = recipientNumbers.length; 280 if (nNumberCount > 0) { 281 Phone phone = PhoneFactory.getDefaultPhone(); 282 EncodedStringValue[] newNumbers = new EncodedStringValue[nNumberCount]; 283 String toNumber; 284 String newToNumber; 285 for (int i = 0; i < nNumberCount; i++) { 286 toNumber = recipientNumbers[i].getString(); 287 newToNumber = SmsNumberUtils.filterDestAddr(phone, toNumber); 288 if (!TextUtils.equals(toNumber, newToNumber)) { 289 isUpdated = true; 290 newNumbers[i] = new EncodedStringValue(newToNumber); 291 } else { 292 newNumbers[i] = recipientNumbers[i]; 293 } 294 } 295 switch (type) { 296 case PduHeaders.TO: 297 pdu.setTo(newNumbers); 298 break; 299 case PduHeaders.CC: 300 pdu.setCc(newNumbers); 301 break; 302 case PduHeaders.BCC: 303 pdu.setBcc(newNumbers); 304 break; 305 } 306 } 307 } 308 309 return isUpdated; 310 } 311 312 /** 313 * Read the pdu from the file descriptor and cache pdu bytes in request 314 * @return true if pdu read successfully 315 */ readPduFromContentUri()316 private boolean readPduFromContentUri() { 317 if (mPduData != null) { 318 return true; 319 } 320 final int bytesTobeRead = mMmsConfig.getInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE); 321 mPduData = mRequestManager.readPduFromContentUri(mPduUri, bytesTobeRead); 322 return (mPduData != null); 323 } 324 325 /** 326 * Transfer the received response to the caller (for send requests the pdu is small and can 327 * just include bytes as extra in the "returned" intent). 328 * 329 * @param fillIn the intent that will be returned to the caller 330 * @param response the pdu to transfer 331 */ 332 @Override transferResponse(Intent fillIn, byte[] response)333 protected boolean transferResponse(Intent fillIn, byte[] response) { 334 // SendConf pdus are always small and can be included in the intent 335 if (response != null) { 336 fillIn.putExtra(SmsManager.EXTRA_MMS_DATA, response); 337 } 338 return true; 339 } 340 341 /** 342 * Read the data from the file descriptor if not yet done 343 * @return whether data successfully read 344 */ 345 @Override prepareForHttpRequest()346 protected boolean prepareForHttpRequest() { 347 return readPduFromContentUri(); 348 } 349 350 /** 351 * Try sending via the carrier app 352 * 353 * @param context the context 354 * @param carrierMessagingServicePackage the carrier messaging service sending the MMS 355 */ trySendingByCarrierApp(Context context, String carrierMessagingServicePackage)356 public void trySendingByCarrierApp(Context context, String carrierMessagingServicePackage) { 357 final CarrierSendManager carrierSendManger = new CarrierSendManager(); 358 final CarrierSendCompleteCallback sendCallback = new CarrierSendCompleteCallback( 359 context, carrierSendManger); 360 carrierSendManger.sendMms(context, carrierMessagingServicePackage, sendCallback); 361 } 362 363 @Override revokeUriPermission(Context context)364 protected void revokeUriPermission(Context context) { 365 if (mPduUri != null) { 366 context.revokeUriPermission(mPduUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); 367 } 368 } 369 370 /** 371 * Sends the MMS through through the carrier app. 372 */ 373 private final class CarrierSendManager extends CarrierMessagingServiceManager { 374 // Initialized in sendMms 375 private volatile CarrierSendCompleteCallback mCarrierSendCompleteCallback; 376 sendMms(Context context, String carrierMessagingServicePackage, CarrierSendCompleteCallback carrierSendCompleteCallback)377 void sendMms(Context context, String carrierMessagingServicePackage, 378 CarrierSendCompleteCallback carrierSendCompleteCallback) { 379 mCarrierSendCompleteCallback = carrierSendCompleteCallback; 380 if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) { 381 LogUtil.v("bindService() for carrier messaging service succeeded"); 382 } else { 383 LogUtil.e("bindService() for carrier messaging service failed"); 384 carrierSendCompleteCallback.onSendMmsComplete( 385 CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 386 null /* no sendConfPdu */); 387 } 388 } 389 390 @Override onServiceReady(ICarrierMessagingService carrierMessagingService)391 protected void onServiceReady(ICarrierMessagingService carrierMessagingService) { 392 try { 393 Uri locationUri = null; 394 if (mLocationUrl != null) { 395 locationUri = Uri.parse(mLocationUrl); 396 } 397 carrierMessagingService.sendMms(mPduUri, mSubId, locationUri, 398 mCarrierSendCompleteCallback); 399 } catch (RemoteException e) { 400 LogUtil.e("Exception sending MMS using the carrier messaging service: " + e, e); 401 mCarrierSendCompleteCallback.onSendMmsComplete( 402 CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 403 null /* no sendConfPdu */); 404 } 405 } 406 } 407 408 /** 409 * A callback which notifies carrier messaging app send result. Once the result is ready, the 410 * carrier messaging service connection is disposed. 411 */ 412 private final class CarrierSendCompleteCallback extends 413 MmsRequest.CarrierMmsActionCallback { 414 private final Context mContext; 415 private final CarrierSendManager mCarrierSendManager; 416 CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager)417 public CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager) { 418 mContext = context; 419 mCarrierSendManager = carrierSendManager; 420 } 421 422 @Override onSendMmsComplete(int result, byte[] sendConfPdu)423 public void onSendMmsComplete(int result, byte[] sendConfPdu) { 424 LogUtil.d("Carrier app result for send: " + result); 425 mCarrierSendManager.disposeConnection(mContext); 426 427 if (!maybeFallbackToRegularDelivery(result)) { 428 processResult(mContext, toSmsManagerResult(result), sendConfPdu, 429 0/* httpStatusCode */); 430 } 431 } 432 433 @Override onDownloadMmsComplete(int result)434 public void onDownloadMmsComplete(int result) { 435 LogUtil.e("Unexpected onDownloadMmsComplete call with result: " + result); 436 } 437 } 438 } 439