1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.net.NetworkInfo.DetailedState;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.Looper;
27import android.os.Message;
28import android.os.Messenger;
29import android.os.RemoteException;
30import android.os.ServiceManager;
31import android.telephony.PhoneStateListener;
32import android.telephony.SignalStrength;
33import android.telephony.TelephonyManager;
34import android.text.TextUtils;
35import android.util.Slog;
36
37import com.android.internal.telephony.DctConstants;
38import com.android.internal.telephony.ITelephony;
39import com.android.internal.telephony.PhoneConstants;
40import com.android.internal.telephony.TelephonyIntents;
41import com.android.internal.util.AsyncChannel;
42
43import java.io.CharArrayWriter;
44import java.io.PrintWriter;
45import java.util.concurrent.atomic.AtomicBoolean;
46
47/**
48 * Track the state of mobile data connectivity. This is done by
49 * receiving broadcast intents from the Phone process whenever
50 * the state of data connectivity changes.
51 *
52 * {@hide}
53 */
54public class MobileDataStateTracker extends BaseNetworkStateTracker {
55
56    private static final String TAG = "MobileDataStateTracker";
57    private static final boolean DBG = true;
58    private static final boolean VDBG = false;
59
60    private PhoneConstants.DataState mMobileDataState;
61    private ITelephony mPhoneService;
62
63    private String mApnType;
64    private NetworkInfo mNetworkInfo;
65    private boolean mTeardownRequested = false;
66    private Handler mTarget;
67    private Context mContext;
68    private LinkProperties mLinkProperties;
69    private LinkCapabilities mLinkCapabilities;
70    private boolean mPrivateDnsRouteSet = false;
71    private boolean mDefaultRouteSet = false;
72
73    // NOTE: these are only kept for debugging output; actual values are
74    // maintained in DataConnectionTracker.
75    protected boolean mUserDataEnabled = true;
76    protected boolean mPolicyDataEnabled = true;
77
78    private Handler mHandler;
79    private AsyncChannel mDataConnectionTrackerAc;
80
81    private AtomicBoolean mIsCaptivePortal = new AtomicBoolean(false);
82
83    private SignalStrength mSignalStrength;
84
85    private SamplingDataTracker mSamplingDataTracker = new SamplingDataTracker();
86
87    private static final int UNKNOWN = LinkQualityInfo.UNKNOWN_INT;
88
89    /**
90     * Create a new MobileDataStateTracker
91     * @param netType the ConnectivityManager network type
92     * @param tag the name of this network
93     */
94    public MobileDataStateTracker(int netType, String tag) {
95        mNetworkInfo = new NetworkInfo(netType,
96                TelephonyManager.getDefault().getNetworkType(), tag,
97                TelephonyManager.getDefault().getNetworkTypeName());
98        mApnType = networkTypeToApnType(netType);
99    }
100
101    /**
102     * Begin monitoring data connectivity.
103     *
104     * @param context is the current Android context
105     * @param target is the Hander to which to return the events.
106     */
107    public void startMonitoring(Context context, Handler target) {
108        mTarget = target;
109        mContext = context;
110
111        mHandler = new MdstHandler(target.getLooper(), this);
112
113        IntentFilter filter = new IntentFilter();
114        filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
115        filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN);
116        filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
117
118        mContext.registerReceiver(new MobileDataStateReceiver(), filter);
119        mMobileDataState = PhoneConstants.DataState.DISCONNECTED;
120
121        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
122                Context.TELEPHONY_SERVICE);
123        tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
124    }
125
126    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
127        @Override
128        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
129            mSignalStrength = signalStrength;
130        }
131    };
132
133    static class MdstHandler extends Handler {
134        private MobileDataStateTracker mMdst;
135
136        MdstHandler(Looper looper, MobileDataStateTracker mdst) {
137            super(looper);
138            mMdst = mdst;
139        }
140
141        @Override
142        public void handleMessage(Message msg) {
143            switch (msg.what) {
144                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
145                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
146                        if (VDBG) {
147                            mMdst.log("MdstHandler connected");
148                        }
149                        mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj;
150                    } else {
151                        if (VDBG) {
152                            mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1);
153                        }
154                    }
155                    break;
156                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
157                    if (VDBG) mMdst.log("Disconnected from DataStateTracker");
158                    mMdst.mDataConnectionTrackerAc = null;
159                    break;
160                default: {
161                    if (VDBG) mMdst.log("Ignorning unknown message=" + msg);
162                    break;
163                }
164            }
165        }
166    }
167
168    public boolean isPrivateDnsRouteSet() {
169        return mPrivateDnsRouteSet;
170    }
171
172    public void privateDnsRouteSet(boolean enabled) {
173        mPrivateDnsRouteSet = enabled;
174    }
175
176    public NetworkInfo getNetworkInfo() {
177        return mNetworkInfo;
178    }
179
180    public boolean isDefaultRouteSet() {
181        return mDefaultRouteSet;
182    }
183
184    public void defaultRouteSet(boolean enabled) {
185        mDefaultRouteSet = enabled;
186    }
187
188    /**
189     * This is not implemented.
190     */
191    public void releaseWakeLock() {
192    }
193
194    private void updateLinkProperitesAndCapatilities(Intent intent) {
195        mLinkProperties = intent.getParcelableExtra(
196                PhoneConstants.DATA_LINK_PROPERTIES_KEY);
197        if (mLinkProperties == null) {
198            loge("CONNECTED event did not supply link properties.");
199            mLinkProperties = new LinkProperties();
200        }
201        mLinkProperties.setMtu(mContext.getResources().getInteger(
202                com.android.internal.R.integer.config_mobile_mtu));
203        mLinkCapabilities = intent.getParcelableExtra(
204                PhoneConstants.DATA_LINK_CAPABILITIES_KEY);
205        if (mLinkCapabilities == null) {
206            loge("CONNECTED event did not supply link capabilities.");
207            mLinkCapabilities = new LinkCapabilities();
208        }
209    }
210
211    private class MobileDataStateReceiver extends BroadcastReceiver {
212        @Override
213        public void onReceive(Context context, Intent intent) {
214            if (intent.getAction().equals(TelephonyIntents.
215                    ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN)) {
216                String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
217                String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
218                if (!TextUtils.equals(mApnType, apnType)) {
219                    return;
220                }
221                if (DBG) {
222                    log("Broadcast received: " + intent.getAction() + " apnType=" + apnType
223                            + " apnName=" + apnName);
224                }
225
226                // Make us in the connecting state until we make a new TYPE_MOBILE_PROVISIONING
227                mMobileDataState = PhoneConstants.DataState.CONNECTING;
228                updateLinkProperitesAndCapatilities(intent);
229                mNetworkInfo.setIsConnectedToProvisioningNetwork(true);
230
231                // Change state to SUSPENDED so setDetailedState
232                // sends EVENT_STATE_CHANGED to connectivityService
233                setDetailedState(DetailedState.SUSPENDED, "", apnName);
234            } else if (intent.getAction().equals(TelephonyIntents.
235                    ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
236                String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
237                if (VDBG) {
238                    log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED"
239                        + "mApnType=%s %s received apnType=%s", mApnType,
240                        TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType));
241                }
242                if (!TextUtils.equals(apnType, mApnType)) {
243                    return;
244                }
245                // Assume this isn't a provisioning network.
246                mNetworkInfo.setIsConnectedToProvisioningNetwork(false);
247                if (DBG) {
248                    log("Broadcast received: " + intent.getAction() + " apnType=" + apnType);
249                }
250
251                int oldSubtype = mNetworkInfo.getSubtype();
252                int newSubType = TelephonyManager.getDefault().getNetworkType();
253                String subTypeName = TelephonyManager.getDefault().getNetworkTypeName();
254                mNetworkInfo.setSubtype(newSubType, subTypeName);
255                if (newSubType != oldSubtype && mNetworkInfo.isConnected()) {
256                    Message msg = mTarget.obtainMessage(EVENT_NETWORK_SUBTYPE_CHANGED,
257                                                        oldSubtype, 0, mNetworkInfo);
258                    msg.sendToTarget();
259                }
260
261                PhoneConstants.DataState state = Enum.valueOf(PhoneConstants.DataState.class,
262                        intent.getStringExtra(PhoneConstants.STATE_KEY));
263                String reason = intent.getStringExtra(PhoneConstants.STATE_CHANGE_REASON_KEY);
264                String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
265                mNetworkInfo.setRoaming(intent.getBooleanExtra(
266                        PhoneConstants.DATA_NETWORK_ROAMING_KEY, false));
267                if (VDBG) {
268                    log(mApnType + " setting isAvailable to " +
269                            intent.getBooleanExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY,false));
270                }
271                mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(
272                        PhoneConstants.NETWORK_UNAVAILABLE_KEY, false));
273
274                if (DBG) {
275                    log("Received state=" + state + ", old=" + mMobileDataState +
276                        ", reason=" + (reason == null ? "(unspecified)" : reason));
277                }
278                if (mMobileDataState != state) {
279                    mMobileDataState = state;
280                    switch (state) {
281                        case DISCONNECTED:
282                            if(isTeardownRequested()) {
283                                setTeardownRequested(false);
284                            }
285
286                            setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
287                            // can't do this here - ConnectivityService needs it to clear stuff
288                            // it's ok though - just leave it to be refreshed next time
289                            // we connect.
290                            //if (DBG) log("clearing mInterfaceName for "+ mApnType +
291                            //        " as it DISCONNECTED");
292                            //mInterfaceName = null;
293                            break;
294                        case CONNECTING:
295                            setDetailedState(DetailedState.CONNECTING, reason, apnName);
296                            break;
297                        case SUSPENDED:
298                            setDetailedState(DetailedState.SUSPENDED, reason, apnName);
299                            break;
300                        case CONNECTED:
301                            updateLinkProperitesAndCapatilities(intent);
302                            setDetailedState(DetailedState.CONNECTED, reason, apnName);
303                            break;
304                    }
305
306                    if (VDBG) {
307                        Slog.d(TAG, "TelephonyMgr.DataConnectionStateChanged");
308                        if (mNetworkInfo != null) {
309                            Slog.d(TAG, "NetworkInfo = " + mNetworkInfo.toString());
310                            Slog.d(TAG, "subType = " + String.valueOf(mNetworkInfo.getSubtype()));
311                            Slog.d(TAG, "subType = " + mNetworkInfo.getSubtypeName());
312                        }
313                        if (mLinkProperties != null) {
314                            Slog.d(TAG, "LinkProperties = " + mLinkProperties.toString());
315                        } else {
316                            Slog.d(TAG, "LinkProperties = " );
317                        }
318
319                        if (mLinkCapabilities != null) {
320                            Slog.d(TAG, "LinkCapabilities = " + mLinkCapabilities.toString());
321                        } else {
322                            Slog.d(TAG, "LinkCapabilities = " );
323                        }
324                    }
325
326
327                    /* lets not sample traffic data across state changes */
328                    mSamplingDataTracker.resetSamplingData();
329                } else {
330                    // There was no state change. Check if LinkProperties has been updated.
331                    if (TextUtils.equals(reason, PhoneConstants.REASON_LINK_PROPERTIES_CHANGED)) {
332                        mLinkProperties = intent.getParcelableExtra(
333                                PhoneConstants.DATA_LINK_PROPERTIES_KEY);
334                        if (mLinkProperties == null) {
335                            loge("No link property in LINK_PROPERTIES change event.");
336                            mLinkProperties = new LinkProperties();
337                        }
338                        // Just update reason field in this NetworkInfo
339                        mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason,
340                                                      mNetworkInfo.getExtraInfo());
341                        Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED,
342                                                            mNetworkInfo);
343                        msg.sendToTarget();
344                    }
345                }
346            } else if (intent.getAction().
347                    equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
348                String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
349                if (!TextUtils.equals(apnType, mApnType)) {
350                    if (DBG) {
351                        log(String.format(
352                                "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " +
353                                "mApnType=%s != received apnType=%s", mApnType, apnType));
354                    }
355                    return;
356                }
357                // Assume this isn't a provisioning network.
358                mNetworkInfo.setIsConnectedToProvisioningNetwork(false);
359                String reason = intent.getStringExtra(PhoneConstants.FAILURE_REASON_KEY);
360                String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
361                if (DBG) {
362                    log("Broadcast received: " + intent.getAction() +
363                                " reason=" + reason == null ? "null" : reason);
364                }
365                setDetailedState(DetailedState.FAILED, reason, apnName);
366            } else {
367                if (DBG) log("Broadcast received: ignore " + intent.getAction());
368            }
369        }
370    }
371
372    private void getPhoneService(boolean forceRefresh) {
373        if ((mPhoneService == null) || forceRefresh) {
374            mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
375        }
376    }
377
378    /**
379     * Report whether data connectivity is possible.
380     */
381    public boolean isAvailable() {
382        return mNetworkInfo.isAvailable();
383    }
384
385    /**
386     * Return the system properties name associated with the tcp buffer sizes
387     * for this network.
388     */
389    public String getTcpBufferSizesPropName() {
390        String networkTypeStr = "unknown";
391        TelephonyManager tm = new TelephonyManager(mContext);
392        //TODO We have to edit the parameter for getNetworkType regarding CDMA
393        switch(tm.getNetworkType()) {
394        case TelephonyManager.NETWORK_TYPE_GPRS:
395            networkTypeStr = "gprs";
396            break;
397        case TelephonyManager.NETWORK_TYPE_EDGE:
398            networkTypeStr = "edge";
399            break;
400        case TelephonyManager.NETWORK_TYPE_UMTS:
401            networkTypeStr = "umts";
402            break;
403        case TelephonyManager.NETWORK_TYPE_HSDPA:
404            networkTypeStr = "hsdpa";
405            break;
406        case TelephonyManager.NETWORK_TYPE_HSUPA:
407            networkTypeStr = "hsupa";
408            break;
409        case TelephonyManager.NETWORK_TYPE_HSPA:
410            networkTypeStr = "hspa";
411            break;
412        case TelephonyManager.NETWORK_TYPE_HSPAP:
413            networkTypeStr = "hspap";
414            break;
415        case TelephonyManager.NETWORK_TYPE_CDMA:
416            networkTypeStr = "cdma";
417            break;
418        case TelephonyManager.NETWORK_TYPE_1xRTT:
419            networkTypeStr = "1xrtt";
420            break;
421        case TelephonyManager.NETWORK_TYPE_EVDO_0:
422            networkTypeStr = "evdo";
423            break;
424        case TelephonyManager.NETWORK_TYPE_EVDO_A:
425            networkTypeStr = "evdo";
426            break;
427        case TelephonyManager.NETWORK_TYPE_EVDO_B:
428            networkTypeStr = "evdo";
429            break;
430        case TelephonyManager.NETWORK_TYPE_IDEN:
431            networkTypeStr = "iden";
432            break;
433        case TelephonyManager.NETWORK_TYPE_LTE:
434            networkTypeStr = "lte";
435            break;
436        case TelephonyManager.NETWORK_TYPE_EHRPD:
437            networkTypeStr = "ehrpd";
438            break;
439        default:
440            loge("unknown network type: " + tm.getNetworkType());
441        }
442        return "net.tcp.buffersize." + networkTypeStr;
443    }
444
445    /**
446     * Tear down mobile data connectivity, i.e., disable the ability to create
447     * mobile data connections.
448     * TODO - make async and return nothing?
449     */
450    public boolean teardown() {
451        setTeardownRequested(true);
452        return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
453    }
454
455    /**
456     * @return true if this is ready to operate
457     */
458    public boolean isReady() {
459        return mDataConnectionTrackerAc != null;
460    }
461
462    @Override
463    public void captivePortalCheckComplete() {
464        // not implemented
465    }
466
467    @Override
468    public void captivePortalCheckCompleted(boolean isCaptivePortal) {
469        if (mIsCaptivePortal.getAndSet(isCaptivePortal) != isCaptivePortal) {
470            // Captive portal change enable/disable failing fast
471            setEnableFailFastMobileData(
472                    isCaptivePortal ? DctConstants.ENABLED : DctConstants.DISABLED);
473        }
474    }
475
476    /**
477     * Record the detailed state of a network, and if it is a
478     * change from the previous state, send a notification to
479     * any listeners.
480     * @param state the new {@code DetailedState}
481     * @param reason a {@code String} indicating a reason for the state change,
482     * if one was supplied. May be {@code null}.
483     * @param extraInfo optional {@code String} providing extra information about the state change
484     */
485    private void setDetailedState(NetworkInfo.DetailedState state, String reason,
486            String extraInfo) {
487        if (DBG) log("setDetailed state, old ="
488                + mNetworkInfo.getDetailedState() + " and new state=" + state);
489        if (state != mNetworkInfo.getDetailedState()) {
490            boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
491            String lastReason = mNetworkInfo.getReason();
492            /*
493             * If a reason was supplied when the CONNECTING state was entered, and no
494             * reason was supplied for entering the CONNECTED state, then retain the
495             * reason that was supplied when going to CONNECTING.
496             */
497            if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
498                    && lastReason != null)
499                reason = lastReason;
500            mNetworkInfo.setDetailedState(state, reason, extraInfo);
501            Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, new NetworkInfo(mNetworkInfo));
502            msg.sendToTarget();
503        }
504    }
505
506    public void setTeardownRequested(boolean isRequested) {
507        mTeardownRequested = isRequested;
508    }
509
510    public boolean isTeardownRequested() {
511        return mTeardownRequested;
512    }
513
514    /**
515     * Re-enable mobile data connectivity after a {@link #teardown()}.
516     * TODO - make async and always get a notification?
517     */
518    public boolean reconnect() {
519        boolean retValue = false; //connected or expect to be?
520        setTeardownRequested(false);
521        switch (setEnableApn(mApnType, true)) {
522            case PhoneConstants.APN_ALREADY_ACTIVE:
523                // need to set self to CONNECTING so the below message is handled.
524                retValue = true;
525                break;
526            case PhoneConstants.APN_REQUEST_STARTED:
527                // set IDLE here , avoid the following second FAILED not sent out
528                mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
529                retValue = true;
530                break;
531            case PhoneConstants.APN_REQUEST_FAILED:
532            case PhoneConstants.APN_TYPE_NOT_AVAILABLE:
533                break;
534            default:
535                loge("Error in reconnect - unexpected response.");
536                break;
537        }
538        return retValue;
539    }
540
541    /**
542     * Turn on or off the mobile radio. No connectivity will be possible while the
543     * radio is off. The operation is a no-op if the radio is already in the desired state.
544     * @param turnOn {@code true} if the radio should be turned on, {@code false} if
545     */
546    public boolean setRadio(boolean turnOn) {
547        getPhoneService(false);
548        /*
549         * If the phone process has crashed in the past, we'll get a
550         * RemoteException and need to re-reference the service.
551         */
552        for (int retry = 0; retry < 2; retry++) {
553            if (mPhoneService == null) {
554                loge("Ignoring mobile radio request because could not acquire PhoneService");
555                break;
556            }
557
558            try {
559                return mPhoneService.setRadio(turnOn);
560            } catch (RemoteException e) {
561                if (retry == 0) getPhoneService(true);
562            }
563        }
564
565        loge("Could not set radio power to " + (turnOn ? "on" : "off"));
566        return false;
567    }
568
569    @Override
570    public void setUserDataEnable(boolean enabled) {
571        if (DBG) log("setUserDataEnable: E enabled=" + enabled);
572        final AsyncChannel channel = mDataConnectionTrackerAc;
573        if (channel != null) {
574            channel.sendMessage(DctConstants.CMD_SET_USER_DATA_ENABLE,
575                    enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
576            mUserDataEnabled = enabled;
577        }
578        if (VDBG) log("setUserDataEnable: X enabled=" + enabled);
579    }
580
581    @Override
582    public void setPolicyDataEnable(boolean enabled) {
583        if (DBG) log("setPolicyDataEnable(enabled=" + enabled + ")");
584        final AsyncChannel channel = mDataConnectionTrackerAc;
585        if (channel != null) {
586            channel.sendMessage(DctConstants.CMD_SET_POLICY_DATA_ENABLE,
587                    enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
588            mPolicyDataEnabled = enabled;
589        }
590    }
591
592    /**
593     * Eanble/disable FailFast
594     *
595     * @param enabled is DctConstants.ENABLED/DISABLED
596     */
597    public void setEnableFailFastMobileData(int enabled) {
598        if (DBG) log("setEnableFailFastMobileData(enabled=" + enabled + ")");
599        final AsyncChannel channel = mDataConnectionTrackerAc;
600        if (channel != null) {
601            channel.sendMessage(DctConstants.CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA, enabled);
602        }
603    }
604
605    /**
606     * carrier dependency is met/unmet
607     * @param met
608     */
609    public void setDependencyMet(boolean met) {
610        Bundle bundle = Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType);
611        try {
612            if (DBG) log("setDependencyMet: E met=" + met);
613            Message msg = Message.obtain();
614            msg.what = DctConstants.CMD_SET_DEPENDENCY_MET;
615            msg.arg1 = (met ? DctConstants.ENABLED : DctConstants.DISABLED);
616            msg.setData(bundle);
617            mDataConnectionTrackerAc.sendMessage(msg);
618            if (VDBG) log("setDependencyMet: X met=" + met);
619        } catch (NullPointerException e) {
620            loge("setDependencyMet: X mAc was null" + e);
621        }
622    }
623
624    /**
625     *  Inform DCT mobile provisioning has started, it ends when provisioning completes.
626     */
627    public void enableMobileProvisioning(String url) {
628        if (DBG) log("enableMobileProvisioning(url=" + url + ")");
629        final AsyncChannel channel = mDataConnectionTrackerAc;
630        if (channel != null) {
631            Message msg = Message.obtain();
632            msg.what = DctConstants.CMD_ENABLE_MOBILE_PROVISIONING;
633            msg.setData(Bundle.forPair(DctConstants.PROVISIONING_URL_KEY, url));
634            channel.sendMessage(msg);
635        }
636    }
637
638    /**
639     * Return if this network is the provisioning network. Valid only if connected.
640     * @param met
641     */
642    public boolean isProvisioningNetwork() {
643        boolean retVal;
644        try {
645            Message msg = Message.obtain();
646            msg.what = DctConstants.CMD_IS_PROVISIONING_APN;
647            msg.setData(Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType));
648            Message result = mDataConnectionTrackerAc.sendMessageSynchronously(msg);
649            retVal = result.arg1 == DctConstants.ENABLED;
650        } catch (NullPointerException e) {
651            loge("isProvisioningNetwork: X " + e);
652            retVal = false;
653        }
654        if (DBG) log("isProvisioningNetwork: retVal=" + retVal);
655        return retVal;
656    }
657
658    @Override
659    public void addStackedLink(LinkProperties link) {
660        mLinkProperties.addStackedLink(link);
661    }
662
663    @Override
664    public void removeStackedLink(LinkProperties link) {
665        mLinkProperties.removeStackedLink(link);
666    }
667
668    @Override
669    public String toString() {
670        final CharArrayWriter writer = new CharArrayWriter();
671        final PrintWriter pw = new PrintWriter(writer);
672        pw.print("Mobile data state: "); pw.println(mMobileDataState);
673        pw.print("Data enabled: user="); pw.print(mUserDataEnabled);
674        pw.print(", policy="); pw.println(mPolicyDataEnabled);
675        return writer.toString();
676    }
677
678    /**
679     * Internal method supporting the ENABLE_MMS feature.
680     * @param apnType the type of APN to be enabled or disabled (e.g., mms)
681     * @param enable {@code true} to enable the specified APN type,
682     * {@code false} to disable it.
683     * @return an integer value representing the outcome of the request.
684     */
685    private int setEnableApn(String apnType, boolean enable) {
686        getPhoneService(false);
687        /*
688         * If the phone process has crashed in the past, we'll get a
689         * RemoteException and need to re-reference the service.
690         */
691        for (int retry = 0; retry < 2; retry++) {
692            if (mPhoneService == null) {
693                loge("Ignoring feature request because could not acquire PhoneService");
694                break;
695            }
696
697            try {
698                if (enable) {
699                    return mPhoneService.enableApnType(apnType);
700                } else {
701                    return mPhoneService.disableApnType(apnType);
702                }
703            } catch (RemoteException e) {
704                if (retry == 0) getPhoneService(true);
705            }
706        }
707
708        loge("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\"");
709        return PhoneConstants.APN_REQUEST_FAILED;
710    }
711
712    public static String networkTypeToApnType(int netType) {
713        switch(netType) {
714            case ConnectivityManager.TYPE_MOBILE:
715                return PhoneConstants.APN_TYPE_DEFAULT;  // TODO - use just one of these
716            case ConnectivityManager.TYPE_MOBILE_MMS:
717                return PhoneConstants.APN_TYPE_MMS;
718            case ConnectivityManager.TYPE_MOBILE_SUPL:
719                return PhoneConstants.APN_TYPE_SUPL;
720            case ConnectivityManager.TYPE_MOBILE_DUN:
721                return PhoneConstants.APN_TYPE_DUN;
722            case ConnectivityManager.TYPE_MOBILE_HIPRI:
723                return PhoneConstants.APN_TYPE_HIPRI;
724            case ConnectivityManager.TYPE_MOBILE_FOTA:
725                return PhoneConstants.APN_TYPE_FOTA;
726            case ConnectivityManager.TYPE_MOBILE_IMS:
727                return PhoneConstants.APN_TYPE_IMS;
728            case ConnectivityManager.TYPE_MOBILE_CBS:
729                return PhoneConstants.APN_TYPE_CBS;
730            case ConnectivityManager.TYPE_MOBILE_IA:
731                return PhoneConstants.APN_TYPE_IA;
732            default:
733                sloge("Error mapping networkType " + netType + " to apnType.");
734                return null;
735        }
736    }
737
738
739    /**
740     * @see android.net.NetworkStateTracker#getLinkProperties()
741     */
742    @Override
743    public LinkProperties getLinkProperties() {
744        return new LinkProperties(mLinkProperties);
745    }
746
747    /**
748     * @see android.net.NetworkStateTracker#getLinkCapabilities()
749     */
750    @Override
751    public LinkCapabilities getLinkCapabilities() {
752        return new LinkCapabilities(mLinkCapabilities);
753    }
754
755    public void supplyMessenger(Messenger messenger) {
756        if (VDBG) log(mApnType + " got supplyMessenger");
757        AsyncChannel ac = new AsyncChannel();
758        ac.connect(mContext, MobileDataStateTracker.this.mHandler, messenger);
759    }
760
761    private void log(String s) {
762        Slog.d(TAG, mApnType + ": " + s);
763    }
764
765    private void loge(String s) {
766        Slog.e(TAG, mApnType + ": " + s);
767    }
768
769    static private void sloge(String s) {
770        Slog.e(TAG, s);
771    }
772
773    @Override
774    public LinkQualityInfo getLinkQualityInfo() {
775        if (mNetworkInfo == null || mNetworkInfo.getType() == ConnectivityManager.TYPE_NONE) {
776            // no data available yet; just return
777            return null;
778        }
779
780        MobileLinkQualityInfo li = new MobileLinkQualityInfo();
781
782        li.setNetworkType(mNetworkInfo.getType());
783
784        mSamplingDataTracker.setCommonLinkQualityInfoFields(li);
785
786        if (mNetworkInfo.getSubtype() != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
787            li.setMobileNetworkType(mNetworkInfo.getSubtype());
788
789            NetworkDataEntry entry = getNetworkDataEntry(mNetworkInfo.getSubtype());
790            if (entry != null) {
791                li.setTheoreticalRxBandwidth(entry.downloadBandwidth);
792                li.setTheoreticalRxBandwidth(entry.uploadBandwidth);
793                li.setTheoreticalLatency(entry.latency);
794            }
795
796            if (mSignalStrength != null) {
797                li.setNormalizedSignalStrength(getNormalizedSignalStrength(
798                        li.getMobileNetworkType(), mSignalStrength));
799            }
800        }
801
802        SignalStrength ss = mSignalStrength;
803        if (ss != null) {
804
805            li.setRssi(ss.getGsmSignalStrength());
806            li.setGsmErrorRate(ss.getGsmBitErrorRate());
807            li.setCdmaDbm(ss.getCdmaDbm());
808            li.setCdmaEcio(ss.getCdmaEcio());
809            li.setEvdoDbm(ss.getEvdoDbm());
810            li.setEvdoEcio(ss.getEvdoEcio());
811            li.setEvdoSnr(ss.getEvdoSnr());
812            li.setLteSignalStrength(ss.getLteSignalStrength());
813            li.setLteRsrp(ss.getLteRsrp());
814            li.setLteRsrq(ss.getLteRsrq());
815            li.setLteRssnr(ss.getLteRssnr());
816            li.setLteCqi(ss.getLteCqi());
817        }
818
819        if (VDBG) {
820            Slog.d(TAG, "Returning LinkQualityInfo with"
821                    + " MobileNetworkType = " + String.valueOf(li.getMobileNetworkType())
822                    + " Theoretical Rx BW = " + String.valueOf(li.getTheoreticalRxBandwidth())
823                    + " gsm Signal Strength = " + String.valueOf(li.getRssi())
824                    + " cdma Signal Strength = " + String.valueOf(li.getCdmaDbm())
825                    + " evdo Signal Strength = " + String.valueOf(li.getEvdoDbm())
826                    + " Lte Signal Strength = " + String.valueOf(li.getLteSignalStrength()));
827        }
828
829        return li;
830    }
831
832    static class NetworkDataEntry {
833        public int networkType;
834        public int downloadBandwidth;               // in kbps
835        public int uploadBandwidth;                 // in kbps
836        public int latency;                         // in millisecond
837
838        NetworkDataEntry(int i1, int i2, int i3, int i4) {
839            networkType = i1;
840            downloadBandwidth = i2;
841            uploadBandwidth = i3;
842            latency = i4;
843        }
844    }
845
846    private static NetworkDataEntry [] mTheoreticalBWTable = new NetworkDataEntry[] {
847            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EDGE,      237,     118, UNKNOWN),
848            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_GPRS,       48,      40, UNKNOWN),
849            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_UMTS,      384,      64, UNKNOWN),
850            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSDPA,   14400, UNKNOWN, UNKNOWN),
851            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSUPA,   14400,    5760, UNKNOWN),
852            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSPA,    14400,    5760, UNKNOWN),
853            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSPAP,   21000,    5760, UNKNOWN),
854            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_CDMA,  UNKNOWN, UNKNOWN, UNKNOWN),
855            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_1xRTT, UNKNOWN, UNKNOWN, UNKNOWN),
856            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_0,   2468,     153, UNKNOWN),
857            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_A,   3072,    1800, UNKNOWN),
858            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_B,  14700,    1800, UNKNOWN),
859            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_IDEN,  UNKNOWN, UNKNOWN, UNKNOWN),
860            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_LTE,    100000,   50000, UNKNOWN),
861            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EHRPD, UNKNOWN, UNKNOWN, UNKNOWN),
862    };
863
864    private static NetworkDataEntry getNetworkDataEntry(int networkType) {
865        for (NetworkDataEntry entry : mTheoreticalBWTable) {
866            if (entry.networkType == networkType) {
867                return entry;
868            }
869        }
870
871        Slog.e(TAG, "Could not find Theoretical BW entry for " + String.valueOf(networkType));
872        return null;
873    }
874
875    private static int getNormalizedSignalStrength(int networkType, SignalStrength ss) {
876
877        int level;
878
879        switch(networkType) {
880            case TelephonyManager.NETWORK_TYPE_EDGE:
881            case TelephonyManager.NETWORK_TYPE_GPRS:
882            case TelephonyManager.NETWORK_TYPE_UMTS:
883            case TelephonyManager.NETWORK_TYPE_HSDPA:
884            case TelephonyManager.NETWORK_TYPE_HSUPA:
885            case TelephonyManager.NETWORK_TYPE_HSPA:
886            case TelephonyManager.NETWORK_TYPE_HSPAP:
887                level = ss.getGsmLevel();
888                break;
889            case TelephonyManager.NETWORK_TYPE_CDMA:
890            case TelephonyManager.NETWORK_TYPE_1xRTT:
891                level = ss.getCdmaLevel();
892                break;
893            case TelephonyManager.NETWORK_TYPE_EVDO_0:
894            case TelephonyManager.NETWORK_TYPE_EVDO_A:
895            case TelephonyManager.NETWORK_TYPE_EVDO_B:
896                level = ss.getEvdoLevel();
897                break;
898            case TelephonyManager.NETWORK_TYPE_LTE:
899                level = ss.getLteLevel();
900                break;
901            case TelephonyManager.NETWORK_TYPE_IDEN:
902            case TelephonyManager.NETWORK_TYPE_EHRPD:
903            default:
904                return UNKNOWN;
905        }
906
907        return (level * LinkQualityInfo.NORMALIZED_SIGNAL_STRENGTH_RANGE) /
908                SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
909    }
910
911    @Override
912    public void startSampling(SamplingDataTracker.SamplingSnapshot s) {
913        mSamplingDataTracker.startSampling(s);
914    }
915
916    @Override
917    public void stopSampling(SamplingDataTracker.SamplingSnapshot s) {
918        mSamplingDataTracker.stopSampling(s);
919    }
920}
921