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