MobileDataStateTracker.java revision b21b62327c10e92baf1a88e77f1c6d5576a17d62
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 java.net.InetAddress;
20
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.os.RemoteException;
26import android.os.Handler;
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.telephony.TelephonyManager;
33import android.util.Log;
34import android.text.TextUtils;
35
36/**
37 * Track the state of mobile data connectivity. This is done by
38 * receiving broadcast intents from the Phone process whenever
39 * the state of data connectivity changes.
40 *
41 * {@hide}
42 */
43public class MobileDataStateTracker extends NetworkStateTracker {
44
45    private static final String TAG = "MobileDataStateTracker";
46    private static final boolean DBG = true;
47
48    private Phone.DataState mMobileDataState;
49    private ITelephony mPhoneService;
50
51    private String mApnType;
52    private String mApnTypeToWatchFor;
53    private String mApnName;
54    private boolean mEnabled;
55    private BroadcastReceiver mStateReceiver;
56
57    // DEFAULT and HIPRI are the same connection.  If we're one of these we need to check if
58    // the other is also disconnected before we reset sockets
59    private boolean mIsDefaultOrHipri = false;
60
61    /**
62     * Create a new MobileDataStateTracker
63     * @param context the application context of the caller
64     * @param target a message handler for getting callbacks about state changes
65     * @param netType the ConnectivityManager network type
66     * @param apnType the Phone apnType
67     * @param tag the name of this network
68     */
69    public MobileDataStateTracker(Context context, Handler target, int netType, String tag) {
70        super(context, target, netType,
71                TelephonyManager.getDefault().getNetworkType(), tag,
72                TelephonyManager.getDefault().getNetworkTypeName());
73        mApnType = networkTypeToApnType(netType);
74        if (TextUtils.equals(mApnType, Phone.APN_TYPE_HIPRI)) {
75            mApnTypeToWatchFor = Phone.APN_TYPE_DEFAULT;
76        } else {
77            mApnTypeToWatchFor = mApnType;
78        }
79        if (netType == ConnectivityManager.TYPE_MOBILE ||
80                netType == ConnectivityManager.TYPE_MOBILE_HIPRI) {
81            mIsDefaultOrHipri = true;
82        }
83
84        mPhoneService = null;
85        if(netType == ConnectivityManager.TYPE_MOBILE) {
86            mEnabled = true;
87        } else {
88            mEnabled = false;
89        }
90
91        mDnsPropNames = new String[] {
92                "net.rmnet0.dns1",
93                "net.rmnet0.dns2",
94                "net.eth0.dns1",
95                "net.eth0.dns2",
96                "net.eth0.dns3",
97                "net.eth0.dns4",
98                "net.gprs.dns1",
99                "net.gprs.dns2",
100                "net.ppp0.dns1",
101                "net.ppp0.dns2"};
102
103    }
104
105    /**
106     * Begin monitoring mobile data connectivity.
107     */
108    public void startMonitoring() {
109        IntentFilter filter =
110                new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
111        filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
112        filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
113
114        mStateReceiver = new MobileDataStateReceiver();
115        Intent intent = mContext.registerReceiver(mStateReceiver, filter);
116        if (intent != null)
117            mMobileDataState = getMobileDataState(intent);
118        else
119            mMobileDataState = Phone.DataState.DISCONNECTED;
120    }
121
122    private Phone.DataState getMobileDataState(Intent intent) {
123        String str = intent.getStringExtra(Phone.STATE_KEY);
124        if (str != null) {
125            String apnTypeList =
126                    intent.getStringExtra(Phone.DATA_APN_TYPES_KEY);
127            if (isApnTypeIncluded(apnTypeList)) {
128                return Enum.valueOf(Phone.DataState.class, str);
129            }
130        }
131        return Phone.DataState.DISCONNECTED;
132    }
133
134    private boolean isApnTypeIncluded(String typeList) {
135        /* comma seperated list - split and check */
136        if (typeList == null)
137            return false;
138
139        String[] list = typeList.split(",");
140        for(int i=0; i< list.length; i++) {
141            if (TextUtils.equals(list[i], mApnTypeToWatchFor) ||
142                TextUtils.equals(list[i], Phone.APN_TYPE_ALL)) {
143                return true;
144            }
145        }
146        return false;
147    }
148
149    private class MobileDataStateReceiver extends BroadcastReceiver {
150        ConnectivityManager mConnectivityManager;
151        public void onReceive(Context context, Intent intent) {
152            synchronized(this) {
153                if (intent.getAction().equals(TelephonyIntents.
154                        ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
155                    Phone.DataState state = getMobileDataState(intent);
156                    String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
157                    String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
158                    String apnTypeList = intent.getStringExtra(Phone.DATA_APN_TYPES_KEY);
159                    mApnName = apnName;
160
161                    boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,
162                            false);
163
164                    // set this regardless of the apnTypeList.  It's all the same radio/network
165                    // underneath
166                    mNetworkInfo.setIsAvailable(!unavailable);
167
168                    if (isApnTypeIncluded(apnTypeList)) {
169                        if (mEnabled == false) {
170                            // if we're not enabled but the APN Type is supported by this connection
171                            // we should record the interface name if one's provided.  If the user
172                            // turns on this network we will need the interfacename but won't get
173                            // a fresh connected message - TODO fix this when we get per-APN
174                            // notifications
175                            if (state == Phone.DataState.CONNECTED) {
176                                if (DBG) Log.d(TAG, "replacing old mInterfaceName (" +
177                                        mInterfaceName + ") with " +
178                                        intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY) +
179                                        " for " + mApnType);
180                                mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY);
181                            }
182                            return;
183                        }
184                    } else {
185                        return;
186                    }
187
188                    if (DBG) Log.d(TAG, mApnType + " Received state= " + state + ", old= " +
189                            mMobileDataState + ", reason= " +
190                            (reason == null ? "(unspecified)" : reason) +
191                            ", apnTypeList= " + apnTypeList);
192
193                    if (mMobileDataState != state) {
194                        mMobileDataState = state;
195                        switch (state) {
196                            case DISCONNECTED:
197                                if(isTeardownRequested()) {
198                                    mEnabled = false;
199                                    setTeardownRequested(false);
200                                }
201
202                                setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
203                                boolean doReset = true;
204                                if (mIsDefaultOrHipri == true) {
205                                    // both default and hipri must go down before we reset
206                                    int typeToCheck = (Phone.APN_TYPE_DEFAULT.equals(mApnType) ?
207                                            ConnectivityManager.TYPE_MOBILE_HIPRI :
208                                            ConnectivityManager.TYPE_MOBILE);
209                                    if (mConnectivityManager == null) {
210                                        mConnectivityManager =
211                                                (ConnectivityManager)context.getSystemService(
212                                                Context.CONNECTIVITY_SERVICE);
213                                    }
214                                    if (mConnectivityManager != null) {
215                                        NetworkInfo info = mConnectivityManager.getNetworkInfo(
216                                                    typeToCheck);
217                                        if (info != null && info.isConnected() == true) {
218                                            doReset = false;
219                                        }
220                                    }
221                                }
222                                if (doReset && mInterfaceName != null) {
223                                    NetworkUtils.resetConnections(mInterfaceName);
224                                }
225                                // can't do this here - ConnectivityService needs it to clear stuff
226                                // it's ok though - just leave it to be refreshed next time
227                                // we connect.
228                                //if (DBG) Log.d(TAG, "clearing mInterfaceName for "+ mApnType +
229                                //        " as it DISCONNECTED");
230                                //mInterfaceName = null;
231                                //mDefaultGatewayAddr = 0;
232                                break;
233                            case CONNECTING:
234                                setDetailedState(DetailedState.CONNECTING, reason, apnName);
235                                break;
236                            case SUSPENDED:
237                                setDetailedState(DetailedState.SUSPENDED, reason, apnName);
238                                break;
239                            case CONNECTED:
240                                mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY);
241                                if (mInterfaceName == null) {
242                                    Log.d(TAG, "CONNECTED event did not supply interface name.");
243                                }
244                                mDefaultGatewayAddr = intent.getIntExtra(Phone.DATA_GATEWAY_KEY, 0);
245                                setDetailedState(DetailedState.CONNECTED, reason, apnName);
246                                break;
247                        }
248                    }
249                } else if (intent.getAction().
250                        equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
251                    mEnabled = false;
252                    String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
253                    String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
254                    if (DBG) Log.d(TAG, "Received " + intent.getAction() + " broadcast" +
255                            reason == null ? "" : "(" + reason + ")");
256                    setDetailedState(DetailedState.FAILED, reason, apnName);
257                }
258                TelephonyManager tm = TelephonyManager.getDefault();
259                setRoamingStatus(tm.isNetworkRoaming());
260                setSubtype(tm.getNetworkType(), tm.getNetworkTypeName());
261            }
262        }
263    }
264
265    private void getPhoneService(boolean forceRefresh) {
266        if ((mPhoneService == null) || forceRefresh) {
267            mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
268        }
269    }
270
271    /**
272     * Report whether data connectivity is possible.
273     */
274    public boolean isAvailable() {
275        getPhoneService(false);
276
277        /*
278         * If the phone process has crashed in the past, we'll get a
279         * RemoteException and need to re-reference the service.
280         */
281        for (int retry = 0; retry < 2; retry++) {
282            if (mPhoneService == null) break;
283
284            try {
285                return mPhoneService.isDataConnectivityPossible();
286            } catch (RemoteException e) {
287                // First-time failed, get the phone service again
288                if (retry == 0) getPhoneService(true);
289            }
290        }
291
292        return false;
293    }
294
295    /**
296     * {@inheritDoc}
297     * The mobile data network subtype indicates what generation network technology is in effect,
298     * e.g., GPRS, EDGE, UMTS, etc.
299     */
300    public int getNetworkSubtype() {
301        return TelephonyManager.getDefault().getNetworkType();
302    }
303
304    /**
305     * Return the system properties name associated with the tcp buffer sizes
306     * for this network.
307     */
308    public String getTcpBufferSizesPropName() {
309        String networkTypeStr = "unknown";
310        TelephonyManager tm = new TelephonyManager(mContext);
311        //TODO We have to edit the parameter for getNetworkType regarding CDMA
312        switch(tm.getNetworkType()) {
313        case TelephonyManager.NETWORK_TYPE_GPRS:
314            networkTypeStr = "gprs";
315            break;
316        case TelephonyManager.NETWORK_TYPE_EDGE:
317            networkTypeStr = "edge";
318            break;
319        case TelephonyManager.NETWORK_TYPE_UMTS:
320            networkTypeStr = "umts";
321            break;
322        case TelephonyManager.NETWORK_TYPE_HSDPA:
323            networkTypeStr = "hsdpa";
324            break;
325        case TelephonyManager.NETWORK_TYPE_HSUPA:
326            networkTypeStr = "hsupa";
327            break;
328        case TelephonyManager.NETWORK_TYPE_HSPA:
329            networkTypeStr = "hspa";
330            break;
331        case TelephonyManager.NETWORK_TYPE_CDMA:
332            networkTypeStr = "cdma";
333            break;
334        case TelephonyManager.NETWORK_TYPE_1xRTT:
335            networkTypeStr = "1xrtt";
336            break;
337        case TelephonyManager.NETWORK_TYPE_EVDO_0:
338            networkTypeStr = "evdo";
339            break;
340        case TelephonyManager.NETWORK_TYPE_EVDO_A:
341            networkTypeStr = "evdo";
342            break;
343        case TelephonyManager.NETWORK_TYPE_EVDO_B:
344            networkTypeStr = "evdo";
345            break;
346        }
347        return "net.tcp.buffersize." + networkTypeStr;
348    }
349
350    /**
351     * Tear down mobile data connectivity, i.e., disable the ability to create
352     * mobile data connections.
353     */
354    @Override
355    public boolean teardown() {
356        // since we won't get a notification currently (TODO - per APN notifications)
357        // we won't get a disconnect message until all APN's on the current connection's
358        // APN list are disabled.  That means privateRoutes for DNS and such will remain on -
359        // not a problem since that's all shared with whatever other APN is still on, but
360        // ugly.
361        setTeardownRequested(true);
362        return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
363    }
364
365    /**
366     * Re-enable mobile data connectivity after a {@link #teardown()}.
367     */
368    public boolean reconnect() {
369        setTeardownRequested(false);
370        switch (setEnableApn(mApnType, true)) {
371            case Phone.APN_ALREADY_ACTIVE:
372                // TODO - remove this when we get per-apn notifications
373                mEnabled = true;
374                // need to set self to CONNECTING so the below message is handled.
375                mMobileDataState = Phone.DataState.CONNECTING;
376                setDetailedState(DetailedState.CONNECTING, Phone.REASON_APN_CHANGED, null);
377                //send out a connected message
378                Intent intent = new Intent(TelephonyIntents.
379                        ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
380                intent.putExtra(Phone.STATE_KEY, Phone.DataState.CONNECTED.toString());
381                intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, Phone.REASON_APN_CHANGED);
382                intent.putExtra(Phone.DATA_APN_TYPES_KEY, mApnTypeToWatchFor);
383                intent.putExtra(Phone.DATA_APN_KEY, mApnName);
384                intent.putExtra(Phone.DATA_IFACE_NAME_KEY, mInterfaceName);
385                intent.putExtra(Phone.NETWORK_UNAVAILABLE_KEY, false);
386                if (mStateReceiver != null) mStateReceiver.onReceive(mContext, intent);
387                break;
388            case Phone.APN_REQUEST_STARTED:
389                mEnabled = true;
390                // no need to do anything - we're already due some status update intents
391                break;
392            case Phone.APN_REQUEST_FAILED:
393                if (mPhoneService == null && mApnType == Phone.APN_TYPE_DEFAULT) {
394                    // on startup we may try to talk to the phone before it's ready
395                    // since the phone will come up enabled, go with that.
396                    // TODO - this also comes up on telephony crash: if we think mobile data is
397                    // off and the telephony stuff crashes and has to restart it will come up
398                    // enabled (making a data connection).  We will then be out of sync.
399                    // A possible solution is a broadcast when telephony restarts.
400                    mEnabled = true;
401                    return false;
402                }
403                // else fall through
404            case Phone.APN_TYPE_NOT_AVAILABLE:
405                // Default is always available, but may be off due to
406                // AirplaneMode or E-Call or whatever..
407                if (mApnType != Phone.APN_TYPE_DEFAULT) {
408                    mEnabled = false;
409                }
410                break;
411            default:
412                Log.e(TAG, "Error in reconnect - unexpected response.");
413                mEnabled = false;
414                break;
415        }
416        return mEnabled;
417    }
418
419    /**
420     * Turn on or off the mobile radio. No connectivity will be possible while the
421     * radio is off. The operation is a no-op if the radio is already in the desired state.
422     * @param turnOn {@code true} if the radio should be turned on, {@code false} if
423     */
424    public boolean setRadio(boolean turnOn) {
425        getPhoneService(false);
426        /*
427         * If the phone process has crashed in the past, we'll get a
428         * RemoteException and need to re-reference the service.
429         */
430        for (int retry = 0; retry < 2; retry++) {
431            if (mPhoneService == null) {
432                Log.w(TAG,
433                    "Ignoring mobile radio request because could not acquire PhoneService");
434                break;
435            }
436
437            try {
438                return mPhoneService.setRadio(turnOn);
439            } catch (RemoteException e) {
440                if (retry == 0) getPhoneService(true);
441            }
442        }
443
444        Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off"));
445        return false;
446    }
447
448    /**
449     * Tells the phone sub-system that the caller wants to
450     * begin using the named feature. The only supported features at
451     * this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
452     * to specify that it wants to send and/or receive MMS data, and
453     * {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS.
454     * @param feature the name of the feature to be used
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.
459     * specific, except that the value {@code -1}
460     * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS},
461     * the other possible return values are
462     * <ul>
463     * <li>{@code Phone.APN_ALREADY_ACTIVE}</li>
464     * <li>{@code Phone.APN_REQUEST_STARTED}</li>
465     * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li>
466     * <li>{@code Phone.APN_REQUEST_FAILED}</li>
467     * </ul>
468     */
469    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
470        return -1;
471    }
472
473    /**
474     * Tells the phone sub-system that the caller is finished
475     * using the named feature. The only supported feature at
476     * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
477     * to specify that it wants to send and/or receive MMS data.
478     * @param feature the name of the feature that is no longer needed
479     * @param callingPid the process ID of the process that is issuing this request
480     * @param callingUid the user ID of the process that is issuing this request
481     * @return an integer value representing the outcome of the request.
482     * The interpretation of this value is feature-specific, except that
483     * the value {@code -1} always indicates failure.
484     */
485    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
486        return -1;
487    }
488
489    /**
490     * Ensure that a network route exists to deliver traffic to the specified
491     * host via the mobile data network.
492     * @param hostAddress the IP address of the host to which the route is desired,
493     * @return {@code true} on success, {@code false} on failure
494     */
495    @Override
496    public boolean requestRouteToHost(InetAddress hostAddress) {
497        if (DBG) {
498            Log.d(TAG, "Requested host route to " + hostAddress.getHostAddress() +
499                    " for " + mApnType + "(" + mInterfaceName + ")");
500        }
501        if (mInterfaceName != null) {
502            return NetworkUtils.addHostRoute(mInterfaceName, hostAddress, null);
503        } else {
504            return false;
505        }
506    }
507
508    @Override
509    public String toString() {
510        StringBuffer sb = new StringBuffer("Mobile data state: ");
511
512        sb.append(mMobileDataState);
513        return sb.toString();
514    }
515
516   /**
517     * Internal method supporting the ENABLE_MMS feature.
518     * @param apnType the type of APN to be enabled or disabled (e.g., mms)
519     * @param enable {@code true} to enable the specified APN type,
520     * {@code false} to disable it.
521     * @return an integer value representing the outcome of the request.
522     */
523    private int setEnableApn(String apnType, boolean enable) {
524        getPhoneService(false);
525        /*
526         * If the phone process has crashed in the past, we'll get a
527         * RemoteException and need to re-reference the service.
528         */
529        for (int retry = 0; retry < 2; retry++) {
530            if (mPhoneService == null) {
531                Log.w(TAG,
532                    "Ignoring feature request because could not acquire PhoneService");
533                break;
534            }
535
536            try {
537                if (enable) {
538                    return mPhoneService.enableApnType(apnType);
539                } else {
540                    return mPhoneService.disableApnType(apnType);
541                }
542            } catch (RemoteException e) {
543                if (retry == 0) getPhoneService(true);
544            }
545        }
546
547        Log.w(TAG, "Could not " + (enable ? "enable" : "disable")
548                + " APN type \"" + apnType + "\"");
549        return Phone.APN_REQUEST_FAILED;
550    }
551
552    public static String networkTypeToApnType(int netType) {
553        switch(netType) {
554            case ConnectivityManager.TYPE_MOBILE:
555                return Phone.APN_TYPE_DEFAULT;  // TODO - use just one of these
556            case ConnectivityManager.TYPE_MOBILE_MMS:
557                return Phone.APN_TYPE_MMS;
558            case ConnectivityManager.TYPE_MOBILE_SUPL:
559                return Phone.APN_TYPE_SUPL;
560            case ConnectivityManager.TYPE_MOBILE_DUN:
561                return Phone.APN_TYPE_DUN;
562            case ConnectivityManager.TYPE_MOBILE_HIPRI:
563                return Phone.APN_TYPE_HIPRI;
564            default:
565                Log.e(TAG, "Error mapping networkType " + netType + " to apnType.");
566                return null;
567        }
568    }
569}
570