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