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