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