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