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