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