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