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