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.NetworkRequest; 24 import android.net.NetworkInfo; 25 import android.os.SystemClock; 26 import android.provider.Settings; 27 import android.util.Log; 28 29 import com.android.mms.service.exception.MmsNetworkException; 30 import com.android.okhttp.ConnectionPool; 31 import com.android.okhttp.HostResolver; 32 33 import java.net.InetAddress; 34 import java.net.UnknownHostException; 35 36 /** 37 * Manages the MMS network connectivity 38 */ 39 public class MmsNetworkManager implements HostResolver { 40 // Timeout used to call ConnectivityManager.requestNetwork 41 private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000; 42 // Wait timeout for this class, a little bit longer than the above timeout 43 // to make sure we don't bail prematurely 44 private static final int NETWORK_ACQUIRE_TIMEOUT_MILLIS = 45 NETWORK_REQUEST_TIMEOUT_MILLIS + (5 * 1000); 46 47 // Borrowed from {@link android.net.Network} 48 private static final boolean httpKeepAlive = 49 Boolean.parseBoolean(System.getProperty("http.keepAlive", "true")); 50 private static final int httpMaxConnections = 51 httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0; 52 private static final long httpKeepAliveDurationMs = 53 Long.parseLong(System.getProperty("http.keepAliveDuration", "300000")); // 5 minutes. 54 55 private final Context mContext; 56 57 // The requested MMS {@link android.net.Network} we are holding 58 // We need this when we unbind from it. This is also used to indicate if the 59 // MMS network is available. 60 private Network mNetwork; 61 // The current count of MMS requests that require the MMS network 62 // If mMmsRequestCount is 0, we should release the MMS network. 63 private int mMmsRequestCount; 64 // This is really just for using the capability 65 private final NetworkRequest mNetworkRequest; 66 // The callback to register when we request MMS network 67 private ConnectivityManager.NetworkCallback mNetworkCallback; 68 69 private volatile ConnectivityManager mConnectivityManager; 70 71 // The OkHttp's ConnectionPool used by the HTTP client associated with this network manager 72 private ConnectionPool mConnectionPool; 73 74 // The MMS HTTP client for this network 75 private MmsHttpClient mMmsHttpClient; 76 77 // The SIM ID which we use to connect 78 private final int mSubId; 79 MmsNetworkManager(Context context, int subId)80 public MmsNetworkManager(Context context, int subId) { 81 mContext = context; 82 mNetworkCallback = null; 83 mNetwork = null; 84 mMmsRequestCount = 0; 85 mConnectivityManager = null; 86 mConnectionPool = null; 87 mMmsHttpClient = null; 88 mSubId = subId; 89 mNetworkRequest = new NetworkRequest.Builder() 90 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) 91 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS) 92 .setNetworkSpecifier(Integer.toString(mSubId)) 93 .build(); 94 } 95 96 /** 97 * Acquire the MMS network 98 * 99 * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it 100 */ acquireNetwork()101 public void acquireNetwork() throws MmsNetworkException { 102 synchronized (this) { 103 mMmsRequestCount += 1; 104 if (mNetwork != null) { 105 // Already available 106 Log.d(MmsService.TAG, "MmsNetworkManager: already available"); 107 return; 108 } 109 Log.d(MmsService.TAG, "MmsNetworkManager: start new network request"); 110 // Not available, so start a new request 111 newRequest(); 112 final long shouldEnd = SystemClock.elapsedRealtime() + NETWORK_ACQUIRE_TIMEOUT_MILLIS; 113 long waitTime = NETWORK_ACQUIRE_TIMEOUT_MILLIS; 114 while (waitTime > 0) { 115 try { 116 this.wait(waitTime); 117 } catch (InterruptedException e) { 118 Log.w(MmsService.TAG, "MmsNetworkManager: acquire network wait interrupted"); 119 } 120 if (mNetwork != null) { 121 // Success 122 return; 123 } 124 // Calculate remaining waiting time to make sure we wait the full timeout period 125 waitTime = shouldEnd - SystemClock.elapsedRealtime(); 126 } 127 // Timed out, so release the request and fail 128 Log.d(MmsService.TAG, "MmsNetworkManager: timed out"); 129 releaseRequestLocked(mNetworkCallback); 130 throw new MmsNetworkException("Acquiring network timed out"); 131 } 132 } 133 134 /** 135 * Release the MMS network when nobody is holding on to it. 136 */ releaseNetwork()137 public void releaseNetwork() { 138 synchronized (this) { 139 if (mMmsRequestCount > 0) { 140 mMmsRequestCount -= 1; 141 Log.d(MmsService.TAG, "MmsNetworkManager: release, count=" + mMmsRequestCount); 142 if (mMmsRequestCount < 1) { 143 releaseRequestLocked(mNetworkCallback); 144 } 145 } 146 } 147 } 148 149 /** 150 * Start a new {@link android.net.NetworkRequest} for MMS 151 */ newRequest()152 private void newRequest() { 153 final ConnectivityManager connectivityManager = getConnectivityManager(); 154 mNetworkCallback = new ConnectivityManager.NetworkCallback() { 155 @Override 156 public void onAvailable(Network network) { 157 super.onAvailable(network); 158 Log.d(MmsService.TAG, "NetworkCallbackListener.onAvailable: network=" + network); 159 synchronized (MmsNetworkManager.this) { 160 mNetwork = network; 161 MmsNetworkManager.this.notifyAll(); 162 } 163 } 164 165 @Override 166 public void onLost(Network network) { 167 super.onLost(network); 168 Log.d(MmsService.TAG, "NetworkCallbackListener.onLost: network=" + network); 169 synchronized (MmsNetworkManager.this) { 170 releaseRequestLocked(this); 171 MmsNetworkManager.this.notifyAll(); 172 } 173 } 174 175 @Override 176 public void onUnavailable() { 177 super.onUnavailable(); 178 Log.d(MmsService.TAG, "NetworkCallbackListener.onUnavailable"); 179 synchronized (MmsNetworkManager.this) { 180 releaseRequestLocked(this); 181 MmsNetworkManager.this.notifyAll(); 182 } 183 } 184 }; 185 connectivityManager.requestNetwork( 186 mNetworkRequest, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS); 187 } 188 189 /** 190 * Release the current {@link android.net.NetworkRequest} for MMS 191 * 192 * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister 193 */ releaseRequestLocked(ConnectivityManager.NetworkCallback callback)194 private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) { 195 if (callback != null) { 196 final ConnectivityManager connectivityManager = getConnectivityManager(); 197 connectivityManager.unregisterNetworkCallback(callback); 198 } 199 resetLocked(); 200 } 201 202 /** 203 * Reset the state 204 */ resetLocked()205 private void resetLocked() { 206 mNetworkCallback = null; 207 mNetwork = null; 208 mMmsRequestCount = 0; 209 // Currently we follow what android.net.Network does with ConnectionPool, 210 // which is per Network object. So if Network changes, we should clear 211 // out the ConnectionPool and thus the MmsHttpClient (since it is linked 212 // to a specific ConnectionPool). 213 mConnectionPool = null; 214 mMmsHttpClient = null; 215 } 216 217 private static final InetAddress[] EMPTY_ADDRESS_ARRAY = new InetAddress[0]; 218 @Override getAllByName(String host)219 public InetAddress[] getAllByName(String host) throws UnknownHostException { 220 Network network = null; 221 synchronized (this) { 222 if (mNetwork == null) { 223 return EMPTY_ADDRESS_ARRAY; 224 } 225 network = mNetwork; 226 } 227 return network.getAllByName(host); 228 } 229 getConnectivityManager()230 private ConnectivityManager getConnectivityManager() { 231 if (mConnectivityManager == null) { 232 mConnectivityManager = (ConnectivityManager) mContext.getSystemService( 233 Context.CONNECTIVITY_SERVICE); 234 } 235 return mConnectivityManager; 236 } 237 getOrCreateConnectionPoolLocked()238 private ConnectionPool getOrCreateConnectionPoolLocked() { 239 if (mConnectionPool == null) { 240 mConnectionPool = new ConnectionPool(httpMaxConnections, httpKeepAliveDurationMs); 241 } 242 return mConnectionPool; 243 } 244 245 /** 246 * Get an MmsHttpClient for the current network 247 * 248 * @return The MmsHttpClient instance 249 */ getOrCreateHttpClient()250 public MmsHttpClient getOrCreateHttpClient() { 251 synchronized (this) { 252 if (mMmsHttpClient == null) { 253 if (mNetwork != null) { 254 // Create new MmsHttpClient for the current Network 255 mMmsHttpClient = new MmsHttpClient( 256 mContext, 257 mNetwork.getSocketFactory(), 258 MmsNetworkManager.this, 259 getOrCreateConnectionPoolLocked()); 260 } 261 } 262 return mMmsHttpClient; 263 } 264 } 265 266 /** 267 * Get the APN name for the active network 268 * 269 * @return The APN name if available, otherwise null 270 */ getApnName()271 public String getApnName() { 272 Network network = null; 273 synchronized (this) { 274 if (mNetwork == null) { 275 Log.d(MmsService.TAG, "MmsNetworkManager: getApnName: network not available"); 276 return null; 277 } 278 network = mNetwork; 279 } 280 String apnName = null; 281 final ConnectivityManager connectivityManager = getConnectivityManager(); 282 NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network); 283 if (mmsNetworkInfo != null) { 284 apnName = mmsNetworkInfo.getExtraInfo(); 285 } 286 Log.d(MmsService.TAG, "MmsNetworkManager: getApnName: " + apnName); 287 return apnName; 288 } 289 } 290