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