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