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