MobileDataStateTracker.java revision f61101f6266be243c481d163b95e65d67b8d1669
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        }
319        return "net.tcp.buffersize." + networkTypeStr;
320    }
321
322    /**
323     * Tear down mobile data connectivity, i.e., disable the ability to create
324     * mobile data connections.
325     * TODO - make async and return nothing?
326     */
327    public boolean teardown() {
328        setTeardownRequested(true);
329        return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
330    }
331
332    /**
333     * Record the detailed state of a network, and if it is a
334     * change from the previous state, send a notification to
335     * any listeners.
336     * @param state the new @{code DetailedState}
337     * @param reason a {@code String} indicating a reason for the state change,
338     * if one was supplied. May be {@code null}.
339     * @param extraInfo optional {@code String} providing extra information about the state change
340     */
341    private void setDetailedState(NetworkInfo.DetailedState state, String reason, String extraInfo) {
342        if (DBG) Log.d(TAG, "setDetailed state, old ="
343                + mNetworkInfo.getDetailedState() + " and new state=" + state);
344        if (state != mNetworkInfo.getDetailedState()) {
345            boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
346            String lastReason = mNetworkInfo.getReason();
347            /*
348             * If a reason was supplied when the CONNECTING state was entered, and no
349             * reason was supplied for entering the CONNECTED state, then retain the
350             * reason that was supplied when going to CONNECTING.
351             */
352            if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
353                    && lastReason != null)
354                reason = lastReason;
355            mNetworkInfo.setDetailedState(state, reason, extraInfo);
356            Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
357            msg.sendToTarget();
358        }
359    }
360
361    public void setTeardownRequested(boolean isRequested) {
362        mTeardownRequested = isRequested;
363    }
364
365    public boolean isTeardownRequested() {
366        return mTeardownRequested;
367    }
368
369    /**
370     * Re-enable mobile data connectivity after a {@link #teardown()}.
371     * TODO - make async and always get a notification?
372     */
373    public boolean reconnect() {
374        boolean retValue = false; //connected or expect to be?
375        setTeardownRequested(false);
376        switch (setEnableApn(mApnType, true)) {
377            case Phone.APN_ALREADY_ACTIVE:
378                // need to set self to CONNECTING so the below message is handled.
379                retValue = true;
380                break;
381            case Phone.APN_REQUEST_STARTED:
382                // no need to do anything - we're already due some status update intents
383                retValue = true;
384                break;
385            case Phone.APN_REQUEST_FAILED:
386            case Phone.APN_TYPE_NOT_AVAILABLE:
387                break;
388            default:
389                Log.e(TAG, "Error in reconnect - unexpected response.");
390                break;
391        }
392        return retValue;
393    }
394
395    /**
396     * Turn on or off the mobile radio. No connectivity will be possible while the
397     * radio is off. The operation is a no-op if the radio is already in the desired state.
398     * @param turnOn {@code true} if the radio should be turned on, {@code false} if
399     */
400    public boolean setRadio(boolean turnOn) {
401        getPhoneService(false);
402        /*
403         * If the phone process has crashed in the past, we'll get a
404         * RemoteException and need to re-reference the service.
405         */
406        for (int retry = 0; retry < 2; retry++) {
407            if (mPhoneService == null) {
408                Log.w(TAG,
409                    "Ignoring mobile radio request because could not acquire PhoneService");
410                break;
411            }
412
413            try {
414                return mPhoneService.setRadio(turnOn);
415            } catch (RemoteException e) {
416                if (retry == 0) getPhoneService(true);
417            }
418        }
419
420        Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off"));
421        return false;
422    }
423
424    /**
425     * Tells the phone sub-system that the caller wants to
426     * begin using the named feature. The only supported features at
427     * this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
428     * to specify that it wants to send and/or receive MMS data, and
429     * {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS.
430     * @param feature the name of the feature to be used
431     * @param callingPid the process ID of the process that is issuing this request
432     * @param callingUid the user ID of the process that is issuing this request
433     * @return an integer value representing the outcome of the request.
434     * The interpretation of this value is feature-specific.
435     * specific, except that the value {@code -1}
436     * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS},
437     * the other possible return values are
438     * <ul>
439     * <li>{@code Phone.APN_ALREADY_ACTIVE}</li>
440     * <li>{@code Phone.APN_REQUEST_STARTED}</li>
441     * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li>
442     * <li>{@code Phone.APN_REQUEST_FAILED}</li>
443     * </ul>
444     */
445    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
446        return -1;
447    }
448
449    /**
450     * Tells the phone sub-system that the caller is finished
451     * using the named feature. The only supported feature at
452     * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
453     * to specify that it wants to send and/or receive MMS data.
454     * @param feature the name of the feature that is no longer needed
455     * @param callingPid the process ID of the process that is issuing this request
456     * @param callingUid the user ID of the process that is issuing this request
457     * @return an integer value representing the outcome of the request.
458     * The interpretation of this value is feature-specific, except that
459     * the value {@code -1} always indicates failure.
460     */
461    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
462        return -1;
463    }
464
465    @Override
466    public String toString() {
467        StringBuffer sb = new StringBuffer("Mobile data state: ");
468
469        sb.append(mMobileDataState);
470        return sb.toString();
471    }
472
473   /**
474     * Internal method supporting the ENABLE_MMS feature.
475     * @param apnType the type of APN to be enabled or disabled (e.g., mms)
476     * @param enable {@code true} to enable the specified APN type,
477     * {@code false} to disable it.
478     * @return an integer value representing the outcome of the request.
479     */
480    private int setEnableApn(String apnType, boolean enable) {
481        getPhoneService(false);
482        /*
483         * If the phone process has crashed in the past, we'll get a
484         * RemoteException and need to re-reference the service.
485         */
486        for (int retry = 0; retry < 2; retry++) {
487            if (mPhoneService == null) {
488                Log.w(TAG,
489                    "Ignoring feature request because could not acquire PhoneService");
490                break;
491            }
492
493            try {
494                if (enable) {
495                    return mPhoneService.enableApnType(apnType);
496                } else {
497                    return mPhoneService.disableApnType(apnType);
498                }
499            } catch (RemoteException e) {
500                if (retry == 0) getPhoneService(true);
501            }
502        }
503
504        Log.w(TAG, "Could not " + (enable ? "enable" : "disable")
505                + " APN type \"" + apnType + "\"");
506        return Phone.APN_REQUEST_FAILED;
507    }
508
509    public static String networkTypeToApnType(int netType) {
510        switch(netType) {
511            case ConnectivityManager.TYPE_MOBILE:
512                return Phone.APN_TYPE_DEFAULT;  // TODO - use just one of these
513            case ConnectivityManager.TYPE_MOBILE_MMS:
514                return Phone.APN_TYPE_MMS;
515            case ConnectivityManager.TYPE_MOBILE_SUPL:
516                return Phone.APN_TYPE_SUPL;
517            case ConnectivityManager.TYPE_MOBILE_DUN:
518                return Phone.APN_TYPE_DUN;
519            case ConnectivityManager.TYPE_MOBILE_HIPRI:
520                return Phone.APN_TYPE_HIPRI;
521            default:
522                Log.e(TAG, "Error mapping networkType " + netType + " to apnType.");
523                return null;
524        }
525    }
526
527    /**
528     * @see android.net.NetworkStateTracker#getLinkProperties()
529     */
530    public LinkProperties getLinkProperties() {
531        return new LinkProperties(mLinkProperties);
532    }
533
534    /**
535     * @see android.net.NetworkStateTracker#getLinkCapabilities()
536     */
537    public LinkCapabilities getLinkCapabilities() {
538        return new LinkCapabilities(mLinkCapabilities);
539    }
540}
541