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