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