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