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