MobileDataStateTracker.java revision d649c12815bcf944b2c97371f3f60716a42a1557
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.Message;
26import android.os.ServiceManager;
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.net.NetworkInfo;
32import android.telephony.TelephonyManager;
33import android.util.Log;
34import android.text.TextUtils;
35
36/**
37 * Track the state of mobile data connectivity. This is done by
38 * receiving broadcast intents from the Phone process whenever
39 * the state of data connectivity changes.
40 *
41 * {@hide}
42 */
43public class MobileDataStateTracker implements NetworkStateTracker {
44
45    private static final String TAG = "MobileDataStateTracker";
46    private static final boolean DBG = true;
47
48    private Phone.DataState mMobileDataState;
49    private ITelephony mPhoneService;
50
51    private String mApnType;
52    private BroadcastReceiver mStateReceiver;
53    private static String[] sDnsPropNames;
54    private NetworkInfo mNetworkInfo;
55    private boolean mTeardownRequested = false;
56    private Handler mTarget;
57    private Context mContext;
58    private String mInterfaceName;
59    private boolean mPrivateDnsRouteSet = false;
60    private int mDefaultGatewayAddr = 0;
61    private boolean mDefaultRouteSet = false;
62
63    /**
64     * Create a new MobileDataStateTracker
65     * @param context the application context of the caller
66     * @param target a message handler for getting callbacks about state changes
67     * @param netType the ConnectivityManager network type
68     * @param apnType the Phone apnType
69     * @param tag the name of this network
70     */
71    public MobileDataStateTracker(Context context, Handler target, int netType, String tag) {
72        mTarget = target;
73        mContext = context;
74        mNetworkInfo = new NetworkInfo(netType,
75                TelephonyManager.getDefault().getNetworkType(), tag,
76                TelephonyManager.getDefault().getNetworkTypeName());
77        mApnType = networkTypeToApnType(netType);
78
79        mPhoneService = null;
80
81        sDnsPropNames = new String[] {
82                "net.rmnet0.dns1",
83                "net.rmnet0.dns2",
84                "net.eth0.dns1",
85                "net.eth0.dns2",
86                "net.eth0.dns3",
87                "net.eth0.dns4",
88                "net.gprs.dns1",
89                "net.gprs.dns2",
90                "net.ppp0.dns1",
91                "net.ppp0.dns2"};
92
93    }
94
95    /**
96     * Return the IP addresses of the DNS servers available for the mobile data
97     * network interface.
98     * @return a list of DNS addresses, with no holes.
99     */
100    public String[] getDnsPropNames() {
101        return sDnsPropNames;
102    }
103
104    /**
105     * Return the name of our network interface.
106     * @return the name of our interface.
107     */
108    public String getInterfaceName() {
109        return mInterfaceName;
110    }
111
112    public boolean isPrivateDnsRouteSet() {
113        return mPrivateDnsRouteSet;
114    }
115
116    public void privateDnsRouteSet(boolean enabled) {
117        mPrivateDnsRouteSet = enabled;
118    }
119
120    public NetworkInfo getNetworkInfo() {
121        return mNetworkInfo;
122    }
123
124    public int getDefaultGatewayAddr() {
125        return mDefaultGatewayAddr;
126    }
127
128    public boolean isDefaultRouteSet() {
129        return mDefaultRouteSet;
130    }
131
132    public void defaultRouteSet(boolean enabled) {
133        mDefaultRouteSet = enabled;
134    }
135
136    /**
137     * This is not implemented.
138     */
139    public void releaseWakeLock() {
140    }
141
142    /**
143     * Begin monitoring mobile data connectivity.
144     */
145    public void startMonitoring() {
146        IntentFilter filter =
147                new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
148        filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
149        filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
150
151        mStateReceiver = new MobileDataStateReceiver();
152        mContext.registerReceiver(mStateReceiver, filter);
153        mMobileDataState = Phone.DataState.DISCONNECTED;
154    }
155
156    /**
157     * Record the roaming status of the device, and if it is a change from the previous
158     * status, send a notification to any listeners.
159     * @param isRoaming {@code true} if the device is now roaming, {@code false}
160     * if it is no longer roaming.
161     */
162    private void setRoamingStatus(boolean isRoaming) {
163        if (isRoaming != mNetworkInfo.isRoaming()) {
164            mNetworkInfo.setRoaming(isRoaming);
165            Message msg = mTarget.obtainMessage(EVENT_ROAMING_CHANGED, mNetworkInfo);
166            msg.sendToTarget();
167        }
168    }
169
170    private void setSubtype(int subtype, String subtypeName) {
171        if (mNetworkInfo.isConnected()) {
172            int oldSubtype = mNetworkInfo.getSubtype();
173            if (subtype != oldSubtype) {
174                mNetworkInfo.setSubtype(subtype, subtypeName);
175                Message msg = mTarget.obtainMessage(
176                        EVENT_NETWORK_SUBTYPE_CHANGED, oldSubtype, 0, mNetworkInfo);
177                msg.sendToTarget();
178            }
179        }
180    }
181
182    private class MobileDataStateReceiver extends BroadcastReceiver {
183        public void onReceive(Context context, Intent intent) {
184            synchronized(this) {
185                if (intent.getAction().equals(TelephonyIntents.
186                        ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
187                    String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
188
189                    if (!TextUtils.equals(apnType, mApnType)) {
190                        return;
191                    }
192                    Phone.DataState state = Enum.valueOf(Phone.DataState.class,
193                            intent.getStringExtra(Phone.STATE_KEY));
194                    String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
195                    String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
196
197                    boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,
198                            false);
199                    mNetworkInfo.setIsAvailable(!unavailable);
200
201                    if (DBG) Log.d(TAG, mApnType + " Received state= " + state + ", old= " +
202                            mMobileDataState + ", reason= " +
203                            (reason == null ? "(unspecified)" : reason));
204
205                    if (mMobileDataState != state) {
206                        mMobileDataState = state;
207                        switch (state) {
208                            case DISCONNECTED:
209                                if(isTeardownRequested()) {
210                                    setTeardownRequested(false);
211                                }
212
213                                setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
214                                if (mInterfaceName != null) {
215                                    NetworkUtils.resetConnections(mInterfaceName);
216                                }
217                                // can't do this here - ConnectivityService needs it to clear stuff
218                                // it's ok though - just leave it to be refreshed next time
219                                // we connect.
220                                //if (DBG) Log.d(TAG, "clearing mInterfaceName for "+ mApnType +
221                                //        " as it DISCONNECTED");
222                                //mInterfaceName = null;
223                                //mDefaultGatewayAddr = 0;
224                                break;
225                            case CONNECTING:
226                                setDetailedState(DetailedState.CONNECTING, reason, apnName);
227                                break;
228                            case SUSPENDED:
229                                setDetailedState(DetailedState.SUSPENDED, reason, apnName);
230                                break;
231                            case CONNECTED:
232                                mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY);
233                                if (mInterfaceName == null) {
234                                    Log.d(TAG, "CONNECTED event did not supply interface name.");
235                                }
236                                setDetailedState(DetailedState.CONNECTED, reason, apnName);
237                                break;
238                        }
239                    }
240                } else if (intent.getAction().
241                        equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
242                    String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
243                    if (!TextUtils.equals(apnType, mApnType)) {
244                        return;
245                    }
246                    String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
247                    String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
248                    if (DBG) Log.d(TAG, mApnType + "Received " + intent.getAction() +
249                            " broadcast" + reason == null ? "" : "(" + reason + ")");
250                    setDetailedState(DetailedState.FAILED, reason, apnName);
251                }
252                TelephonyManager tm = TelephonyManager.getDefault();
253                setRoamingStatus(tm.isNetworkRoaming());
254                setSubtype(tm.getNetworkType(), tm.getNetworkTypeName());
255            }
256        }
257    }
258
259    private void getPhoneService(boolean forceRefresh) {
260        if ((mPhoneService == null) || forceRefresh) {
261            mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
262        }
263    }
264
265    /**
266     * Report whether data connectivity is possible.
267     */
268    public boolean isAvailable() {
269        getPhoneService(false);
270
271        /*
272         * If the phone process has crashed in the past, we'll get a
273         * RemoteException and need to re-reference the service.
274         */
275        for (int retry = 0; retry < 2; retry++) {
276            if (mPhoneService == null) break;
277
278            try {
279                return mPhoneService.isDataConnectivityPossible();
280            } catch (RemoteException e) {
281                // First-time failed, get the phone service again
282                if (retry == 0) getPhoneService(true);
283            }
284        }
285
286        return false;
287    }
288
289    /**
290     * {@inheritDoc}
291     * The mobile data network subtype indicates what generation network technology is in effect,
292     * e.g., GPRS, EDGE, UMTS, etc.
293     */
294    public int getNetworkSubtype() {
295        return TelephonyManager.getDefault().getNetworkType();
296    }
297
298    /**
299     * Return the system properties name associated with the tcp buffer sizes
300     * for this network.
301     */
302    public String getTcpBufferSizesPropName() {
303        String networkTypeStr = "unknown";
304        TelephonyManager tm = new TelephonyManager(mContext);
305        //TODO We have to edit the parameter for getNetworkType regarding CDMA
306        switch(tm.getNetworkType()) {
307        case TelephonyManager.NETWORK_TYPE_GPRS:
308            networkTypeStr = "gprs";
309            break;
310        case TelephonyManager.NETWORK_TYPE_EDGE:
311            networkTypeStr = "edge";
312            break;
313        case TelephonyManager.NETWORK_TYPE_UMTS:
314            networkTypeStr = "umts";
315            break;
316        case TelephonyManager.NETWORK_TYPE_HSDPA:
317            networkTypeStr = "hsdpa";
318            break;
319        case TelephonyManager.NETWORK_TYPE_HSUPA:
320            networkTypeStr = "hsupa";
321            break;
322        case TelephonyManager.NETWORK_TYPE_HSPA:
323            networkTypeStr = "hspa";
324            break;
325        case TelephonyManager.NETWORK_TYPE_CDMA:
326            networkTypeStr = "cdma";
327            break;
328        case TelephonyManager.NETWORK_TYPE_1xRTT:
329            networkTypeStr = "1xrtt";
330            break;
331        case TelephonyManager.NETWORK_TYPE_EVDO_0:
332            networkTypeStr = "evdo";
333            break;
334        case TelephonyManager.NETWORK_TYPE_EVDO_A:
335            networkTypeStr = "evdo";
336            break;
337        case TelephonyManager.NETWORK_TYPE_EVDO_B:
338            networkTypeStr = "evdo";
339            break;
340        }
341        return "net.tcp.buffersize." + networkTypeStr;
342    }
343
344    /**
345     * Tear down mobile data connectivity, i.e., disable the ability to create
346     * mobile data connections.
347     * TODO - make async and return nothing?
348     */
349    public boolean teardown() {
350        setTeardownRequested(true);
351        return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
352    }
353
354    /**
355     * Record the detailed state of a network, and if it is a
356     * change from the previous state, send a notification to
357     * any listeners.
358     * @param state the new @{code DetailedState}
359     */
360    private void setDetailedState(NetworkInfo.DetailedState state) {
361        setDetailedState(state, null, null);
362    }
363
364    /**
365     * Record the detailed state of a network, and if it is a
366     * change from the previous state, send a notification to
367     * any listeners.
368     * @param state the new @{code DetailedState}
369     * @param reason a {@code String} indicating a reason for the state change,
370     * if one was supplied. May be {@code null}.
371     * @param extraInfo optional {@code String} providing extra information about the state change
372     */
373    private void setDetailedState(NetworkInfo.DetailedState state, String reason, String extraInfo) {
374        if (DBG) Log.d(TAG, "setDetailed state, old ="
375                + mNetworkInfo.getDetailedState() + " and new state=" + state);
376        if (state != mNetworkInfo.getDetailedState()) {
377            boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
378            String lastReason = mNetworkInfo.getReason();
379            /*
380             * If a reason was supplied when the CONNECTING state was entered, and no
381             * reason was supplied for entering the CONNECTED state, then retain the
382             * reason that was supplied when going to CONNECTING.
383             */
384            if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
385                    && lastReason != null)
386                reason = lastReason;
387            mNetworkInfo.setDetailedState(state, reason, extraInfo);
388            Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
389            msg.sendToTarget();
390        }
391    }
392
393    private void setDetailedStateInternal(NetworkInfo.DetailedState state) {
394        mNetworkInfo.setDetailedState(state, null, null);
395    }
396
397    public void setTeardownRequested(boolean isRequested) {
398        mTeardownRequested = isRequested;
399    }
400
401    public boolean isTeardownRequested() {
402        return mTeardownRequested;
403    }
404
405    /**
406     * Re-enable mobile data connectivity after a {@link #teardown()}.
407     * TODO - make async and always get a notification?
408     */
409    public boolean reconnect() {
410        boolean retValue = false; //connected or expect to be?
411        setTeardownRequested(false);
412        switch (setEnableApn(mApnType, true)) {
413            case Phone.APN_ALREADY_ACTIVE:
414                // need to set self to CONNECTING so the below message is handled.
415                retValue = true;
416                break;
417            case Phone.APN_REQUEST_STARTED:
418                // no need to do anything - we're already due some status update intents
419                retValue = true;
420                break;
421            case Phone.APN_REQUEST_FAILED:
422            case Phone.APN_TYPE_NOT_AVAILABLE:
423                break;
424            default:
425                Log.e(TAG, "Error in reconnect - unexpected response.");
426                break;
427        }
428        return retValue;
429    }
430
431    /**
432     * Turn on or off the mobile radio. No connectivity will be possible while the
433     * radio is off. The operation is a no-op if the radio is already in the desired state.
434     * @param turnOn {@code true} if the radio should be turned on, {@code false} if
435     */
436    public boolean setRadio(boolean turnOn) {
437        getPhoneService(false);
438        /*
439         * If the phone process has crashed in the past, we'll get a
440         * RemoteException and need to re-reference the service.
441         */
442        for (int retry = 0; retry < 2; retry++) {
443            if (mPhoneService == null) {
444                Log.w(TAG,
445                    "Ignoring mobile radio request because could not acquire PhoneService");
446                break;
447            }
448
449            try {
450                return mPhoneService.setRadio(turnOn);
451            } catch (RemoteException e) {
452                if (retry == 0) getPhoneService(true);
453            }
454        }
455
456        Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off"));
457        return false;
458    }
459
460    /**
461     * Tells the phone sub-system that the caller wants to
462     * begin using the named feature. The only supported features at
463     * this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
464     * to specify that it wants to send and/or receive MMS data, and
465     * {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS.
466     * @param feature the name of the feature to be used
467     * @param callingPid the process ID of the process that is issuing this request
468     * @param callingUid the user ID of the process that is issuing this request
469     * @return an integer value representing the outcome of the request.
470     * The interpretation of this value is feature-specific.
471     * specific, except that the value {@code -1}
472     * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS},
473     * the other possible return values are
474     * <ul>
475     * <li>{@code Phone.APN_ALREADY_ACTIVE}</li>
476     * <li>{@code Phone.APN_REQUEST_STARTED}</li>
477     * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li>
478     * <li>{@code Phone.APN_REQUEST_FAILED}</li>
479     * </ul>
480     */
481    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
482        return -1;
483    }
484
485    /**
486     * Tells the phone sub-system that the caller is finished
487     * using the named feature. The only supported feature at
488     * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
489     * to specify that it wants to send and/or receive MMS data.
490     * @param feature the name of the feature that is no longer needed
491     * @param callingPid the process ID of the process that is issuing this request
492     * @param callingUid the user ID of the process that is issuing this request
493     * @return an integer value representing the outcome of the request.
494     * The interpretation of this value is feature-specific, except that
495     * the value {@code -1} always indicates failure.
496     */
497    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
498        return -1;
499    }
500
501    /**
502     * This is not supported.
503     */
504    public void interpretScanResultsAvailable() {
505    }
506
507    @Override
508    public String toString() {
509        StringBuffer sb = new StringBuffer("Mobile data state: ");
510
511        sb.append(mMobileDataState);
512        return sb.toString();
513    }
514
515   /**
516     * Internal method supporting the ENABLE_MMS feature.
517     * @param apnType the type of APN to be enabled or disabled (e.g., mms)
518     * @param enable {@code true} to enable the specified APN type,
519     * {@code false} to disable it.
520     * @return an integer value representing the outcome of the request.
521     */
522    private int setEnableApn(String apnType, boolean enable) {
523        getPhoneService(false);
524        /*
525         * If the phone process has crashed in the past, we'll get a
526         * RemoteException and need to re-reference the service.
527         */
528        for (int retry = 0; retry < 2; retry++) {
529            if (mPhoneService == null) {
530                Log.w(TAG,
531                    "Ignoring feature request because could not acquire PhoneService");
532                break;
533            }
534
535            try {
536                if (enable) {
537                    return mPhoneService.enableApnType(apnType);
538                } else {
539                    return mPhoneService.disableApnType(apnType);
540                }
541            } catch (RemoteException e) {
542                if (retry == 0) getPhoneService(true);
543            }
544        }
545
546        Log.w(TAG, "Could not " + (enable ? "enable" : "disable")
547                + " APN type \"" + apnType + "\"");
548        return Phone.APN_REQUEST_FAILED;
549    }
550
551    public static String networkTypeToApnType(int netType) {
552        switch(netType) {
553            case ConnectivityManager.TYPE_MOBILE:
554                return Phone.APN_TYPE_DEFAULT;  // TODO - use just one of these
555            case ConnectivityManager.TYPE_MOBILE_MMS:
556                return Phone.APN_TYPE_MMS;
557            case ConnectivityManager.TYPE_MOBILE_SUPL:
558                return Phone.APN_TYPE_SUPL;
559            case ConnectivityManager.TYPE_MOBILE_DUN:
560                return Phone.APN_TYPE_DUN;
561            case ConnectivityManager.TYPE_MOBILE_HIPRI:
562                return Phone.APN_TYPE_HIPRI;
563            default:
564                Log.e(TAG, "Error mapping networkType " + netType + " to apnType.");
565                return null;
566        }
567    }
568}
569