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