MobileDataStateTracker.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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        switch(tm.getNetworkType()) {
247          case TelephonyManager.NETWORK_TYPE_GPRS:
248            networkTypeStr = "gprs";
249            break;
250          case TelephonyManager.NETWORK_TYPE_EDGE:
251            networkTypeStr = "edge";
252            break;
253          case TelephonyManager.NETWORK_TYPE_UMTS:
254            networkTypeStr = "umts";
255            break;
256        }
257        return "net.tcp.buffersize." + networkTypeStr;
258    }
259
260    /**
261     * Tear down mobile data connectivity, i.e., disable the ability to create
262     * mobile data connections.
263     */
264    @Override
265    public boolean teardown() {
266        getPhoneService(false);
267        /*
268         * If the phone process has crashed in the past, we'll get a
269         * RemoteException and need to re-reference the service.
270         */
271        for (int retry = 0; retry < 2; retry++) {
272            if (mPhoneService == null) {
273                Log.w(TAG,
274                    "Ignoring mobile data teardown request because could not acquire PhoneService");
275                break;
276            }
277
278            try {
279                return mPhoneService.disableDataConnectivity();
280            } catch (RemoteException e) {
281                if (retry == 0) getPhoneService(true);
282            }
283        }
284
285        Log.w(TAG, "Failed to tear down mobile data connectivity");
286        return false;
287    }
288
289    /**
290     * Re-enable mobile data connectivity after a {@link #teardown()}.
291     */
292    public boolean reconnect() {
293        getPhoneService(false);
294        /*
295         * If the phone process has crashed in the past, we'll get a
296         * RemoteException and need to re-reference the service.
297         */
298        for (int retry = 0; retry < 2; retry++) {
299            if (mPhoneService == null) {
300                Log.w(TAG,
301                    "Ignoring mobile data connect request because could not acquire PhoneService");
302                break;
303            }
304
305            try {
306                return mPhoneService.enableDataConnectivity();
307            } catch (RemoteException e) {
308                if (retry == 0) getPhoneService(true);
309            }
310        }
311
312        Log.w(TAG, "Failed to set up mobile data connectivity");
313        return false;
314    }
315
316    /**
317     * Turn on or off the mobile radio. No connectivity will be possible while the
318     * radio is off. The operation is a no-op if the radio is already in the desired state.
319     * @param turnOn {@code true} if the radio should be turned on, {@code false} if
320     */
321    public boolean setRadio(boolean turnOn) {
322        getPhoneService(false);
323        /*
324         * If the phone process has crashed in the past, we'll get a
325         * RemoteException and need to re-reference the service.
326         */
327        for (int retry = 0; retry < 2; retry++) {
328            if (mPhoneService == null) {
329                Log.w(TAG,
330                    "Ignoring mobile radio request because could not acquire PhoneService");
331                break;
332            }
333
334            try {
335                return mPhoneService.setRadio(turnOn);
336            } catch (RemoteException e) {
337                if (retry == 0) getPhoneService(true);
338            }
339        }
340
341        Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off"));
342        return false;
343    }
344
345    /**
346     * Tells the phone sub-system that the caller wants to
347     * begin using the named feature. The only supported feature at
348     * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
349     * to specify that it wants to send and/or receive MMS data.
350     * @param feature the name of the feature to be used
351     * @param callingPid the process ID of the process that is issuing this request
352     * @param callingUid the user ID of the process that is issuing this request
353     * @return an integer value representing the outcome of the request.
354     * The interpretation of this value is feature-specific.
355     * specific, except that the value {@code -1}
356     * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS},
357     * the other possible return values are
358     * <ul>
359     * <li>{@code Phone.APN_ALREADY_ACTIVE}</li>
360     * <li>{@code Phone.APN_REQUEST_STARTED}</li>
361     * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li>
362     * <li>{@code Phone.APN_REQUEST_FAILED}</li>
363     * </ul>
364     */
365    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
366        if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
367            mLastCallingPid = callingPid;
368            return setEnableApn(Phone.APN_TYPE_MMS, true);
369        } else {
370            return -1;
371        }
372    }
373
374    /**
375     * Tells the phone sub-system that the caller is finished
376     * using the named feature. The only supported feature at
377     * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
378     * to specify that it wants to send and/or receive MMS data.
379     * @param feature the name of the feature that is no longer needed
380     * @param callingPid the process ID of the process that is issuing this request
381     * @param callingUid the user ID of the process that is issuing this request
382     * @return an integer value representing the outcome of the request.
383     * The interpretation of this value is feature-specific, except that
384     * the value {@code -1} always indicates failure.
385     */
386    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
387        if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
388            return setEnableApn(Phone.APN_TYPE_MMS, false);
389        } else {
390            return -1;
391        }
392    }
393
394    /**
395     * Ensure that a network route exists to deliver traffic to the specified
396     * host via the mobile data network.
397     * @param hostAddress the IP address of the host to which the route is desired,
398     * in network byte order.
399     * @return {@code true} on success, {@code false} on failure
400     */
401    @Override
402    public boolean requestRouteToHost(int hostAddress) {
403        if (mInterfaceName != null && hostAddress != -1) {
404            if (DBG) {
405                Log.d(TAG, "Requested host route to " + Integer.toHexString(hostAddress));
406            }
407            return NetworkUtils.addHostRoute(mInterfaceName, hostAddress) == 0;
408        } else {
409            return false;
410        }
411    }
412
413    @Override
414    public String toString() {
415        StringBuffer sb = new StringBuffer("Mobile data state: ");
416
417        sb.append(mMobileDataState);
418        return sb.toString();
419    }
420
421    private void setupDnsProperties() {
422        mDnsServers.clear();
423        // Set up per-process DNS server list on behalf of the MMS process
424        int i = 1;
425        if (mInterfaceName != null) {
426            for (String propName : sDnsPropNames) {
427                if (propName.indexOf(mInterfaceName) != -1) {
428                    String propVal = SystemProperties.get(propName);
429                    if (propVal != null && propVal.length() != 0 && !propVal.equals("0.0.0.0")) {
430                        mDnsServers.add(propVal);
431                        if (mLastCallingPid != -1) {
432                            SystemProperties.set("net.dns"  + i + "." + mLastCallingPid, propVal);
433                        }
434                        ++i;
435                    }
436                }
437            }
438        }
439        if (i == 1) {
440            Log.d(TAG, "DNS server addresses are not known.");
441        } else if (mLastCallingPid != -1) {
442            /*
443            * Bump the property that tells the name resolver library
444            * to reread the DNS server list from the properties.
445            */
446            String propVal = SystemProperties.get("net.dnschange");
447            if (propVal.length() != 0) {
448                try {
449                    int n = Integer.parseInt(propVal);
450                    SystemProperties.set("net.dnschange", "" + (n+1));
451                } catch (NumberFormatException e) {
452                }
453            }
454        }
455        mLastCallingPid = -1;
456    }
457
458   /**
459     * Internal method supporting the ENABLE_MMS feature.
460     * @param apnType the type of APN to be enabled or disabled (e.g., mms)
461     * @param enable {@code true} to enable the specified APN type,
462     * {@code false} to disable it.
463     * @return an integer value representing the outcome of the request.
464     */
465    private int setEnableApn(String apnType, boolean enable) {
466        getPhoneService(false);
467        /*
468         * If the phone process has crashed in the past, we'll get a
469         * RemoteException and need to re-reference the service.
470         */
471        for (int retry = 0; retry < 2; retry++) {
472            if (mPhoneService == null) {
473                Log.w(TAG,
474                    "Ignoring feature request because could not acquire PhoneService");
475                break;
476            }
477
478            try {
479                if (enable) {
480                    return mPhoneService.enableApnType(apnType);
481                } else {
482                    return mPhoneService.disableApnType(apnType);
483                }
484            } catch (RemoteException e) {
485                if (retry == 0) getPhoneService(true);
486            }
487        }
488
489        Log.w(TAG, "Could not " + (enable ? "enable" : "disable")
490                + " APN type \"" + apnType + "\"");
491        return Phone.APN_REQUEST_FAILED;
492    }
493}
494