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