MobileDataStateTracker.java revision 8e28b7d78232f6cf08739ca0d129cc7f9e650801
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
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        mHandler = new MdstHandler(target.getLooper(), this);
99
100        IntentFilter filter = new IntentFilter();
101        filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
102        filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
103        filter.addAction(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER);
104
105        mContext.registerReceiver(new MobileDataStateReceiver(), filter);
106        mMobileDataState = Phone.DataState.DISCONNECTED;
107    }
108
109    static class MdstHandler extends Handler {
110        private MobileDataStateTracker mMdst;
111
112        MdstHandler(Looper looper, MobileDataStateTracker mdst) {
113            super(looper);
114            mMdst = mdst;
115        }
116
117        @Override
118        public void handleMessage(Message msg) {
119            switch (msg.what) {
120                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
121                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
122                        if (VDBG) {
123                            mMdst.log("MdstHandler connected");
124                        }
125                        mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj;
126                    } else {
127                        if (VDBG) {
128                            mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1);
129                        }
130                    }
131                    break;
132                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
133                    if (VDBG) mMdst.log("Disconnected from DataStateTracker");
134                    mMdst.mDataConnectionTrackerAc = null;
135                    break;
136                default: {
137                    if (VDBG) mMdst.log("Ignorning unknown message=" + msg);
138                    break;
139                }
140            }
141        }
142    }
143
144    public boolean isPrivateDnsRouteSet() {
145        return mPrivateDnsRouteSet;
146    }
147
148    public void privateDnsRouteSet(boolean enabled) {
149        mPrivateDnsRouteSet = enabled;
150    }
151
152    public NetworkInfo getNetworkInfo() {
153        return mNetworkInfo;
154    }
155
156    public boolean isDefaultRouteSet() {
157        return mDefaultRouteSet;
158    }
159
160    public void defaultRouteSet(boolean enabled) {
161        mDefaultRouteSet = enabled;
162    }
163
164    /**
165     * This is not implemented.
166     */
167    public void releaseWakeLock() {
168    }
169
170    private class MobileDataStateReceiver extends BroadcastReceiver {
171        @Override
172        public void onReceive(Context context, Intent intent) {
173            if (intent.getAction().equals(TelephonyIntents.
174                    ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
175                String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
176                if (VDBG) {
177                    log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED"
178                        + "mApnType=%s %s received apnType=%s", mApnType,
179                        TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType));
180                }
181                if (!TextUtils.equals(apnType, mApnType)) {
182                    return;
183                }
184                mNetworkInfo.setSubtype(TelephonyManager.getDefault().getNetworkType(),
185                        TelephonyManager.getDefault().getNetworkTypeName());
186                Phone.DataState state = Enum.valueOf(Phone.DataState.class,
187                        intent.getStringExtra(Phone.STATE_KEY));
188                String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
189                String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
190
191                mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,
192                        false));
193
194                if (DBG) {
195                    log("Received state=" + state + ", old=" + mMobileDataState +
196                        ", reason=" + (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                            // can't do this here - ConnectivityService needs it to clear stuff
208                            // it's ok though - just leave it to be refreshed next time
209                            // we connect.
210                            //if (DBG) log("clearing mInterfaceName for "+ mApnType +
211                            //        " as it DISCONNECTED");
212                            //mInterfaceName = null;
213                            break;
214                        case CONNECTING:
215                            setDetailedState(DetailedState.CONNECTING, reason, apnName);
216                            break;
217                        case SUSPENDED:
218                            setDetailedState(DetailedState.SUSPENDED, reason, apnName);
219                            break;
220                        case CONNECTED:
221                            mLinkProperties = intent.getParcelableExtra(
222                                    Phone.DATA_LINK_PROPERTIES_KEY);
223                            if (mLinkProperties == null) {
224                                loge("CONNECTED event did not supply link properties.");
225                                mLinkProperties = new LinkProperties();
226                            }
227                            mLinkCapabilities = intent.getParcelableExtra(
228                                    Phone.DATA_LINK_CAPABILITIES_KEY);
229                            if (mLinkCapabilities == null) {
230                                loge("CONNECTED event did not supply link capabilities.");
231                                mLinkCapabilities = new LinkCapabilities();
232                            }
233                            setDetailedState(DetailedState.CONNECTED, reason, apnName);
234                            break;
235                    }
236                } else {
237                    // There was no state change. Check if LinkProperties has been updated.
238                    if (TextUtils.equals(reason, Phone.REASON_LINK_PROPERTIES_CHANGED)) {
239                        mLinkProperties = intent.getParcelableExtra(Phone.DATA_LINK_PROPERTIES_KEY);
240                        if (mLinkProperties == null) {
241                            loge("No link property in LINK_PROPERTIES change event.");
242                            mLinkProperties = new LinkProperties();
243                        }
244                        // Just update reason field in this NetworkInfo
245                        mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason,
246                                                      mNetworkInfo.getExtraInfo());
247                        Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED,
248                                                            mNetworkInfo);
249                        msg.sendToTarget();
250                    }
251                }
252            } else if (intent.getAction().
253                    equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
254                String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
255                if (!TextUtils.equals(apnType, mApnType)) {
256                    if (DBG) {
257                        log(String.format(
258                                "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " +
259                                "mApnType=%s != received apnType=%s", mApnType, apnType));
260                    }
261                    return;
262                }
263                String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
264                String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
265                if (DBG) {
266                    log("Received " + intent.getAction() +
267                                " broadcast" + reason == null ? "" : "(" + reason + ")");
268                }
269                setDetailedState(DetailedState.FAILED, reason, apnName);
270            } else if (intent.getAction().
271                    equals(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER)) {
272                if (VDBG) log(mApnType + " got ACTION_DATA_CONNECTION_TRACKER_MESSENGER");
273                mMessenger = intent.getParcelableExtra(DataConnectionTracker.EXTRA_MESSENGER);
274                AsyncChannel ac = new AsyncChannel();
275                ac.connect(mContext, MobileDataStateTracker.this.mHandler, mMessenger);
276            } else {
277                if (DBG) log("Broadcast received: ignore " + intent.getAction());
278            }
279        }
280    }
281
282    private void getPhoneService(boolean forceRefresh) {
283        if ((mPhoneService == null) || forceRefresh) {
284            mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
285        }
286    }
287
288    /**
289     * Report whether data connectivity is possible.
290     */
291    public boolean isAvailable() {
292        return mNetworkInfo.isAvailable();
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        case TelephonyManager.NETWORK_TYPE_IDEN:
338            networkTypeStr = "iden";
339            break;
340        case TelephonyManager.NETWORK_TYPE_LTE:
341            networkTypeStr = "lte";
342            break;
343        case TelephonyManager.NETWORK_TYPE_EHRPD:
344            networkTypeStr = "ehrpd";
345            break;
346        default:
347            loge("unknown network type: " + tm.getNetworkType());
348        }
349        return "net.tcp.buffersize." + networkTypeStr;
350    }
351
352    /**
353     * Tear down mobile data connectivity, i.e., disable the ability to create
354     * mobile data connections.
355     * TODO - make async and return nothing?
356     */
357    public boolean teardown() {
358        setTeardownRequested(true);
359        return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
360    }
361
362    /**
363     * Record the detailed state of a network, and if it is a
364     * change from the previous state, send a notification to
365     * any listeners.
366     * @param state the new @{code DetailedState}
367     * @param reason a {@code String} indicating a reason for the state change,
368     * if one was supplied. May be {@code null}.
369     * @param extraInfo optional {@code String} providing extra information about the state change
370     */
371    private void setDetailedState(NetworkInfo.DetailedState state, String reason,
372            String extraInfo) {
373        if (DBG) log("setDetailed state, old ="
374                + mNetworkInfo.getDetailedState() + " and new state=" + state);
375        if (state != mNetworkInfo.getDetailedState()) {
376            boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
377            String lastReason = mNetworkInfo.getReason();
378            /*
379             * If a reason was supplied when the CONNECTING state was entered, and no
380             * reason was supplied for entering the CONNECTED state, then retain the
381             * reason that was supplied when going to CONNECTING.
382             */
383            if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
384                    && lastReason != null)
385                reason = lastReason;
386            mNetworkInfo.setDetailedState(state, reason, extraInfo);
387            Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
388            msg.sendToTarget();
389        }
390    }
391
392    public void setTeardownRequested(boolean isRequested) {
393        mTeardownRequested = isRequested;
394    }
395
396    public boolean isTeardownRequested() {
397        return mTeardownRequested;
398    }
399
400    /**
401     * Re-enable mobile data connectivity after a {@link #teardown()}.
402     * TODO - make async and always get a notification?
403     */
404    public boolean reconnect() {
405        boolean retValue = false; //connected or expect to be?
406        setTeardownRequested(false);
407        switch (setEnableApn(mApnType, true)) {
408            case Phone.APN_ALREADY_ACTIVE:
409                // need to set self to CONNECTING so the below message is handled.
410                retValue = true;
411                break;
412            case Phone.APN_REQUEST_STARTED:
413                // set IDLE here , avoid the following second FAILED not sent out
414                mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
415                retValue = true;
416                break;
417            case Phone.APN_REQUEST_FAILED:
418            case Phone.APN_TYPE_NOT_AVAILABLE:
419                break;
420            default:
421                loge("Error in reconnect - unexpected response.");
422                break;
423        }
424        return retValue;
425    }
426
427    /**
428     * Turn on or off the mobile radio. No connectivity will be possible while the
429     * radio is off. The operation is a no-op if the radio is already in the desired state.
430     * @param turnOn {@code true} if the radio should be turned on, {@code false} if
431     */
432    public boolean setRadio(boolean turnOn) {
433        getPhoneService(false);
434        /*
435         * If the phone process has crashed in the past, we'll get a
436         * RemoteException and need to re-reference the service.
437         */
438        for (int retry = 0; retry < 2; retry++) {
439            if (mPhoneService == null) {
440                loge("Ignoring mobile radio request because could not acquire PhoneService");
441                break;
442            }
443
444            try {
445                return mPhoneService.setRadio(turnOn);
446            } catch (RemoteException e) {
447                if (retry == 0) getPhoneService(true);
448            }
449        }
450
451        loge("Could not set radio power to " + (turnOn ? "on" : "off"));
452        return false;
453    }
454
455    @Override
456    public void setUserDataEnable(boolean enabled) {
457        if (DBG) log("setUserDataEnable: E enabled=" + enabled);
458        final AsyncChannel channel = mDataConnectionTrackerAc;
459        if (channel != null) {
460            channel.sendMessage(CMD_SET_USER_DATA_ENABLE, enabled ? ENABLED : DISABLED);
461        }
462        if (VDBG) log("setUserDataEnable: X enabled=" + enabled);
463    }
464
465    @Override
466    public void setPolicyDataEnable(boolean enabled) {
467        if (DBG) log("setPolicyDataEnable(enabled=" + enabled + ")");
468        final AsyncChannel channel = mDataConnectionTrackerAc;
469        if (channel != null) {
470            channel.sendMessage(CMD_SET_POLICY_DATA_ENABLE, enabled ? ENABLED : DISABLED);
471        }
472    }
473
474    /**
475     * carrier dependency is met/unmet
476     * @param met
477     */
478    public void setDependencyMet(boolean met) {
479        Bundle bundle = Bundle.forPair(DataConnectionTracker.APN_TYPE_KEY, mApnType);
480        try {
481            if (DBG) log("setDependencyMet: E met=" + met);
482            Message msg = Message.obtain();
483            msg.what = DataConnectionTracker.CMD_SET_DEPENDENCY_MET;
484            msg.arg1 = (met ? DataConnectionTracker.ENABLED : DataConnectionTracker.DISABLED);
485            msg.setData(bundle);
486            mDataConnectionTrackerAc.sendMessage(msg);
487            if (VDBG) log("setDependencyMet: X met=" + met);
488        } catch (NullPointerException e) {
489            loge("setDependencyMet: X mAc was null" + e);
490        }
491    }
492
493    @Override
494    public String toString() {
495        StringBuffer sb = new StringBuffer("Mobile data state: ");
496
497        sb.append(mMobileDataState);
498        return sb.toString();
499    }
500
501   /**
502     * Internal method supporting the ENABLE_MMS feature.
503     * @param apnType the type of APN to be enabled or disabled (e.g., mms)
504     * @param enable {@code true} to enable the specified APN type,
505     * {@code false} to disable it.
506     * @return an integer value representing the outcome of the request.
507     */
508    private int setEnableApn(String apnType, boolean enable) {
509        getPhoneService(false);
510        /*
511         * If the phone process has crashed in the past, we'll get a
512         * RemoteException and need to re-reference the service.
513         */
514        for (int retry = 0; retry < 2; retry++) {
515            if (mPhoneService == null) {
516                loge("Ignoring feature request because could not acquire PhoneService");
517                break;
518            }
519
520            try {
521                if (enable) {
522                    return mPhoneService.enableApnType(apnType);
523                } else {
524                    return mPhoneService.disableApnType(apnType);
525                }
526            } catch (RemoteException e) {
527                if (retry == 0) getPhoneService(true);
528            }
529        }
530
531        loge("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\"");
532        return Phone.APN_REQUEST_FAILED;
533    }
534
535    public static String networkTypeToApnType(int netType) {
536        switch(netType) {
537            case ConnectivityManager.TYPE_MOBILE:
538                return Phone.APN_TYPE_DEFAULT;  // TODO - use just one of these
539            case ConnectivityManager.TYPE_MOBILE_MMS:
540                return Phone.APN_TYPE_MMS;
541            case ConnectivityManager.TYPE_MOBILE_SUPL:
542                return Phone.APN_TYPE_SUPL;
543            case ConnectivityManager.TYPE_MOBILE_DUN:
544                return Phone.APN_TYPE_DUN;
545            case ConnectivityManager.TYPE_MOBILE_HIPRI:
546                return Phone.APN_TYPE_HIPRI;
547            case ConnectivityManager.TYPE_MOBILE_FOTA:
548                return Phone.APN_TYPE_FOTA;
549            case ConnectivityManager.TYPE_MOBILE_IMS:
550                return Phone.APN_TYPE_IMS;
551            case ConnectivityManager.TYPE_MOBILE_CBS:
552                return Phone.APN_TYPE_CBS;
553            default:
554                sloge("Error mapping networkType " + netType + " to apnType.");
555                return null;
556        }
557    }
558
559    /**
560     * @see android.net.NetworkStateTracker#getLinkProperties()
561     */
562    public LinkProperties getLinkProperties() {
563        return new LinkProperties(mLinkProperties);
564    }
565
566    /**
567     * @see android.net.NetworkStateTracker#getLinkCapabilities()
568     */
569    public LinkCapabilities getLinkCapabilities() {
570        return new LinkCapabilities(mLinkCapabilities);
571    }
572
573    private void log(String s) {
574        Slog.d(TAG, mApnType + ": " + s);
575    }
576
577    private void loge(String s) {
578        Slog.e(TAG, mApnType + ": " + s);
579    }
580
581    static private void sloge(String s) {
582        Slog.e(TAG, s);
583    }
584}
585