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