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