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.content.Context;
     20 import android.net.ConnectivityManager;
     21 import android.net.Network;
     22 import android.net.NetworkCapabilities;
     23 import android.net.NetworkInfo;
     24 import android.net.NetworkRequest;
     25 import android.os.Handler;
     26 import android.os.Looper;
     27 import android.os.SystemClock;
     28 
     29 import com.android.mms.service.exception.MmsNetworkException;
     30 
     31 /**
     32  * Manages the MMS network connectivity
     33  */
     34 public class MmsNetworkManager {
     35     // Timeout used to call ConnectivityManager.requestNetwork
     36     // Given that the telephony layer will retry on failures, this timeout should be high enough.
     37     private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 30 * 60 * 1000;
     38     // Wait timeout for this class, a little bit longer than the above timeout
     39     // to make sure we don't bail prematurely
     40     private static final int NETWORK_ACQUIRE_TIMEOUT_MILLIS =
     41             NETWORK_REQUEST_TIMEOUT_MILLIS + (5 * 1000);
     42     // Waiting time used before releasing a network prematurely. This allows the MMS download
     43     // acknowledgement messages to be sent using the same network that was used to download the data
     44     private static final int NETWORK_RELEASE_TIMEOUT_MILLIS = 5 * 1000;
     45 
     46     private final Context mContext;
     47 
     48     // The requested MMS {@link android.net.Network} we are holding
     49     // We need this when we unbind from it. This is also used to indicate if the
     50     // MMS network is available.
     51     private Network mNetwork;
     52     // The current count of MMS requests that require the MMS network
     53     // If mMmsRequestCount is 0, we should release the MMS network.
     54     private int mMmsRequestCount;
     55     // This is really just for using the capability
     56     private final NetworkRequest mNetworkRequest;
     57     // The callback to register when we request MMS network
     58     private ConnectivityManager.NetworkCallback mNetworkCallback;
     59 
     60     private volatile ConnectivityManager mConnectivityManager;
     61 
     62     // The MMS HTTP client for this network
     63     private MmsHttpClient mMmsHttpClient;
     64 
     65     // The handler used for delayed release of the network
     66     private final Handler mReleaseHandler;
     67 
     68     // The task that does the delayed releasing of the network.
     69     private final Runnable mNetworkReleaseTask;
     70 
     71     // The SIM ID which we use to connect
     72     private final int mSubId;
     73 
     74     /**
     75      * Network callback for our network request
     76      */
     77     private class NetworkRequestCallback extends ConnectivityManager.NetworkCallback {
     78         @Override
     79         public void onAvailable(Network network) {
     80             super.onAvailable(network);
     81             LogUtil.i("NetworkCallbackListener.onAvailable: network=" + network);
     82             synchronized (MmsNetworkManager.this) {
     83                 mNetwork = network;
     84                 MmsNetworkManager.this.notifyAll();
     85             }
     86         }
     87 
     88         @Override
     89         public void onLost(Network network) {
     90             super.onLost(network);
     91             LogUtil.w("NetworkCallbackListener.onLost: network=" + network);
     92             synchronized (MmsNetworkManager.this) {
     93                 releaseRequestLocked(this);
     94                 MmsNetworkManager.this.notifyAll();
     95             }
     96         }
     97 
     98         @Override
     99         public void onUnavailable() {
    100             super.onUnavailable();
    101             LogUtil.w("NetworkCallbackListener.onUnavailable");
    102             synchronized (MmsNetworkManager.this) {
    103                 releaseRequestLocked(this);
    104                 MmsNetworkManager.this.notifyAll();
    105             }
    106         }
    107     }
    108 
    109     public MmsNetworkManager(Context context, int subId) {
    110         mContext = context;
    111         mNetworkCallback = null;
    112         mNetwork = null;
    113         mMmsRequestCount = 0;
    114         mConnectivityManager = null;
    115         mMmsHttpClient = null;
    116         mSubId = subId;
    117         mReleaseHandler = new Handler(Looper.getMainLooper());
    118         mNetworkRequest = new NetworkRequest.Builder()
    119                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
    120                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
    121                 .setNetworkSpecifier(Integer.toString(mSubId))
    122                 .build();
    123 
    124         mNetworkReleaseTask = new Runnable() {
    125             @Override
    126             public void run() {
    127                 synchronized (this) {
    128                     if (mMmsRequestCount < 1) {
    129                         releaseRequestLocked(mNetworkCallback);
    130                     }
    131                 }
    132             }
    133         };
    134     }
    135 
    136     /**
    137      * Acquire the MMS network
    138      *
    139      * @param requestId request ID for logging
    140      * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it
    141      */
    142     public void acquireNetwork(final String requestId) throws MmsNetworkException {
    143         synchronized (this) {
    144             // Since we are acquiring the network, remove the network release task if exists.
    145             mReleaseHandler.removeCallbacks(mNetworkReleaseTask);
    146             mMmsRequestCount += 1;
    147             if (mNetwork != null) {
    148                 // Already available
    149                 LogUtil.d(requestId, "MmsNetworkManager: already available");
    150                 return;
    151             }
    152             // Not available, so start a new request if not done yet
    153             if (mNetworkCallback == null) {
    154                 LogUtil.d(requestId, "MmsNetworkManager: start new network request");
    155                 startNewNetworkRequestLocked();
    156             }
    157             final long shouldEnd = SystemClock.elapsedRealtime() + NETWORK_ACQUIRE_TIMEOUT_MILLIS;
    158             long waitTime = NETWORK_ACQUIRE_TIMEOUT_MILLIS;
    159             while (waitTime > 0) {
    160                 try {
    161                     this.wait(waitTime);
    162                 } catch (InterruptedException e) {
    163                     LogUtil.w(requestId, "MmsNetworkManager: acquire network wait interrupted");
    164                 }
    165                 if (mNetwork != null) {
    166                     // Success
    167                     return;
    168                 }
    169                 // Calculate remaining waiting time to make sure we wait the full timeout period
    170                 waitTime = shouldEnd - SystemClock.elapsedRealtime();
    171             }
    172             // Timed out, so release the request and fail
    173             LogUtil.e(requestId, "MmsNetworkManager: timed out");
    174             releaseRequestLocked(mNetworkCallback);
    175             throw new MmsNetworkException("Acquiring network timed out");
    176         }
    177     }
    178 
    179     /**
    180      * Release the MMS network when nobody is holding on to it.
    181      *
    182      * @param requestId request ID for logging
    183      * @param shouldDelayRelease whether the release should be delayed for 5 seconds, the regular
    184      *                           use case is to delay this for DownloadRequests to use the network
    185      *                           for sending an acknowledgement on the same network
    186      */
    187     public void releaseNetwork(final String requestId, final boolean shouldDelayRelease) {
    188         synchronized (this) {
    189             if (mMmsRequestCount > 0) {
    190                 mMmsRequestCount -= 1;
    191                 LogUtil.d(requestId, "MmsNetworkManager: release, count=" + mMmsRequestCount);
    192                 if (mMmsRequestCount < 1) {
    193                     if (shouldDelayRelease) {
    194                         // remove previously posted task and post a delayed task on the release
    195                         // handler to release the network
    196                         mReleaseHandler.removeCallbacks(mNetworkReleaseTask);
    197                         mReleaseHandler.postDelayed(mNetworkReleaseTask,
    198                                 NETWORK_RELEASE_TIMEOUT_MILLIS);
    199                     } else {
    200                         releaseRequestLocked(mNetworkCallback);
    201                     }
    202                 }
    203             }
    204         }
    205     }
    206 
    207     /**
    208      * Start a new {@link android.net.NetworkRequest} for MMS
    209      */
    210     private void startNewNetworkRequestLocked() {
    211         final ConnectivityManager connectivityManager = getConnectivityManager();
    212         mNetworkCallback = new NetworkRequestCallback();
    213         connectivityManager.requestNetwork(
    214                 mNetworkRequest, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS);
    215     }
    216 
    217     /**
    218      * Release the current {@link android.net.NetworkRequest} for MMS
    219      *
    220      * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister
    221      */
    222     private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) {
    223         if (callback != null) {
    224             final ConnectivityManager connectivityManager = getConnectivityManager();
    225             try {
    226                 connectivityManager.unregisterNetworkCallback(callback);
    227             } catch (IllegalArgumentException e) {
    228                 // It is possible ConnectivityManager.requestNetwork may fail silently due
    229                 // to RemoteException. When that happens, we may get an invalid
    230                 // NetworkCallback, which causes an IllegalArgumentexception when we try to
    231                 // unregisterNetworkCallback. This exception in turn causes
    232                 // MmsNetworkManager to skip resetLocked() in the below. Thus MMS service
    233                 // would get stuck in the bad state until the device restarts. This fix
    234                 // catches the exception so that state clean up can be executed.
    235                 LogUtil.w("Unregister network callback exception", e);
    236             }
    237         }
    238         resetLocked();
    239     }
    240 
    241     /**
    242      * Reset the state
    243      */
    244     private void resetLocked() {
    245         mNetworkCallback = null;
    246         mNetwork = null;
    247         mMmsRequestCount = 0;
    248         mMmsHttpClient = null;
    249     }
    250 
    251     private ConnectivityManager getConnectivityManager() {
    252         if (mConnectivityManager == null) {
    253             mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
    254                     Context.CONNECTIVITY_SERVICE);
    255         }
    256         return mConnectivityManager;
    257     }
    258 
    259     /**
    260      * Get an MmsHttpClient for the current network
    261      *
    262      * @return The MmsHttpClient instance
    263      */
    264     public MmsHttpClient getOrCreateHttpClient() {
    265         synchronized (this) {
    266             if (mMmsHttpClient == null) {
    267                 if (mNetwork != null) {
    268                     // Create new MmsHttpClient for the current Network
    269                     mMmsHttpClient = new MmsHttpClient(mContext, mNetwork, mConnectivityManager);
    270                 }
    271             }
    272             return mMmsHttpClient;
    273         }
    274     }
    275 
    276     /**
    277      * Get the APN name for the active network
    278      *
    279      * @return The APN name if available, otherwise null
    280      */
    281     public String getApnName() {
    282         Network network = null;
    283         synchronized (this) {
    284             if (mNetwork == null) {
    285                 return null;
    286             }
    287             network = mNetwork;
    288         }
    289         String apnName = null;
    290         final ConnectivityManager connectivityManager = getConnectivityManager();
    291         final NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network);
    292         if (mmsNetworkInfo != null) {
    293             apnName = mmsNetworkInfo.getExtraInfo();
    294         }
    295         return apnName;
    296     }
    297 }
    298