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