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.AsyncTask;
26 import android.os.Binder;
27 import android.os.Bundle;
28 import android.provider.BlockedNumberContract;
29 import android.provider.Telephony;
30 import android.service.carrier.CarrierMessagingService;
31 import android.service.carrier.CarrierMessagingServiceWrapper;
32 import android.telephony.SmsManager;
33 import android.telephony.TelephonyManager;
34 import android.text.TextUtils;
35 
36 import com.android.internal.telephony.SmsApplication;
37 import com.android.internal.telephony.SmsNumberUtils;
38 import com.android.mms.service.exception.MmsHttpException;
39 import com.google.android.mms.MmsException;
40 import com.google.android.mms.pdu.EncodedStringValue;
41 import com.google.android.mms.pdu.GenericPdu;
42 import com.google.android.mms.pdu.PduComposer;
43 import com.google.android.mms.pdu.PduHeaders;
44 import com.google.android.mms.pdu.PduParser;
45 import com.google.android.mms.pdu.PduPersister;
46 import com.google.android.mms.pdu.SendConf;
47 import com.google.android.mms.pdu.SendReq;
48 import com.google.android.mms.util.SqliteWrapper;
49 
50 /**
51  * Request to send an MMS
52  */
53 public class SendRequest extends MmsRequest {
54     private final Uri mPduUri;
55     private byte[] mPduData;
56     private final String mLocationUrl;
57     private final PendingIntent mSentIntent;
58 
SendRequest(RequestManager manager, int subId, Uri contentUri, String locationUrl, PendingIntent sentIntent, String creator, Bundle configOverrides, Context context, long messageId)59     public SendRequest(RequestManager manager, int subId, Uri contentUri, String locationUrl,
60             PendingIntent sentIntent, String creator, Bundle configOverrides, Context context,
61             long messageId) {
62         super(manager, subId, creator, configOverrides, context, messageId);
63         mPduUri = contentUri;
64         mPduData = null;
65         mLocationUrl = locationUrl;
66         mSentIntent = sentIntent;
67     }
68 
69     @Override
doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)70     protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)
71             throws MmsHttpException {
72         final String requestId = getRequestId();
73         final MmsHttpClient mmsHttpClient = netMgr.getOrCreateHttpClient();
74         if (mmsHttpClient == null) {
75             String notReady = "MMS network is not ready! messageId: " + mMessageId;
76             LogUtil.e(requestId, notReady);
77             throw new MmsHttpException(0/*statusCode*/, notReady);
78         }
79         final GenericPdu parsedPdu = parsePdu();
80         notifyIfEmergencyContactNoThrow(parsedPdu);
81         updateDestinationAddress(parsedPdu);
82         return mmsHttpClient.execute(
83                 mLocationUrl != null ? mLocationUrl : apn.getMmscUrl(),
84                 mPduData,
85                 MmsHttpClient.METHOD_POST,
86                 apn.isProxySet(),
87                 apn.getProxyAddress(),
88                 apn.getProxyPort(),
89                 mMmsConfig,
90                 mSubId,
91                 requestId);
92     }
93 
parsePdu()94     private GenericPdu parsePdu() {
95         final String requestId = getRequestId();
96         try {
97             if (mPduData == null) {
98                 LogUtil.w(requestId, "Empty PDU raw data. messageId: " + mMessageId);
99                 return null;
100             }
101             final boolean supportContentDisposition =
102                     mMmsConfig.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION);
103             return new PduParser(mPduData, supportContentDisposition).parse();
104         } catch (final Exception e) {
105             LogUtil.w(requestId, "Failed to parse PDU raw data. messageId: " + mMessageId);
106         }
107         return null;
108     }
109 
110     /**
111      * If the MMS is being sent to an emergency number, the blocked number provider is notified
112      * so that it can disable number blocking.
113      */
notifyIfEmergencyContactNoThrow(final GenericPdu parsedPdu)114     private void notifyIfEmergencyContactNoThrow(final GenericPdu parsedPdu) {
115         try {
116             notifyIfEmergencyContact(parsedPdu);
117         } catch (Exception e) {
118             LogUtil.w(getRequestId(), "Error in notifyIfEmergencyContact. messageId: " + mMessageId, e);
119         }
120     }
121 
notifyIfEmergencyContact(final GenericPdu parsedPdu)122     private void notifyIfEmergencyContact(final GenericPdu parsedPdu) {
123         if (parsedPdu != null && parsedPdu.getMessageType() == PduHeaders.MESSAGE_TYPE_SEND_REQ) {
124             SendReq sendReq = (SendReq) parsedPdu;
125             for (EncodedStringValue encodedStringValue : sendReq.getTo()) {
126                 if (isEmergencyNumber(encodedStringValue.getString())) {
127                     LogUtil.i(getRequestId(), "Notifying emergency contact. messageId: "
128                             + mMessageId);
129                     new AsyncTask<Void, Void, Void>() {
130                         @Override
131                         protected Void doInBackground(Void... voids) {
132                             try {
133                                 BlockedNumberContract.SystemContract
134                                         .notifyEmergencyContact(mContext);
135                             } catch (Exception e) {
136                                 LogUtil.e(getRequestId(),
137                                     "Exception notifying emergency contact. messageId: "
138                                             + mMessageId + e);
139                             }
140                             return null;
141                         }
142                     }.execute();
143                     return;
144                 }
145             }
146         }
147     }
148 
isEmergencyNumber(String address)149     private boolean isEmergencyNumber(String address) {
150         if (!TextUtils.isEmpty(address)) {
151             TelephonyManager telephonyManager = ((TelephonyManager) mContext
152                 .getSystemService(Context.TELEPHONY_SERVICE)).createForSubscriptionId(mSubId);
153             return telephonyManager.isEmergencyNumber(address);
154         }
155         return false;
156     }
157 
158     @Override
getPendingIntent()159     protected PendingIntent getPendingIntent() {
160         return mSentIntent;
161     }
162 
163     @Override
getQueueType()164     protected int getQueueType() {
165         return MmsService.QUEUE_INDEX_SEND;
166     }
167 
168     @Override
persistIfRequired(Context context, int result, byte[] response)169     protected Uri persistIfRequired(Context context, int result, byte[] response) {
170         final String requestId = getRequestId();
171         if (!SmsApplication.shouldWriteMessageForPackage(mCreator, context)) {
172             // Not required to persist
173             return null;
174         }
175         LogUtil.d(requestId, "persistIfRequired. messageId: " + mMessageId);
176         if (mPduData == null) {
177             LogUtil.e(requestId, "persistIfRequired: empty PDU. messageId: " + mMessageId);
178             return null;
179         }
180         final long identity = Binder.clearCallingIdentity();
181         try {
182             final boolean supportContentDisposition =
183                     mMmsConfig.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION);
184             // Persist the request PDU first
185             GenericPdu pdu = (new PduParser(mPduData, supportContentDisposition)).parse();
186             if (pdu == null) {
187                 LogUtil.e(requestId, "persistIfRequired: can't parse input PDU. messageId: "
188                         + mMessageId);
189                 return null;
190             }
191             if (!(pdu instanceof SendReq)) {
192                 LogUtil.d(requestId, "persistIfRequired: not SendReq. messageId: " + mMessageId);
193                 return null;
194             }
195             final PduPersister persister = PduPersister.getPduPersister(context);
196             final Uri messageUri = persister.persist(
197                     pdu,
198                     Telephony.Mms.Sent.CONTENT_URI,
199                     true/*createThreadId*/,
200                     true/*groupMmsEnabled*/,
201                     null/*preOpenedFiles*/);
202             if (messageUri == null) {
203                 LogUtil.e(requestId, "persistIfRequired: can not persist message. messageId: "
204                         + mMessageId);
205                 return null;
206             }
207             // Update the additional columns based on the send result
208             final ContentValues values = new ContentValues();
209             SendConf sendConf = null;
210             if (response != null && response.length > 0) {
211                 pdu = (new PduParser(response, supportContentDisposition)).parse();
212                 if (pdu != null && pdu instanceof SendConf) {
213                     sendConf = (SendConf) pdu;
214                 }
215             }
216             if (result != Activity.RESULT_OK
217                     || sendConf == null
218                     || sendConf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
219                 // Since we can't persist a message directly into FAILED box,
220                 // we have to update the column after we persist it into SENT box.
221                 // The gap between the state change is tiny so I would not expect
222                 // it to cause any serious problem
223                 // TODO: we should add a "failed" URI for this in MmsProvider?
224                 values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_FAILED);
225             }
226             if (sendConf != null) {
227                 values.put(Telephony.Mms.RESPONSE_STATUS, sendConf.getResponseStatus());
228                 byte[] messageId = sendConf.getMessageId();
229                 if (messageId != null) {
230                     values.put(Telephony.Mms.MESSAGE_ID, PduPersister.toIsoString(messageId));
231                 }
232             }
233             values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L);
234             values.put(Telephony.Mms.READ, 1);
235             values.put(Telephony.Mms.SEEN, 1);
236             if (!TextUtils.isEmpty(mCreator)) {
237                 values.put(Telephony.Mms.CREATOR, mCreator);
238             }
239             values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId);
240             if (SqliteWrapper.update(context, context.getContentResolver(), messageUri, values,
241                     null/*where*/, null/*selectionArg*/) != 1) {
242                 LogUtil.e(requestId, "persistIfRequired: failed to update message. messageId: "
243                         + mMessageId);
244             }
245             return messageUri;
246         } catch (MmsException e) {
247             LogUtil.e(requestId, "persistIfRequired: can not persist message. messageId: "
248                     + mMessageId, e);
249         } catch (RuntimeException e) {
250             LogUtil.e(requestId, "persistIfRequired: unexpected parsing failure. messageId: "
251                     + mMessageId, e);
252         } finally {
253             Binder.restoreCallingIdentity(identity);
254         }
255         return null;
256     }
257 
258     /**
259      * Update the destination Address of MO MMS before sending.
260      * This is special for VZW requirement. Follow the specificaitons of assisted dialing
261      * of MO MMS while traveling on VZW CDMA, international CDMA or GSM markets.
262      */
updateDestinationAddress(final GenericPdu pdu)263     private void updateDestinationAddress(final GenericPdu pdu) {
264         final String requestId = getRequestId();
265         if (pdu == null) {
266             LogUtil.e(requestId, "updateDestinationAddress: can't parse input PDU. messageId: "
267                     + mMessageId);
268             return ;
269         }
270         if (!(pdu instanceof SendReq)) {
271             LogUtil.i(requestId, "updateDestinationAddress: not SendReq. messageId: " + mMessageId);
272             return;
273         }
274 
275        boolean isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.TO);
276        isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.CC) || isUpdated;
277        isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.BCC) || isUpdated;
278 
279        if (isUpdated) {
280            mPduData = new PduComposer(mContext, (SendReq)pdu).make();
281        }
282    }
283 
updateDestinationAddressPerType(SendReq pdu, int type)284     private boolean updateDestinationAddressPerType(SendReq pdu, int type) {
285         boolean isUpdated = false;
286         EncodedStringValue[] recipientNumbers = null;
287 
288         switch (type) {
289             case PduHeaders.TO:
290                 recipientNumbers = pdu.getTo();
291                 break;
292             case PduHeaders.CC:
293                 recipientNumbers = pdu.getCc();
294                 break;
295             case PduHeaders.BCC:
296                 recipientNumbers = pdu.getBcc();
297                 break;
298             default:
299                 return false;
300         }
301 
302         if (recipientNumbers != null) {
303             int nNumberCount = recipientNumbers.length;
304             if (nNumberCount > 0) {
305                 EncodedStringValue[] newNumbers = new EncodedStringValue[nNumberCount];
306                 String toNumber;
307                 String newToNumber;
308                 for (int i = 0; i < nNumberCount; i++) {
309                     toNumber = recipientNumbers[i].getString();
310                     newToNumber = SmsNumberUtils.filterDestAddr(mContext, mSubId, toNumber);
311                     if (!TextUtils.equals(toNumber, newToNumber)) {
312                         isUpdated = true;
313                         newNumbers[i] = new EncodedStringValue(newToNumber);
314                     } else {
315                         newNumbers[i] = recipientNumbers[i];
316                     }
317                 }
318                 switch (type) {
319                     case PduHeaders.TO:
320                         pdu.setTo(newNumbers);
321                         break;
322                     case PduHeaders.CC:
323                         pdu.setCc(newNumbers);
324                         break;
325                     case PduHeaders.BCC:
326                         pdu.setBcc(newNumbers);
327                         break;
328                 }
329             }
330         }
331 
332         return isUpdated;
333     }
334 
335     /**
336      * Read the pdu from the file descriptor and cache pdu bytes in request
337      * @return true if pdu read successfully
338      */
readPduFromContentUri()339     private boolean readPduFromContentUri() {
340         if (mPduData != null) {
341             return true;
342         }
343         final int bytesTobeRead = mMmsConfig.getInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE);
344         mPduData = mRequestManager.readPduFromContentUri(mPduUri, bytesTobeRead);
345         return (mPduData != null);
346     }
347 
348     /**
349      * Transfer the received response to the caller (for send requests the pdu is small and can
350      *  just include bytes as extra in the "returned" intent).
351      *
352      * @param fillIn the intent that will be returned to the caller
353      * @param response the pdu to transfer
354      */
355     @Override
transferResponse(Intent fillIn, byte[] response)356     protected boolean transferResponse(Intent fillIn, byte[] response) {
357         // SendConf pdus are always small and can be included in the intent
358         if (response != null) {
359             fillIn.putExtra(SmsManager.EXTRA_MMS_DATA, response);
360         }
361         return true;
362     }
363 
364     /**
365      * Read the data from the file descriptor if not yet done
366      * @return whether data successfully read
367      */
368     @Override
prepareForHttpRequest()369     protected boolean prepareForHttpRequest() {
370         return readPduFromContentUri();
371     }
372 
373     /**
374      * Try sending via the carrier app
375      *
376      * @param context the context
377      * @param carrierMessagingServicePackage the carrier messaging service sending the MMS
378      */
trySendingByCarrierApp(Context context, String carrierMessagingServicePackage)379     public void trySendingByCarrierApp(Context context, String carrierMessagingServicePackage) {
380         final CarrierSendManager carrierSendManger = new CarrierSendManager();
381         final CarrierSendCompleteCallback sendCallback = new CarrierSendCompleteCallback(
382                 context, carrierSendManger);
383         carrierSendManger.sendMms(context, carrierMessagingServicePackage, sendCallback);
384     }
385 
386     @Override
revokeUriPermission(Context context)387     protected void revokeUriPermission(Context context) {
388         if (mPduUri != null) {
389             context.revokeUriPermission(mPduUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
390         }
391     }
392 
393     /**
394      * Sends the MMS through through the carrier app.
395      */
396     private final class CarrierSendManager extends CarrierMessagingServiceWrapper {
397         // Initialized in sendMms
398         private volatile CarrierSendCompleteCallback mCarrierSendCompleteCallback;
399 
sendMms(Context context, String carrierMessagingServicePackage, CarrierSendCompleteCallback carrierSendCompleteCallback)400         void sendMms(Context context, String carrierMessagingServicePackage,
401                 CarrierSendCompleteCallback carrierSendCompleteCallback) {
402             mCarrierSendCompleteCallback = carrierSendCompleteCallback;
403             if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) {
404                 LogUtil.v("bindService() for carrier messaging service succeeded. messageId: "
405                         + mMessageId);
406             } else {
407                 LogUtil.e("bindService() for carrier messaging service failed. messageId: "
408                         + mMessageId);
409                 carrierSendCompleteCallback.onSendMmsComplete(
410                         CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
411                         null /* no sendConfPdu */);
412             }
413         }
414 
415         @Override
onServiceReady()416         public void onServiceReady() {
417             try {
418                 Uri locationUri = null;
419                 if (mLocationUrl != null) {
420                     locationUri = Uri.parse(mLocationUrl);
421                 }
422                 sendMms(mPduUri, mSubId, locationUri, mCarrierSendCompleteCallback);
423             } catch (RuntimeException e) {
424                 LogUtil.e("Exception sending MMS using the carrier messaging service. messageId: "
425                         + mMessageId + e, e);
426                 mCarrierSendCompleteCallback.onSendMmsComplete(
427                         CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
428                         null /* no sendConfPdu */);
429             }
430         }
431     }
432 
433     /**
434      * A callback which notifies carrier messaging app send result. Once the result is ready, the
435      * carrier messaging service connection is disposed.
436      */
437     private final class CarrierSendCompleteCallback extends
438             MmsRequest.CarrierMmsActionCallback {
439         private final Context mContext;
440         private final CarrierSendManager mCarrierSendManager;
441 
CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager)442         public CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager) {
443             mContext = context;
444             mCarrierSendManager = carrierSendManager;
445         }
446 
447         @Override
onSendMmsComplete(int result, byte[] sendConfPdu)448         public void onSendMmsComplete(int result, byte[] sendConfPdu) {
449             LogUtil.d("Carrier app result for sending messageId " + mMessageId + ": " + result);
450             mCarrierSendManager.disposeConnection(mContext);
451 
452             if (!maybeFallbackToRegularDelivery(result)) {
453                 processResult(mContext, toSmsManagerResult(result), sendConfPdu,
454                         0/* httpStatusCode */);
455             }
456         }
457 
458         @Override
onDownloadMmsComplete(int result)459         public void onDownloadMmsComplete(int result) {
460             LogUtil.e("Unexpected onDownloadMmsComplete call for messageId " + mMessageId
461                     + " with result: " + result);
462         }
463     }
464 }
465