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