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 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 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 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 */ 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 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 136 private boolean isEmergencyNumber(String address) { 137 return !TextUtils.isEmpty(address) && PhoneNumberUtils.isEmergencyNumber(mSubId, address); 138 } 139 140 @Override 141 protected PendingIntent getPendingIntent() { 142 return mSentIntent; 143 } 144 145 @Override 146 protected int getQueueType() { 147 return MmsService.QUEUE_INDEX_SEND; 148 } 149 150 @Override 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 */ 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 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 */ 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 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 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 */ 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 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 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 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 417 public CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager) { 418 mContext = context; 419 mCarrierSendManager = carrierSendManager; 420 } 421 422 @Override 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 434 public void onDownloadMmsComplete(int result) { 435 LogUtil.e("Unexpected onDownloadMmsComplete call with result: " + result); 436 } 437 } 438 } 439