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