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.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.net.ConnectivityManager; 24 import android.net.Network; 25 import android.net.NetworkCapabilities; 26 import android.net.NetworkInfo; 27 import android.net.NetworkRequest; 28 import android.net.TelephonyNetworkSpecifier; 29 import android.os.Handler; 30 import android.os.Looper; 31 import android.provider.DeviceConfig; 32 import android.telephony.SubscriptionManager; 33 import android.telephony.TelephonyManager; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.internal.telephony.PhoneConstants; 37 import com.android.mms.service.exception.MmsNetworkException; 38 39 /** 40 * Manages the MMS network connectivity 41 */ 42 public class MmsNetworkManager { 43 private static final String MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS = 44 "mms_service_network_request_timeout_millis"; 45 46 // Default timeout used to call ConnectivityManager.requestNetwork if the 47 // MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS flag is not set. 48 // Given that the telephony layer will retry on failures, this timeout should be high enough. 49 private static final int DEFAULT_MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS = 30 * 60 * 1000; 50 51 // Wait timeout for this class, this is an additional delay after waiting the network request 52 // timeout to make sure we don't bail prematurely. 53 private static final int ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS = (5 * 1000); 54 55 // Waiting time used before releasing a network prematurely. This allows the MMS download 56 // acknowledgement messages to be sent using the same network that was used to download the data 57 private static final int NETWORK_RELEASE_TIMEOUT_MILLIS = 5 * 1000; 58 59 private final Context mContext; 60 61 // The requested MMS {@link android.net.Network} we are holding 62 // We need this when we unbind from it. This is also used to indicate if the 63 // MMS network is available. 64 private Network mNetwork; 65 // The current count of MMS requests that require the MMS network 66 // If mMmsRequestCount is 0, we should release the MMS network. 67 private int mMmsRequestCount; 68 // This is really just for using the capability 69 private final NetworkRequest mNetworkRequest; 70 // The callback to register when we request MMS network 71 private ConnectivityManager.NetworkCallback mNetworkCallback; 72 73 private volatile ConnectivityManager mConnectivityManager; 74 75 // The MMS HTTP client for this network 76 private MmsHttpClient mMmsHttpClient; 77 78 // The handler used for delayed release of the network 79 private final Handler mReleaseHandler; 80 81 // The task that does the delayed releasing of the network. 82 private final Runnable mNetworkReleaseTask; 83 84 // The SIM ID which we use to connect 85 private final int mSubId; 86 87 // The current Phone ID for this MmsNetworkManager 88 private int mPhoneId; 89 90 // If ACTION_SIM_CARD_STATE_CHANGED intent receiver is registered 91 private boolean mReceiverRegistered; 92 93 private final Dependencies mDeps; 94 95 /** 96 * This receiver listens to ACTION_SIM_CARD_STATE_CHANGED after starting a new NetworkRequest. 97 * If ACTION_SIM_CARD_STATE_CHANGED with SIM_STATE_ABSENT for a SIM card corresponding to the 98 * current NetworkRequest is received, it just releases the NetworkRequest without waiting for 99 * timeout. 100 */ 101 private final BroadcastReceiver mReceiver = 102 new BroadcastReceiver() { 103 @Override 104 public void onReceive(Context context, Intent intent) { 105 final int simState = 106 intent.getIntExtra( 107 TelephonyManager.EXTRA_SIM_STATE, 108 TelephonyManager.SIM_STATE_UNKNOWN); 109 final int phoneId = 110 intent.getIntExtra( 111 PhoneConstants.PHONE_KEY, 112 SubscriptionManager.INVALID_PHONE_INDEX); 113 LogUtil.i("MmsNetworkManager: received ACTION_SIM_CARD_STATE_CHANGED" 114 + ", state=" + simStateString(simState) + ", phoneId=" + phoneId); 115 116 if (mPhoneId == phoneId && simState == TelephonyManager.SIM_STATE_ABSENT) { 117 synchronized (MmsNetworkManager.this) { 118 releaseRequestLocked(mNetworkCallback); 119 MmsNetworkManager.this.notifyAll(); 120 } 121 } 122 } 123 }; 124 simStateString(int state)125 private static String simStateString(int state) { 126 switch (state) { 127 case TelephonyManager.SIM_STATE_UNKNOWN: 128 return "UNKNOWN"; 129 case TelephonyManager.SIM_STATE_ABSENT: 130 return "ABSENT"; 131 case TelephonyManager.SIM_STATE_CARD_IO_ERROR: 132 return "CARD_IO_ERROR"; 133 case TelephonyManager.SIM_STATE_CARD_RESTRICTED: 134 return "CARD_RESTRICTED"; 135 case TelephonyManager.SIM_STATE_PRESENT: 136 return "PRESENT"; 137 default: 138 return "INVALID"; 139 } 140 } 141 142 /** 143 * Network callback for our network request 144 */ 145 private class NetworkRequestCallback extends ConnectivityManager.NetworkCallback { 146 @Override onLost(Network network)147 public void onLost(Network network) { 148 super.onLost(network); 149 LogUtil.w("NetworkCallbackListener.onLost: network=" + network); 150 synchronized (MmsNetworkManager.this) { 151 // Wait for other available network. Not notify. 152 if (network.equals(mNetwork)) { 153 mNetwork = null; 154 mMmsHttpClient = null; 155 } 156 } 157 } 158 159 @Override onUnavailable()160 public void onUnavailable() { 161 super.onUnavailable(); 162 LogUtil.w("NetworkCallbackListener.onUnavailable"); 163 synchronized (MmsNetworkManager.this) { 164 releaseRequestLocked(this); 165 MmsNetworkManager.this.notifyAll(); 166 } 167 } 168 169 @Override onCapabilitiesChanged(Network network, NetworkCapabilities nc)170 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { 171 // onAvailable will always immediately be followed by a onCapabilitiesChanged. Check 172 // network status here is enough. 173 super.onCapabilitiesChanged(network, nc); 174 LogUtil.w("NetworkCallbackListener.onCapabilitiesChanged: network=" 175 + network + ", nc=" + nc); 176 synchronized (MmsNetworkManager.this) { 177 final boolean isAvailable = 178 nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); 179 if (network.equals(mNetwork) && !isAvailable) { 180 // Current network becomes suspended. 181 mNetwork = null; 182 mMmsHttpClient = null; 183 // Not notify. Either wait for other available network or current network to 184 // become available again. 185 return; 186 } 187 188 // New available network 189 if (mNetwork == null && isAvailable) { 190 mNetwork = network; 191 MmsNetworkManager.this.notifyAll(); 192 } 193 } 194 } 195 } 196 197 /** 198 * Dependencies of MmsNetworkManager, for injection in tests. 199 */ 200 @VisibleForTesting 201 public static class Dependencies { 202 /** Get phone Id from the given subId */ getPhoneId(int subId)203 public int getPhoneId(int subId) { 204 return SubscriptionManager.getPhoneId(subId); 205 } 206 207 // Timeout used to call ConnectivityManager.requestNetwork. Given that the telephony layer 208 // will retry on failures, this timeout should be high enough. getNetworkRequestTimeoutMillis()209 public int getNetworkRequestTimeoutMillis() { 210 return DeviceConfig.getInt( 211 DeviceConfig.NAMESPACE_TELEPHONY, MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS, 212 DEFAULT_MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS); 213 } 214 getAdditionalNetworkAcquireTimeoutMillis()215 public int getAdditionalNetworkAcquireTimeoutMillis() { 216 return ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS; 217 } 218 } 219 220 @VisibleForTesting MmsNetworkManager(Context context, int subId, Dependencies dependencies)221 protected MmsNetworkManager(Context context, int subId, Dependencies dependencies) { 222 mContext = context; 223 mDeps = dependencies; 224 mNetworkCallback = null; 225 mNetwork = null; 226 mMmsRequestCount = 0; 227 mConnectivityManager = null; 228 mMmsHttpClient = null; 229 mSubId = subId; 230 mReleaseHandler = new Handler(Looper.getMainLooper()); 231 mNetworkRequest = new NetworkRequest.Builder() 232 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) 233 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS) 234 .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() 235 .setSubscriptionId(mSubId).build()) 236 .build(); 237 238 mNetworkReleaseTask = new Runnable() { 239 @Override 240 public void run() { 241 synchronized (this) { 242 if (mMmsRequestCount < 1) { 243 releaseRequestLocked(mNetworkCallback); 244 } 245 } 246 } 247 }; 248 } 249 MmsNetworkManager(Context context, int subId)250 public MmsNetworkManager(Context context, int subId) { 251 this(context, subId, new Dependencies()); 252 } 253 254 /** 255 * Acquire the MMS network 256 * 257 * @param requestId request ID for logging 258 * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it 259 */ acquireNetwork(final String requestId)260 public void acquireNetwork(final String requestId) throws MmsNetworkException { 261 int networkRequestTimeoutMillis = mDeps.getNetworkRequestTimeoutMillis(); 262 263 synchronized (this) { 264 // Since we are acquiring the network, remove the network release task if exists. 265 mReleaseHandler.removeCallbacks(mNetworkReleaseTask); 266 mMmsRequestCount += 1; 267 if (mNetwork != null) { 268 // Already available 269 LogUtil.d(requestId, "MmsNetworkManager: already available"); 270 return; 271 } 272 273 if (!mReceiverRegistered) { 274 mPhoneId = mDeps.getPhoneId(mSubId); 275 if (mPhoneId == SubscriptionManager.INVALID_PHONE_INDEX 276 || mPhoneId == SubscriptionManager.DEFAULT_PHONE_INDEX) { 277 throw new MmsNetworkException("Invalid Phone Id: " + mPhoneId); 278 } 279 280 // Register a receiver to listen to ACTION_SIM_CARD_STATE_CHANGED 281 mContext.registerReceiver( 282 mReceiver, 283 new IntentFilter(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)); 284 mReceiverRegistered = true; 285 } 286 287 // Not available, so start a new request if not done yet 288 if (mNetworkCallback == null) { 289 LogUtil.d(requestId, "MmsNetworkManager: start new network request"); 290 startNewNetworkRequestLocked(networkRequestTimeoutMillis); 291 } 292 293 try { 294 this.wait(networkRequestTimeoutMillis 295 + mDeps.getAdditionalNetworkAcquireTimeoutMillis()); 296 } catch (InterruptedException e) { 297 LogUtil.w(requestId, "MmsNetworkManager: acquire network wait interrupted"); 298 } 299 300 if (mReceiverRegistered) { 301 // Unregister the receiver. 302 mContext.unregisterReceiver(mReceiver); 303 mReceiverRegistered = false; 304 } 305 306 if (mNetwork != null) { 307 // Success 308 return; 309 } 310 311 if (mNetworkCallback != null) { // Timed out 312 LogUtil.e(requestId, 313 "MmsNetworkManager: timed out with networkRequestTimeoutMillis=" 314 + networkRequestTimeoutMillis 315 + " and ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS=" 316 + mDeps.getAdditionalNetworkAcquireTimeoutMillis()); 317 // Release the network request and wake up all the MmsRequests for fast-fail 318 // together. 319 // TODO: Start new network request for remaining MmsRequests? 320 releaseRequestLocked(mNetworkCallback); 321 this.notifyAll(); 322 } 323 324 throw new MmsNetworkException("Acquiring network failed"); 325 } 326 } 327 328 /** 329 * Release the MMS network when nobody is holding on to it. 330 * 331 * @param requestId request ID for logging 332 * @param shouldDelayRelease whether the release should be delayed for 5 seconds, the regular 333 * use case is to delay this for DownloadRequests to use the network 334 * for sending an acknowledgement on the same network 335 */ releaseNetwork(final String requestId, final boolean shouldDelayRelease)336 public void releaseNetwork(final String requestId, final boolean shouldDelayRelease) { 337 synchronized (this) { 338 if (mMmsRequestCount > 0) { 339 mMmsRequestCount -= 1; 340 LogUtil.d(requestId, "MmsNetworkManager: release, count=" + mMmsRequestCount); 341 if (mMmsRequestCount < 1) { 342 if (shouldDelayRelease) { 343 // remove previously posted task and post a delayed task on the release 344 // handler to release the network 345 mReleaseHandler.removeCallbacks(mNetworkReleaseTask); 346 mReleaseHandler.postDelayed(mNetworkReleaseTask, 347 NETWORK_RELEASE_TIMEOUT_MILLIS); 348 } else { 349 releaseRequestLocked(mNetworkCallback); 350 } 351 } 352 } 353 } 354 } 355 356 /** 357 * Start a new {@link android.net.NetworkRequest} for MMS 358 */ startNewNetworkRequestLocked(int networkRequestTimeoutMillis)359 private void startNewNetworkRequestLocked(int networkRequestTimeoutMillis) { 360 final ConnectivityManager connectivityManager = getConnectivityManager(); 361 mNetworkCallback = new NetworkRequestCallback(); 362 connectivityManager.requestNetwork( 363 mNetworkRequest, mNetworkCallback, networkRequestTimeoutMillis); 364 } 365 366 /** 367 * Release the current {@link android.net.NetworkRequest} for MMS 368 * 369 * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister 370 */ releaseRequestLocked(ConnectivityManager.NetworkCallback callback)371 private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) { 372 if (callback != null) { 373 final ConnectivityManager connectivityManager = getConnectivityManager(); 374 try { 375 connectivityManager.unregisterNetworkCallback(callback); 376 } catch (IllegalArgumentException e) { 377 // It is possible ConnectivityManager.requestNetwork may fail silently due 378 // to RemoteException. When that happens, we may get an invalid 379 // NetworkCallback, which causes an IllegalArgumentexception when we try to 380 // unregisterNetworkCallback. This exception in turn causes 381 // MmsNetworkManager to skip resetLocked() in the below. Thus MMS service 382 // would get stuck in the bad state until the device restarts. This fix 383 // catches the exception so that state clean up can be executed. 384 LogUtil.w("Unregister network callback exception", e); 385 } 386 } 387 resetLocked(); 388 } 389 390 /** 391 * Reset the state 392 */ resetLocked()393 private void resetLocked() { 394 mNetworkCallback = null; 395 mNetwork = null; 396 mMmsRequestCount = 0; 397 mMmsHttpClient = null; 398 } 399 getConnectivityManager()400 private ConnectivityManager getConnectivityManager() { 401 if (mConnectivityManager == null) { 402 mConnectivityManager = (ConnectivityManager) mContext.getSystemService( 403 Context.CONNECTIVITY_SERVICE); 404 } 405 return mConnectivityManager; 406 } 407 408 /** 409 * Get an MmsHttpClient for the current network 410 * 411 * @return The MmsHttpClient instance 412 */ getOrCreateHttpClient()413 public MmsHttpClient getOrCreateHttpClient() { 414 synchronized (this) { 415 if (mMmsHttpClient == null) { 416 if (mNetwork != null) { 417 // Create new MmsHttpClient for the current Network 418 mMmsHttpClient = new MmsHttpClient(mContext, mNetwork, mConnectivityManager); 419 } 420 } 421 return mMmsHttpClient; 422 } 423 } 424 425 /** 426 * Get the APN name for the active network 427 * 428 * @return The APN name if available, otherwise null 429 */ getApnName()430 public String getApnName() { 431 Network network = null; 432 synchronized (this) { 433 if (mNetwork == null) { 434 return null; 435 } 436 network = mNetwork; 437 } 438 String apnName = null; 439 final ConnectivityManager connectivityManager = getConnectivityManager(); 440 final NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network); 441 if (mmsNetworkInfo != null) { 442 apnName = mmsNetworkInfo.getExtraInfo(); 443 } 444 return apnName; 445 } 446 } 447