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