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