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