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 onAvailable(Network network)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 onLost(Network network)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 onUnavailable()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 MmsNetworkManager(Context context, int subId)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 */ acquireNetwork(final String requestId)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 */ releaseNetwork(final String requestId, final boolean shouldDelayRelease)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 */ startNewNetworkRequestLocked()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 */ releaseRequestLocked(ConnectivityManager.NetworkCallback callback)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 */ resetLocked()244 private void resetLocked() { 245 mNetworkCallback = null; 246 mNetwork = null; 247 mMmsRequestCount = 0; 248 mMmsHttpClient = null; 249 } 250 getConnectivityManager()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 */ getOrCreateHttpClient()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 */ getApnName()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