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.telephony.PhoneConstants; 36 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 /** 94 * This receiver listens to ACTION_SIM_CARD_STATE_CHANGED after starting a new NetworkRequest. 95 * If ACTION_SIM_CARD_STATE_CHANGED with SIM_STATE_ABSENT for a SIM card corresponding to the 96 * current NetworkRequest is received, it just releases the NetworkRequest without waiting for 97 * timeout. 98 */ 99 private final BroadcastReceiver mReceiver = 100 new BroadcastReceiver() { 101 @Override 102 public void onReceive(Context context, Intent intent) { 103 final int simState = 104 intent.getIntExtra( 105 TelephonyManager.EXTRA_SIM_STATE, 106 TelephonyManager.SIM_STATE_UNKNOWN); 107 final int phoneId = 108 intent.getIntExtra( 109 PhoneConstants.PHONE_KEY, 110 SubscriptionManager.INVALID_PHONE_INDEX); 111 LogUtil.i("MmsNetworkManager: received ACTION_SIM_CARD_STATE_CHANGED" 112 + ", state=" + simStateString(simState) + ", phoneId=" + phoneId); 113 114 if (mPhoneId == phoneId && simState == TelephonyManager.SIM_STATE_ABSENT) { 115 synchronized (MmsNetworkManager.this) { 116 releaseRequestLocked(mNetworkCallback); 117 MmsNetworkManager.this.notifyAll(); 118 } 119 } 120 } 121 }; 122 simStateString(int state)123 private static String simStateString(int state) { 124 switch (state) { 125 case TelephonyManager.SIM_STATE_UNKNOWN: 126 return "UNKNOWN"; 127 case TelephonyManager.SIM_STATE_ABSENT: 128 return "ABSENT"; 129 case TelephonyManager.SIM_STATE_CARD_IO_ERROR: 130 return "CARD_IO_ERROR"; 131 case TelephonyManager.SIM_STATE_CARD_RESTRICTED: 132 return "CARD_RESTRICTED"; 133 case TelephonyManager.SIM_STATE_PRESENT: 134 return "PRESENT"; 135 default: 136 return "INVALID"; 137 } 138 } 139 140 /** 141 * Network callback for our network request 142 */ 143 private class NetworkRequestCallback extends ConnectivityManager.NetworkCallback { 144 @Override onAvailable(Network network)145 public void onAvailable(Network network) { 146 super.onAvailable(network); 147 LogUtil.i("NetworkCallbackListener.onAvailable: network=" + network); 148 synchronized (MmsNetworkManager.this) { 149 mNetwork = network; 150 MmsNetworkManager.this.notifyAll(); 151 } 152 } 153 154 @Override onLost(Network network)155 public void onLost(Network network) { 156 super.onLost(network); 157 LogUtil.w("NetworkCallbackListener.onLost: network=" + network); 158 synchronized (MmsNetworkManager.this) { 159 releaseRequestLocked(this); 160 MmsNetworkManager.this.notifyAll(); 161 } 162 } 163 164 @Override onUnavailable()165 public void onUnavailable() { 166 super.onUnavailable(); 167 LogUtil.w("NetworkCallbackListener.onUnavailable"); 168 synchronized (MmsNetworkManager.this) { 169 releaseRequestLocked(this); 170 MmsNetworkManager.this.notifyAll(); 171 } 172 } 173 } 174 MmsNetworkManager(Context context, int subId)175 public MmsNetworkManager(Context context, int subId) { 176 mContext = context; 177 mNetworkCallback = null; 178 mNetwork = null; 179 mMmsRequestCount = 0; 180 mConnectivityManager = null; 181 mMmsHttpClient = null; 182 mSubId = subId; 183 mReleaseHandler = new Handler(Looper.getMainLooper()); 184 mNetworkRequest = new NetworkRequest.Builder() 185 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) 186 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS) 187 .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() 188 .setSubscriptionId(mSubId).build()) 189 .build(); 190 191 mNetworkReleaseTask = new Runnable() { 192 @Override 193 public void run() { 194 synchronized (this) { 195 if (mMmsRequestCount < 1) { 196 releaseRequestLocked(mNetworkCallback); 197 } 198 } 199 } 200 }; 201 } 202 203 /** 204 * Acquire the MMS network 205 * 206 * @param requestId request ID for logging 207 * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it 208 */ acquireNetwork(final String requestId)209 public void acquireNetwork(final String requestId) throws MmsNetworkException { 210 int networkRequestTimeoutMillis = getNetworkRequestTimeoutMillis(); 211 212 synchronized (this) { 213 // Since we are acquiring the network, remove the network release task if exists. 214 mReleaseHandler.removeCallbacks(mNetworkReleaseTask); 215 mMmsRequestCount += 1; 216 if (mNetwork != null) { 217 // Already available 218 LogUtil.d(requestId, "MmsNetworkManager: already available"); 219 return; 220 } 221 // Not available, so start a new request if not done yet 222 if (mNetworkCallback == null) { 223 mPhoneId = SubscriptionManager.getPhoneId(mSubId); 224 if (mPhoneId == SubscriptionManager.INVALID_PHONE_INDEX 225 || mPhoneId == SubscriptionManager.DEFAULT_PHONE_INDEX) { 226 throw new MmsNetworkException("Invalid Phone Id: " + mPhoneId); 227 } 228 229 LogUtil.d(requestId, "MmsNetworkManager: start new network request"); 230 startNewNetworkRequestLocked(networkRequestTimeoutMillis); 231 232 // Register a receiver to listen to ACTION_SIM_CARD_STATE_CHANGED 233 mContext.registerReceiver( 234 mReceiver, 235 new IntentFilter(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)); 236 mReceiverRegistered = true; 237 } 238 try { 239 this.wait(networkRequestTimeoutMillis + ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS); 240 } catch (InterruptedException e) { 241 LogUtil.w(requestId, "MmsNetworkManager: acquire network wait interrupted"); 242 } 243 244 if (mReceiverRegistered) { 245 // Unregister the receiver. 246 mContext.unregisterReceiver(mReceiver); 247 mReceiverRegistered = false; 248 } 249 250 if (mNetwork != null) { 251 // Success 252 return; 253 } 254 255 if (mNetworkCallback != null) { // Timed out 256 LogUtil.e(requestId, 257 "MmsNetworkManager: timed out with networkRequestTimeoutMillis=" 258 + networkRequestTimeoutMillis 259 + " and ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS=" 260 + ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS); 261 // Release the network request and wake up all the MmsRequests for fast-fail 262 // together. 263 // TODO: Start new network request for remaining MmsRequests? 264 releaseRequestLocked(mNetworkCallback); 265 this.notifyAll(); 266 } 267 268 throw new MmsNetworkException("Acquiring network failed"); 269 } 270 } 271 272 // Timeout used to call ConnectivityManager.requestNetwork 273 // Given that the telephony layer will retry on failures, this timeout should be high enough. getNetworkRequestTimeoutMillis()274 private 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 280 281 /** 282 * Release the MMS network when nobody is holding on to it. 283 * 284 * @param requestId request ID for logging 285 * @param shouldDelayRelease whether the release should be delayed for 5 seconds, the regular 286 * use case is to delay this for DownloadRequests to use the network 287 * for sending an acknowledgement on the same network 288 */ releaseNetwork(final String requestId, final boolean shouldDelayRelease)289 public void releaseNetwork(final String requestId, final boolean shouldDelayRelease) { 290 synchronized (this) { 291 if (mMmsRequestCount > 0) { 292 mMmsRequestCount -= 1; 293 LogUtil.d(requestId, "MmsNetworkManager: release, count=" + mMmsRequestCount); 294 if (mMmsRequestCount < 1) { 295 if (shouldDelayRelease) { 296 // remove previously posted task and post a delayed task on the release 297 // handler to release the network 298 mReleaseHandler.removeCallbacks(mNetworkReleaseTask); 299 mReleaseHandler.postDelayed(mNetworkReleaseTask, 300 NETWORK_RELEASE_TIMEOUT_MILLIS); 301 } else { 302 releaseRequestLocked(mNetworkCallback); 303 } 304 } 305 } 306 } 307 } 308 309 /** 310 * Start a new {@link android.net.NetworkRequest} for MMS 311 */ startNewNetworkRequestLocked(int networkRequestTimeoutMillis)312 private void startNewNetworkRequestLocked(int networkRequestTimeoutMillis) { 313 final ConnectivityManager connectivityManager = getConnectivityManager(); 314 mNetworkCallback = new NetworkRequestCallback(); 315 connectivityManager.requestNetwork( 316 mNetworkRequest, mNetworkCallback, networkRequestTimeoutMillis); 317 } 318 319 /** 320 * Release the current {@link android.net.NetworkRequest} for MMS 321 * 322 * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister 323 */ releaseRequestLocked(ConnectivityManager.NetworkCallback callback)324 private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) { 325 if (callback != null) { 326 final ConnectivityManager connectivityManager = getConnectivityManager(); 327 try { 328 connectivityManager.unregisterNetworkCallback(callback); 329 } catch (IllegalArgumentException e) { 330 // It is possible ConnectivityManager.requestNetwork may fail silently due 331 // to RemoteException. When that happens, we may get an invalid 332 // NetworkCallback, which causes an IllegalArgumentexception when we try to 333 // unregisterNetworkCallback. This exception in turn causes 334 // MmsNetworkManager to skip resetLocked() in the below. Thus MMS service 335 // would get stuck in the bad state until the device restarts. This fix 336 // catches the exception so that state clean up can be executed. 337 LogUtil.w("Unregister network callback exception", e); 338 } 339 } 340 resetLocked(); 341 } 342 343 /** 344 * Reset the state 345 */ resetLocked()346 private void resetLocked() { 347 mNetworkCallback = null; 348 mNetwork = null; 349 mMmsRequestCount = 0; 350 mMmsHttpClient = null; 351 } 352 getConnectivityManager()353 private ConnectivityManager getConnectivityManager() { 354 if (mConnectivityManager == null) { 355 mConnectivityManager = (ConnectivityManager) mContext.getSystemService( 356 Context.CONNECTIVITY_SERVICE); 357 } 358 return mConnectivityManager; 359 } 360 361 /** 362 * Get an MmsHttpClient for the current network 363 * 364 * @return The MmsHttpClient instance 365 */ getOrCreateHttpClient()366 public MmsHttpClient getOrCreateHttpClient() { 367 synchronized (this) { 368 if (mMmsHttpClient == null) { 369 if (mNetwork != null) { 370 // Create new MmsHttpClient for the current Network 371 mMmsHttpClient = new MmsHttpClient(mContext, mNetwork, mConnectivityManager); 372 } 373 } 374 return mMmsHttpClient; 375 } 376 } 377 378 /** 379 * Get the APN name for the active network 380 * 381 * @return The APN name if available, otherwise null 382 */ getApnName()383 public String getApnName() { 384 Network network = null; 385 synchronized (this) { 386 if (mNetwork == null) { 387 return null; 388 } 389 network = mNetwork; 390 } 391 String apnName = null; 392 final ConnectivityManager connectivityManager = getConnectivityManager(); 393 final NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network); 394 if (mmsNetworkInfo != null) { 395 apnName = mmsNetworkInfo.getExtraInfo(); 396 } 397 return apnName; 398 } 399 } 400