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.Context;
     22 import android.content.Intent;
     23 import android.net.Uri;
     24 import android.os.Bundle;
     25 import android.provider.Settings;
     26 import android.service.carrier.CarrierMessagingService;
     27 import android.service.carrier.ICarrierMessagingCallback;
     28 import android.telephony.SmsManager;
     29 import android.telephony.TelephonyManager;
     30 import android.util.Log;
     31 
     32 import com.android.mms.service.exception.ApnException;
     33 import com.android.mms.service.exception.MmsHttpException;
     34 import com.android.mms.service.exception.MmsNetworkException;
     35 
     36 /**
     37  * Base class for MMS requests. This has the common logic of sending/downloading MMS.
     38  */
     39 public abstract class MmsRequest {
     40     private static final int RETRY_TIMES = 3;
     41 
     42     /**
     43      * Interface for certain functionalities from MmsService
     44      */
     45     public static interface RequestManager {
     46         /**
     47          * Enqueue an MMS request
     48          *
     49          * @param request the request to enqueue
     50          */
     51         public void addSimRequest(MmsRequest request);
     52 
     53         /*
     54          * @return Whether to auto persist received MMS
     55          */
     56         public boolean getAutoPersistingPref();
     57 
     58         /**
     59          * Read pdu (up to maxSize bytes) from supplied content uri
     60          * @param contentUri content uri from which to read
     61          * @param maxSize maximum number of bytes to read
     62          * @return read pdu (else null in case of error or too big)
     63          */
     64         public byte[] readPduFromContentUri(final Uri contentUri, final int maxSize);
     65 
     66         /**
     67          * Write pdu to supplied content uri
     68          * @param contentUri content uri to which bytes should be written
     69          * @param pdu pdu bytes to write
     70          * @return true in case of success (else false)
     71          */
     72         public boolean writePduToContentUri(final Uri contentUri, final byte[] pdu);
     73     }
     74 
     75     // The reference to the pending requests manager (i.e. the MmsService)
     76     protected RequestManager mRequestManager;
     77     // The SIM id
     78     protected int mSubId;
     79     // The creator app
     80     protected String mCreator;
     81     // MMS config
     82     protected MmsConfig.Overridden mMmsConfig;
     83     // MMS config overrides
     84     protected Bundle mMmsConfigOverrides;
     85 
     86     public MmsRequest(RequestManager requestManager, int subId, String creator,
     87             Bundle configOverrides) {
     88         mRequestManager = requestManager;
     89         mSubId = subId;
     90         mCreator = creator;
     91         mMmsConfigOverrides = configOverrides;
     92         mMmsConfig = null;
     93     }
     94 
     95     public int getSubId() {
     96         return mSubId;
     97     }
     98 
     99     private boolean ensureMmsConfigLoaded() {
    100         if (mMmsConfig == null) {
    101             // Not yet retrieved from mms config manager. Try getting it.
    102             final MmsConfig config = MmsConfigManager.getInstance().getMmsConfigBySubId(mSubId);
    103             if (config != null) {
    104                 mMmsConfig = new MmsConfig.Overridden(config, mMmsConfigOverrides);
    105             }
    106         }
    107         return mMmsConfig != null;
    108     }
    109 
    110     private static boolean inAirplaneMode(final Context context) {
    111         return Settings.System.getInt(context.getContentResolver(),
    112                 Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
    113     }
    114 
    115     private static boolean isMobileDataEnabled(final Context context, final int subId) {
    116         final TelephonyManager telephonyManager =
    117                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    118         return telephonyManager.getDataEnabled(subId);
    119     }
    120 
    121     private static boolean isDataNetworkAvailable(final Context context, final int subId) {
    122         return !inAirplaneMode(context) && isMobileDataEnabled(context, subId);
    123     }
    124 
    125     /**
    126      * Execute the request
    127      *
    128      * @param context The context
    129      * @param networkManager The network manager to use
    130      */
    131     public void execute(Context context, MmsNetworkManager networkManager) {
    132         int result = SmsManager.MMS_ERROR_UNSPECIFIED;
    133         int httpStatusCode = 0;
    134         byte[] response = null;
    135         if (!ensureMmsConfigLoaded()) { // Check mms config
    136             Log.e(MmsService.TAG, "MmsRequest: mms config is not loaded yet");
    137             result = SmsManager.MMS_ERROR_CONFIGURATION_ERROR;
    138         } else if (!prepareForHttpRequest()) { // Prepare request, like reading pdu data from user
    139             Log.e(MmsService.TAG, "MmsRequest: failed to prepare for request");
    140             result = SmsManager.MMS_ERROR_IO_ERROR;
    141         } else if (!isDataNetworkAvailable(context, mSubId)) {
    142             Log.e(MmsService.TAG, "MmsRequest: in airplane mode or mobile data disabled");
    143             result = SmsManager.MMS_ERROR_NO_DATA_NETWORK;
    144         } else { // Execute
    145             long retryDelaySecs = 2;
    146             // Try multiple times of MMS HTTP request
    147             for (int i = 0; i < RETRY_TIMES; i++) {
    148                 try {
    149                     networkManager.acquireNetwork();
    150                     final String apnName = networkManager.getApnName();
    151                     try {
    152                         ApnSettings apn = null;
    153                         try {
    154                             apn = ApnSettings.load(context, apnName, mSubId);
    155                         } catch (ApnException e) {
    156                             // If no APN could be found, fall back to trying without the APN name
    157                             if (apnName == null) {
    158                                 // If the APN name was already null then don't need to retry
    159                                 throw (e);
    160                             }
    161                             Log.i(MmsService.TAG, "MmsRequest: No match with APN name:"
    162                                     + apnName + ", try with no name");
    163                             apn = ApnSettings.load(context, null, mSubId);
    164                         }
    165                         Log.i(MmsService.TAG, "MmsRequest: using " + apn.toString());
    166                         response = doHttp(context, networkManager, apn);
    167                         result = Activity.RESULT_OK;
    168                         // Success
    169                         break;
    170                     } finally {
    171                         networkManager.releaseNetwork();
    172                     }
    173                 } catch (ApnException e) {
    174                     Log.e(MmsService.TAG, "MmsRequest: APN failure", e);
    175                     result = SmsManager.MMS_ERROR_INVALID_APN;
    176                     break;
    177                 } catch (MmsNetworkException e) {
    178                     Log.e(MmsService.TAG, "MmsRequest: MMS network acquiring failure", e);
    179                     result = SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS;
    180                     // Retry
    181                 } catch (MmsHttpException e) {
    182                     Log.e(MmsService.TAG, "MmsRequest: HTTP or network I/O failure", e);
    183                     result = SmsManager.MMS_ERROR_HTTP_FAILURE;
    184                     httpStatusCode = e.getStatusCode();
    185                     // Retry
    186                 } catch (Exception e) {
    187                     Log.e(MmsService.TAG, "MmsRequest: unexpected failure", e);
    188                     result = SmsManager.MMS_ERROR_UNSPECIFIED;
    189                     break;
    190                 }
    191                 try {
    192                     Thread.sleep(retryDelaySecs * 1000, 0/*nano*/);
    193                 } catch (InterruptedException e) {}
    194                 retryDelaySecs <<= 1;
    195             }
    196         }
    197         processResult(context, result, response, httpStatusCode);
    198     }
    199 
    200     /**
    201      * Process the result of the completed request, including updating the message status
    202      * in database and sending back the result via pending intents.
    203      *  @param context The context
    204      * @param result The result code of execution
    205      * @param response The response body
    206      * @param httpStatusCode The optional http status code in case of http failure
    207      */
    208     public void processResult(Context context, int result, byte[] response, int httpStatusCode) {
    209         final Uri messageUri = persistIfRequired(context, result, response);
    210 
    211         // Return MMS HTTP request result via PendingIntent
    212         final PendingIntent pendingIntent = getPendingIntent();
    213         if (pendingIntent != null) {
    214             boolean succeeded = true;
    215             // Extra information to send back with the pending intent
    216             Intent fillIn = new Intent();
    217             if (response != null) {
    218                 succeeded = transferResponse(fillIn, response);
    219             }
    220             if (messageUri != null) {
    221                 fillIn.putExtra("uri", messageUri.toString());
    222             }
    223             if (result == SmsManager.MMS_ERROR_HTTP_FAILURE && httpStatusCode != 0) {
    224                 fillIn.putExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, httpStatusCode);
    225             }
    226             try {
    227                 if (!succeeded) {
    228                     result = SmsManager.MMS_ERROR_IO_ERROR;
    229                 }
    230                 pendingIntent.send(context, result, fillIn);
    231             } catch (PendingIntent.CanceledException e) {
    232                 Log.e(MmsService.TAG, "MmsRequest: sending pending intent canceled", e);
    233             }
    234         }
    235 
    236         revokeUriPermission(context);
    237     }
    238 
    239     /**
    240      * Returns true if sending / downloading using the carrier app has failed and completes the
    241      * action using platform API's, otherwise false.
    242      */
    243     protected boolean maybeFallbackToRegularDelivery(int carrierMessagingAppResult) {
    244         if (carrierMessagingAppResult
    245                 == CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK
    246                 || carrierMessagingAppResult
    247                         == CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK) {
    248             Log.d(MmsService.TAG, "Sending/downloading MMS by IP failed.");
    249             mRequestManager.addSimRequest(MmsRequest.this);
    250             return true;
    251         } else {
    252             return false;
    253         }
    254     }
    255 
    256     /**
    257      * Converts from {@code carrierMessagingAppResult} to a platform result code.
    258      */
    259     protected static int toSmsManagerResult(int carrierMessagingAppResult) {
    260         switch (carrierMessagingAppResult) {
    261             case CarrierMessagingService.SEND_STATUS_OK:
    262                 return Activity.RESULT_OK;
    263             case CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK:
    264                 return SmsManager.MMS_ERROR_RETRY;
    265             default:
    266                 return SmsManager.MMS_ERROR_UNSPECIFIED;
    267         }
    268     }
    269 
    270     /**
    271      * Making the HTTP request to MMSC
    272      *
    273      * @param context The context
    274      * @param netMgr The current {@link MmsNetworkManager}
    275      * @param apn The APN setting
    276      * @return The HTTP response data
    277      * @throws MmsHttpException If any network error happens
    278      */
    279     protected abstract byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)
    280             throws MmsHttpException;
    281 
    282     /**
    283      * @return The PendingIntent associate with the MMS sending invocation
    284      */
    285     protected abstract PendingIntent getPendingIntent();
    286 
    287     /**
    288      * @return The queue should be used by this request, 0 is sending and 1 is downloading
    289      */
    290     protected abstract int getQueueType();
    291 
    292     /**
    293      * Persist message into telephony if required (i.e. when auto-persisting is on or
    294      * the calling app is non-default sms app for sending)
    295      *
    296      * @param context The context
    297      * @param result The result code of execution
    298      * @param response The response body
    299      * @return The persisted URI of the message or null if we don't persist or fail
    300      */
    301     protected abstract Uri persistIfRequired(Context context, int result, byte[] response);
    302 
    303     /**
    304      * Prepare to make the HTTP request - will download message for sending
    305      * @return true if preparation succeeds (and request can proceed) else false
    306      */
    307     protected abstract boolean prepareForHttpRequest();
    308 
    309     /**
    310      * Transfer the received response to the caller
    311      *
    312      * @param fillIn the intent that will be returned to the caller
    313      * @param response the pdu to transfer
    314      * @return true if response transfer succeeds else false
    315      */
    316     protected abstract boolean transferResponse(Intent fillIn, byte[] response);
    317 
    318     /**
    319      * Revoke the content URI permission granted by the MMS app to the phone package.
    320      *
    321      * @param context The context
    322      */
    323     protected abstract void revokeUriPermission(Context context);
    324 
    325     /**
    326      * Base class for handling carrier app send / download result.
    327      */
    328     protected abstract class CarrierMmsActionCallback extends ICarrierMessagingCallback.Stub {
    329         @Override
    330         public void onSendSmsComplete(int result, int messageRef) {
    331             Log.e(MmsService.TAG, "Unexpected onSendSmsComplete call with result: " + result);
    332         }
    333 
    334         @Override
    335         public void onSendMultipartSmsComplete(int result, int[] messageRefs) {
    336             Log.e(MmsService.TAG, "Unexpected onSendMultipartSmsComplete call with result: "
    337                   + result);
    338         }
    339 
    340         @Override
    341         public void onFilterComplete(boolean keepMessage) {
    342             Log.e(MmsService.TAG, "Unexpected onFilterComplete call with result: " + keepMessage);
    343         }
    344     }
    345 }
    346