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 values.put(Telephony.Mms.MESSAGE_ID, 209 PduPersister.toIsoString(sendConf.getMessageId())); 210 } 211 values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L); 212 values.put(Telephony.Mms.READ, 1); 213 values.put(Telephony.Mms.SEEN, 1); 214 if (!TextUtils.isEmpty(mCreator)) { 215 values.put(Telephony.Mms.CREATOR, mCreator); 216 } 217 values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId); 218 if (SqliteWrapper.update(context, context.getContentResolver(), messageUri, values, 219 null/*where*/, null/*selectionArg*/) != 1) { 220 LogUtil.e(requestId, "persistIfRequired: failed to update message"); 221 } 222 return messageUri; 223 } catch (MmsException e) { 224 LogUtil.e(requestId, "persistIfRequired: can not persist message", e); 225 } catch (RuntimeException e) { 226 LogUtil.e(requestId, "persistIfRequired: unexpected parsing failure", e); 227 } finally { 228 Binder.restoreCallingIdentity(identity); 229 } 230 return null; 231 } 232 233 /** 234 * Update the destination Address of MO MMS before sending. 235 * This is special for VZW requirement. Follow the specificaitons of assisted dialing 236 * of MO MMS while traveling on VZW CDMA, international CDMA or GSM markets. 237 */ 238 private void updateDestinationAddress(final GenericPdu pdu) { 239 final String requestId = getRequestId(); 240 if (pdu == null) { 241 LogUtil.e(requestId, "updateDestinationAddress: can't parse input PDU"); 242 return ; 243 } 244 if (!(pdu instanceof SendReq)) { 245 LogUtil.i(requestId, "updateDestinationAddress: not SendReq"); 246 return; 247 } 248 249 boolean isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.TO); 250 isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.CC) || isUpdated; 251 isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.BCC) || isUpdated; 252 253 if (isUpdated) { 254 mPduData = new PduComposer(mContext, (SendReq)pdu).make(); 255 } 256 } 257 258 private boolean updateDestinationAddressPerType(SendReq pdu, int type) { 259 boolean isUpdated = false; 260 EncodedStringValue[] recipientNumbers = null; 261 262 switch (type) { 263 case PduHeaders.TO: 264 recipientNumbers = pdu.getTo(); 265 break; 266 case PduHeaders.CC: 267 recipientNumbers = pdu.getCc(); 268 break; 269 case PduHeaders.BCC: 270 recipientNumbers = pdu.getBcc(); 271 break; 272 default: 273 return false; 274 } 275 276 if (recipientNumbers != null) { 277 int nNumberCount = recipientNumbers.length; 278 if (nNumberCount > 0) { 279 Phone phone = PhoneFactory.getDefaultPhone(); 280 EncodedStringValue[] newNumbers = new EncodedStringValue[nNumberCount]; 281 String toNumber; 282 String newToNumber; 283 for (int i = 0; i < nNumberCount; i++) { 284 toNumber = recipientNumbers[i].getString(); 285 newToNumber = SmsNumberUtils.filterDestAddr(phone, toNumber); 286 if (!TextUtils.equals(toNumber, newToNumber)) { 287 isUpdated = true; 288 newNumbers[i] = new EncodedStringValue(newToNumber); 289 } else { 290 newNumbers[i] = recipientNumbers[i]; 291 } 292 } 293 switch (type) { 294 case PduHeaders.TO: 295 pdu.setTo(newNumbers); 296 break; 297 case PduHeaders.CC: 298 pdu.setCc(newNumbers); 299 break; 300 case PduHeaders.BCC: 301 pdu.setBcc(newNumbers); 302 break; 303 } 304 } 305 } 306 307 return isUpdated; 308 } 309 310 /** 311 * Read the pdu from the file descriptor and cache pdu bytes in request 312 * @return true if pdu read successfully 313 */ 314 private boolean readPduFromContentUri() { 315 if (mPduData != null) { 316 return true; 317 } 318 final int bytesTobeRead = mMmsConfig.getInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE); 319 mPduData = mRequestManager.readPduFromContentUri(mPduUri, bytesTobeRead); 320 return (mPduData != null); 321 } 322 323 /** 324 * Transfer the received response to the caller (for send requests the pdu is small and can 325 * just include bytes as extra in the "returned" intent). 326 * 327 * @param fillIn the intent that will be returned to the caller 328 * @param response the pdu to transfer 329 */ 330 @Override 331 protected boolean transferResponse(Intent fillIn, byte[] response) { 332 // SendConf pdus are always small and can be included in the intent 333 if (response != null) { 334 fillIn.putExtra(SmsManager.EXTRA_MMS_DATA, response); 335 } 336 return true; 337 } 338 339 /** 340 * Read the data from the file descriptor if not yet done 341 * @return whether data successfully read 342 */ 343 @Override 344 protected boolean prepareForHttpRequest() { 345 return readPduFromContentUri(); 346 } 347 348 /** 349 * Try sending via the carrier app 350 * 351 * @param context the context 352 * @param carrierMessagingServicePackage the carrier messaging service sending the MMS 353 */ 354 public void trySendingByCarrierApp(Context context, String carrierMessagingServicePackage) { 355 final CarrierSendManager carrierSendManger = new CarrierSendManager(); 356 final CarrierSendCompleteCallback sendCallback = new CarrierSendCompleteCallback( 357 context, carrierSendManger); 358 carrierSendManger.sendMms(context, carrierMessagingServicePackage, sendCallback); 359 } 360 361 @Override 362 protected void revokeUriPermission(Context context) { 363 if (mPduUri != null) { 364 context.revokeUriPermission(mPduUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); 365 } 366 } 367 368 /** 369 * Sends the MMS through through the carrier app. 370 */ 371 private final class CarrierSendManager extends CarrierMessagingServiceManager { 372 // Initialized in sendMms 373 private volatile CarrierSendCompleteCallback mCarrierSendCompleteCallback; 374 375 void sendMms(Context context, String carrierMessagingServicePackage, 376 CarrierSendCompleteCallback carrierSendCompleteCallback) { 377 mCarrierSendCompleteCallback = carrierSendCompleteCallback; 378 if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) { 379 LogUtil.v("bindService() for carrier messaging service succeeded"); 380 } else { 381 LogUtil.e("bindService() for carrier messaging service failed"); 382 carrierSendCompleteCallback.onSendMmsComplete( 383 CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 384 null /* no sendConfPdu */); 385 } 386 } 387 388 @Override 389 protected void onServiceReady(ICarrierMessagingService carrierMessagingService) { 390 try { 391 Uri locationUri = null; 392 if (mLocationUrl != null) { 393 locationUri = Uri.parse(mLocationUrl); 394 } 395 carrierMessagingService.sendMms(mPduUri, mSubId, locationUri, 396 mCarrierSendCompleteCallback); 397 } catch (RemoteException e) { 398 LogUtil.e("Exception sending MMS using the carrier messaging service: " + e, e); 399 mCarrierSendCompleteCallback.onSendMmsComplete( 400 CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 401 null /* no sendConfPdu */); 402 } 403 } 404 } 405 406 /** 407 * A callback which notifies carrier messaging app send result. Once the result is ready, the 408 * carrier messaging service connection is disposed. 409 */ 410 private final class CarrierSendCompleteCallback extends 411 MmsRequest.CarrierMmsActionCallback { 412 private final Context mContext; 413 private final CarrierSendManager mCarrierSendManager; 414 415 public CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager) { 416 mContext = context; 417 mCarrierSendManager = carrierSendManager; 418 } 419 420 @Override 421 public void onSendMmsComplete(int result, byte[] sendConfPdu) { 422 LogUtil.d("Carrier app result for send: " + result); 423 mCarrierSendManager.disposeConnection(mContext); 424 425 if (!maybeFallbackToRegularDelivery(result)) { 426 processResult(mContext, toSmsManagerResult(result), sendConfPdu, 427 0/* httpStatusCode */); 428 } 429 } 430 431 @Override 432 public void onDownloadMmsComplete(int result) { 433 LogUtil.e("Unexpected onDownloadMmsComplete call with result: " + result); 434 } 435 } 436 } 437