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