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