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