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