MobileDataStateTracker.java revision d14e17627266ff8cf804d1f8241a0a4bde7a0ab4
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.net.NetworkInfo.DetailedState;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.Looper;
27import android.os.Message;
28import android.os.Messenger;
29import android.os.RemoteException;
30import android.os.ServiceManager;
31import android.telephony.TelephonyManager;
32import android.text.TextUtils;
33import android.util.Slog;
34
35import com.android.internal.telephony.DctConstants;
36import com.android.internal.telephony.ITelephony;
37import com.android.internal.telephony.PhoneConstants;
38import com.android.internal.telephony.TelephonyIntents;
39import com.android.internal.util.AsyncChannel;
40
41import java.io.CharArrayWriter;
42import java.io.PrintWriter;
43
44/**
45 * Track the state of mobile data connectivity. This is done by
46 * receiving broadcast intents from the Phone process whenever
47 * the state of data connectivity changes.
48 *
49 * {@hide}
50 */
51public class MobileDataStateTracker implements NetworkStateTracker {
52
53    private static final String TAG = "MobileDataStateTracker";
54    private static final boolean DBG = false;
55    private static final boolean VDBG = false;
56
57    private PhoneConstants.DataState mMobileDataState;
58    private ITelephony mPhoneService;
59
60    private String mApnType;
61    private NetworkInfo mNetworkInfo;
62    private boolean mTeardownRequested = false;
63    private Handler mTarget;
64    private Context mContext;
65    private LinkProperties mLinkProperties;
66    private LinkCapabilities mLinkCapabilities;
67    private boolean mPrivateDnsRouteSet = false;
68    private boolean mDefaultRouteSet = false;
69
70    // NOTE: these are only kept for debugging output; actual values are
71    // maintained in DataConnectionTracker.
72    protected boolean mUserDataEnabled = true;
73    protected boolean mPolicyDataEnabled = true;
74
75    private Handler mHandler;
76    private AsyncChannel mDataConnectionTrackerAc;
77    private Messenger mMessenger;
78
79    /**
80     * Create a new MobileDataStateTracker
81     * @param netType the ConnectivityManager network type
82     * @param tag the name of this network
83     */
84    public MobileDataStateTracker(int netType, String tag) {
85        mNetworkInfo = new NetworkInfo(netType,
86                TelephonyManager.getDefault().getNetworkType(), tag,
87                TelephonyManager.getDefault().getNetworkTypeName());
88        mApnType = networkTypeToApnType(netType);
89    }
90
91    /**
92     * Begin monitoring data connectivity.
93     *
94     * @param context is the current Android context
95     * @param target is the Hander to which to return the events.
96     */
97    public void startMonitoring(Context context, Handler target) {
98        mTarget = target;
99        mContext = context;
100
101        mHandler = new MdstHandler(target.getLooper(), this);
102
103        IntentFilter filter = new IntentFilter();
104        filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
105        filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
106        filter.addAction(DctConstants.ACTION_DATA_CONNECTION_TRACKER_MESSENGER);
107
108        mContext.registerReceiver(new MobileDataStateReceiver(), filter);
109        mMobileDataState = PhoneConstants.DataState.DISCONNECTED;
110    }
111
112    static class MdstHandler extends Handler {
113        private MobileDataStateTracker mMdst;
114
115        MdstHandler(Looper looper, MobileDataStateTracker mdst) {
116            super(looper);
117            mMdst = mdst;
118        }
119
120        @Override
121        public void handleMessage(Message msg) {
122            switch (msg.what) {
123                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
124                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
125                        if (VDBG) {
126                            mMdst.log("MdstHandler connected");
127                        }
128                        mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj;
129                    } else {
130                        if (VDBG) {
131                            mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1);
132                        }
133                    }
134                    break;
135                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
136                    if (VDBG) mMdst.log("Disconnected from DataStateTracker");
137                    mMdst.mDataConnectionTrackerAc = null;
138                    break;
139                default: {
140                    if (VDBG) mMdst.log("Ignorning unknown message=" + msg);
141                    break;
142                }
143            }
144        }
145    }
146
147    public boolean isPrivateDnsRouteSet() {
148        return mPrivateDnsRouteSet;
149    }
150
151    public void privateDnsRouteSet(boolean enabled) {
152        mPrivateDnsRouteSet = enabled;
153    }
154
155    public NetworkInfo getNetworkInfo() {
156        return mNetworkInfo;
157    }
158
159    public boolean isDefaultRouteSet() {
160        return mDefaultRouteSet;
161    }
162
163    public void defaultRouteSet(boolean enabled) {
164        mDefaultRouteSet = enabled;
165    }
166
167    /**
168     * This is not implemented.
169     */
170    public void releaseWakeLock() {
171    }
172
173    private class MobileDataStateReceiver extends BroadcastReceiver {
174        @Override
175        public void onReceive(Context context, Intent intent) {
176            if (intent.getAction().equals(TelephonyIntents.
177                    ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
178                String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
179                if (VDBG) {
180                    log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED"
181                        + "mApnType=%s %s received apnType=%s", mApnType,
182                        TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType));
183                }
184                if (!TextUtils.equals(apnType, mApnType)) {
185                    return;
186                }
187
188                int oldSubtype = mNetworkInfo.getSubtype();
189                int newSubType = TelephonyManager.getDefault().getNetworkType();
190                String subTypeName = TelephonyManager.getDefault().getNetworkTypeName();
191                mNetworkInfo.setSubtype(newSubType, subTypeName);
192                if (newSubType != oldSubtype && mNetworkInfo.isConnected()) {
193                    Message msg = mTarget.obtainMessage(EVENT_NETWORK_SUBTYPE_CHANGED,
194                                                        oldSubtype, 0, mNetworkInfo);
195                    msg.sendToTarget();
196                }
197
198                PhoneConstants.DataState state = Enum.valueOf(PhoneConstants.DataState.class,
199                        intent.getStringExtra(PhoneConstants.STATE_KEY));
200                String reason = intent.getStringExtra(PhoneConstants.STATE_CHANGE_REASON_KEY);
201                String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
202                mNetworkInfo.setRoaming(intent.getBooleanExtra(
203                        PhoneConstants.DATA_NETWORK_ROAMING_KEY, false));
204                if (VDBG) {
205                    log(mApnType + " setting isAvailable to " +
206                            intent.getBooleanExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY,false));
207                }
208                mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(
209                        PhoneConstants.NETWORK_UNAVAILABLE_KEY, false));
210
211                if (DBG) {
212                    log("Received state=" + state + ", old=" + mMobileDataState +
213                        ", reason=" + (reason == null ? "(unspecified)" : reason));
214                }
215                if (mMobileDataState != state) {
216                    mMobileDataState = state;
217                    switch (state) {
218                        case DISCONNECTED:
219                            if(isTeardownRequested()) {
220                                setTeardownRequested(false);
221                            }
222
223                            setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
224                            // can't do this here - ConnectivityService needs it to clear stuff
225                            // it's ok though - just leave it to be refreshed next time
226                            // we connect.
227                            //if (DBG) log("clearing mInterfaceName for "+ mApnType +
228                            //        " as it DISCONNECTED");
229                            //mInterfaceName = null;
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                                    PhoneConstants.DATA_LINK_PROPERTIES_KEY);
240                            if (mLinkProperties == null) {
241                                loge("CONNECTED event did not supply link properties.");
242                                mLinkProperties = new LinkProperties();
243                            }
244                            mLinkCapabilities = intent.getParcelableExtra(
245                                    PhoneConstants.DATA_LINK_CAPABILITIES_KEY);
246                            if (mLinkCapabilities == null) {
247                                loge("CONNECTED event did not supply link capabilities.");
248                                mLinkCapabilities = new LinkCapabilities();
249                            }
250                            setDetailedState(DetailedState.CONNECTED, reason, apnName);
251                            break;
252                    }
253                } else {
254                    // There was no state change. Check if LinkProperties has been updated.
255                    if (TextUtils.equals(reason, PhoneConstants.REASON_LINK_PROPERTIES_CHANGED)) {
256                        mLinkProperties = intent.getParcelableExtra(
257                                PhoneConstants.DATA_LINK_PROPERTIES_KEY);
258                        if (mLinkProperties == null) {
259                            loge("No link property in LINK_PROPERTIES change event.");
260                            mLinkProperties = new LinkProperties();
261                        }
262                        // Just update reason field in this NetworkInfo
263                        mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason,
264                                                      mNetworkInfo.getExtraInfo());
265                        Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED,
266                                                            mNetworkInfo);
267                        msg.sendToTarget();
268                    }
269                }
270            } else if (intent.getAction().
271                    equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
272                String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
273                if (!TextUtils.equals(apnType, mApnType)) {
274                    if (DBG) {
275                        log(String.format(
276                                "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " +
277                                "mApnType=%s != received apnType=%s", mApnType, apnType));
278                    }
279                    return;
280                }
281                String reason = intent.getStringExtra(PhoneConstants.FAILURE_REASON_KEY);
282                String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
283                if (DBG) {
284                    log("Received " + intent.getAction() +
285                                " broadcast" + reason == null ? "" : "(" + reason + ")");
286                }
287                setDetailedState(DetailedState.FAILED, reason, apnName);
288            } else if (intent.getAction().equals(DctConstants
289                    .ACTION_DATA_CONNECTION_TRACKER_MESSENGER)) {
290                if (VDBG) log(mApnType + " got ACTION_DATA_CONNECTION_TRACKER_MESSENGER");
291                mMessenger =
292                    intent.getParcelableExtra(DctConstants.EXTRA_MESSENGER);
293                AsyncChannel ac = new AsyncChannel();
294                ac.connect(mContext, MobileDataStateTracker.this.mHandler, mMessenger);
295            } else {
296                if (DBG) log("Broadcast received: ignore " + intent.getAction());
297            }
298        }
299    }
300
301    private void getPhoneService(boolean forceRefresh) {
302        if ((mPhoneService == null) || forceRefresh) {
303            mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
304        }
305    }
306
307    /**
308     * Report whether data connectivity is possible.
309     */
310    public boolean isAvailable() {
311        return mNetworkInfo.isAvailable();
312    }
313
314    /**
315     * Return the system properties name associated with the tcp buffer sizes
316     * for this network.
317     */
318    public String getTcpBufferSizesPropName() {
319        String networkTypeStr = "unknown";
320        TelephonyManager tm = new TelephonyManager(mContext);
321        //TODO We have to edit the parameter for getNetworkType regarding CDMA
322        switch(tm.getNetworkType()) {
323        case TelephonyManager.NETWORK_TYPE_GPRS:
324            networkTypeStr = "gprs";
325            break;
326        case TelephonyManager.NETWORK_TYPE_EDGE:
327            networkTypeStr = "edge";
328            break;
329        case TelephonyManager.NETWORK_TYPE_UMTS:
330            networkTypeStr = "umts";
331            break;
332        case TelephonyManager.NETWORK_TYPE_HSDPA:
333            networkTypeStr = "hsdpa";
334            break;
335        case TelephonyManager.NETWORK_TYPE_HSUPA:
336            networkTypeStr = "hsupa";
337            break;
338        case TelephonyManager.NETWORK_TYPE_HSPA:
339            networkTypeStr = "hspa";
340            break;
341        case TelephonyManager.NETWORK_TYPE_HSPAP:
342            networkTypeStr = "hspap";
343            break;
344        case TelephonyManager.NETWORK_TYPE_CDMA:
345            networkTypeStr = "cdma";
346            break;
347        case TelephonyManager.NETWORK_TYPE_1xRTT:
348            networkTypeStr = "1xrtt";
349            break;
350        case TelephonyManager.NETWORK_TYPE_EVDO_0:
351            networkTypeStr = "evdo";
352            break;
353        case TelephonyManager.NETWORK_TYPE_EVDO_A:
354            networkTypeStr = "evdo";
355            break;
356        case TelephonyManager.NETWORK_TYPE_EVDO_B:
357            networkTypeStr = "evdo";
358            break;
359        case TelephonyManager.NETWORK_TYPE_IDEN:
360            networkTypeStr = "iden";
361            break;
362        case TelephonyManager.NETWORK_TYPE_LTE:
363            networkTypeStr = "lte";
364            break;
365        case TelephonyManager.NETWORK_TYPE_EHRPD:
366            networkTypeStr = "ehrpd";
367            break;
368        default:
369            loge("unknown network type: " + tm.getNetworkType());
370        }
371        return "net.tcp.buffersize." + networkTypeStr;
372    }
373
374    /**
375     * Tear down mobile data connectivity, i.e., disable the ability to create
376     * mobile data connections.
377     * TODO - make async and return nothing?
378     */
379    public boolean teardown() {
380        setTeardownRequested(true);
381        return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
382    }
383
384    /**
385     * Record the detailed state of a network, and if it is a
386     * change from the previous state, send a notification to
387     * any listeners.
388     * @param state the new {@code DetailedState}
389     * @param reason a {@code String} indicating a reason for the state change,
390     * if one was supplied. May be {@code null}.
391     * @param extraInfo optional {@code String} providing extra information about the state change
392     */
393    private void setDetailedState(NetworkInfo.DetailedState state, String reason,
394            String extraInfo) {
395        if (DBG) log("setDetailed state, old ="
396                + mNetworkInfo.getDetailedState() + " and new state=" + state);
397        if (state != mNetworkInfo.getDetailedState()) {
398            boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
399            String lastReason = mNetworkInfo.getReason();
400            /*
401             * If a reason was supplied when the CONNECTING state was entered, and no
402             * reason was supplied for entering the CONNECTED state, then retain the
403             * reason that was supplied when going to CONNECTING.
404             */
405            if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
406                    && lastReason != null)
407                reason = lastReason;
408            mNetworkInfo.setDetailedState(state, reason, extraInfo);
409            Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, new NetworkInfo(mNetworkInfo));
410            msg.sendToTarget();
411        }
412    }
413
414    public void setTeardownRequested(boolean isRequested) {
415        mTeardownRequested = isRequested;
416    }
417
418    public boolean isTeardownRequested() {
419        return mTeardownRequested;
420    }
421
422    /**
423     * Re-enable mobile data connectivity after a {@link #teardown()}.
424     * TODO - make async and always get a notification?
425     */
426    public boolean reconnect() {
427        boolean retValue = false; //connected or expect to be?
428        setTeardownRequested(false);
429        switch (setEnableApn(mApnType, true)) {
430            case PhoneConstants.APN_ALREADY_ACTIVE:
431                // need to set self to CONNECTING so the below message is handled.
432                retValue = true;
433                break;
434            case PhoneConstants.APN_REQUEST_STARTED:
435                // set IDLE here , avoid the following second FAILED not sent out
436                mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
437                retValue = true;
438                break;
439            case PhoneConstants.APN_REQUEST_FAILED:
440            case PhoneConstants.APN_TYPE_NOT_AVAILABLE:
441                break;
442            default:
443                loge("Error in reconnect - unexpected response.");
444                break;
445        }
446        return retValue;
447    }
448
449    /**
450     * Turn on or off the mobile radio. No connectivity will be possible while the
451     * radio is off. The operation is a no-op if the radio is already in the desired state.
452     * @param turnOn {@code true} if the radio should be turned on, {@code false} if
453     */
454    public boolean setRadio(boolean turnOn) {
455        getPhoneService(false);
456        /*
457         * If the phone process has crashed in the past, we'll get a
458         * RemoteException and need to re-reference the service.
459         */
460        for (int retry = 0; retry < 2; retry++) {
461            if (mPhoneService == null) {
462                loge("Ignoring mobile radio request because could not acquire PhoneService");
463                break;
464            }
465
466            try {
467                return mPhoneService.setRadio(turnOn);
468            } catch (RemoteException e) {
469                if (retry == 0) getPhoneService(true);
470            }
471        }
472
473        loge("Could not set radio power to " + (turnOn ? "on" : "off"));
474        return false;
475    }
476
477    @Override
478    public void setUserDataEnable(boolean enabled) {
479        if (DBG) log("setUserDataEnable: E enabled=" + enabled);
480        final AsyncChannel channel = mDataConnectionTrackerAc;
481        if (channel != null) {
482            channel.sendMessage(DctConstants.CMD_SET_USER_DATA_ENABLE,
483                    enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
484            mUserDataEnabled = enabled;
485        }
486        if (VDBG) log("setUserDataEnable: X enabled=" + enabled);
487    }
488
489    @Override
490    public void setPolicyDataEnable(boolean enabled) {
491        if (DBG) log("setPolicyDataEnable(enabled=" + enabled + ")");
492        final AsyncChannel channel = mDataConnectionTrackerAc;
493        if (channel != null) {
494            channel.sendMessage(DctConstants.CMD_SET_POLICY_DATA_ENABLE,
495                    enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
496            mPolicyDataEnabled = enabled;
497        }
498    }
499
500    /**
501     * carrier dependency is met/unmet
502     * @param met
503     */
504    public void setDependencyMet(boolean met) {
505        Bundle bundle = Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType);
506        try {
507            if (DBG) log("setDependencyMet: E met=" + met);
508            Message msg = Message.obtain();
509            msg.what = DctConstants.CMD_SET_DEPENDENCY_MET;
510            msg.arg1 = (met ? DctConstants.ENABLED : DctConstants.DISABLED);
511            msg.setData(bundle);
512            mDataConnectionTrackerAc.sendMessage(msg);
513            if (VDBG) log("setDependencyMet: X met=" + met);
514        } catch (NullPointerException e) {
515            loge("setDependencyMet: X mAc was null" + e);
516        }
517    }
518
519    @Override
520    public String toString() {
521        final CharArrayWriter writer = new CharArrayWriter();
522        final PrintWriter pw = new PrintWriter(writer);
523        pw.print("Mobile data state: "); pw.println(mMobileDataState);
524        pw.print("Data enabled: user="); pw.print(mUserDataEnabled);
525        pw.print(", policy="); pw.println(mPolicyDataEnabled);
526        return writer.toString();
527    }
528
529   /**
530     * Internal method supporting the ENABLE_MMS feature.
531     * @param apnType the type of APN to be enabled or disabled (e.g., mms)
532     * @param enable {@code true} to enable the specified APN type,
533     * {@code false} to disable it.
534     * @return an integer value representing the outcome of the request.
535     */
536    private int setEnableApn(String apnType, boolean enable) {
537        getPhoneService(false);
538        /*
539         * If the phone process has crashed in the past, we'll get a
540         * RemoteException and need to re-reference the service.
541         */
542        for (int retry = 0; retry < 2; retry++) {
543            if (mPhoneService == null) {
544                loge("Ignoring feature request because could not acquire PhoneService");
545                break;
546            }
547
548            try {
549                if (enable) {
550                    return mPhoneService.enableApnType(apnType);
551                } else {
552                    return mPhoneService.disableApnType(apnType);
553                }
554            } catch (RemoteException e) {
555                if (retry == 0) getPhoneService(true);
556            }
557        }
558
559        loge("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\"");
560        return PhoneConstants.APN_REQUEST_FAILED;
561    }
562
563    public static String networkTypeToApnType(int netType) {
564        switch(netType) {
565            case ConnectivityManager.TYPE_MOBILE:
566                return PhoneConstants.APN_TYPE_DEFAULT;  // TODO - use just one of these
567            case ConnectivityManager.TYPE_MOBILE_MMS:
568                return PhoneConstants.APN_TYPE_MMS;
569            case ConnectivityManager.TYPE_MOBILE_SUPL:
570                return PhoneConstants.APN_TYPE_SUPL;
571            case ConnectivityManager.TYPE_MOBILE_DUN:
572                return PhoneConstants.APN_TYPE_DUN;
573            case ConnectivityManager.TYPE_MOBILE_HIPRI:
574                return PhoneConstants.APN_TYPE_HIPRI;
575            case ConnectivityManager.TYPE_MOBILE_FOTA:
576                return PhoneConstants.APN_TYPE_FOTA;
577            case ConnectivityManager.TYPE_MOBILE_IMS:
578                return PhoneConstants.APN_TYPE_IMS;
579            case ConnectivityManager.TYPE_MOBILE_CBS:
580                return PhoneConstants.APN_TYPE_CBS;
581            default:
582                sloge("Error mapping networkType " + netType + " to apnType.");
583                return null;
584        }
585    }
586
587    /**
588     * @see android.net.NetworkStateTracker#getLinkProperties()
589     */
590    public LinkProperties getLinkProperties() {
591        return new LinkProperties(mLinkProperties);
592    }
593
594    /**
595     * @see android.net.NetworkStateTracker#getLinkCapabilities()
596     */
597    public LinkCapabilities getLinkCapabilities() {
598        return new LinkCapabilities(mLinkCapabilities);
599    }
600
601    private void log(String s) {
602        Slog.d(TAG, mApnType + ": " + s);
603    }
604
605    private void loge(String s) {
606        Slog.e(TAG, mApnType + ": " + s);
607    }
608
609    static private void sloge(String s) {
610        Slog.e(TAG, s);
611    }
612}
613