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 
SendRequest(RequestManager manager, int subId, Uri contentUri, String locationUrl, PendingIntent sentIntent, String creator, Bundle configOverrides, Context context)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
doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)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 
parsePdu()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      */
notifyIfEmergencyContactNoThrow(final GenericPdu parsedPdu)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 
notifyIfEmergencyContact(final GenericPdu parsedPdu)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 
isEmergencyNumber(String address)136     private boolean isEmergencyNumber(String address) {
137         return !TextUtils.isEmpty(address) && PhoneNumberUtils.isEmergencyNumber(mSubId, address);
138     }
139 
140     @Override
getPendingIntent()141     protected PendingIntent getPendingIntent() {
142         return mSentIntent;
143     }
144 
145     @Override
getQueueType()146     protected int getQueueType() {
147         return MmsService.QUEUE_INDEX_SEND;
148     }
149 
150     @Override
persistIfRequired(Context context, int result, byte[] response)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      */
updateDestinationAddress(final GenericPdu pdu)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 
updateDestinationAddressPerType(SendReq pdu, int type)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      */
readPduFromContentUri()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
transferResponse(Intent fillIn, byte[] response)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
prepareForHttpRequest()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      */
trySendingByCarrierApp(Context context, String carrierMessagingServicePackage)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
revokeUriPermission(Context context)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 
sendMms(Context context, String carrierMessagingServicePackage, CarrierSendCompleteCallback carrierSendCompleteCallback)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
onServiceReady(ICarrierMessagingService carrierMessagingService)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 
CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager)415         public CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager) {
416             mContext = context;
417             mCarrierSendManager = carrierSendManager;
418         }
419 
420         @Override
onSendMmsComplete(int result, byte[] sendConfPdu)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
onDownloadMmsComplete(int result)432         public void onDownloadMmsComplete(int result) {
433             LogUtil.e("Unexpected onDownloadMmsComplete call with result: " + result);
434         }
435     }
436 }
437