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 static com.android.internal.telephony.DataConnectionTracker.CMD_SET_POLICY_DATA_ENABLE; 20 import static com.android.internal.telephony.DataConnectionTracker.CMD_SET_USER_DATA_ENABLE; 21 import static com.android.internal.telephony.DataConnectionTracker.DISABLED; 22 import static com.android.internal.telephony.DataConnectionTracker.ENABLED; 23 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.net.NetworkInfo.DetailedState; 29 import android.os.Bundle; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.os.Message; 33 import android.os.Messenger; 34 import android.os.RemoteException; 35 import android.os.ServiceManager; 36 import android.telephony.TelephonyManager; 37 import android.text.TextUtils; 38 import android.util.Slog; 39 40 import com.android.internal.telephony.DataConnectionTracker; 41 import com.android.internal.telephony.ITelephony; 42 import com.android.internal.telephony.Phone; 43 import com.android.internal.telephony.TelephonyIntents; 44 import com.android.internal.util.AsyncChannel; 45 46 import java.io.CharArrayWriter; 47 import java.io.PrintWriter; 48 49 /** 50 * Track the state of mobile data connectivity. This is done by 51 * receiving broadcast intents from the Phone process whenever 52 * the state of data connectivity changes. 53 * 54 * {@hide} 55 */ 56 public class MobileDataStateTracker implements NetworkStateTracker { 57 58 private static final String TAG = "MobileDataStateTracker"; 59 private static final boolean DBG = false; 60 private static final boolean VDBG = false; 61 62 private Phone.DataState mMobileDataState; 63 private ITelephony mPhoneService; 64 65 private String mApnType; 66 private NetworkInfo mNetworkInfo; 67 private boolean mTeardownRequested = false; 68 private Handler mTarget; 69 private Context mContext; 70 private LinkProperties mLinkProperties; 71 private LinkCapabilities mLinkCapabilities; 72 private boolean mPrivateDnsRouteSet = false; 73 private boolean mDefaultRouteSet = false; 74 75 // NOTE: these are only kept for debugging output; actual values are 76 // maintained in DataConnectionTracker. 77 protected boolean mUserDataEnabled = true; 78 protected boolean mPolicyDataEnabled = true; 79 80 private Handler mHandler; 81 private AsyncChannel mDataConnectionTrackerAc; 82 private Messenger mMessenger; 83 84 /** 85 * Create a new MobileDataStateTracker 86 * @param netType the ConnectivityManager network type 87 * @param tag the name of this network 88 */ MobileDataStateTracker(int netType, String tag)89 public MobileDataStateTracker(int netType, String tag) { 90 mNetworkInfo = new NetworkInfo(netType, 91 TelephonyManager.getDefault().getNetworkType(), tag, 92 TelephonyManager.getDefault().getNetworkTypeName()); 93 mApnType = networkTypeToApnType(netType); 94 } 95 96 /** 97 * Begin monitoring data connectivity. 98 * 99 * @param context is the current Android context 100 * @param target is the Hander to which to return the events. 101 */ startMonitoring(Context context, Handler target)102 public void startMonitoring(Context context, Handler target) { 103 mTarget = target; 104 mContext = context; 105 106 mHandler = new MdstHandler(target.getLooper(), this); 107 108 IntentFilter filter = new IntentFilter(); 109 filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); 110 filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED); 111 filter.addAction(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER); 112 113 mContext.registerReceiver(new MobileDataStateReceiver(), filter); 114 mMobileDataState = Phone.DataState.DISCONNECTED; 115 } 116 117 static class MdstHandler extends Handler { 118 private MobileDataStateTracker mMdst; 119 MdstHandler(Looper looper, MobileDataStateTracker mdst)120 MdstHandler(Looper looper, MobileDataStateTracker mdst) { 121 super(looper); 122 mMdst = mdst; 123 } 124 125 @Override handleMessage(Message msg)126 public void handleMessage(Message msg) { 127 switch (msg.what) { 128 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: 129 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { 130 if (VDBG) { 131 mMdst.log("MdstHandler connected"); 132 } 133 mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj; 134 } else { 135 if (VDBG) { 136 mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1); 137 } 138 } 139 break; 140 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: 141 if (VDBG) mMdst.log("Disconnected from DataStateTracker"); 142 mMdst.mDataConnectionTrackerAc = null; 143 break; 144 default: { 145 if (VDBG) mMdst.log("Ignorning unknown message=" + msg); 146 break; 147 } 148 } 149 } 150 } 151 isPrivateDnsRouteSet()152 public boolean isPrivateDnsRouteSet() { 153 return mPrivateDnsRouteSet; 154 } 155 privateDnsRouteSet(boolean enabled)156 public void privateDnsRouteSet(boolean enabled) { 157 mPrivateDnsRouteSet = enabled; 158 } 159 getNetworkInfo()160 public NetworkInfo getNetworkInfo() { 161 return mNetworkInfo; 162 } 163 isDefaultRouteSet()164 public boolean isDefaultRouteSet() { 165 return mDefaultRouteSet; 166 } 167 defaultRouteSet(boolean enabled)168 public void defaultRouteSet(boolean enabled) { 169 mDefaultRouteSet = enabled; 170 } 171 172 /** 173 * This is not implemented. 174 */ releaseWakeLock()175 public void releaseWakeLock() { 176 } 177 178 private class MobileDataStateReceiver extends BroadcastReceiver { 179 @Override onReceive(Context context, Intent intent)180 public void onReceive(Context context, Intent intent) { 181 if (intent.getAction().equals(TelephonyIntents. 182 ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) { 183 String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY); 184 if (VDBG) { 185 log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED" 186 + "mApnType=%s %s received apnType=%s", mApnType, 187 TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType)); 188 } 189 if (!TextUtils.equals(apnType, mApnType)) { 190 return; 191 } 192 mNetworkInfo.setSubtype(TelephonyManager.getDefault().getNetworkType(), 193 TelephonyManager.getDefault().getNetworkTypeName()); 194 Phone.DataState state = Enum.valueOf(Phone.DataState.class, 195 intent.getStringExtra(Phone.STATE_KEY)); 196 String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY); 197 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 198 mNetworkInfo.setRoaming(intent.getBooleanExtra(Phone.DATA_NETWORK_ROAMING_KEY, 199 false)); 200 if (VDBG) { 201 log(mApnType + " setting isAvailable to " + 202 intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,false)); 203 } 204 mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY, 205 false)); 206 207 if (DBG) { 208 log("Received state=" + state + ", old=" + mMobileDataState + 209 ", reason=" + (reason == null ? "(unspecified)" : reason)); 210 } 211 if (mMobileDataState != state) { 212 mMobileDataState = state; 213 switch (state) { 214 case DISCONNECTED: 215 if(isTeardownRequested()) { 216 setTeardownRequested(false); 217 } 218 219 setDetailedState(DetailedState.DISCONNECTED, reason, apnName); 220 // can't do this here - ConnectivityService needs it to clear stuff 221 // it's ok though - just leave it to be refreshed next time 222 // we connect. 223 //if (DBG) log("clearing mInterfaceName for "+ mApnType + 224 // " as it DISCONNECTED"); 225 //mInterfaceName = null; 226 break; 227 case CONNECTING: 228 setDetailedState(DetailedState.CONNECTING, reason, apnName); 229 break; 230 case SUSPENDED: 231 setDetailedState(DetailedState.SUSPENDED, reason, apnName); 232 break; 233 case CONNECTED: 234 mLinkProperties = intent.getParcelableExtra( 235 Phone.DATA_LINK_PROPERTIES_KEY); 236 if (mLinkProperties == null) { 237 loge("CONNECTED event did not supply link properties."); 238 mLinkProperties = new LinkProperties(); 239 } 240 mLinkCapabilities = intent.getParcelableExtra( 241 Phone.DATA_LINK_CAPABILITIES_KEY); 242 if (mLinkCapabilities == null) { 243 loge("CONNECTED event did not supply link capabilities."); 244 mLinkCapabilities = new LinkCapabilities(); 245 } 246 setDetailedState(DetailedState.CONNECTED, reason, apnName); 247 break; 248 } 249 } else { 250 // There was no state change. Check if LinkProperties has been updated. 251 if (TextUtils.equals(reason, Phone.REASON_LINK_PROPERTIES_CHANGED)) { 252 mLinkProperties = intent.getParcelableExtra(Phone.DATA_LINK_PROPERTIES_KEY); 253 if (mLinkProperties == null) { 254 loge("No link property in LINK_PROPERTIES change event."); 255 mLinkProperties = new LinkProperties(); 256 } 257 // Just update reason field in this NetworkInfo 258 mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason, 259 mNetworkInfo.getExtraInfo()); 260 Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED, 261 mNetworkInfo); 262 msg.sendToTarget(); 263 } 264 } 265 } else if (intent.getAction(). 266 equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) { 267 String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY); 268 if (!TextUtils.equals(apnType, mApnType)) { 269 if (DBG) { 270 log(String.format( 271 "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " + 272 "mApnType=%s != received apnType=%s", mApnType, apnType)); 273 } 274 return; 275 } 276 String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY); 277 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 278 if (DBG) { 279 log("Received " + intent.getAction() + 280 " broadcast" + reason == null ? "" : "(" + reason + ")"); 281 } 282 setDetailedState(DetailedState.FAILED, reason, apnName); 283 } else if (intent.getAction(). 284 equals(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER)) { 285 if (VDBG) log(mApnType + " got ACTION_DATA_CONNECTION_TRACKER_MESSENGER"); 286 mMessenger = intent.getParcelableExtra(DataConnectionTracker.EXTRA_MESSENGER); 287 AsyncChannel ac = new AsyncChannel(); 288 ac.connect(mContext, MobileDataStateTracker.this.mHandler, mMessenger); 289 } else { 290 if (DBG) log("Broadcast received: ignore " + intent.getAction()); 291 } 292 } 293 } 294 getPhoneService(boolean forceRefresh)295 private void getPhoneService(boolean forceRefresh) { 296 if ((mPhoneService == null) || forceRefresh) { 297 mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone")); 298 } 299 } 300 301 /** 302 * Report whether data connectivity is possible. 303 */ isAvailable()304 public boolean isAvailable() { 305 return mNetworkInfo.isAvailable(); 306 } 307 308 /** 309 * Return the system properties name associated with the tcp buffer sizes 310 * for this network. 311 */ getTcpBufferSizesPropName()312 public String getTcpBufferSizesPropName() { 313 String networkTypeStr = "unknown"; 314 TelephonyManager tm = new TelephonyManager(mContext); 315 //TODO We have to edit the parameter for getNetworkType regarding CDMA 316 switch(tm.getNetworkType()) { 317 case TelephonyManager.NETWORK_TYPE_GPRS: 318 networkTypeStr = "gprs"; 319 break; 320 case TelephonyManager.NETWORK_TYPE_EDGE: 321 networkTypeStr = "edge"; 322 break; 323 case TelephonyManager.NETWORK_TYPE_UMTS: 324 networkTypeStr = "umts"; 325 break; 326 case TelephonyManager.NETWORK_TYPE_HSDPA: 327 networkTypeStr = "hsdpa"; 328 break; 329 case TelephonyManager.NETWORK_TYPE_HSUPA: 330 networkTypeStr = "hsupa"; 331 break; 332 case TelephonyManager.NETWORK_TYPE_HSPA: 333 networkTypeStr = "hspa"; 334 break; 335 case TelephonyManager.NETWORK_TYPE_CDMA: 336 networkTypeStr = "cdma"; 337 break; 338 case TelephonyManager.NETWORK_TYPE_1xRTT: 339 networkTypeStr = "1xrtt"; 340 break; 341 case TelephonyManager.NETWORK_TYPE_EVDO_0: 342 networkTypeStr = "evdo"; 343 break; 344 case TelephonyManager.NETWORK_TYPE_EVDO_A: 345 networkTypeStr = "evdo"; 346 break; 347 case TelephonyManager.NETWORK_TYPE_EVDO_B: 348 networkTypeStr = "evdo"; 349 break; 350 case TelephonyManager.NETWORK_TYPE_IDEN: 351 networkTypeStr = "iden"; 352 break; 353 case TelephonyManager.NETWORK_TYPE_LTE: 354 networkTypeStr = "lte"; 355 break; 356 case TelephonyManager.NETWORK_TYPE_EHRPD: 357 networkTypeStr = "ehrpd"; 358 break; 359 default: 360 loge("unknown network type: " + tm.getNetworkType()); 361 } 362 return "net.tcp.buffersize." + networkTypeStr; 363 } 364 365 /** 366 * Tear down mobile data connectivity, i.e., disable the ability to create 367 * mobile data connections. 368 * TODO - make async and return nothing? 369 */ teardown()370 public boolean teardown() { 371 setTeardownRequested(true); 372 return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED); 373 } 374 375 /** 376 * Record the detailed state of a network, and if it is a 377 * change from the previous state, send a notification to 378 * any listeners. 379 * @param state the new @{code DetailedState} 380 * @param reason a {@code String} indicating a reason for the state change, 381 * if one was supplied. May be {@code null}. 382 * @param extraInfo optional {@code String} providing extra information about the state change 383 */ setDetailedState(NetworkInfo.DetailedState state, String reason, String extraInfo)384 private void setDetailedState(NetworkInfo.DetailedState state, String reason, 385 String extraInfo) { 386 if (DBG) log("setDetailed state, old =" 387 + mNetworkInfo.getDetailedState() + " and new state=" + state); 388 if (state != mNetworkInfo.getDetailedState()) { 389 boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING); 390 String lastReason = mNetworkInfo.getReason(); 391 /* 392 * If a reason was supplied when the CONNECTING state was entered, and no 393 * reason was supplied for entering the CONNECTED state, then retain the 394 * reason that was supplied when going to CONNECTING. 395 */ 396 if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null 397 && lastReason != null) 398 reason = lastReason; 399 mNetworkInfo.setDetailedState(state, reason, extraInfo); 400 Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, new NetworkInfo(mNetworkInfo)); 401 msg.sendToTarget(); 402 } 403 } 404 setTeardownRequested(boolean isRequested)405 public void setTeardownRequested(boolean isRequested) { 406 mTeardownRequested = isRequested; 407 } 408 isTeardownRequested()409 public boolean isTeardownRequested() { 410 return mTeardownRequested; 411 } 412 413 /** 414 * Re-enable mobile data connectivity after a {@link #teardown()}. 415 * TODO - make async and always get a notification? 416 */ reconnect()417 public boolean reconnect() { 418 boolean retValue = false; //connected or expect to be? 419 setTeardownRequested(false); 420 switch (setEnableApn(mApnType, true)) { 421 case Phone.APN_ALREADY_ACTIVE: 422 // need to set self to CONNECTING so the below message is handled. 423 retValue = true; 424 break; 425 case Phone.APN_REQUEST_STARTED: 426 // set IDLE here , avoid the following second FAILED not sent out 427 mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null); 428 retValue = true; 429 break; 430 case Phone.APN_REQUEST_FAILED: 431 case Phone.APN_TYPE_NOT_AVAILABLE: 432 break; 433 default: 434 loge("Error in reconnect - unexpected response."); 435 break; 436 } 437 return retValue; 438 } 439 440 /** 441 * Turn on or off the mobile radio. No connectivity will be possible while the 442 * radio is off. The operation is a no-op if the radio is already in the desired state. 443 * @param turnOn {@code true} if the radio should be turned on, {@code false} if 444 */ setRadio(boolean turnOn)445 public boolean setRadio(boolean turnOn) { 446 getPhoneService(false); 447 /* 448 * If the phone process has crashed in the past, we'll get a 449 * RemoteException and need to re-reference the service. 450 */ 451 for (int retry = 0; retry < 2; retry++) { 452 if (mPhoneService == null) { 453 loge("Ignoring mobile radio request because could not acquire PhoneService"); 454 break; 455 } 456 457 try { 458 return mPhoneService.setRadio(turnOn); 459 } catch (RemoteException e) { 460 if (retry == 0) getPhoneService(true); 461 } 462 } 463 464 loge("Could not set radio power to " + (turnOn ? "on" : "off")); 465 return false; 466 } 467 468 @Override setUserDataEnable(boolean enabled)469 public void setUserDataEnable(boolean enabled) { 470 if (DBG) log("setUserDataEnable: E enabled=" + enabled); 471 final AsyncChannel channel = mDataConnectionTrackerAc; 472 if (channel != null) { 473 channel.sendMessage(CMD_SET_USER_DATA_ENABLE, enabled ? ENABLED : DISABLED); 474 mUserDataEnabled = enabled; 475 } 476 if (VDBG) log("setUserDataEnable: X enabled=" + enabled); 477 } 478 479 @Override setPolicyDataEnable(boolean enabled)480 public void setPolicyDataEnable(boolean enabled) { 481 if (DBG) log("setPolicyDataEnable(enabled=" + enabled + ")"); 482 final AsyncChannel channel = mDataConnectionTrackerAc; 483 if (channel != null) { 484 channel.sendMessage(CMD_SET_POLICY_DATA_ENABLE, enabled ? ENABLED : DISABLED); 485 mPolicyDataEnabled = enabled; 486 } 487 } 488 489 /** 490 * carrier dependency is met/unmet 491 * @param met 492 */ setDependencyMet(boolean met)493 public void setDependencyMet(boolean met) { 494 Bundle bundle = Bundle.forPair(DataConnectionTracker.APN_TYPE_KEY, mApnType); 495 try { 496 if (DBG) log("setDependencyMet: E met=" + met); 497 Message msg = Message.obtain(); 498 msg.what = DataConnectionTracker.CMD_SET_DEPENDENCY_MET; 499 msg.arg1 = (met ? DataConnectionTracker.ENABLED : DataConnectionTracker.DISABLED); 500 msg.setData(bundle); 501 mDataConnectionTrackerAc.sendMessage(msg); 502 if (VDBG) log("setDependencyMet: X met=" + met); 503 } catch (NullPointerException e) { 504 loge("setDependencyMet: X mAc was null" + e); 505 } 506 } 507 508 @Override toString()509 public String toString() { 510 final CharArrayWriter writer = new CharArrayWriter(); 511 final PrintWriter pw = new PrintWriter(writer); 512 pw.print("Mobile data state: "); pw.println(mMobileDataState); 513 pw.print("Data enabled: user="); pw.print(mUserDataEnabled); 514 pw.print(", policy="); pw.println(mPolicyDataEnabled); 515 return writer.toString(); 516 } 517 518 /** 519 * Internal method supporting the ENABLE_MMS feature. 520 * @param apnType the type of APN to be enabled or disabled (e.g., mms) 521 * @param enable {@code true} to enable the specified APN type, 522 * {@code false} to disable it. 523 * @return an integer value representing the outcome of the request. 524 */ setEnableApn(String apnType, boolean enable)525 private int setEnableApn(String apnType, boolean enable) { 526 getPhoneService(false); 527 /* 528 * If the phone process has crashed in the past, we'll get a 529 * RemoteException and need to re-reference the service. 530 */ 531 for (int retry = 0; retry < 2; retry++) { 532 if (mPhoneService == null) { 533 loge("Ignoring feature request because could not acquire PhoneService"); 534 break; 535 } 536 537 try { 538 if (enable) { 539 return mPhoneService.enableApnType(apnType); 540 } else { 541 return mPhoneService.disableApnType(apnType); 542 } 543 } catch (RemoteException e) { 544 if (retry == 0) getPhoneService(true); 545 } 546 } 547 548 loge("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\""); 549 return Phone.APN_REQUEST_FAILED; 550 } 551 networkTypeToApnType(int netType)552 public static String networkTypeToApnType(int netType) { 553 switch(netType) { 554 case ConnectivityManager.TYPE_MOBILE: 555 return Phone.APN_TYPE_DEFAULT; // TODO - use just one of these 556 case ConnectivityManager.TYPE_MOBILE_MMS: 557 return Phone.APN_TYPE_MMS; 558 case ConnectivityManager.TYPE_MOBILE_SUPL: 559 return Phone.APN_TYPE_SUPL; 560 case ConnectivityManager.TYPE_MOBILE_DUN: 561 return Phone.APN_TYPE_DUN; 562 case ConnectivityManager.TYPE_MOBILE_HIPRI: 563 return Phone.APN_TYPE_HIPRI; 564 case ConnectivityManager.TYPE_MOBILE_FOTA: 565 return Phone.APN_TYPE_FOTA; 566 case ConnectivityManager.TYPE_MOBILE_IMS: 567 return Phone.APN_TYPE_IMS; 568 case ConnectivityManager.TYPE_MOBILE_CBS: 569 return Phone.APN_TYPE_CBS; 570 default: 571 sloge("Error mapping networkType " + netType + " to apnType."); 572 return null; 573 } 574 } 575 576 /** 577 * @see android.net.NetworkStateTracker#getLinkProperties() 578 */ getLinkProperties()579 public LinkProperties getLinkProperties() { 580 return new LinkProperties(mLinkProperties); 581 } 582 583 /** 584 * @see android.net.NetworkStateTracker#getLinkCapabilities() 585 */ getLinkCapabilities()586 public LinkCapabilities getLinkCapabilities() { 587 return new LinkCapabilities(mLinkCapabilities); 588 } 589 log(String s)590 private void log(String s) { 591 Slog.d(TAG, mApnType + ": " + s); 592 } 593 loge(String s)594 private void loge(String s) { 595 Slog.e(TAG, mApnType + ": " + s); 596 } 597 sloge(String s)598 static private void sloge(String s) { 599 Slog.e(TAG, s); 600 } 601 } 602