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