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