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