MobileDataStateTracker.java revision 9bc709d46e1165ca0c9a02bd970767c401b990e5
1/* 2 * Copyright (C) 2008 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 17package android.net; 18 19import java.net.InetAddress; 20 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.os.RemoteException; 26import android.os.Handler; 27import android.os.ServiceManager; 28import com.android.internal.telephony.ITelephony; 29import com.android.internal.telephony.Phone; 30import com.android.internal.telephony.TelephonyIntents; 31import android.net.NetworkInfo.DetailedState; 32import android.telephony.TelephonyManager; 33import android.util.Log; 34import android.text.TextUtils; 35 36/** 37 * Track the state of mobile data connectivity. This is done by 38 * receiving broadcast intents from the Phone process whenever 39 * the state of data connectivity changes. 40 * 41 * {@hide} 42 */ 43public class MobileDataStateTracker extends NetworkStateTracker { 44 45 private static final String TAG = "MobileDataStateTracker"; 46 private static final boolean DBG = true; 47 48 private Phone.DataState mMobileDataState; 49 private ITelephony mPhoneService; 50 51 private String mApnType; 52 private String mApnTypeToWatchFor; 53 private String mApnName; 54 private boolean mEnabled; 55 private BroadcastReceiver mStateReceiver; 56 57 /** 58 * Create a new MobileDataStateTracker 59 * @param context the application context of the caller 60 * @param target a message handler for getting callbacks about state changes 61 * @param netType the ConnectivityManager network type 62 * @param apnType the Phone apnType 63 * @param tag the name of this network 64 */ 65 public MobileDataStateTracker(Context context, Handler target, int netType, String tag) { 66 super(context, target, netType, 67 TelephonyManager.getDefault().getNetworkType(), tag, 68 TelephonyManager.getDefault().getNetworkTypeName()); 69 mApnType = networkTypeToApnType(netType); 70 if (TextUtils.equals(mApnType, Phone.APN_TYPE_HIPRI)) { 71 mApnTypeToWatchFor = Phone.APN_TYPE_DEFAULT; 72 } else { 73 mApnTypeToWatchFor = mApnType; 74 } 75 76 mPhoneService = null; 77 if(netType == ConnectivityManager.TYPE_MOBILE) { 78 mEnabled = true; 79 } else { 80 mEnabled = false; 81 } 82 83 mDnsPropNames = new String[] { 84 "net.rmnet0.dns1", 85 "net.rmnet0.dns2", 86 "net.eth0.dns1", 87 "net.eth0.dns2", 88 "net.eth0.dns3", 89 "net.eth0.dns4", 90 "net.gprs.dns1", 91 "net.gprs.dns2", 92 "net.ppp0.dns1", 93 "net.ppp0.dns2"}; 94 95 } 96 97 /** 98 * Begin monitoring mobile data connectivity. 99 */ 100 public void startMonitoring() { 101 IntentFilter filter = 102 new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); 103 filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED); 104 filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED); 105 106 mStateReceiver = new MobileDataStateReceiver(); 107 Intent intent = mContext.registerReceiver(mStateReceiver, filter); 108 if (intent != null) 109 mMobileDataState = getMobileDataState(intent); 110 else 111 mMobileDataState = Phone.DataState.DISCONNECTED; 112 } 113 114 private Phone.DataState getMobileDataState(Intent intent) { 115 String str = intent.getStringExtra(Phone.STATE_KEY); 116 if (str != null) { 117 String apnTypeList = 118 intent.getStringExtra(Phone.DATA_APN_TYPES_KEY); 119 if (isApnTypeIncluded(apnTypeList)) { 120 return Enum.valueOf(Phone.DataState.class, str); 121 } 122 } 123 return Phone.DataState.DISCONNECTED; 124 } 125 126 private boolean isApnTypeIncluded(String typeList) { 127 /* comma seperated list - split and check */ 128 if (typeList == null) 129 return false; 130 131 String[] list = typeList.split(","); 132 for(int i=0; i< list.length; i++) { 133 if (TextUtils.equals(list[i], mApnTypeToWatchFor) || 134 TextUtils.equals(list[i], Phone.APN_TYPE_ALL)) { 135 return true; 136 } 137 } 138 return false; 139 } 140 141 private class MobileDataStateReceiver extends BroadcastReceiver { 142 public void onReceive(Context context, Intent intent) { 143 synchronized(this) { 144 if (intent.getAction().equals(TelephonyIntents. 145 ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) { 146 Phone.DataState state = getMobileDataState(intent); 147 String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY); 148 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 149 String apnTypeList = intent.getStringExtra(Phone.DATA_APN_TYPES_KEY); 150 mApnName = apnName; 151 152 boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY, 153 false); 154 155 // set this regardless of the apnTypeList. It's all the same radio/network 156 // underneath 157 mNetworkInfo.setIsAvailable(!unavailable); 158 159 if (isApnTypeIncluded(apnTypeList)) { 160 if (mEnabled == false) { 161 // if we're not enabled but the APN Type is supported by this connection 162 // we should record the interface name if one's provided. If the user 163 // turns on this network we will need the interfacename but won't get 164 // a fresh connected message - TODO fix this when we get per-APN 165 // notifications 166 if (state == Phone.DataState.CONNECTED) { 167 if (DBG) Log.d(TAG, "replacing old mInterfaceName (" + 168 mInterfaceName + ") with " + 169 intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY) + 170 " for " + mApnType); 171 mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY); 172 } 173 return; 174 } 175 } else { 176 return; 177 } 178 179 if (DBG) Log.d(TAG, mApnType + " Received state= " + state + ", old= " + 180 mMobileDataState + ", reason= " + 181 (reason == null ? "(unspecified)" : reason) + 182 ", apnTypeList= " + apnTypeList); 183 184 if (mMobileDataState != state) { 185 mMobileDataState = state; 186 switch (state) { 187 case DISCONNECTED: 188 if(isTeardownRequested()) { 189 mEnabled = false; 190 setTeardownRequested(false); 191 } 192 193 setDetailedState(DetailedState.DISCONNECTED, reason, apnName); 194 if (mInterfaceName != null) { 195 NetworkUtils.resetConnections(mInterfaceName); 196 } 197 // can't do this here - ConnectivityService needs it to clear stuff 198 // it's ok though - just leave it to be refreshed next time 199 // we connect. 200 //if (DBG) Log.d(TAG, "clearing mInterfaceName for "+ mApnType + 201 // " as it DISCONNECTED"); 202 //mInterfaceName = null; 203 //mDefaultGatewayAddr = 0; 204 break; 205 case CONNECTING: 206 setDetailedState(DetailedState.CONNECTING, reason, apnName); 207 break; 208 case SUSPENDED: 209 setDetailedState(DetailedState.SUSPENDED, reason, apnName); 210 break; 211 case CONNECTED: 212 mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY); 213 if (mInterfaceName == null) { 214 Log.d(TAG, "CONNECTED event did not supply interface name."); 215 } 216 setDetailedState(DetailedState.CONNECTED, reason, apnName); 217 break; 218 } 219 } 220 } else if (intent.getAction(). 221 equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) { 222 mEnabled = false; 223 String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY); 224 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 225 if (DBG) Log.d(TAG, "Received " + intent.getAction() + " broadcast" + 226 reason == null ? "" : "(" + reason + ")"); 227 setDetailedState(DetailedState.FAILED, reason, apnName); 228 } 229 TelephonyManager tm = TelephonyManager.getDefault(); 230 setRoamingStatus(tm.isNetworkRoaming()); 231 setSubtype(tm.getNetworkType(), tm.getNetworkTypeName()); 232 } 233 } 234 } 235 236 private void getPhoneService(boolean forceRefresh) { 237 if ((mPhoneService == null) || forceRefresh) { 238 mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone")); 239 } 240 } 241 242 /** 243 * Report whether data connectivity is possible. 244 */ 245 public boolean isAvailable() { 246 getPhoneService(false); 247 248 /* 249 * If the phone process has crashed in the past, we'll get a 250 * RemoteException and need to re-reference the service. 251 */ 252 for (int retry = 0; retry < 2; retry++) { 253 if (mPhoneService == null) break; 254 255 try { 256 return mPhoneService.isDataConnectivityPossible(); 257 } catch (RemoteException e) { 258 // First-time failed, get the phone service again 259 if (retry == 0) getPhoneService(true); 260 } 261 } 262 263 return false; 264 } 265 266 /** 267 * {@inheritDoc} 268 * The mobile data network subtype indicates what generation network technology is in effect, 269 * e.g., GPRS, EDGE, UMTS, etc. 270 */ 271 public int getNetworkSubtype() { 272 return TelephonyManager.getDefault().getNetworkType(); 273 } 274 275 /** 276 * Return the system properties name associated with the tcp buffer sizes 277 * for this network. 278 */ 279 public String getTcpBufferSizesPropName() { 280 String networkTypeStr = "unknown"; 281 TelephonyManager tm = new TelephonyManager(mContext); 282 //TODO We have to edit the parameter for getNetworkType regarding CDMA 283 switch(tm.getNetworkType()) { 284 case TelephonyManager.NETWORK_TYPE_GPRS: 285 networkTypeStr = "gprs"; 286 break; 287 case TelephonyManager.NETWORK_TYPE_EDGE: 288 networkTypeStr = "edge"; 289 break; 290 case TelephonyManager.NETWORK_TYPE_UMTS: 291 networkTypeStr = "umts"; 292 break; 293 case TelephonyManager.NETWORK_TYPE_HSDPA: 294 networkTypeStr = "hsdpa"; 295 break; 296 case TelephonyManager.NETWORK_TYPE_HSUPA: 297 networkTypeStr = "hsupa"; 298 break; 299 case TelephonyManager.NETWORK_TYPE_HSPA: 300 networkTypeStr = "hspa"; 301 break; 302 case TelephonyManager.NETWORK_TYPE_CDMA: 303 networkTypeStr = "cdma"; 304 break; 305 case TelephonyManager.NETWORK_TYPE_1xRTT: 306 networkTypeStr = "1xrtt"; 307 break; 308 case TelephonyManager.NETWORK_TYPE_EVDO_0: 309 networkTypeStr = "evdo"; 310 break; 311 case TelephonyManager.NETWORK_TYPE_EVDO_A: 312 networkTypeStr = "evdo"; 313 break; 314 case TelephonyManager.NETWORK_TYPE_EVDO_B: 315 networkTypeStr = "evdo"; 316 break; 317 } 318 return "net.tcp.buffersize." + networkTypeStr; 319 } 320 321 /** 322 * Tear down mobile data connectivity, i.e., disable the ability to create 323 * mobile data connections. 324 */ 325 @Override 326 public boolean teardown() { 327 // since we won't get a notification currently (TODO - per APN notifications) 328 // we won't get a disconnect message until all APN's on the current connection's 329 // APN list are disabled. That means privateRoutes for DNS and such will remain on - 330 // not a problem since that's all shared with whatever other APN is still on, but 331 // ugly. 332 setTeardownRequested(true); 333 return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED); 334 } 335 336 /** 337 * Re-enable mobile data connectivity after a {@link #teardown()}. 338 */ 339 public boolean reconnect() { 340 setTeardownRequested(false); 341 switch (setEnableApn(mApnType, true)) { 342 case Phone.APN_ALREADY_ACTIVE: 343 // TODO - remove this when we get per-apn notifications 344 mEnabled = true; 345 // need to set self to CONNECTING so the below message is handled. 346 mMobileDataState = Phone.DataState.CONNECTING; 347 setDetailedState(DetailedState.CONNECTING, Phone.REASON_APN_CHANGED, null); 348 //send out a connected message 349 Intent intent = new Intent(TelephonyIntents. 350 ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); 351 intent.putExtra(Phone.STATE_KEY, Phone.DataState.CONNECTED.toString()); 352 intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, Phone.REASON_APN_CHANGED); 353 intent.putExtra(Phone.DATA_APN_TYPES_KEY, mApnTypeToWatchFor); 354 intent.putExtra(Phone.DATA_APN_KEY, mApnName); 355 intent.putExtra(Phone.DATA_IFACE_NAME_KEY, mInterfaceName); 356 intent.putExtra(Phone.NETWORK_UNAVAILABLE_KEY, false); 357 if (mStateReceiver != null) mStateReceiver.onReceive(mContext, intent); 358 break; 359 case Phone.APN_REQUEST_STARTED: 360 mEnabled = true; 361 // no need to do anything - we're already due some status update intents 362 break; 363 case Phone.APN_REQUEST_FAILED: 364 if (mPhoneService == null && mApnType == Phone.APN_TYPE_DEFAULT) { 365 // on startup we may try to talk to the phone before it's ready 366 // since the phone will come up enabled, go with that. 367 // TODO - this also comes up on telephony crash: if we think mobile data is 368 // off and the telephony stuff crashes and has to restart it will come up 369 // enabled (making a data connection). We will then be out of sync. 370 // A possible solution is a broadcast when telephony restarts. 371 mEnabled = true; 372 return false; 373 } 374 // else fall through 375 case Phone.APN_TYPE_NOT_AVAILABLE: 376 // Default is always available, but may be off due to 377 // AirplaneMode or E-Call or whatever.. 378 if (mApnType != Phone.APN_TYPE_DEFAULT) { 379 mEnabled = false; 380 } 381 break; 382 default: 383 Log.e(TAG, "Error in reconnect - unexpected response."); 384 mEnabled = false; 385 break; 386 } 387 return mEnabled; 388 } 389 390 /** 391 * Turn on or off the mobile radio. No connectivity will be possible while the 392 * radio is off. The operation is a no-op if the radio is already in the desired state. 393 * @param turnOn {@code true} if the radio should be turned on, {@code false} if 394 */ 395 public boolean setRadio(boolean turnOn) { 396 getPhoneService(false); 397 /* 398 * If the phone process has crashed in the past, we'll get a 399 * RemoteException and need to re-reference the service. 400 */ 401 for (int retry = 0; retry < 2; retry++) { 402 if (mPhoneService == null) { 403 Log.w(TAG, 404 "Ignoring mobile radio request because could not acquire PhoneService"); 405 break; 406 } 407 408 try { 409 return mPhoneService.setRadio(turnOn); 410 } catch (RemoteException e) { 411 if (retry == 0) getPhoneService(true); 412 } 413 } 414 415 Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off")); 416 return false; 417 } 418 419 /** 420 * Tells the phone sub-system that the caller wants to 421 * begin using the named feature. The only supported features at 422 * this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application 423 * to specify that it wants to send and/or receive MMS data, and 424 * {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS. 425 * @param feature the name of the feature to be used 426 * @param callingPid the process ID of the process that is issuing this request 427 * @param callingUid the user ID of the process that is issuing this request 428 * @return an integer value representing the outcome of the request. 429 * The interpretation of this value is feature-specific. 430 * specific, except that the value {@code -1} 431 * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS}, 432 * the other possible return values are 433 * <ul> 434 * <li>{@code Phone.APN_ALREADY_ACTIVE}</li> 435 * <li>{@code Phone.APN_REQUEST_STARTED}</li> 436 * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li> 437 * <li>{@code Phone.APN_REQUEST_FAILED}</li> 438 * </ul> 439 */ 440 public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) { 441 return -1; 442 } 443 444 /** 445 * Tells the phone sub-system that the caller is finished 446 * using the named feature. The only supported feature at 447 * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application 448 * to specify that it wants to send and/or receive MMS data. 449 * @param feature the name of the feature that is no longer needed 450 * @param callingPid the process ID of the process that is issuing this request 451 * @param callingUid the user ID of the process that is issuing this request 452 * @return an integer value representing the outcome of the request. 453 * The interpretation of this value is feature-specific, except that 454 * the value {@code -1} always indicates failure. 455 */ 456 public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) { 457 return -1; 458 } 459 460 /** 461 * Ensure that a network route exists to deliver traffic to the specified 462 * host via the mobile data network. 463 * @param hostAddress the IP address of the host to which the route is desired, 464 * @return {@code true} on success, {@code false} on failure 465 */ 466 @Override 467 public boolean requestRouteToHost(InetAddress hostAddress) { 468 if (DBG) { 469 Log.d(TAG, "Requested host route to " + hostAddress.getHostAddress() + 470 " for " + mApnType + "(" + mInterfaceName + ")"); 471 } 472 if (mInterfaceName != null) { 473 return NetworkUtils.addHostRoute(mInterfaceName, hostAddress, null); 474 } else { 475 return false; 476 } 477 } 478 479 @Override 480 public String toString() { 481 StringBuffer sb = new StringBuffer("Mobile data state: "); 482 483 sb.append(mMobileDataState); 484 return sb.toString(); 485 } 486 487 /** 488 * Internal method supporting the ENABLE_MMS feature. 489 * @param apnType the type of APN to be enabled or disabled (e.g., mms) 490 * @param enable {@code true} to enable the specified APN type, 491 * {@code false} to disable it. 492 * @return an integer value representing the outcome of the request. 493 */ 494 private int setEnableApn(String apnType, boolean enable) { 495 getPhoneService(false); 496 /* 497 * If the phone process has crashed in the past, we'll get a 498 * RemoteException and need to re-reference the service. 499 */ 500 for (int retry = 0; retry < 2; retry++) { 501 if (mPhoneService == null) { 502 Log.w(TAG, 503 "Ignoring feature request because could not acquire PhoneService"); 504 break; 505 } 506 507 try { 508 if (enable) { 509 return mPhoneService.enableApnType(apnType); 510 } else { 511 return mPhoneService.disableApnType(apnType); 512 } 513 } catch (RemoteException e) { 514 if (retry == 0) getPhoneService(true); 515 } 516 } 517 518 Log.w(TAG, "Could not " + (enable ? "enable" : "disable") 519 + " APN type \"" + apnType + "\""); 520 return Phone.APN_REQUEST_FAILED; 521 } 522 523 public static String networkTypeToApnType(int netType) { 524 switch(netType) { 525 case ConnectivityManager.TYPE_MOBILE: 526 return Phone.APN_TYPE_DEFAULT; // TODO - use just one of these 527 case ConnectivityManager.TYPE_MOBILE_MMS: 528 return Phone.APN_TYPE_MMS; 529 case ConnectivityManager.TYPE_MOBILE_SUPL: 530 return Phone.APN_TYPE_SUPL; 531 case ConnectivityManager.TYPE_MOBILE_DUN: 532 return Phone.APN_TYPE_DUN; 533 case ConnectivityManager.TYPE_MOBILE_HIPRI: 534 return Phone.APN_TYPE_HIPRI; 535 default: 536 Log.e(TAG, "Error mapping networkType " + netType + " to apnType."); 537 return null; 538 } 539 } 540} 541