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 com.android.mms.service.exception.ApnException;
     20 import com.android.mms.service.exception.MmsHttpException;
     21 import com.android.mms.service.exception.MmsNetworkException;
     22 
     23 import android.app.Activity;
     24 import android.app.PendingIntent;
     25 import android.content.BroadcastReceiver;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.net.ConnectivityManager;
     29 import android.net.LinkProperties;
     30 import android.net.Network;
     31 import android.net.Uri;
     32 import android.os.Bundle;
     33 import android.provider.Telephony;
     34 import android.telephony.SmsManager;
     35 import android.util.Log;
     36 
     37 import java.net.Inet4Address;
     38 import java.net.Inet6Address;
     39 import java.net.InetAddress;
     40 import java.net.UnknownHostException;
     41 import java.util.ArrayList;
     42 import java.util.List;
     43 
     44 /**
     45  * Base class for MMS requests. This has the common logic of sending/downloading MMS.
     46  */
     47 public abstract class MmsRequest {
     48     private static final int RETRY_TIMES = 3;
     49 
     50     protected static final String EXTRA_MESSAGE_REF = "messageref";
     51 
     52     /**
     53      * Interface for certain functionalities from MmsService
     54      */
     55     public static interface RequestManager {
     56         /**
     57          * Add a request to pending queue when it is executed by carrier app
     58          *
     59          * @param key The message ref key from carrier app
     60          * @param request The request in pending
     61          */
     62         public void addPending(int key, MmsRequest request);
     63 
     64         /**
     65          * Enqueue an MMS request for running
     66          *
     67          * @param request the request to enqueue
     68          */
     69         public void addRunning(MmsRequest request);
     70 
     71         /*
     72          * @return Whether to auto persist received MMS
     73          */
     74         public boolean getAutoPersistingPref();
     75 
     76         /**
     77          * Read pdu (up to maxSize bytes) from supplied content uri
     78          * @param contentUri content uri from which to read
     79          * @param maxSize maximum number of bytes to read
     80          * @return read pdu (else null in case of error or too big)
     81          */
     82         public byte[] readPduFromContentUri(final Uri contentUri, final int maxSize);
     83 
     84         /**
     85          * Write pdu to supplied content uri
     86          * @param contentUri content uri to which bytes should be written
     87          * @param pdu pdu bytes to write
     88          * @return true in case of success (else false)
     89          */
     90         public boolean writePduToContentUri(final Uri contentUri, final byte[] pdu);
     91     }
     92 
     93     // The URI of persisted message
     94     protected Uri mMessageUri;
     95     // The reference to the pending requests manager (i.e. the MmsService)
     96     protected RequestManager mRequestManager;
     97     // The SIM id
     98     protected long mSubId;
     99     // The creator app
    100     protected String mCreator;
    101     // MMS config
    102     protected MmsConfig.Overridden mMmsConfig;
    103     // MMS config overrides
    104     protected Bundle mMmsConfigOverrides;
    105 
    106     // Intent result receiver for carrier app
    107     protected final BroadcastReceiver mCarrierAppResultReceiver = new BroadcastReceiver() {
    108         @Override
    109         public void onReceive(Context context, Intent intent) {
    110             final String action = intent.getAction();
    111             if (action.equals(Telephony.Mms.Intents.MMS_SEND_ACTION) ||
    112                     action.equals(Telephony.Mms.Intents.MMS_DOWNLOAD_ACTION)) {
    113                 Log.d(MmsService.TAG, "Carrier app result for " + action);
    114                 final int rc = getResultCode();
    115                 if (rc == Activity.RESULT_OK) {
    116                     // Handled by carrier app, waiting for result
    117                     Log.d(MmsService.TAG, "Sending/downloading MMS by IP pending.");
    118                     final Bundle resultExtras = getResultExtras(false);
    119                     if (resultExtras != null && resultExtras.containsKey(EXTRA_MESSAGE_REF)) {
    120                         final int ref = resultExtras.getInt(EXTRA_MESSAGE_REF);
    121                         Log.d(MmsService.TAG, "messageref = " + ref);
    122                         mRequestManager.addPending(ref, MmsRequest.this);
    123                     } else {
    124                         // Bad, no message ref provided
    125                         Log.e(MmsService.TAG, "Can't find messageref in result extras.");
    126                     }
    127                 } else {
    128                     // No carrier app present, sending normally
    129                     Log.d(MmsService.TAG, "Sending/downloading MMS by IP failed.");
    130                     mRequestManager.addRunning(MmsRequest.this);
    131                 }
    132             } else {
    133                 Log.e(MmsService.TAG, "unexpected BroadcastReceiver action: " + action);
    134             }
    135 
    136         }
    137     };
    138 
    139     public MmsRequest(RequestManager requestManager, Uri messageUri, long subId,
    140             String creator, Bundle configOverrides) {
    141         mRequestManager = requestManager;
    142         mMessageUri = messageUri;
    143         mSubId = subId;
    144         mCreator = creator;
    145         mMmsConfigOverrides = configOverrides;
    146         mMmsConfig = null;
    147     }
    148 
    149     private boolean ensureMmsConfigLoaded() {
    150         if (mMmsConfig == null) {
    151             // Not yet retrieved from mms config manager. Try getting it.
    152             final MmsConfig config = MmsConfigManager.getInstance().getMmsConfigBySubId(mSubId);
    153             if (config != null) {
    154                 mMmsConfig = new MmsConfig.Overridden(config, mMmsConfigOverrides);
    155             }
    156         }
    157         return mMmsConfig != null;
    158     }
    159 
    160     /**
    161      * Execute the request
    162      *
    163      * @param context The context
    164      * @param networkManager The network manager to use
    165      */
    166     public void execute(Context context, MmsNetworkManager networkManager) {
    167         int result = SmsManager.MMS_ERROR_UNSPECIFIED;
    168         byte[] response = null;
    169         if (!ensureMmsConfigLoaded()) { // Check mms config
    170             Log.e(MmsService.TAG, "MmsRequest: mms config is not loaded yet");
    171             result = SmsManager.MMS_ERROR_CONFIGURATION_ERROR;
    172         } else if (!prepareForHttpRequest()) { // Prepare request, like reading pdu data from user
    173             Log.e(MmsService.TAG, "MmsRequest: failed to prepare for request");
    174             result = SmsManager.MMS_ERROR_IO_ERROR;
    175         } else { // Execute
    176             long retryDelaySecs = 2;
    177             // Try multiple times of MMS HTTP request
    178             for (int i = 0; i < RETRY_TIMES; i++) {
    179                 try {
    180                     networkManager.acquireNetwork();
    181                     try {
    182                         final ApnSettings apn = ApnSettings.load(context, null/*apnName*/, mSubId);
    183                         response = doHttp(context, networkManager, apn);
    184                         result = Activity.RESULT_OK;
    185                         // Success
    186                         break;
    187                     } finally {
    188                         networkManager.releaseNetwork();
    189                     }
    190                 } catch (ApnException e) {
    191                     Log.e(MmsService.TAG, "MmsRequest: APN failure", e);
    192                     result = SmsManager.MMS_ERROR_INVALID_APN;
    193                     break;
    194                 } catch (MmsNetworkException e) {
    195                     Log.e(MmsService.TAG, "MmsRequest: MMS network acquiring failure", e);
    196                     result = SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS;
    197                     // Retry
    198                 } catch (MmsHttpException e) {
    199                     Log.e(MmsService.TAG, "MmsRequest: HTTP or network I/O failure", e);
    200                     result = SmsManager.MMS_ERROR_HTTP_FAILURE;
    201                     // Retry
    202                 } catch (Exception e) {
    203                     Log.e(MmsService.TAG, "MmsRequest: unexpected failure", e);
    204                     result = SmsManager.MMS_ERROR_UNSPECIFIED;
    205                     break;
    206                 }
    207                 try {
    208                     Thread.sleep(retryDelaySecs * 1000, 0/*nano*/);
    209                 } catch (InterruptedException e) {}
    210                 retryDelaySecs <<= 1;
    211             }
    212         }
    213         processResult(context, result, response);
    214     }
    215 
    216     /**
    217      * Try running MMS HTTP request for all the addresses that we can resolve to
    218      *
    219      * @param context The context
    220      * @param netMgr The {@link com.android.mms.service.MmsNetworkManager}
    221      * @param url The HTTP URL
    222      * @param pdu The PDU to send
    223      * @param method The HTTP method to use
    224      * @param apn The APN setting to use
    225      * @return The response data
    226      * @throws MmsHttpException If there is any HTTP/network failure
    227      */
    228     protected byte[] doHttpForResolvedAddresses(Context context, MmsNetworkManager netMgr,
    229             String url, byte[] pdu, int method, ApnSettings apn) throws MmsHttpException {
    230         MmsHttpException lastException = null;
    231         final ConnectivityManager connMgr =
    232                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    233         // Do HTTP on all the addresses we can resolve to
    234         for (final InetAddress address : resolveDestination(connMgr, netMgr, url, apn)) {
    235             try {
    236                 // TODO: we have to use a deprecated API here because with the new
    237                 // ConnectivityManager APIs in LMP, we need to either use a bound process
    238                 // or a bound socket. The former can not be used since we share the
    239                 // phone process with others. The latter is not supported by any HTTP
    240                 // library yet. We have to rely on this API to get things work. Once
    241                 // a multinet aware HTTP lib is ready, we should switch to that and
    242                 // remove all the unnecessary code.
    243                 if (!connMgr.requestRouteToHostAddress(
    244                         ConnectivityManager.TYPE_MOBILE_MMS, address)) {
    245                     throw new MmsHttpException("MmsRequest: can not request a route for host "
    246                             + address);
    247                 }
    248                 return HttpUtils.httpConnection(
    249                         context,
    250                         url,
    251                         pdu,
    252                         method,
    253                         apn.isProxySet(),
    254                         apn.getProxyAddress(),
    255                         apn.getProxyPort(),
    256                         netMgr,
    257                         address instanceof Inet6Address,
    258                         mMmsConfig);
    259             } catch (MmsHttpException e) {
    260                 lastException = e;
    261                 Log.e(MmsService.TAG, "MmsRequest: failure in trying address " + address, e);
    262             }
    263         }
    264         if (lastException != null) {
    265             throw lastException;
    266         } else {
    267             // Should not reach here
    268             throw new MmsHttpException("MmsRequest: unknown failure");
    269         }
    270     }
    271 
    272     /**
    273      * Resolve the name of the host we are about to connect to, which can be the URL host or
    274      * the proxy host. We only resolve to the supported address types (IPv4 or IPv6 or both)
    275      * based on the MMS network interface's address type, i.e. we only need addresses that
    276      * match the link address type.
    277      *
    278      * @param connMgr The connectivity manager
    279      * @param netMgr The current {@link MmsNetworkManager}
    280      * @param url The HTTP URL
    281      * @param apn The APN setting to use
    282      * @return A list of matching resolved addresses
    283      * @throws MmsHttpException For any network failure
    284      */
    285     private static List<InetAddress> resolveDestination(ConnectivityManager connMgr,
    286             MmsNetworkManager netMgr, String url, ApnSettings apn) throws MmsHttpException {
    287         Log.d(MmsService.TAG, "MmsRequest: resolve url " + url);
    288         // Find the real host to connect to
    289         String host = null;
    290         if (apn.isProxySet()) {
    291             host = apn.getProxyAddress();
    292         } else {
    293             final Uri uri = Uri.parse(url);
    294             host = uri.getHost();
    295         }
    296         // Find out the link address types: ipv4 or ipv6 or both
    297         final int addressTypes = getMmsLinkAddressTypes(connMgr, netMgr.getNetwork());
    298         Log.d(MmsService.TAG, "MmsRequest: addressTypes=" + addressTypes);
    299         // Resolve the host to a list of addresses based on supported address types
    300         return resolveHostName(netMgr, host, addressTypes);
    301     }
    302 
    303     // Address type masks
    304     private static final int ADDRESS_TYPE_IPV4 = 1;
    305     private static final int ADDRESS_TYPE_IPV6 = 1 << 1;
    306 
    307     /**
    308      * Try to find out if we should use IPv6 or IPv4 for MMS. Basically we check if the MMS
    309      * network interface has IPv6 address or not. If so, we will use IPv6. Otherwise, use
    310      * IPv4.
    311      *
    312      * @param connMgr The connectivity manager
    313      * @return A bit mask indicating what address types we have
    314      */
    315     private static int getMmsLinkAddressTypes(ConnectivityManager connMgr, Network network) {
    316         int result = 0;
    317         // Return none if network is not available
    318         if (network == null) {
    319             return result;
    320         }
    321         final LinkProperties linkProperties = connMgr.getLinkProperties(network);
    322         if (linkProperties != null) {
    323             for (InetAddress addr : linkProperties.getAddresses()) {
    324                 if (addr instanceof Inet4Address) {
    325                     result |= ADDRESS_TYPE_IPV4;
    326                 } else if (addr instanceof Inet6Address) {
    327                     result |= ADDRESS_TYPE_IPV6;
    328                 }
    329             }
    330         }
    331         return result;
    332     }
    333 
    334     /**
    335      * Resolve host name to address by specified address types.
    336      *
    337      * @param netMgr The current {@link MmsNetworkManager}
    338      * @param host The host name
    339      * @param addressTypes The required address type in a bit mask
    340      *  (0x01: IPv4, 0x10: IPv6, 0x11: both)
    341      * @return
    342      * @throws MmsHttpException
    343      */
    344     private static List<InetAddress> resolveHostName(MmsNetworkManager netMgr, String host,
    345             int addressTypes) throws MmsHttpException {
    346         final List<InetAddress> resolved = new ArrayList<InetAddress>();
    347         try {
    348             if (addressTypes != 0) {
    349                 for (final InetAddress addr : netMgr.getAllByName(host)) {
    350                     if ((addressTypes & ADDRESS_TYPE_IPV6) != 0
    351                             && addr instanceof Inet6Address) {
    352                         // Should use IPv6 and this is IPv6 address, add it
    353                         resolved.add(addr);
    354                     } else if ((addressTypes & ADDRESS_TYPE_IPV4) != 0
    355                             && addr instanceof Inet4Address) {
    356                         // Should use IPv4 and this is IPv4 address, add it
    357                         resolved.add(addr);
    358                     }
    359                 }
    360             }
    361             if (resolved.size() < 1) {
    362                 throw new MmsHttpException("Failed to resolve " + host
    363                         + " for allowed address types: " + addressTypes);
    364             }
    365             return resolved;
    366         } catch (final UnknownHostException e) {
    367             throw new MmsHttpException("Failed to resolve " + host, e);
    368         }
    369     }
    370 
    371     /**
    372      * Process the result of the completed request, including updating the message status
    373      * in database and sending back the result via pending intents.
    374      *
    375      * @param context The context
    376      * @param result The result code of execution
    377      * @param response The response body
    378      */
    379     public void processResult(Context context, int result, byte[] response) {
    380         updateStatus(context, result, response);
    381 
    382         // Return MMS HTTP request result via PendingIntent
    383         final PendingIntent pendingIntent = getPendingIntent();
    384         if (pendingIntent != null) {
    385             boolean succeeded = true;
    386             // Extra information to send back with the pending intent
    387             Intent fillIn = new Intent();
    388             if (response != null) {
    389                 succeeded = transferResponse(fillIn, response);
    390             }
    391             if (mMessageUri != null) {
    392                 fillIn.putExtra("uri", mMessageUri.toString());
    393             }
    394             try {
    395                 if (!succeeded) {
    396                     result = SmsManager.MMS_ERROR_IO_ERROR;
    397                 }
    398                 pendingIntent.send(context, result, fillIn);
    399             } catch (PendingIntent.CanceledException e) {
    400                 Log.e(MmsService.TAG, "MmsRequest: sending pending intent canceled", e);
    401             }
    402         }
    403 
    404         revokeUriPermission(context);
    405     }
    406 
    407     /**
    408      * Making the HTTP request to MMSC
    409      *
    410      * @param context The context
    411      * @param netMgr The current {@link MmsNetworkManager}
    412      * @param apn The APN setting
    413      * @return The HTTP response data
    414      * @throws MmsHttpException If any network error happens
    415      */
    416     protected abstract byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)
    417             throws MmsHttpException;
    418 
    419     /**
    420      * @return The PendingIntent associate with the MMS sending invocation
    421      */
    422     protected abstract PendingIntent getPendingIntent();
    423 
    424     /**
    425      * @return The running queue should be used by this request
    426      */
    427     protected abstract int getRunningQueue();
    428 
    429     /**
    430      * Update database status of the message represented by this request
    431      *
    432      * @param context The context
    433      * @param result The result code of execution
    434      * @param response The response body
    435      */
    436     protected abstract void updateStatus(Context context, int result, byte[] response);
    437 
    438     /**
    439      * Prepare to make the HTTP request - will download message for sending
    440      * @return true if preparation succeeds (and request can proceed) else false
    441      */
    442     protected abstract boolean prepareForHttpRequest();
    443 
    444     /**
    445      * Transfer the received response to the caller
    446      *
    447      * @param fillIn the intent that will be returned to the caller
    448      * @param response the pdu to transfer
    449      * @return true if response transfer succeeds else false
    450      */
    451     protected abstract boolean transferResponse(Intent fillIn, byte[] response);
    452 
    453     /**
    454      * Revoke the content URI permission granted by the MMS app to the phone package.
    455      *
    456      * @param context The context
    457      */
    458     protected abstract void revokeUriPermission(Context context);
    459 }
    460