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