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