MobileDataStateTracker.java revision 767a662ecde33c3979bf02b793d392aca0403162
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 feature at
358     * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
359     * to specify that it wants to send and/or receive MMS data.
360     * @param feature the name of the feature to be used
361     * @param callingPid the process ID of the process that is issuing this request
362     * @param callingUid the user ID of the process that is issuing this request
363     * @return an integer value representing the outcome of the request.
364     * The interpretation of this value is feature-specific.
365     * specific, except that the value {@code -1}
366     * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS},
367     * the other possible return values are
368     * <ul>
369     * <li>{@code Phone.APN_ALREADY_ACTIVE}</li>
370     * <li>{@code Phone.APN_REQUEST_STARTED}</li>
371     * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li>
372     * <li>{@code Phone.APN_REQUEST_FAILED}</li>
373     * </ul>
374     */
375    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
376        if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
377            mLastCallingPid = callingPid;
378            return setEnableApn(Phone.APN_TYPE_MMS, true);
379        } else {
380            return -1;
381        }
382    }
383
384    /**
385     * Tells the phone sub-system that the caller is finished
386     * using the named feature. The only supported feature at
387     * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
388     * to specify that it wants to send and/or receive MMS data.
389     * @param feature the name of the feature that is no longer needed
390     * @param callingPid the process ID of the process that is issuing this request
391     * @param callingUid the user ID of the process that is issuing this request
392     * @return an integer value representing the outcome of the request.
393     * The interpretation of this value is feature-specific, except that
394     * the value {@code -1} always indicates failure.
395     */
396    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
397        if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
398            return setEnableApn(Phone.APN_TYPE_MMS, false);
399        } else {
400            return -1;
401        }
402    }
403
404    /**
405     * Ensure that a network route exists to deliver traffic to the specified
406     * host via the mobile data network.
407     * @param hostAddress the IP address of the host to which the route is desired,
408     * in network byte order.
409     * @return {@code true} on success, {@code false} on failure
410     */
411    @Override
412    public boolean requestRouteToHost(int hostAddress) {
413        if (mInterfaceName != null && hostAddress != -1) {
414            if (DBG) {
415                Log.d(TAG, "Requested host route to " + Integer.toHexString(hostAddress));
416            }
417            return NetworkUtils.addHostRoute(mInterfaceName, hostAddress) == 0;
418        } else {
419            return false;
420        }
421    }
422
423    @Override
424    public String toString() {
425        StringBuffer sb = new StringBuffer("Mobile data state: ");
426
427        sb.append(mMobileDataState);
428        return sb.toString();
429    }
430
431    private void setupDnsProperties() {
432        mDnsServers.clear();
433        // Set up per-process DNS server list on behalf of the MMS process
434        int i = 1;
435        if (mInterfaceName != null) {
436            for (String propName : sDnsPropNames) {
437                if (propName.indexOf(mInterfaceName) != -1) {
438                    String propVal = SystemProperties.get(propName);
439                    if (propVal != null && propVal.length() != 0 && !propVal.equals("0.0.0.0")) {
440                        mDnsServers.add(propVal);
441                        if (mLastCallingPid != -1) {
442                            SystemProperties.set("net.dns"  + i + "." + mLastCallingPid, propVal);
443                        }
444                        ++i;
445                    }
446                }
447            }
448        }
449        if (i == 1) {
450            Log.d(TAG, "DNS server addresses are not known.");
451        } else if (mLastCallingPid != -1) {
452            /*
453            * Bump the property that tells the name resolver library
454            * to reread the DNS server list from the properties.
455            */
456            String propVal = SystemProperties.get("net.dnschange");
457            if (propVal.length() != 0) {
458                try {
459                    int n = Integer.parseInt(propVal);
460                    SystemProperties.set("net.dnschange", "" + (n+1));
461                } catch (NumberFormatException e) {
462                }
463            }
464        }
465        mLastCallingPid = -1;
466    }
467
468   /**
469     * Internal method supporting the ENABLE_MMS feature.
470     * @param apnType the type of APN to be enabled or disabled (e.g., mms)
471     * @param enable {@code true} to enable the specified APN type,
472     * {@code false} to disable it.
473     * @return an integer value representing the outcome of the request.
474     */
475    private int setEnableApn(String apnType, boolean enable) {
476        getPhoneService(false);
477        /*
478         * If the phone process has crashed in the past, we'll get a
479         * RemoteException and need to re-reference the service.
480         */
481        for (int retry = 0; retry < 2; retry++) {
482            if (mPhoneService == null) {
483                Log.w(TAG,
484                    "Ignoring feature request because could not acquire PhoneService");
485                break;
486            }
487
488            try {
489                if (enable) {
490                    return mPhoneService.enableApnType(apnType);
491                } else {
492                    return mPhoneService.disableApnType(apnType);
493                }
494            } catch (RemoteException e) {
495                if (retry == 0) getPhoneService(true);
496            }
497        }
498
499        Log.w(TAG, "Could not " + (enable ? "enable" : "disable")
500                + " APN type \"" + apnType + "\"");
501        return Phone.APN_REQUEST_FAILED;
502    }
503}
504