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