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