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