Home | History | Annotate | Download | only in net
      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      */
     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      */
    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 
    120         MdstHandler(Looper looper, MobileDataStateTracker mdst) {
    121             super(looper);
    122             mMdst = mdst;
    123         }
    124 
    125         @Override
    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 
    152     public boolean isPrivateDnsRouteSet() {
    153         return mPrivateDnsRouteSet;
    154     }
    155 
    156     public void privateDnsRouteSet(boolean enabled) {
    157         mPrivateDnsRouteSet = enabled;
    158     }
    159 
    160     public NetworkInfo getNetworkInfo() {
    161         return mNetworkInfo;
    162     }
    163 
    164     public boolean isDefaultRouteSet() {
    165         return mDefaultRouteSet;
    166     }
    167 
    168     public void defaultRouteSet(boolean enabled) {
    169         mDefaultRouteSet = enabled;
    170     }
    171 
    172     /**
    173      * This is not implemented.
    174      */
    175     public void releaseWakeLock() {
    176     }
    177 
    178     private class MobileDataStateReceiver extends BroadcastReceiver {
    179         @Override
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    405     public void setTeardownRequested(boolean isRequested) {
    406         mTeardownRequested = isRequested;
    407     }
    408 
    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      */
    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      */
    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
    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
    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      */
    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
    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      */
    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 
    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      */
    579     public LinkProperties getLinkProperties() {
    580         return new LinkProperties(mLinkProperties);
    581     }
    582 
    583     /**
    584      * @see android.net.NetworkStateTracker#getLinkCapabilities()
    585      */
    586     public LinkCapabilities getLinkCapabilities() {
    587         return new LinkCapabilities(mLinkCapabilities);
    588     }
    589 
    590     private void log(String s) {
    591         Slog.d(TAG, mApnType + ": " + s);
    592     }
    593 
    594     private void loge(String s) {
    595         Slog.e(TAG, mApnType + ": " + s);
    596     }
    597 
    598     static private void sloge(String s) {
    599         Slog.e(TAG, s);
    600     }
    601 }
    602