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