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.SmsManager; 33 import android.text.TextUtils; 34 35 import com.android.internal.telephony.SmsApplication; 36 import com.android.mms.service.exception.MmsHttpException; 37 import com.google.android.mms.MmsException; 38 import com.google.android.mms.pdu.GenericPdu; 39 import com.google.android.mms.pdu.PduHeaders; 40 import com.google.android.mms.pdu.PduParser; 41 import com.google.android.mms.pdu.PduPersister; 42 import com.google.android.mms.pdu.SendConf; 43 import com.google.android.mms.pdu.SendReq; 44 import com.google.android.mms.util.SqliteWrapper; 45 46 /** 47 * Request to send an MMS 48 */ 49 public class SendRequest extends MmsRequest { 50 private final Uri mPduUri; 51 private byte[] mPduData; 52 private final String mLocationUrl; 53 private final PendingIntent mSentIntent; 54 55 public SendRequest(RequestManager manager, int subId, Uri contentUri, String locationUrl, 56 PendingIntent sentIntent, String creator, Bundle configOverrides, Context context) { 57 super(manager, subId, creator, configOverrides, context); 58 mPduUri = contentUri; 59 mPduData = null; 60 mLocationUrl = locationUrl; 61 mSentIntent = sentIntent; 62 } 63 64 @Override 65 protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn) 66 throws MmsHttpException { 67 final String requestId = this.toString(); 68 final MmsHttpClient mmsHttpClient = netMgr.getOrCreateHttpClient(); 69 if (mmsHttpClient == null) { 70 LogUtil.e(requestId, "MMS network is not ready!"); 71 throw new MmsHttpException(0/*statusCode*/, "MMS network is not ready"); 72 } 73 return mmsHttpClient.execute( 74 mLocationUrl != null ? mLocationUrl : apn.getMmscUrl(), 75 mPduData, 76 MmsHttpClient.METHOD_POST, 77 apn.isProxySet(), 78 apn.getProxyAddress(), 79 apn.getProxyPort(), 80 mMmsConfig, 81 mSubId, 82 requestId); 83 } 84 85 @Override 86 protected PendingIntent getPendingIntent() { 87 return mSentIntent; 88 } 89 90 @Override 91 protected int getQueueType() { 92 return MmsService.QUEUE_INDEX_SEND; 93 } 94 95 @Override 96 protected Uri persistIfRequired(Context context, int result, byte[] response) { 97 final String requestId = this.toString(); 98 if (!SmsApplication.shouldWriteMessageForPackage(mCreator, context)) { 99 // Not required to persist 100 return null; 101 } 102 LogUtil.d(requestId, "persistIfRequired"); 103 if (mPduData == null) { 104 LogUtil.e(requestId, "persistIfRequired: empty PDU"); 105 return null; 106 } 107 final long identity = Binder.clearCallingIdentity(); 108 try { 109 final boolean supportContentDisposition = 110 mMmsConfig.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION); 111 // Persist the request PDU first 112 GenericPdu pdu = (new PduParser(mPduData, supportContentDisposition)).parse(); 113 if (pdu == null) { 114 LogUtil.e(requestId, "persistIfRequired: can't parse input PDU"); 115 return null; 116 } 117 if (!(pdu instanceof SendReq)) { 118 LogUtil.d(requestId, "persistIfRequired: not SendReq"); 119 return null; 120 } 121 final PduPersister persister = PduPersister.getPduPersister(context); 122 final Uri messageUri = persister.persist( 123 pdu, 124 Telephony.Mms.Sent.CONTENT_URI, 125 true/*createThreadId*/, 126 true/*groupMmsEnabled*/, 127 null/*preOpenedFiles*/); 128 if (messageUri == null) { 129 LogUtil.e(requestId, "persistIfRequired: can not persist message"); 130 return null; 131 } 132 // Update the additional columns based on the send result 133 final ContentValues values = new ContentValues(); 134 SendConf sendConf = null; 135 if (response != null && response.length > 0) { 136 pdu = (new PduParser(response, supportContentDisposition)).parse(); 137 if (pdu != null && pdu instanceof SendConf) { 138 sendConf = (SendConf) pdu; 139 } 140 } 141 if (result != Activity.RESULT_OK 142 || sendConf == null 143 || sendConf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) { 144 // Since we can't persist a message directly into FAILED box, 145 // we have to update the column after we persist it into SENT box. 146 // The gap between the state change is tiny so I would not expect 147 // it to cause any serious problem 148 // TODO: we should add a "failed" URI for this in MmsProvider? 149 values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_FAILED); 150 } 151 if (sendConf != null) { 152 values.put(Telephony.Mms.RESPONSE_STATUS, sendConf.getResponseStatus()); 153 values.put(Telephony.Mms.MESSAGE_ID, 154 PduPersister.toIsoString(sendConf.getMessageId())); 155 } 156 values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L); 157 values.put(Telephony.Mms.READ, 1); 158 values.put(Telephony.Mms.SEEN, 1); 159 if (!TextUtils.isEmpty(mCreator)) { 160 values.put(Telephony.Mms.CREATOR, mCreator); 161 } 162 values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId); 163 if (SqliteWrapper.update(context, context.getContentResolver(), messageUri, values, 164 null/*where*/, null/*selectionArg*/) != 1) { 165 LogUtil.e(requestId, "persistIfRequired: failed to update message"); 166 } 167 return messageUri; 168 } catch (MmsException e) { 169 LogUtil.e(requestId, "persistIfRequired: can not persist message", e); 170 } catch (RuntimeException e) { 171 LogUtil.e(requestId, "persistIfRequired: unexpected parsing failure", e); 172 } finally { 173 Binder.restoreCallingIdentity(identity); 174 } 175 return null; 176 } 177 178 /** 179 * Read the pdu from the file descriptor and cache pdu bytes in request 180 * @return true if pdu read successfully 181 */ 182 private boolean readPduFromContentUri() { 183 if (mPduData != null) { 184 return true; 185 } 186 final int bytesTobeRead = mMmsConfig.getInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE); 187 mPduData = mRequestManager.readPduFromContentUri(mPduUri, bytesTobeRead); 188 return (mPduData != null); 189 } 190 191 /** 192 * Transfer the received response to the caller (for send requests the pdu is small and can 193 * just include bytes as extra in the "returned" intent). 194 * 195 * @param fillIn the intent that will be returned to the caller 196 * @param response the pdu to transfer 197 */ 198 @Override 199 protected boolean transferResponse(Intent fillIn, byte[] response) { 200 // SendConf pdus are always small and can be included in the intent 201 if (response != null) { 202 fillIn.putExtra(SmsManager.EXTRA_MMS_DATA, response); 203 } 204 return true; 205 } 206 207 /** 208 * Read the data from the file descriptor if not yet done 209 * @return whether data successfully read 210 */ 211 @Override 212 protected boolean prepareForHttpRequest() { 213 return readPduFromContentUri(); 214 } 215 216 /** 217 * Try sending via the carrier app 218 * 219 * @param context the context 220 * @param carrierMessagingServicePackage the carrier messaging service sending the MMS 221 */ 222 public void trySendingByCarrierApp(Context context, String carrierMessagingServicePackage) { 223 final CarrierSendManager carrierSendManger = new CarrierSendManager(); 224 final CarrierSendCompleteCallback sendCallback = new CarrierSendCompleteCallback( 225 context, carrierSendManger); 226 carrierSendManger.sendMms(context, carrierMessagingServicePackage, sendCallback); 227 } 228 229 @Override 230 protected void revokeUriPermission(Context context) { 231 if (mPduUri != null) { 232 context.revokeUriPermission(mPduUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); 233 } 234 } 235 236 /** 237 * Sends the MMS through through the carrier app. 238 */ 239 private final class CarrierSendManager extends CarrierMessagingServiceManager { 240 // Initialized in sendMms 241 private volatile CarrierSendCompleteCallback mCarrierSendCompleteCallback; 242 243 void sendMms(Context context, String carrierMessagingServicePackage, 244 CarrierSendCompleteCallback carrierSendCompleteCallback) { 245 mCarrierSendCompleteCallback = carrierSendCompleteCallback; 246 if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) { 247 LogUtil.v("bindService() for carrier messaging service succeeded"); 248 } else { 249 LogUtil.e("bindService() for carrier messaging service failed"); 250 carrierSendCompleteCallback.onSendMmsComplete( 251 CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 252 null /* no sendConfPdu */); 253 } 254 } 255 256 @Override 257 protected void onServiceReady(ICarrierMessagingService carrierMessagingService) { 258 try { 259 Uri locationUri = null; 260 if (mLocationUrl != null) { 261 locationUri = Uri.parse(mLocationUrl); 262 } 263 carrierMessagingService.sendMms(mPduUri, mSubId, locationUri, 264 mCarrierSendCompleteCallback); 265 } catch (RemoteException e) { 266 LogUtil.e("Exception sending MMS using the carrier messaging service: " + e, e); 267 mCarrierSendCompleteCallback.onSendMmsComplete( 268 CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 269 null /* no sendConfPdu */); 270 } 271 } 272 } 273 274 /** 275 * A callback which notifies carrier messaging app send result. Once the result is ready, the 276 * carrier messaging service connection is disposed. 277 */ 278 private final class CarrierSendCompleteCallback extends 279 MmsRequest.CarrierMmsActionCallback { 280 private final Context mContext; 281 private final CarrierSendManager mCarrierSendManager; 282 283 public CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager) { 284 mContext = context; 285 mCarrierSendManager = carrierSendManager; 286 } 287 288 @Override 289 public void onSendMmsComplete(int result, byte[] sendConfPdu) { 290 LogUtil.d("Carrier app result for send: " + result); 291 mCarrierSendManager.disposeConnection(mContext); 292 293 if (!maybeFallbackToRegularDelivery(result)) { 294 processResult(mContext, toSmsManagerResult(result), sendConfPdu, 295 0/* httpStatusCode */); 296 } 297 } 298 299 @Override 300 public void onDownloadMmsComplete(int result) { 301 LogUtil.e("Unexpected onDownloadMmsComplete call with result: " + result); 302 } 303 } 304 } 305