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