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