ConnectivityService.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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 com.android.server;
18
19import android.app.Notification;
20import android.app.NotificationManager;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.net.ConnectivityManager;
26import android.net.IConnectivityManager;
27import android.net.MobileDataStateTracker;
28import android.net.NetworkInfo;
29import android.net.NetworkStateTracker;
30import android.net.wifi.WifiStateTracker;
31import android.os.Binder;
32import android.os.Handler;
33import android.os.Looper;
34import android.os.Message;
35import android.os.ServiceManager;
36import android.os.SystemProperties;
37import android.provider.Settings;
38import android.util.EventLog;
39import android.util.Log;
40
41import java.io.FileDescriptor;
42import java.io.PrintWriter;
43
44/**
45 * @hide
46 */
47public class ConnectivityService extends IConnectivityManager.Stub {
48
49    private static final boolean DBG = false;
50    private static final String TAG = "ConnectivityService";
51
52    // Event log tags (must be in sync with event-log-tags)
53    private static final int EVENTLOG_CONNECTIVITY_STATE_CHANGED = 50020;
54
55    /**
56     * Sometimes we want to refer to the individual network state
57     * trackers separately, and sometimes we just want to treat them
58     * abstractly.
59     */
60    private NetworkStateTracker mNetTrackers[];
61    private WifiStateTracker mWifiStateTracker;
62    private MobileDataStateTracker mMobileDataStateTracker;
63    private WifiWatchdogService mWifiWatchdogService;
64
65    private Context mContext;
66    private int mNetworkPreference;
67    private NetworkStateTracker mActiveNetwork;
68
69    private int mNumDnsEntries;
70    private static int sDnsChangeCounter;
71
72    private boolean mTestMode;
73    private static ConnectivityService sServiceInstance;
74
75    private static class ConnectivityThread extends Thread {
76        private Context mContext;
77
78        private ConnectivityThread(Context context) {
79            super("ConnectivityThread");
80            mContext = context;
81        }
82
83        @Override
84        public void run() {
85            Looper.prepare();
86            synchronized (this) {
87                sServiceInstance = new ConnectivityService(mContext);
88                notifyAll();
89            }
90            Looper.loop();
91        }
92
93        public static ConnectivityService getServiceInstance(Context context) {
94            ConnectivityThread thread = new ConnectivityThread(context);
95            thread.start();
96
97            synchronized (thread) {
98                while (sServiceInstance == null) {
99                    try {
100                        // Wait until sServiceInstance has been initialized.
101                        thread.wait();
102                    } catch (InterruptedException ignore) {
103                        Log.e(TAG,
104                            "Unexpected InterruptedException while waiting for ConnectivityService thread");
105                    }
106                }
107            }
108
109            return sServiceInstance;
110        }
111    }
112
113    public static ConnectivityService getInstance(Context context) {
114        return ConnectivityThread.getServiceInstance(context);
115    }
116
117    private ConnectivityService(Context context) {
118        if (DBG) Log.v(TAG, "ConnectivityService starting up");
119        mContext = context;
120        mNetTrackers = new NetworkStateTracker[2];
121        Handler handler = new MyHandler();
122
123        mNetworkPreference = getPersistedNetworkPreference();
124
125        /*
126         * Create the network state trackers for Wi-Fi and mobile
127         * data. Maybe this could be done with a factory class,
128         * but it's not clear that it's worth it, given that
129         * the number of different network types is not going
130         * to change very often.
131         */
132        if (DBG) Log.v(TAG, "Starting Wifi Service.");
133        mWifiStateTracker = new WifiStateTracker(context, handler);
134        WifiService wifiService = new WifiService(context, mWifiStateTracker);
135        ServiceManager.addService(Context.WIFI_SERVICE, wifiService);
136        mNetTrackers[ConnectivityManager.TYPE_WIFI] = mWifiStateTracker;
137
138        mMobileDataStateTracker = new MobileDataStateTracker(context, handler);
139        mNetTrackers[ConnectivityManager.TYPE_MOBILE] = mMobileDataStateTracker;
140
141        mActiveNetwork = null;
142        mNumDnsEntries = 0;
143
144        mTestMode = SystemProperties.get("cm.test.mode").equals("true")
145                && SystemProperties.get("ro.build.type").equals("eng");
146
147        for (NetworkStateTracker t : mNetTrackers)
148            t.startMonitoring();
149
150        // Constructing this starts it too
151        mWifiWatchdogService = new WifiWatchdogService(context, mWifiStateTracker);
152    }
153
154    /**
155     * Sets the preferred network.
156     * @param preference the new preference
157     */
158    public synchronized void setNetworkPreference(int preference) {
159        enforceChangePermission();
160        if (ConnectivityManager.isNetworkTypeValid(preference)) {
161            if (mNetworkPreference != preference) {
162                persistNetworkPreference(preference);
163                mNetworkPreference = preference;
164                enforcePreference();
165            }
166        }
167    }
168
169    public int getNetworkPreference() {
170        enforceAccessPermission();
171        return mNetworkPreference;
172    }
173
174    private void persistNetworkPreference(int networkPreference) {
175        final ContentResolver cr = mContext.getContentResolver();
176        Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, networkPreference);
177    }
178
179    private int getPersistedNetworkPreference() {
180        final ContentResolver cr = mContext.getContentResolver();
181
182        final int networkPrefSetting = Settings.Secure
183                .getInt(cr, Settings.Secure.NETWORK_PREFERENCE, -1);
184        if (networkPrefSetting != -1) {
185            return networkPrefSetting;
186        }
187
188        return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE;
189    }
190
191    /**
192     * Make the state of network connectivity conform to the preference settings.
193     * In this method, we only tear down a non-preferred network. Establishing
194     * a connection to the preferred network is taken care of when we handle
195     * the disconnect event from the non-preferred network
196     * (see {@link #handleDisconnect(NetworkInfo)}).
197     */
198    private void enforcePreference() {
199        if (mActiveNetwork == null)
200            return;
201
202        for (NetworkStateTracker t : mNetTrackers) {
203            if (t == mActiveNetwork) {
204                int netType = t.getNetworkInfo().getType();
205                int otherNetType = ((netType == ConnectivityManager.TYPE_WIFI) ?
206                        ConnectivityManager.TYPE_MOBILE :
207                        ConnectivityManager.TYPE_WIFI);
208
209                if (t.getNetworkInfo().getType() != mNetworkPreference) {
210                    NetworkStateTracker otherTracker = mNetTrackers[otherNetType];
211                    if (otherTracker.isAvailable()) {
212                        teardown(t);
213                    }
214                }
215            }
216        }
217    }
218
219    private boolean teardown(NetworkStateTracker netTracker) {
220        if (netTracker.teardown()) {
221            netTracker.setTeardownRequested(true);
222            return true;
223        } else {
224            return false;
225        }
226    }
227
228    /**
229     * Return NetworkInfo for the active (i.e., connected) network interface.
230     * It is assumed that at most one network is active at a time. If more
231     * than one is active, it is indeterminate which will be returned.
232     * @return the info for the active network, or {@code null} if none is active
233     */
234    public NetworkInfo getActiveNetworkInfo() {
235        enforceAccessPermission();
236        for (NetworkStateTracker t : mNetTrackers) {
237            NetworkInfo info = t.getNetworkInfo();
238            if (info.isConnected()) {
239                return info;
240            }
241        }
242        return null;
243    }
244
245    public NetworkInfo getNetworkInfo(int networkType) {
246        enforceAccessPermission();
247        if (ConnectivityManager.isNetworkTypeValid(networkType)) {
248            NetworkStateTracker t = mNetTrackers[networkType];
249            if (t != null)
250                return t.getNetworkInfo();
251        }
252        return null;
253    }
254
255    public NetworkInfo[] getAllNetworkInfo() {
256        enforceAccessPermission();
257        NetworkInfo[] result = new NetworkInfo[mNetTrackers.length];
258        int i = 0;
259        for (NetworkStateTracker t : mNetTrackers) {
260            result[i++] = t.getNetworkInfo();
261        }
262        return result;
263    }
264
265    public boolean setRadios(boolean turnOn) {
266        boolean result = true;
267        enforceChangePermission();
268        for (NetworkStateTracker t : mNetTrackers) {
269            result = t.setRadio(turnOn) && result;
270        }
271        return result;
272    }
273
274    public boolean setRadio(int netType, boolean turnOn) {
275        enforceChangePermission();
276        if (!ConnectivityManager.isNetworkTypeValid(netType)) {
277            return false;
278        }
279        NetworkStateTracker tracker = mNetTrackers[netType];
280        return tracker != null && tracker.setRadio(turnOn);
281    }
282
283    public int startUsingNetworkFeature(int networkType, String feature) {
284        enforceChangePermission();
285        if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
286            return -1;
287        }
288        NetworkStateTracker tracker = mNetTrackers[networkType];
289        if (tracker != null) {
290            return tracker.startUsingNetworkFeature(feature, getCallingPid(), getCallingUid());
291        }
292        return -1;
293    }
294
295    public int stopUsingNetworkFeature(int networkType, String feature) {
296        enforceChangePermission();
297        if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
298            return -1;
299        }
300        NetworkStateTracker tracker = mNetTrackers[networkType];
301        if (tracker != null) {
302            return tracker.stopUsingNetworkFeature(feature, getCallingPid(), getCallingUid());
303        }
304        return -1;
305    }
306
307    /**
308     * Ensure that a network route exists to deliver traffic to the specified
309     * host via the specified network interface.
310     * @param networkType the type of the network over which traffic to the specified
311     * host is to be routed
312     * @param hostAddress the IP address of the host to which the route is desired
313     * @return {@code true} on success, {@code false} on failure
314     */
315    public boolean requestRouteToHost(int networkType, int hostAddress) {
316        enforceChangePermission();
317        if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
318            return false;
319        }
320        NetworkStateTracker tracker = mNetTrackers[networkType];
321        /*
322         * If there's only one connected network, and it's the one requested,
323         * then we don't have to do anything - the requested route already
324         * exists. If it's not the requested network, then it's not possible
325         * to establish the requested route. Finally, if there is more than
326         * one connected network, then we must insert an entry in the routing
327         * table.
328         */
329        if (getNumConnectedNetworks() > 1) {
330            return tracker.requestRouteToHost(hostAddress);
331        } else {
332            return tracker.getNetworkInfo().getType() == networkType;
333        }
334    }
335
336    private int getNumConnectedNetworks() {
337        int numConnectedNets = 0;
338
339        for (NetworkStateTracker nt : mNetTrackers) {
340            if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) {
341                ++numConnectedNets;
342            }
343        }
344        return numConnectedNets;
345    }
346
347    private void enforceAccessPermission() {
348        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE,
349                                          "ConnectivityService");
350    }
351
352    private void enforceChangePermission() {
353        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE,
354                                          "ConnectivityService");
355
356    }
357
358    /**
359     * Handle a {@code DISCONNECTED} event. If this pertains to the non-active network,
360     * we ignore it. If it is for the active network, we send out a broadcast.
361     * But first, we check whether it might be possible to connect to a different
362     * network.
363     * @param info the {@code NetworkInfo} for the network
364     */
365    private void handleDisconnect(NetworkInfo info) {
366
367        if (DBG) Log.v(TAG, "Handle DISCONNECT for " + info.getTypeName());
368
369        mNetTrackers[info.getType()].setTeardownRequested(false);
370        /*
371         * If the disconnected network is not the active one, then don't report
372         * this as a loss of connectivity. What probably happened is that we're
373         * getting the disconnect for a network that we explicitly disabled
374         * in accordance with network preference policies.
375         */
376        if (mActiveNetwork == null ||  info.getType() != mActiveNetwork.getNetworkInfo().getType())
377            return;
378
379        NetworkStateTracker newNet;
380        if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
381            newNet = mWifiStateTracker;
382        } else /* info().getType() == TYPE_WIFI */ {
383            newNet = mMobileDataStateTracker;
384        }
385
386        /**
387         * See if the other network is available to fail over to.
388         * If is not available, we enable it anyway, so that it
389         * will be able to connect when it does become available,
390         * but we report a total loss of connectivity rather than
391         * report that we are attempting to fail over.
392         */
393        NetworkInfo switchTo = null;
394        if (newNet.isAvailable()) {
395            mActiveNetwork = newNet;
396            switchTo = newNet.getNetworkInfo();
397            switchTo.setFailover(true);
398            if (!switchTo.isConnectedOrConnecting()) {
399                newNet.reconnect();
400            }
401        } else {
402            newNet.reconnect();
403        }
404
405        boolean otherNetworkConnected = false;
406        Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
407        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
408        if (info.isFailover()) {
409            intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
410            info.setFailover(false);
411        }
412        if (info.getReason() != null) {
413            intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
414        }
415        if (info.getExtraInfo() != null) {
416            intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo());
417        }
418        if (switchTo != null) {
419            otherNetworkConnected = switchTo.isConnected();
420            if (DBG) {
421                if (otherNetworkConnected) {
422                    Log.v(TAG, "Switching to already connected " + switchTo.getTypeName());
423                } else {
424                    Log.v(TAG, "Attempting to switch to " + switchTo.getTypeName());
425                }
426            }
427            intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
428        } else {
429            intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
430        }
431        if (DBG) Log.v(TAG, "Sending DISCONNECT bcast for " + info.getTypeName() +
432                (switchTo == null ? "" : " other=" + switchTo.getTypeName()));
433
434        mContext.sendStickyBroadcast(intent);
435        /*
436         * If the failover network is already connected, then immediately send out
437         * a followup broadcast indicating successful failover
438         */
439        if (switchTo != null && otherNetworkConnected)
440            sendConnectedBroadcast(switchTo);
441    }
442
443    private void sendConnectedBroadcast(NetworkInfo info) {
444        Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
445        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
446        if (info.isFailover()) {
447            intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
448            info.setFailover(false);
449        }
450        if (info.getReason() != null) {
451            intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
452        }
453        if (info.getExtraInfo() != null) {
454            intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo());
455        }
456        mContext.sendStickyBroadcast(intent);
457    }
458
459    /**
460     * Called when an attempt to fail over to another network has failed.
461     * @param info the {@link NetworkInfo} for the failed network
462     */
463    private void handleConnectionFailure(NetworkInfo info) {
464        mNetTrackers[info.getType()].setTeardownRequested(false);
465        if (getActiveNetworkInfo() == null) {
466            String reason = info.getReason();
467            String extraInfo = info.getExtraInfo();
468
469            if (DBG) {
470                String reasonText;
471                if (reason == null) {
472                    reasonText = ".";
473                } else {
474                    reasonText = " (" + reason + ").";
475                }
476                Log.v(TAG, "Attempt to connect to " + info.getTypeName() + " failed" + reasonText);
477            }
478
479            Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
480            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
481            intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
482            if (reason != null) {
483                intent.putExtra(ConnectivityManager.EXTRA_REASON, reason);
484            }
485            if (extraInfo != null) {
486                intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo);
487            }
488            if (info.isFailover()) {
489                intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
490                info.setFailover(false);
491            }
492            mContext.sendStickyBroadcast(intent);
493        }
494    }
495
496    private void handleConnect(NetworkInfo info) {
497        if (DBG) Log.v(TAG, "Handle CONNECT for " + info.getTypeName());
498
499        // snapshot isFailover, because sendConnectedBroadcast() resets it
500        boolean isFailover = info.isFailover();
501        NetworkStateTracker thisNet = mNetTrackers[info.getType()];
502        NetworkStateTracker deadnet = null;
503        NetworkStateTracker otherNet;
504        if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
505            otherNet = mWifiStateTracker;
506        } else /* info().getType() == TYPE_WIFI */ {
507            otherNet = mMobileDataStateTracker;
508        }
509        /*
510         * Check policy to see whether we are connected to a non-preferred
511         * network that now needs to be torn down.
512         */
513        NetworkInfo wifiInfo = mWifiStateTracker.getNetworkInfo();
514        NetworkInfo mobileInfo = mMobileDataStateTracker.getNetworkInfo();
515        if (wifiInfo.isConnected() && mobileInfo.isConnected()) {
516            if (mNetworkPreference == ConnectivityManager.TYPE_WIFI)
517                deadnet = mMobileDataStateTracker;
518            else
519                deadnet = mWifiStateTracker;
520        }
521
522        boolean toredown = false;
523        thisNet.setTeardownRequested(false);
524        if (!mTestMode && deadnet != null) {
525            if (DBG) Log.v(TAG, "Policy requires " +
526                  deadnet.getNetworkInfo().getTypeName() + " teardown");
527            toredown = teardown(deadnet);
528            if (DBG && !toredown) {
529                Log.d(TAG, "Network declined teardown request");
530            }
531        }
532
533        /*
534         * Note that if toredown is true, deadnet cannot be null, so there is
535         * no danger of a null pointer exception here..
536         */
537        if (!toredown || deadnet.getNetworkInfo().getType() != info.getType()) {
538            mActiveNetwork = thisNet;
539            if (DBG) Log.v(TAG, "Sending CONNECT bcast for " + info.getTypeName());
540            thisNet.updateNetworkSettings();
541            sendConnectedBroadcast(info);
542            if (isFailover) {
543                otherNet.releaseWakeLock();
544            }
545        } else {
546            if (DBG) Log.v(TAG, "Not broadcasting CONNECT_ACTION to torn down network " +
547                info.getTypeName());
548        }
549    }
550
551    private void handleScanResultsAvailable(NetworkInfo info) {
552        int networkType = info.getType();
553        if (networkType != ConnectivityManager.TYPE_WIFI) {
554            if (DBG) Log.v(TAG, "Got ScanResultsAvailable for " + info.getTypeName() + " network."
555                + " Don't know how to handle.");
556        }
557
558        mNetTrackers[networkType].interpretScanResultsAvailable();
559    }
560
561    private void handleNotificationChange(boolean visible, int id, Notification notification) {
562        NotificationManager notificationManager = (NotificationManager) mContext
563                .getSystemService(Context.NOTIFICATION_SERVICE);
564
565        if (visible) {
566            notificationManager.notify(id, notification);
567        } else {
568            notificationManager.cancel(id);
569        }
570    }
571
572    /**
573     * After any kind of change in the connectivity state of any network,
574     * make sure that anything that depends on the connectivity state of
575     * more than one network is set up correctly. We're mainly concerned
576     * with making sure that the list of DNS servers is set up  according
577     * to which networks are connected, and ensuring that the right routing
578     * table entries exist.
579     */
580    private void handleConnectivityChange() {
581        /*
582         * If both mobile and wifi are enabled, add the host routes that
583         * will allow MMS traffic to pass on the mobile network. But
584         * remove the default route for the mobile network, so that there
585         * will be only one default route, to ensure that all traffic
586         * except MMS will travel via Wi-Fi.
587         */
588        int numConnectedNets = handleConfigurationChange();
589        if (numConnectedNets > 1) {
590            mMobileDataStateTracker.addPrivateRoutes();
591            mMobileDataStateTracker.removeDefaultRoute();
592        } else if (mMobileDataStateTracker.getNetworkInfo().isConnected()) {
593            mMobileDataStateTracker.removePrivateRoutes();
594            mMobileDataStateTracker.restoreDefaultRoute();
595        }
596    }
597
598    private int handleConfigurationChange() {
599        /*
600         * Set DNS properties. Always put Wi-Fi entries at the front of
601         * the list if it is active.
602         */
603        int index = 1;
604        String lastDns = "";
605        int numConnectedNets = 0;
606        int incrValue = ConnectivityManager.TYPE_MOBILE - ConnectivityManager.TYPE_WIFI;
607        int stopValue = ConnectivityManager.TYPE_MOBILE + incrValue;
608
609        for (int netType = ConnectivityManager.TYPE_WIFI; netType != stopValue; netType += incrValue) {
610            NetworkStateTracker nt = mNetTrackers[netType];
611            if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) {
612                ++numConnectedNets;
613                String[] dnsList = nt.getNameServers();
614                for (int i = 0; i < dnsList.length && dnsList[i] != null; i++) {
615                    // skip duplicate entries
616                    if (!dnsList[i].equals(lastDns)) {
617                        SystemProperties.set("net.dns" + index++, dnsList[i]);
618                        lastDns = dnsList[i];
619                    }
620                }
621            }
622        }
623        // Null out any DNS properties that are no longer used
624        for (int i = index; i <= mNumDnsEntries; i++) {
625            SystemProperties.set("net.dns" + i, "");
626        }
627        mNumDnsEntries = index - 1;
628        // Notify the name resolver library of the change
629        SystemProperties.set("net.dnschange", String.valueOf(sDnsChangeCounter++));
630        return numConnectedNets;
631    }
632
633    @Override
634    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
635        if (mContext.checkCallingPermission(android.Manifest.permission.DUMP)
636                != PackageManager.PERMISSION_GRANTED) {
637            pw.println("Permission Denial: can't dump ConnectivityService from from pid="
638                    + Binder.getCallingPid()
639                    + ", uid=" + Binder.getCallingUid());
640            return;
641        }
642        if (mActiveNetwork == null) {
643            pw.println("No active network");
644        } else {
645            pw.println("Active network: " + mActiveNetwork.getNetworkInfo().getTypeName());
646        }
647        pw.println();
648        for (NetworkStateTracker nst : mNetTrackers) {
649            pw.println(nst.getNetworkInfo());
650            pw.println(nst);
651            pw.println();
652        }
653    }
654
655    private class MyHandler extends Handler {
656        @Override
657        public void handleMessage(Message msg) {
658            NetworkInfo info;
659            switch (msg.what) {
660                case NetworkStateTracker.EVENT_STATE_CHANGED:
661                    info = (NetworkInfo) msg.obj;
662                    if (DBG) Log.v(TAG, "ConnectivityChange for " + info.getTypeName() + ": " +
663                            info.getState() + "/" + info.getDetailedState());
664
665                    // Connectivity state changed:
666                    // [31-13] Reserved for future use
667                    // [12-9] Network subtype (for mobile network, as defined by TelephonyManager)
668                    // [8-3] Detailed state ordinal (as defined by NetworkInfo.DetailedState)
669                    // [2-0] Network type (as defined by ConnectivityManager)
670                    int eventLogParam = (info.getType() & 0x7) |
671                            ((info.getDetailedState().ordinal() & 0x3f) << 3) |
672                            (info.getSubtype() << 9);
673                    EventLog.writeEvent(EVENTLOG_CONNECTIVITY_STATE_CHANGED, eventLogParam);
674
675                    if (info.getDetailedState() == NetworkInfo.DetailedState.FAILED) {
676                        handleConnectionFailure(info);
677                    } else if (info.getState() == NetworkInfo.State.DISCONNECTED) {
678                        handleDisconnect(info);
679                    } else if (info.getState() == NetworkInfo.State.SUSPENDED) {
680                        // TODO: need to think this over.
681                        // the logic here is, handle SUSPENDED the same as DISCONNECTED. The
682                        // only difference being we are broadcasting an intent with NetworkInfo
683                        // that's suspended. This allows the applications an opportunity to
684                        // handle DISCONNECTED and SUSPENDED differently, or not.
685                        handleDisconnect(info);
686                    } else if (info.getState() == NetworkInfo.State.CONNECTED) {
687                        handleConnect(info);
688                    }
689                    handleConnectivityChange();
690                    break;
691
692                case NetworkStateTracker.EVENT_SCAN_RESULTS_AVAILABLE:
693                    info = (NetworkInfo) msg.obj;
694                    handleScanResultsAvailable(info);
695                    break;
696
697                case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED:
698                    handleNotificationChange(msg.arg1 == 1, msg.arg2, (Notification) msg.obj);
699
700                case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED:
701                    handleConfigurationChange();
702                    break;
703
704                case NetworkStateTracker.EVENT_ROAMING_CHANGED:
705                    // fill me in
706                    break;
707
708                case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED:
709                    // fill me in
710                    break;
711            }
712        }
713    }
714}
715