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