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.SmsManager;
     33 import android.text.TextUtils;
     34 
     35 import com.android.internal.telephony.SmsApplication;
     36 import com.android.mms.service.exception.MmsHttpException;
     37 import com.google.android.mms.MmsException;
     38 import com.google.android.mms.pdu.GenericPdu;
     39 import com.google.android.mms.pdu.PduHeaders;
     40 import com.google.android.mms.pdu.PduParser;
     41 import com.google.android.mms.pdu.PduPersister;
     42 import com.google.android.mms.pdu.SendConf;
     43 import com.google.android.mms.pdu.SendReq;
     44 import com.google.android.mms.util.SqliteWrapper;
     45 
     46 /**
     47  * Request to send an MMS
     48  */
     49 public class SendRequest extends MmsRequest {
     50     private final Uri mPduUri;
     51     private byte[] mPduData;
     52     private final String mLocationUrl;
     53     private final PendingIntent mSentIntent;
     54 
     55     public SendRequest(RequestManager manager, int subId, Uri contentUri, String locationUrl,
     56             PendingIntent sentIntent, String creator, Bundle configOverrides, Context context) {
     57         super(manager, subId, creator, configOverrides, context);
     58         mPduUri = contentUri;
     59         mPduData = null;
     60         mLocationUrl = locationUrl;
     61         mSentIntent = sentIntent;
     62     }
     63 
     64     @Override
     65     protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)
     66             throws MmsHttpException {
     67         final String requestId = this.toString();
     68         final MmsHttpClient mmsHttpClient = netMgr.getOrCreateHttpClient();
     69         if (mmsHttpClient == null) {
     70             LogUtil.e(requestId, "MMS network is not ready!");
     71             throw new MmsHttpException(0/*statusCode*/, "MMS network is not ready");
     72         }
     73         return mmsHttpClient.execute(
     74                 mLocationUrl != null ? mLocationUrl : apn.getMmscUrl(),
     75                 mPduData,
     76                 MmsHttpClient.METHOD_POST,
     77                 apn.isProxySet(),
     78                 apn.getProxyAddress(),
     79                 apn.getProxyPort(),
     80                 mMmsConfig,
     81                 mSubId,
     82                 requestId);
     83     }
     84 
     85     @Override
     86     protected PendingIntent getPendingIntent() {
     87         return mSentIntent;
     88     }
     89 
     90     @Override
     91     protected int getQueueType() {
     92         return MmsService.QUEUE_INDEX_SEND;
     93     }
     94 
     95     @Override
     96     protected Uri persistIfRequired(Context context, int result, byte[] response) {
     97         final String requestId = this.toString();
     98         if (!SmsApplication.shouldWriteMessageForPackage(mCreator, context)) {
     99             // Not required to persist
    100             return null;
    101         }
    102         LogUtil.d(requestId, "persistIfRequired");
    103         if (mPduData == null) {
    104             LogUtil.e(requestId, "persistIfRequired: empty PDU");
    105             return null;
    106         }
    107         final long identity = Binder.clearCallingIdentity();
    108         try {
    109             final boolean supportContentDisposition =
    110                     mMmsConfig.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION);
    111             // Persist the request PDU first
    112             GenericPdu pdu = (new PduParser(mPduData, supportContentDisposition)).parse();
    113             if (pdu == null) {
    114                 LogUtil.e(requestId, "persistIfRequired: can't parse input PDU");
    115                 return null;
    116             }
    117             if (!(pdu instanceof SendReq)) {
    118                 LogUtil.d(requestId, "persistIfRequired: not SendReq");
    119                 return null;
    120             }
    121             final PduPersister persister = PduPersister.getPduPersister(context);
    122             final Uri messageUri = persister.persist(
    123                     pdu,
    124                     Telephony.Mms.Sent.CONTENT_URI,
    125                     true/*createThreadId*/,
    126                     true/*groupMmsEnabled*/,
    127                     null/*preOpenedFiles*/);
    128             if (messageUri == null) {
    129                 LogUtil.e(requestId, "persistIfRequired: can not persist message");
    130                 return null;
    131             }
    132             // Update the additional columns based on the send result
    133             final ContentValues values = new ContentValues();
    134             SendConf sendConf = null;
    135             if (response != null && response.length > 0) {
    136                 pdu = (new PduParser(response, supportContentDisposition)).parse();
    137                 if (pdu != null && pdu instanceof SendConf) {
    138                     sendConf = (SendConf) pdu;
    139                 }
    140             }
    141             if (result != Activity.RESULT_OK
    142                     || sendConf == null
    143                     || sendConf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
    144                 // Since we can't persist a message directly into FAILED box,
    145                 // we have to update the column after we persist it into SENT box.
    146                 // The gap between the state change is tiny so I would not expect
    147                 // it to cause any serious problem
    148                 // TODO: we should add a "failed" URI for this in MmsProvider?
    149                 values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_FAILED);
    150             }
    151             if (sendConf != null) {
    152                 values.put(Telephony.Mms.RESPONSE_STATUS, sendConf.getResponseStatus());
    153                 values.put(Telephony.Mms.MESSAGE_ID,
    154                         PduPersister.toIsoString(sendConf.getMessageId()));
    155             }
    156             values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L);
    157             values.put(Telephony.Mms.READ, 1);
    158             values.put(Telephony.Mms.SEEN, 1);
    159             if (!TextUtils.isEmpty(mCreator)) {
    160                 values.put(Telephony.Mms.CREATOR, mCreator);
    161             }
    162             values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId);
    163             if (SqliteWrapper.update(context, context.getContentResolver(), messageUri, values,
    164                     null/*where*/, null/*selectionArg*/) != 1) {
    165                 LogUtil.e(requestId, "persistIfRequired: failed to update message");
    166             }
    167             return messageUri;
    168         } catch (MmsException e) {
    169             LogUtil.e(requestId, "persistIfRequired: can not persist message", e);
    170         } catch (RuntimeException e) {
    171             LogUtil.e(requestId, "persistIfRequired: unexpected parsing failure", e);
    172         } finally {
    173             Binder.restoreCallingIdentity(identity);
    174         }
    175         return null;
    176     }
    177 
    178     /**
    179      * Read the pdu from the file descriptor and cache pdu bytes in request
    180      * @return true if pdu read successfully
    181      */
    182     private boolean readPduFromContentUri() {
    183         if (mPduData != null) {
    184             return true;
    185         }
    186         final int bytesTobeRead = mMmsConfig.getInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE);
    187         mPduData = mRequestManager.readPduFromContentUri(mPduUri, bytesTobeRead);
    188         return (mPduData != null);
    189     }
    190 
    191     /**
    192      * Transfer the received response to the caller (for send requests the pdu is small and can
    193      *  just include bytes as extra in the "returned" intent).
    194      *
    195      * @param fillIn the intent that will be returned to the caller
    196      * @param response the pdu to transfer
    197      */
    198     @Override
    199     protected boolean transferResponse(Intent fillIn, byte[] response) {
    200         // SendConf pdus are always small and can be included in the intent
    201         if (response != null) {
    202             fillIn.putExtra(SmsManager.EXTRA_MMS_DATA, response);
    203         }
    204         return true;
    205     }
    206 
    207     /**
    208      * Read the data from the file descriptor if not yet done
    209      * @return whether data successfully read
    210      */
    211     @Override
    212     protected boolean prepareForHttpRequest() {
    213         return readPduFromContentUri();
    214     }
    215 
    216     /**
    217      * Try sending via the carrier app
    218      *
    219      * @param context the context
    220      * @param carrierMessagingServicePackage the carrier messaging service sending the MMS
    221      */
    222     public void trySendingByCarrierApp(Context context, String carrierMessagingServicePackage) {
    223         final CarrierSendManager carrierSendManger = new CarrierSendManager();
    224         final CarrierSendCompleteCallback sendCallback = new CarrierSendCompleteCallback(
    225                 context, carrierSendManger);
    226         carrierSendManger.sendMms(context, carrierMessagingServicePackage, sendCallback);
    227     }
    228 
    229     @Override
    230     protected void revokeUriPermission(Context context) {
    231         if (mPduUri != null) {
    232             context.revokeUriPermission(mPduUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    233         }
    234     }
    235 
    236     /**
    237      * Sends the MMS through through the carrier app.
    238      */
    239     private final class CarrierSendManager extends CarrierMessagingServiceManager {
    240         // Initialized in sendMms
    241         private volatile CarrierSendCompleteCallback mCarrierSendCompleteCallback;
    242 
    243         void sendMms(Context context, String carrierMessagingServicePackage,
    244                 CarrierSendCompleteCallback carrierSendCompleteCallback) {
    245             mCarrierSendCompleteCallback = carrierSendCompleteCallback;
    246             if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) {
    247                 LogUtil.v("bindService() for carrier messaging service succeeded");
    248             } else {
    249                 LogUtil.e("bindService() for carrier messaging service failed");
    250                 carrierSendCompleteCallback.onSendMmsComplete(
    251                         CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
    252                         null /* no sendConfPdu */);
    253             }
    254         }
    255 
    256         @Override
    257         protected void onServiceReady(ICarrierMessagingService carrierMessagingService) {
    258             try {
    259                 Uri locationUri = null;
    260                 if (mLocationUrl != null) {
    261                     locationUri = Uri.parse(mLocationUrl);
    262                 }
    263                 carrierMessagingService.sendMms(mPduUri, mSubId, locationUri,
    264                         mCarrierSendCompleteCallback);
    265             } catch (RemoteException e) {
    266                 LogUtil.e("Exception sending MMS using the carrier messaging service: " + e, e);
    267                 mCarrierSendCompleteCallback.onSendMmsComplete(
    268                         CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
    269                         null /* no sendConfPdu */);
    270             }
    271         }
    272     }
    273 
    274     /**
    275      * A callback which notifies carrier messaging app send result. Once the result is ready, the
    276      * carrier messaging service connection is disposed.
    277      */
    278     private final class CarrierSendCompleteCallback extends
    279             MmsRequest.CarrierMmsActionCallback {
    280         private final Context mContext;
    281         private final CarrierSendManager mCarrierSendManager;
    282 
    283         public CarrierSendCompleteCallback(Context context, CarrierSendManager carrierSendManager) {
    284             mContext = context;
    285             mCarrierSendManager = carrierSendManager;
    286         }
    287 
    288         @Override
    289         public void onSendMmsComplete(int result, byte[] sendConfPdu) {
    290             LogUtil.d("Carrier app result for send: " + result);
    291             mCarrierSendManager.disposeConnection(mContext);
    292 
    293             if (!maybeFallbackToRegularDelivery(result)) {
    294                 processResult(mContext, toSmsManagerResult(result), sendConfPdu,
    295                         0/* httpStatusCode */);
    296             }
    297         }
    298 
    299         @Override
    300         public void onDownloadMmsComplete(int result) {
    301             LogUtil.e("Unexpected onDownloadMmsComplete call with result: " + result);
    302         }
    303     }
    304 }
    305