• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                 byte[] messageId = sendConf.getMessageId();
209                 if (messageId != null) {
210                     values.put(Telephony.Mms.MESSAGE_ID, PduPersister.toIsoString(messageId));
211                 }
212             }
213             values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L);
214             values.put(Telephony.Mms.READ, 1);
215             values.put(Telephony.Mms.SEEN, 1);
216             if (!TextUtils.isEmpty(mCreator)) {
217                 values.put(Telephony.Mms.CREATOR, mCreator);
218             }
219             values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId);
220             if (SqliteWrapper.update(context, context.getContentResolver(), messageUri, values,
221                     null/*where*/, null/*selectionArg*/) != 1) {
222                 LogUtil.e(requestId, "persistIfRequired: failed to update message");
223             }
224             return messageUri;
225         } catch (MmsException e) {
226             LogUtil.e(requestId, "persistIfRequired: can not persist message", e);
227         } catch (RuntimeException e) {
228             LogUtil.e(requestId, "persistIfRequired: unexpected parsing failure", e);
229         } finally {
230             Binder.restoreCallingIdentity(identity);
231         }
232         return null;
233     }
234 
235     /**
236      * Update the destination Address of MO MMS before sending.
237      * This is special for VZW requirement. Follow the specificaitons of assisted dialing
238      * of MO MMS while traveling on VZW CDMA, international CDMA or GSM markets.
239      */
updateDestinationAddress(final GenericPdu pdu)240     private void updateDestinationAddress(final GenericPdu pdu) {
241         final String requestId = getRequestId();
242         if (pdu == null) {
243             LogUtil.e(requestId, "updateDestinationAddress: can't parse input PDU");
244             return ;
245         }
246         if (!(pdu instanceof SendReq)) {
247             LogUtil.i(requestId, "updateDestinationAddress: not SendReq");
248             return;
249         }
250 
251        boolean isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.TO);
252        isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.CC) || isUpdated;
253        isUpdated = updateDestinationAddressPerType((SendReq)pdu, PduHeaders.BCC) || isUpdated;
254 
255        if (isUpdated) {
256            mPduData = new PduComposer(mContext, (SendReq)pdu).make();
257        }
258    }
259 
updateDestinationAddressPerType(SendReq pdu, int type)260     private boolean updateDestinationAddressPerType(SendReq pdu, int type) {
261         boolean isUpdated = false;
262         EncodedStringValue[] recipientNumbers = null;
263 
264         switch (type) {
265             case PduHeaders.TO:
266                 recipientNumbers = pdu.getTo();
267                 break;
268             case PduHeaders.CC:
269                 recipientNumbers = pdu.getCc();
270                 break;
271             case PduHeaders.BCC:
272                 recipientNumbers = pdu.getBcc();
273                 break;
274             default:
275                 return false;
276         }
277 
278         if (recipientNumbers != null) {
279             int nNumberCount = recipientNumbers.length;
280             if (nNumberCount > 0) {
281                 Phone phone = PhoneFactory.getDefaultPhone();
282                 EncodedStringValue[] newNumbers = new EncodedStringValue[nNumberCount];
283                 String toNumber;
284                 String newToNumber;
285                 for (int i = 0; i < nNumberCount; i++) {
286                     toNumber = recipientNumbers[i].getString();
287                     newToNumber = SmsNumberUtils.filterDestAddr(phone, toNumber);
288                     if (!TextUtils.equals(toNumber, newToNumber)) {
289                         isUpdated = true;
290                         newNumbers[i] = new EncodedStringValue(newToNumber);
291                     } else {
292                         newNumbers[i] = recipientNumbers[i];
293                     }
294                 }
295                 switch (type) {
296                     case PduHeaders.TO:
297                         pdu.setTo(newNumbers);
298                         break;
299                     case PduHeaders.CC:
300                         pdu.setCc(newNumbers);
301                         break;
302                     case PduHeaders.BCC:
303                         pdu.setBcc(newNumbers);
304                         break;
305                 }
306             }
307         }
308 
309         return isUpdated;
310     }
311 
312     /**
313      * Read the pdu from the file descriptor and cache pdu bytes in request
314      * @return true if pdu read successfully
315      */
readPduFromContentUri()316     private boolean readPduFromContentUri() {
317         if (mPduData != null) {
318             return true;
319         }
320         final int bytesTobeRead = mMmsConfig.getInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE);
321         mPduData = mRequestManager.readPduFromContentUri(mPduUri, bytesTobeRead);
322         return (mPduData != null);
323     }
324 
325     /**
326      * Transfer the received response to the caller (for send requests the pdu is small and can
327      *  just include bytes as extra in the "returned" intent).
328      *
329      * @param fillIn the intent that will be returned to the caller
330      * @param response the pdu to transfer
331      */
332     @Override
transferResponse(Intent fillIn, byte[] response)333     protected boolean transferResponse(Intent fillIn, byte[] response) {
334         // SendConf pdus are always small and can be included in the intent
335         if (response != null) {
336             fillIn.putExtra(SmsManager.EXTRA_MMS_DATA, response);
337         }
338         return true;
339     }
340 
341     /**
342      * Read the data from the file descriptor if not yet done
343      * @return whether data successfully read
344      */
345     @Override
prepareForHttpRequest()346     protected boolean prepareForHttpRequest() {
347         return readPduFromContentUri();
348     }
349 
350     /**
351      * Try sending via the carrier app
352      *
353      * @param context the context
354      * @param carrierMessagingServicePackage the carrier messaging service sending the MMS
355      */
trySendingByCarrierApp(Context context, String carrierMessagingServicePackage)356     public void trySendingByCarrierApp(Context context, String carrierMessagingServicePackage) {
357         final CarrierSendManager carrierSendManger = new CarrierSendManager();
358         final CarrierSendCompleteCallback sendCallback = new CarrierSendCompleteCallback(
359                 context, carrierSendManger);
360         carrierSendManger.sendMms(context, carrierMessagingServicePackage, sendCallback);
361     }
362 
363     @Override
revokeUriPermission(Context context)364     protected void revokeUriPermission(Context context) {
365         if (mPduUri != null) {
366             context.revokeUriPermission(mPduUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
367         }
368     }
369 
370     /**
371      * Sends the MMS through through the carrier app.
372      */
373     private final class CarrierSendManager extends CarrierMessagingServiceManager {
374         // Initialized in sendMms
375         private volatile CarrierSendCompleteCallback mCarrierSendCompleteCallback;
376 
sendMms(Context context, String carrierMessagingServicePackage, CarrierSendCompleteCallback carrierSendCompleteCallback)377         void sendMms(Context context, String carrierMessagingServicePackage,
378                 CarrierSendCompleteCallback carrierSendCompleteCallback) {
379             mCarrierSendCompleteCallback = carrierSendCompleteCallback;
380             if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) {
381                 LogUtil.v("bindService() for carrier messaging service succeeded");
382             } else {
383                 LogUtil.e("bindService() for carrier messaging service failed");
384                 carrierSendCompleteCallback.onSendMmsComplete(
385                         CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
386                         null /* no sendConfPdu */);
387             }
388         }
389 
390         @Override
onServiceReady(ICarrierMessagingService carrierMessagingService)391         protected void onServiceReady(ICarrierMessagingService carrierMessagingService) {
392             try {
393                 Uri locationUri = null;
394                 if (mLocationUrl != null) {
395                     locationUri = Uri.parse(mLocationUrl);
396                 }
397                 carrierMessagingService.sendMms(mPduUri, mSubId, locationUri,
398                         mCarrierSendCompleteCallback);
399             } catch (RemoteException e) {
400                 LogUtil.e("Exception sending MMS using the carrier messaging service: " + e, e);
401                 mCarrierSendCompleteCallback.onSendMmsComplete(
402                         CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
403                         null /* no sendConfPdu */);
404             }
405         }
406     }
407 
408     /**
409      * A callback which notifies carrier messaging app send result. Once the result is ready, the
410      * carrier messaging service connection is disposed.
411      */
412     private final class CarrierSendCompleteCallback extends
413             MmsRequest.CarrierMmsActionCallback {
414         private final Context mContext;
415         private final CarrierSendManager mCarrierSendManager;
416 
CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager)417         public CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager) {
418             mContext = context;
419             mCarrierSendManager = carrierSendManager;
420         }
421 
422         @Override
onSendMmsComplete(int result, byte[] sendConfPdu)423         public void onSendMmsComplete(int result, byte[] sendConfPdu) {
424             LogUtil.d("Carrier app result for send: " + result);
425             mCarrierSendManager.disposeConnection(mContext);
426 
427             if (!maybeFallbackToRegularDelivery(result)) {
428                 processResult(mContext, toSmsManagerResult(result), sendConfPdu,
429                         0/* httpStatusCode */);
430             }
431         }
432 
433         @Override
onDownloadMmsComplete(int result)434         public void onDownloadMmsComplete(int result) {
435             LogUtil.e("Unexpected onDownloadMmsComplete call with result: " + result);
436         }
437     }
438 }
439