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