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 com.google.android.mms.MmsException; 20 import com.google.android.mms.pdu.GenericPdu; 21 import com.google.android.mms.pdu.PduParser; 22 import com.google.android.mms.pdu.PduPersister; 23 import com.google.android.mms.pdu.SendConf; 24 import com.google.android.mms.pdu.SendReq; 25 import com.google.android.mms.util.SqliteWrapper; 26 27 import com.android.mms.service.exception.MmsHttpException; 28 29 import android.app.Activity; 30 import android.app.AppOpsManager; 31 import android.app.PendingIntent; 32 import android.content.ContentValues; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.database.sqlite.SQLiteException; 36 import android.net.Uri; 37 import android.os.Binder; 38 import android.os.Bundle; 39 import android.os.UserHandle; 40 import android.provider.Telephony; 41 import android.telephony.SmsManager; 42 import android.telephony.TelephonyManager; 43 import android.text.TextUtils; 44 import android.util.Log; 45 46 import java.util.List; 47 48 /** 49 * Request to send an MMS 50 */ 51 public class SendRequest extends MmsRequest { 52 private final Uri mPduUri; 53 private byte[] mPduData; 54 private final String mLocationUrl; 55 private final PendingIntent mSentIntent; 56 SendRequest(RequestManager manager, long subId, Uri contentUri, Uri messageUri, String locationUrl, PendingIntent sentIntent, String creator, Bundle configOverrides)57 public SendRequest(RequestManager manager, long subId, Uri contentUri, Uri messageUri, 58 String locationUrl, PendingIntent sentIntent, String creator, 59 Bundle configOverrides) { 60 super(manager, messageUri, subId, creator, configOverrides); 61 mPduUri = contentUri; 62 mPduData = null; 63 mLocationUrl = locationUrl; 64 mSentIntent = sentIntent; 65 } 66 67 @Override doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)68 protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn) 69 throws MmsHttpException { 70 return doHttpForResolvedAddresses(context, 71 netMgr, 72 mLocationUrl != null ? mLocationUrl : apn.getMmscUrl(), 73 mPduData, 74 HttpUtils.HTTP_POST_METHOD, 75 apn); 76 } 77 78 @Override getPendingIntent()79 protected PendingIntent getPendingIntent() { 80 return mSentIntent; 81 } 82 83 @Override getRunningQueue()84 protected int getRunningQueue() { 85 return MmsService.QUEUE_INDEX_SEND; 86 } 87 storeInOutbox(Context context)88 public void storeInOutbox(Context context) { 89 final long identity = Binder.clearCallingIdentity(); 90 try { 91 // Read message using phone process identity 92 if (!readPduFromContentUri()) { 93 Log.e(MmsService.TAG, "SendRequest.storeInOutbox: empty PDU"); 94 return; 95 } 96 if (mMessageUri == null) { 97 // This is a new message to send 98 final GenericPdu pdu = (new PduParser(mPduData)).parse(); 99 if (pdu == null) { 100 Log.e(MmsService.TAG, "SendRequest.storeInOutbox: can't parse input PDU"); 101 return; 102 } 103 if (!(pdu instanceof SendReq)) { 104 Log.d(MmsService.TAG, "SendRequest.storeInOutbox: not SendReq"); 105 return; 106 } 107 final PduPersister persister = PduPersister.getPduPersister(context); 108 mMessageUri = persister.persist( 109 pdu, 110 Telephony.Mms.Outbox.CONTENT_URI, 111 true/*createThreadId*/, 112 true/*groupMmsEnabled*/, 113 null/*preOpenedFiles*/); 114 if (mMessageUri == null) { 115 Log.e(MmsService.TAG, "SendRequest.storeInOutbox: can not persist message"); 116 return; 117 } 118 final ContentValues values = new ContentValues(5); 119 values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L); 120 values.put(Telephony.Mms.READ, 1); 121 values.put(Telephony.Mms.SEEN, 1); 122 if (!TextUtils.isEmpty(mCreator)) { 123 values.put(Telephony.Mms.CREATOR, mCreator); 124 } 125 values.put(Telephony.Mms.SUB_ID, mSubId); 126 if (SqliteWrapper.update(context, context.getContentResolver(), mMessageUri, values, 127 null/*where*/, null/*selectionArg*/) != 1) { 128 Log.e(MmsService.TAG, "SendRequest.storeInOutbox: failed to update message"); 129 } 130 } else { 131 // This is a stored message, either in FAILED or DRAFT 132 // Move this to OUTBOX for sending 133 final ContentValues values = new ContentValues(3); 134 // Reset the timestamp 135 values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L); 136 values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_OUTBOX); 137 values.put(Telephony.Mms.SUB_ID, mSubId); 138 if (SqliteWrapper.update(context, context.getContentResolver(), mMessageUri, values, 139 null/*where*/, null/*selectionArg*/) != 1) { 140 Log.e(MmsService.TAG, "SendRequest.storeInOutbox: failed to update message"); 141 } 142 } 143 } catch (MmsException e) { 144 Log.e(MmsService.TAG, "SendRequest.storeInOutbox: can not persist/update message", e); 145 } catch (RuntimeException e) { 146 Log.e(MmsService.TAG, "SendRequest.storeInOutbox: unexpected parsing failure", e); 147 } finally { 148 Binder.restoreCallingIdentity(identity); 149 } 150 } 151 152 /** 153 * Read the pdu from the file descriptor and cache pdu bytes in request 154 * @return true if pdu read successfully 155 */ readPduFromContentUri()156 private boolean readPduFromContentUri() { 157 if (mPduData != null) { 158 return true; 159 } 160 final int bytesTobeRead = mMmsConfig.getMaxMessageSize(); 161 mPduData = mRequestManager.readPduFromContentUri(mPduUri, bytesTobeRead); 162 return (mPduData != null); 163 } 164 165 @Override updateStatus(Context context, int result, byte[] response)166 protected void updateStatus(Context context, int result, byte[] response) { 167 if (mMessageUri == null) { 168 return; 169 } 170 final long identity = Binder.clearCallingIdentity(); 171 try { 172 final int messageStatus = result == Activity.RESULT_OK ? 173 Telephony.Mms.MESSAGE_BOX_SENT : Telephony.Mms.MESSAGE_BOX_FAILED; 174 SendConf sendConf = null; 175 if (response != null && response.length > 0) { 176 final GenericPdu pdu = (new PduParser(response)).parse(); 177 if (pdu != null && pdu instanceof SendConf) { 178 sendConf = (SendConf) pdu; 179 } 180 } 181 final ContentValues values = new ContentValues(3); 182 values.put(Telephony.Mms.MESSAGE_BOX, messageStatus); 183 if (sendConf != null) { 184 values.put(Telephony.Mms.RESPONSE_STATUS, sendConf.getResponseStatus()); 185 values.put(Telephony.Mms.MESSAGE_ID, 186 PduPersister.toIsoString(sendConf.getMessageId())); 187 } 188 SqliteWrapper.update(context, context.getContentResolver(), mMessageUri, values, 189 null/*where*/, null/*selectionArg*/); 190 } catch (SQLiteException e) { 191 Log.e(MmsService.TAG, "SendRequest.updateStatus: can not update message", e); 192 } catch (RuntimeException e) { 193 Log.e(MmsService.TAG, "SendRequest.updateStatus: can not parse response", e); 194 } finally { 195 Binder.restoreCallingIdentity(identity); 196 } 197 } 198 199 /** 200 * Transfer the received response to the caller (for send requests the pdu is small and can 201 * just include bytes as extra in the "returned" intent). 202 * 203 * @param fillIn the intent that will be returned to the caller 204 * @param response the pdu to transfer 205 */ 206 @Override transferResponse(Intent fillIn, byte[] response)207 protected boolean transferResponse(Intent fillIn, byte[] response) { 208 // SendConf pdus are always small and can be included in the intent 209 if (response != null) { 210 fillIn.putExtra(SmsManager.EXTRA_MMS_DATA, response); 211 } 212 return true; 213 } 214 215 /** 216 * Read the data from the file descriptor if not yet done 217 * @return whether data successfully read 218 */ prepareForHttpRequest()219 protected boolean prepareForHttpRequest() { 220 return readPduFromContentUri(); 221 } 222 223 /** 224 * Try sending via the carrier app by sending an intent 225 * 226 * @param context The context 227 */ trySendingByCarrierApp(Context context)228 public void trySendingByCarrierApp(Context context) { 229 TelephonyManager telephonyManager = 230 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 231 Intent intent = new Intent(Telephony.Mms.Intents.MMS_SEND_ACTION); 232 List<String> carrierPackages = telephonyManager.getCarrierPackageNamesForIntent( 233 intent); 234 235 if (carrierPackages == null || carrierPackages.size() != 1) { 236 mRequestManager.addRunning(this); 237 } else { 238 intent.setPackage(carrierPackages.get(0)); 239 intent.putExtra(Telephony.Mms.Intents.EXTRA_MMS_CONTENT_URI, mPduUri); 240 intent.putExtra(Telephony.Mms.Intents.EXTRA_MMS_LOCATION_URL, mLocationUrl); 241 intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT); 242 context.sendOrderedBroadcastAsUser( 243 intent, 244 UserHandle.OWNER, 245 android.Manifest.permission.RECEIVE_MMS, 246 AppOpsManager.OP_RECEIVE_MMS, 247 mCarrierAppResultReceiver, 248 null/*scheduler*/, 249 Activity.RESULT_CANCELED, 250 null/*initialData*/, 251 null/*initialExtras*/); 252 } 253 } 254 255 @Override revokeUriPermission(Context context)256 protected void revokeUriPermission(Context context) { 257 context.revokeUriPermission(mPduUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); 258 } 259 } 260