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