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