MobileDataStateTracker.java revision 0badd0b700ed618dac421cb6cde4654b51acb3a4
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.ServiceManager; 26import android.os.SystemProperties; 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.telephony.TelephonyManager; 32import android.util.Log; 33import android.text.TextUtils; 34 35/** 36 * Track the state of mobile data connectivity. This is done by 37 * receiving broadcast intents from the Phone process whenever 38 * the state of data connectivity changes. 39 * 40 * {@hide} 41 */ 42public class MobileDataStateTracker extends NetworkStateTracker { 43 44 private static final String TAG = "MobileDataStateTracker"; 45 private static final boolean DBG = true; 46 47 private Phone.DataState mMobileDataState; 48 private ITelephony mPhoneService; 49 50 private String mApnType; 51 private boolean mEnabled; 52 private BroadcastReceiver mStateReceiver; 53 54 /** 55 * Create a new MobileDataStateTracker 56 * @param context the application context of the caller 57 * @param target a message handler for getting callbacks about state changes 58 * @param netType the ConnectivityManager network type 59 * @param apnType the Phone apnType 60 * @param tag the name of this network 61 */ 62 public MobileDataStateTracker(Context context, Handler target, 63 int netType, String apnType, String tag) { 64 super(context, target, netType, 65 TelephonyManager.getDefault().getNetworkType(), tag, 66 TelephonyManager.getDefault().getNetworkTypeName()); 67 mApnType = apnType; 68 mPhoneService = null; 69 if(netType == ConnectivityManager.TYPE_MOBILE) { 70 mEnabled = true; 71 } else { 72 mEnabled = false; 73 } 74 75 mDnsPropNames = new String[] { 76 "net.rmnet0.dns1", 77 "net.rmnet0.dns2", 78 "net.eth0.dns1", 79 "net.eth0.dns2", 80 "net.eth0.dns3", 81 "net.eth0.dns4", 82 "net.gprs.dns1", 83 "net.gprs.dns2", 84 "net.ppp0.dns1", 85 "net.ppp0.dns2"}; 86 87 } 88 89 /** 90 * Begin monitoring mobile data connectivity. 91 */ 92 public void startMonitoring() { 93 IntentFilter filter = 94 new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); 95 filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED); 96 filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED); 97 98 mStateReceiver = new MobileDataStateReceiver(); 99 Intent intent = mContext.registerReceiver(mStateReceiver, filter); 100 if (intent != null) 101 mMobileDataState = getMobileDataState(intent); 102 else 103 mMobileDataState = Phone.DataState.DISCONNECTED; 104 } 105 106 private Phone.DataState getMobileDataState(Intent intent) { 107 String str = intent.getStringExtra(Phone.STATE_KEY); 108 if (str != null) { 109 String apnTypeList = 110 intent.getStringExtra(Phone.DATA_APN_TYPES_KEY); 111 if (isApnTypeIncluded(apnTypeList)) { 112 return Enum.valueOf(Phone.DataState.class, str); 113 } 114 } 115 return Phone.DataState.DISCONNECTED; 116 } 117 118 private boolean isApnTypeIncluded(String typeList) { 119 /* comma seperated list - split and check */ 120 if (typeList == null) 121 return false; 122 123 String[] list = typeList.split(","); 124 for(int i=0; i< list.length; i++) { 125 if (TextUtils.equals(list[i], mApnType) || 126 TextUtils.equals(list[i], Phone.APN_TYPE_ALL)) { 127 return true; 128 } 129 } 130 return false; 131 } 132 133 private class MobileDataStateReceiver extends BroadcastReceiver { 134 public void onReceive(Context context, Intent intent) { 135 synchronized(this) { 136 if (intent.getAction().equals(TelephonyIntents. 137 ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) { 138 Phone.DataState state = getMobileDataState(intent); 139 String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY); 140 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 141 String apnTypeList = intent.getStringExtra(Phone.DATA_APN_TYPES_KEY); 142 143 boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY, 144 false); 145 if (DBG) Log.d(TAG, mApnType + " Received " + intent.getAction() + 146 " broadcast - state = " + state + ", oldstate = " + mMobileDataState + 147 ", unavailable = " + unavailable + ", reason = " + 148 (reason == null ? "(unspecified)" : reason)); 149 150 151 if (isApnTypeIncluded(apnTypeList)) { 152 // set this even if the apn isn't Enabled 153 mNetworkInfo.setIsAvailable(!unavailable); 154 if (mEnabled == false) { 155 // if we're not enabled but the APN Type is supported by this connection 156 // we should record the interface name if one's provided. If the user 157 // turns on this network we will need the interfacename but won't get 158 // a fresh connected message - TODO fix this.. 159 if (state == Phone.DataState.CONNECTED) { 160 if (DBG) Log.d(TAG, "replacing old mInterfaceName (" + 161 mInterfaceName + ") with " + 162 intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY) + 163 " for " + mApnType); 164 mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY); 165 } 166 if (DBG) Log.d(TAG, " dropped - mEnabled = false"); 167 return; 168 } 169 } else { 170 if (DBG) Log.d(TAG, " dropped - wrong Apn"); 171 return; 172 } 173 174 if (mMobileDataState != state) { 175 mMobileDataState = state; 176 switch (state) { 177 case DISCONNECTED: 178 if(isTeardownRequested()) { 179 mEnabled = false; 180 setTeardownRequested(false); 181 } 182 183 setDetailedState(DetailedState.DISCONNECTED, reason, apnName); 184 if (mInterfaceName != null) { 185 NetworkUtils.resetConnections(mInterfaceName); 186 } 187 if (DBG) Log.d(TAG, "clearing mInterfaceName for "+ mApnType + 188 " as it DISCONNECTED"); 189 mInterfaceName = null; 190 mDefaultGatewayAddr = 0; 191 break; 192 case CONNECTING: 193 setDetailedState(DetailedState.CONNECTING, reason, apnName); 194 break; 195 case SUSPENDED: 196 setDetailedState(DetailedState.SUSPENDED, reason, apnName); 197 break; 198 case CONNECTED: 199 mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY); 200 if (mInterfaceName == null) { 201 Log.d(TAG, "CONNECTED event did not supply interface name."); 202 } 203 setDetailedState(DetailedState.CONNECTED, reason, apnName); 204 break; 205 } 206 } 207 } else if (intent.getAction(). 208 equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) { 209 mEnabled = false; 210 String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY); 211 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 212 if (DBG) Log.d(TAG, "Received " + intent.getAction() + " broadcast" + 213 reason == null ? "" : "(" + reason + ")"); 214 setDetailedState(DetailedState.FAILED, reason, apnName); 215 } 216 TelephonyManager tm = TelephonyManager.getDefault(); 217 setRoamingStatus(tm.isNetworkRoaming()); 218 setSubtype(tm.getNetworkType(), tm.getNetworkTypeName()); 219 } 220 } 221 } 222 223 private void getPhoneService(boolean forceRefresh) { 224 if ((mPhoneService == null) || forceRefresh) { 225 mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone")); 226 } 227 } 228 229 /** 230 * Report whether data connectivity is possible. 231 */ 232 public boolean isAvailable() { 233 getPhoneService(false); 234 235 /* 236 * If the phone process has crashed in the past, we'll get a 237 * RemoteException and need to re-reference the service. 238 */ 239 for (int retry = 0; retry < 2; retry++) { 240 if (mPhoneService == null) break; 241 242 try { 243 return mPhoneService.isDataConnectivityPossible(); 244 } catch (RemoteException e) { 245 // First-time failed, get the phone service again 246 if (retry == 0) getPhoneService(true); 247 } 248 } 249 250 return false; 251 } 252 253 /** 254 * {@inheritDoc} 255 * The mobile data network subtype indicates what generation network technology is in effect, 256 * e.g., GPRS, EDGE, UMTS, etc. 257 */ 258 public int getNetworkSubtype() { 259 return TelephonyManager.getDefault().getNetworkType(); 260 } 261 262 /** 263 * Return the system properties name associated with the tcp buffer sizes 264 * for this network. 265 */ 266 public String getTcpBufferSizesPropName() { 267 String networkTypeStr = "unknown"; 268 TelephonyManager tm = new TelephonyManager(mContext); 269 //TODO We have to edit the parameter for getNetworkType regarding CDMA 270 switch(tm.getNetworkType()) { 271 case TelephonyManager.NETWORK_TYPE_GPRS: 272 networkTypeStr = "gprs"; 273 break; 274 case TelephonyManager.NETWORK_TYPE_EDGE: 275 networkTypeStr = "edge"; 276 break; 277 case TelephonyManager.NETWORK_TYPE_UMTS: 278 networkTypeStr = "umts"; 279 break; 280 case TelephonyManager.NETWORK_TYPE_HSDPA: 281 networkTypeStr = "hsdpa"; 282 break; 283 case TelephonyManager.NETWORK_TYPE_HSUPA: 284 networkTypeStr = "hsupa"; 285 break; 286 case TelephonyManager.NETWORK_TYPE_HSPA: 287 networkTypeStr = "hspa"; 288 break; 289 case TelephonyManager.NETWORK_TYPE_CDMA: 290 networkTypeStr = "cdma"; 291 break; 292 case TelephonyManager.NETWORK_TYPE_1xRTT: 293 networkTypeStr = "1xrtt"; 294 break; 295 case TelephonyManager.NETWORK_TYPE_EVDO_0: 296 networkTypeStr = "evdo"; 297 break; 298 case TelephonyManager.NETWORK_TYPE_EVDO_A: 299 networkTypeStr = "evdo"; 300 break; 301 } 302 return "net.tcp.buffersize." + networkTypeStr; 303 } 304 305 /** 306 * Tear down mobile data connectivity, i.e., disable the ability to create 307 * mobile data connections. 308 */ 309 @Override 310 public boolean teardown() { 311 setTeardownRequested(true); 312 return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED); 313 } 314 315 /** 316 * Re-enable mobile data connectivity after a {@link #teardown()}. 317 */ 318 public boolean reconnect() { 319 setTeardownRequested(false); 320 switch (setEnableApn(mApnType, true)) { 321 case Phone.APN_ALREADY_ACTIVE: 322 mEnabled = true; 323 // need to set self to CONNECTING so the below message is handled. 324 mMobileDataState = Phone.DataState.CONNECTING; 325 setDetailedState(DetailedState.CONNECTING, Phone.REASON_APN_CHANGED, null); 326 //send out a connected message 327 Intent intent = new Intent(TelephonyIntents. 328 ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); 329 intent.putExtra(Phone.STATE_KEY, Phone.DataState.CONNECTED.toString()); 330 intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, Phone.REASON_APN_CHANGED); 331 intent.putExtra(Phone.DATA_APN_TYPES_KEY, mApnType); 332 intent.putExtra(Phone.DATA_IFACE_NAME_KEY, mInterfaceName); 333 intent.putExtra(Phone.NETWORK_UNAVAILABLE_KEY, false); 334 if (mStateReceiver != null) mStateReceiver.onReceive(mContext, intent); 335 break; 336 case Phone.APN_REQUEST_STARTED: 337 mEnabled = true; 338 // no need to do anything - we're already due some status update intents 339 break; 340 case Phone.APN_REQUEST_FAILED: 341 if (mPhoneService == null && mApnType == Phone.APN_TYPE_DEFAULT) { 342 // on startup we may try to talk to the phone before it's ready 343 // just leave mEnabled as it is for the default apn. 344 return false; 345 } 346 // else fall through 347 case Phone.APN_TYPE_NOT_AVAILABLE: 348 mEnabled = false; 349 break; 350 default: 351 Log.e(TAG, "Error in reconnect - unexpected response."); 352 mEnabled = false; 353 break; 354 } 355 return mEnabled; 356 } 357 358 /** 359 * Turn on or off the mobile radio. No connectivity will be possible while the 360 * radio is off. The operation is a no-op if the radio is already in the desired state. 361 * @param turnOn {@code true} if the radio should be turned on, {@code false} if 362 */ 363 public boolean setRadio(boolean turnOn) { 364 getPhoneService(false); 365 /* 366 * If the phone process has crashed in the past, we'll get a 367 * RemoteException and need to re-reference the service. 368 */ 369 for (int retry = 0; retry < 2; retry++) { 370 if (mPhoneService == null) { 371 Log.w(TAG, 372 "Ignoring mobile radio request because could not acquire PhoneService"); 373 break; 374 } 375 376 try { 377 return mPhoneService.setRadio(turnOn); 378 } catch (RemoteException e) { 379 if (retry == 0) getPhoneService(true); 380 } 381 } 382 383 Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off")); 384 return false; 385 } 386 387 /** 388 * Tells the phone sub-system that the caller wants to 389 * begin using the named feature. The only supported features at 390 * this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application 391 * to specify that it wants to send and/or receive MMS data, and 392 * {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS. 393 * @param feature the name of the feature to be used 394 * @param callingPid the process ID of the process that is issuing this request 395 * @param callingUid the user ID of the process that is issuing this request 396 * @return an integer value representing the outcome of the request. 397 * The interpretation of this value is feature-specific. 398 * specific, except that the value {@code -1} 399 * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS}, 400 * the other possible return values are 401 * <ul> 402 * <li>{@code Phone.APN_ALREADY_ACTIVE}</li> 403 * <li>{@code Phone.APN_REQUEST_STARTED}</li> 404 * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li> 405 * <li>{@code Phone.APN_REQUEST_FAILED}</li> 406 * </ul> 407 */ 408 public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) { 409 return -1; 410 } 411 412 /** 413 * Tells the phone sub-system that the caller is finished 414 * using the named feature. The only supported feature at 415 * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application 416 * to specify that it wants to send and/or receive MMS data. 417 * @param feature the name of the feature that is no longer needed 418 * @param callingPid the process ID of the process that is issuing this request 419 * @param callingUid the user ID of the process that is issuing this request 420 * @return an integer value representing the outcome of the request. 421 * The interpretation of this value is feature-specific, except that 422 * the value {@code -1} always indicates failure. 423 */ 424 public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) { 425 return -1; 426 } 427 428 /** 429 * Ensure that a network route exists to deliver traffic to the specified 430 * host via the mobile data network. 431 * @param hostAddress the IP address of the host to which the route is desired, 432 * in network byte order. 433 * @return {@code true} on success, {@code false} on failure 434 */ 435 @Override 436 public boolean requestRouteToHost(int hostAddress) { 437 if (DBG) { 438 Log.d(TAG, "Requested host route to " + Integer.toHexString(hostAddress) + 439 " for " + mApnType + "(" + mInterfaceName + ")"); 440 } 441 if (mInterfaceName != null && hostAddress != -1) { 442 return NetworkUtils.addHostRoute(mInterfaceName, hostAddress) == 0; 443 } else { 444 return false; 445 } 446 } 447 448 @Override 449 public String toString() { 450 StringBuffer sb = new StringBuffer("Mobile data state: "); 451 452 sb.append(mMobileDataState); 453 return sb.toString(); 454 } 455 456 /** 457 * Internal method supporting the ENABLE_MMS feature. 458 * @param apnType the type of APN to be enabled or disabled (e.g., mms) 459 * @param enable {@code true} to enable the specified APN type, 460 * {@code false} to disable it. 461 * @return an integer value representing the outcome of the request. 462 */ 463 private int setEnableApn(String apnType, boolean enable) { 464 getPhoneService(false); 465 /* 466 * If the phone process has crashed in the past, we'll get a 467 * RemoteException and need to re-reference the service. 468 */ 469 for (int retry = 0; retry < 2; retry++) { 470 if (mPhoneService == null) { 471 Log.w(TAG, 472 "Ignoring feature request because could not acquire PhoneService"); 473 break; 474 } 475 476 try { 477 if (enable) { 478 return mPhoneService.enableApnType(apnType); 479 } else { 480 return mPhoneService.disableApnType(apnType); 481 } 482 } catch (RemoteException e) { 483 if (retry == 0) getPhoneService(true); 484 } 485 } 486 487 Log.w(TAG, "Could not " + (enable ? "enable" : "disable") 488 + " APN type \"" + apnType + "\""); 489 return Phone.APN_REQUEST_FAILED; 490 } 491} 492