MobileDataStateTracker.java revision c7a98341b053bd8090accca151e8f3522f2dd126
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.NetworkProperties;
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 NetworkProperties mNetworkProperties;
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 =
109                new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
110        filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
111        filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
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
164                if (!TextUtils.equals(apnType, mApnType)) {
165                    return;
166                }
167                Phone.DataState state = Enum.valueOf(Phone.DataState.class,
168                        intent.getStringExtra(Phone.STATE_KEY));
169                String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
170                String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
171
172                boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,
173                        false);
174                mNetworkInfo.setIsAvailable(!unavailable);
175
176                if (DBG) Log.d(TAG, mApnType + " Received state= " + state + ", old= " +
177                        mMobileDataState + ", reason= " +
178                        (reason == null ? "(unspecified)" : reason));
179
180                if (mMobileDataState != state) {
181                    mMobileDataState = state;
182                    switch (state) {
183                        case DISCONNECTED:
184                            if(isTeardownRequested()) {
185                                setTeardownRequested(false);
186                            }
187
188                            setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
189                            boolean doReset = true;
190                            if (mIsDefaultOrHipri == true) {
191                                // both default and hipri must go down before we reset
192                                int typeToCheck = (Phone.APN_TYPE_DEFAULT.equals(mApnType) ?
193                                    ConnectivityManager.TYPE_MOBILE_HIPRI :
194                                    ConnectivityManager.TYPE_MOBILE);
195                                if (mConnectivityManager == null) {
196                                    IBinder b = ServiceManager.getService(
197                                            mContext.CONNECTIVITY_SERVICE);
198                                    mConnectivityManager = IConnectivityManager.Stub.asInterface(b);
199                                }
200                                try {
201                                    if (mConnectivityManager != null) {
202                                        NetworkInfo info = mConnectivityManager.getNetworkInfo(
203                                                typeToCheck);
204                                        if (info.isConnected() == true) {
205                                            doReset = false;
206                                        }
207                                    }
208                                } catch (RemoteException e) {
209                                    // just go ahead with the reset
210                                    Log.e(TAG, "Exception trying to contact ConnService: "
211                                            + e);
212                                }
213                            }
214                            if (doReset && mNetworkProperties != null) {
215                                String iface = mNetworkProperties.getInterfaceName();
216                                if (iface != null) NetworkUtils.resetConnections(iface);
217                            }
218                            // TODO - check this
219                            // can't do this here - ConnectivityService needs it to clear stuff
220                            // it's ok though - just leave it to be refreshed next time
221                            // we connect.
222                            //if (DBG) Log.d(TAG, "clearing mInterfaceName for "+ mApnType +
223                            //        " as it DISCONNECTED");
224                            //mInterfaceName = null;
225                            //mDefaultGatewayAddr = 0;
226                            break;
227                        case CONNECTING:
228                            setDetailedState(DetailedState.CONNECTING, reason, apnName);
229                            break;
230                        case SUSPENDED:
231                            setDetailedState(DetailedState.SUSPENDED, reason, apnName);
232                            break;
233                        case CONNECTED:
234                            mNetworkProperties = intent.getParcelableExtra(
235                                    Phone.DATA_NETWORK_PROPERTIES_KEY);
236                            if (mNetworkProperties == null) {
237                                Log.d(TAG,
238                                        "CONNECTED event did not supply network properties.");
239                            }
240                            setDetailedState(DetailedState.CONNECTED, reason, apnName);
241                            break;
242                    }
243                }
244            } else if (intent.getAction().
245                    equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
246                String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
247                if (!TextUtils.equals(apnType, mApnType)) {
248                    return;
249                }
250                String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
251                String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
252                if (DBG) Log.d(TAG, mApnType + "Received " + intent.getAction() +
253                        " broadcast" + reason == null ? "" : "(" + reason + ")");
254                setDetailedState(DetailedState.FAILED, reason, apnName);
255            }
256            TelephonyManager tm = TelephonyManager.getDefault();
257        }
258    }
259
260    private void getPhoneService(boolean forceRefresh) {
261        if ((mPhoneService == null) || forceRefresh) {
262            mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
263        }
264    }
265
266    /**
267     * Report whether data connectivity is possible.
268     */
269    public boolean isAvailable() {
270        getPhoneService(false);
271
272        /*
273         * If the phone process has crashed in the past, we'll get a
274         * RemoteException and need to re-reference the service.
275         */
276        for (int retry = 0; retry < 2; retry++) {
277            if (mPhoneService == null) break;
278
279            try {
280                return mPhoneService.isDataConnectivityPossible();
281            } catch (RemoteException e) {
282                // First-time failed, get the phone service again
283                if (retry == 0) getPhoneService(true);
284            }
285        }
286
287        return false;
288    }
289
290    /**
291     * {@inheritDoc}
292     * The mobile data network subtype indicates what generation network technology is in effect,
293     * e.g., GPRS, EDGE, UMTS, etc.
294     */
295    public int getNetworkSubtype() {
296        return TelephonyManager.getDefault().getNetworkType();
297    }
298
299    /**
300     * Return the system properties name associated with the tcp buffer sizes
301     * for this network.
302     */
303    public String getTcpBufferSizesPropName() {
304        String networkTypeStr = "unknown";
305        TelephonyManager tm = new TelephonyManager(mContext);
306        //TODO We have to edit the parameter for getNetworkType regarding CDMA
307        switch(tm.getNetworkType()) {
308        case TelephonyManager.NETWORK_TYPE_GPRS:
309            networkTypeStr = "gprs";
310            break;
311        case TelephonyManager.NETWORK_TYPE_EDGE:
312            networkTypeStr = "edge";
313            break;
314        case TelephonyManager.NETWORK_TYPE_UMTS:
315            networkTypeStr = "umts";
316            break;
317        case TelephonyManager.NETWORK_TYPE_HSDPA:
318            networkTypeStr = "hsdpa";
319            break;
320        case TelephonyManager.NETWORK_TYPE_HSUPA:
321            networkTypeStr = "hsupa";
322            break;
323        case TelephonyManager.NETWORK_TYPE_HSPA:
324            networkTypeStr = "hspa";
325            break;
326        case TelephonyManager.NETWORK_TYPE_CDMA:
327            networkTypeStr = "cdma";
328            break;
329        case TelephonyManager.NETWORK_TYPE_1xRTT:
330            networkTypeStr = "1xrtt";
331            break;
332        case TelephonyManager.NETWORK_TYPE_EVDO_0:
333            networkTypeStr = "evdo";
334            break;
335        case TelephonyManager.NETWORK_TYPE_EVDO_A:
336            networkTypeStr = "evdo";
337            break;
338        case TelephonyManager.NETWORK_TYPE_EVDO_B:
339            networkTypeStr = "evdo";
340            break;
341        }
342        return "net.tcp.buffersize." + networkTypeStr;
343    }
344
345    /**
346     * Tear down mobile data connectivity, i.e., disable the ability to create
347     * mobile data connections.
348     * TODO - make async and return nothing?
349     */
350    public boolean teardown() {
351        setTeardownRequested(true);
352        return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
353    }
354
355    /**
356     * Record the detailed state of a network, and if it is a
357     * change from the previous state, send a notification to
358     * any listeners.
359     * @param state the new @{code DetailedState}
360     */
361    private void setDetailedState(NetworkInfo.DetailedState state) {
362        setDetailedState(state, null, null);
363    }
364
365    /**
366     * Record the detailed state of a network, and if it is a
367     * change from the previous state, send a notification to
368     * any listeners.
369     * @param state the new @{code DetailedState}
370     * @param reason a {@code String} indicating a reason for the state change,
371     * if one was supplied. May be {@code null}.
372     * @param extraInfo optional {@code String} providing extra information about the state change
373     */
374    private void setDetailedState(NetworkInfo.DetailedState state, String reason, String extraInfo) {
375        if (DBG) Log.d(TAG, "setDetailed state, old ="
376                + mNetworkInfo.getDetailedState() + " and new state=" + state);
377        if (state != mNetworkInfo.getDetailedState()) {
378            boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
379            String lastReason = mNetworkInfo.getReason();
380            /*
381             * If a reason was supplied when the CONNECTING state was entered, and no
382             * reason was supplied for entering the CONNECTED state, then retain the
383             * reason that was supplied when going to CONNECTING.
384             */
385            if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
386                    && lastReason != null)
387                reason = lastReason;
388            mNetworkInfo.setDetailedState(state, reason, extraInfo);
389            Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
390            msg.sendToTarget();
391        }
392    }
393
394    private void setDetailedStateInternal(NetworkInfo.DetailedState state) {
395        mNetworkInfo.setDetailedState(state, null, null);
396    }
397
398    public void setTeardownRequested(boolean isRequested) {
399        mTeardownRequested = isRequested;
400    }
401
402    public boolean isTeardownRequested() {
403        return mTeardownRequested;
404    }
405
406    /**
407     * Re-enable mobile data connectivity after a {@link #teardown()}.
408     * TODO - make async and always get a notification?
409     */
410    public boolean reconnect() {
411        boolean retValue = false; //connected or expect to be?
412        setTeardownRequested(false);
413        switch (setEnableApn(mApnType, true)) {
414            case Phone.APN_ALREADY_ACTIVE:
415                // need to set self to CONNECTING so the below message is handled.
416                retValue = true;
417                break;
418            case Phone.APN_REQUEST_STARTED:
419                // no need to do anything - we're already due some status update intents
420                retValue = true;
421                break;
422            case Phone.APN_REQUEST_FAILED:
423            case Phone.APN_TYPE_NOT_AVAILABLE:
424                break;
425            default:
426                Log.e(TAG, "Error in reconnect - unexpected response.");
427                break;
428        }
429        return retValue;
430    }
431
432    /**
433     * Turn on or off the mobile radio. No connectivity will be possible while the
434     * radio is off. The operation is a no-op if the radio is already in the desired state.
435     * @param turnOn {@code true} if the radio should be turned on, {@code false} if
436     */
437    public boolean setRadio(boolean turnOn) {
438        getPhoneService(false);
439        /*
440         * If the phone process has crashed in the past, we'll get a
441         * RemoteException and need to re-reference the service.
442         */
443        for (int retry = 0; retry < 2; retry++) {
444            if (mPhoneService == null) {
445                Log.w(TAG,
446                    "Ignoring mobile radio request because could not acquire PhoneService");
447                break;
448            }
449
450            try {
451                return mPhoneService.setRadio(turnOn);
452            } catch (RemoteException e) {
453                if (retry == 0) getPhoneService(true);
454            }
455        }
456
457        Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off"));
458        return false;
459    }
460
461    /**
462     * Tells the phone sub-system that the caller wants to
463     * begin using the named feature. The only supported features at
464     * this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
465     * to specify that it wants to send and/or receive MMS data, and
466     * {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS.
467     * @param feature the name of the feature to be used
468     * @param callingPid the process ID of the process that is issuing this request
469     * @param callingUid the user ID of the process that is issuing this request
470     * @return an integer value representing the outcome of the request.
471     * The interpretation of this value is feature-specific.
472     * specific, except that the value {@code -1}
473     * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS},
474     * the other possible return values are
475     * <ul>
476     * <li>{@code Phone.APN_ALREADY_ACTIVE}</li>
477     * <li>{@code Phone.APN_REQUEST_STARTED}</li>
478     * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li>
479     * <li>{@code Phone.APN_REQUEST_FAILED}</li>
480     * </ul>
481     */
482    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
483        return -1;
484    }
485
486    /**
487     * Tells the phone sub-system that the caller is finished
488     * using the named feature. The only supported feature at
489     * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
490     * to specify that it wants to send and/or receive MMS data.
491     * @param feature the name of the feature that is no longer needed
492     * @param callingPid the process ID of the process that is issuing this request
493     * @param callingUid the user ID of the process that is issuing this request
494     * @return an integer value representing the outcome of the request.
495     * The interpretation of this value is feature-specific, except that
496     * the value {@code -1} always indicates failure.
497     */
498    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
499        return -1;
500    }
501
502    @Override
503    public String toString() {
504        StringBuffer sb = new StringBuffer("Mobile data state: ");
505
506        sb.append(mMobileDataState);
507        return sb.toString();
508    }
509
510   /**
511     * Internal method supporting the ENABLE_MMS feature.
512     * @param apnType the type of APN to be enabled or disabled (e.g., mms)
513     * @param enable {@code true} to enable the specified APN type,
514     * {@code false} to disable it.
515     * @return an integer value representing the outcome of the request.
516     */
517    private int setEnableApn(String apnType, boolean enable) {
518        getPhoneService(false);
519        /*
520         * If the phone process has crashed in the past, we'll get a
521         * RemoteException and need to re-reference the service.
522         */
523        for (int retry = 0; retry < 2; retry++) {
524            if (mPhoneService == null) {
525                Log.w(TAG,
526                    "Ignoring feature request because could not acquire PhoneService");
527                break;
528            }
529
530            try {
531                if (enable) {
532                    return mPhoneService.enableApnType(apnType);
533                } else {
534                    return mPhoneService.disableApnType(apnType);
535                }
536            } catch (RemoteException e) {
537                if (retry == 0) getPhoneService(true);
538            }
539        }
540
541        Log.w(TAG, "Could not " + (enable ? "enable" : "disable")
542                + " APN type \"" + apnType + "\"");
543        return Phone.APN_REQUEST_FAILED;
544    }
545
546    public static String networkTypeToApnType(int netType) {
547        switch(netType) {
548            case ConnectivityManager.TYPE_MOBILE:
549                return Phone.APN_TYPE_DEFAULT;  // TODO - use just one of these
550            case ConnectivityManager.TYPE_MOBILE_MMS:
551                return Phone.APN_TYPE_MMS;
552            case ConnectivityManager.TYPE_MOBILE_SUPL:
553                return Phone.APN_TYPE_SUPL;
554            case ConnectivityManager.TYPE_MOBILE_DUN:
555                return Phone.APN_TYPE_DUN;
556            case ConnectivityManager.TYPE_MOBILE_HIPRI:
557                return Phone.APN_TYPE_HIPRI;
558            default:
559                Log.e(TAG, "Error mapping networkType " + netType + " to apnType.");
560                return null;
561        }
562    }
563
564    public NetworkProperties getNetworkProperties() {
565        return mNetworkProperties;
566    }
567}
568