1/*
2 * Copyright (C) 2010, 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.connectivitymanagertest;
18
19import android.app.KeyguardManager;
20import android.content.Context;
21import android.content.BroadcastReceiver;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.net.ConnectivityManager;
25import android.net.NetworkInfo;
26import android.net.NetworkInfo.State;
27import android.net.wifi.WifiConfiguration;
28import android.net.wifi.WifiManager;
29import android.net.wifi.WifiConfiguration.KeyMgmt;
30import android.os.Handler;
31import android.os.Message;
32import android.os.PowerManager;
33import android.os.SystemClock;
34import android.test.InstrumentationTestCase;
35import android.util.Log;
36import android.view.KeyEvent;
37
38import com.android.internal.util.AsyncChannel;
39
40import java.io.IOException;
41import java.io.InputStream;
42import java.net.UnknownHostException;
43import java.util.ArrayList;
44import java.util.List;
45
46
47/**
48 * Base InstrumentationTestCase for Connectivity Manager (CM) test suite
49 *
50 * It registers connectivity manager broadcast and WiFi broadcast to provide
51 * network connectivity information, also provides a set of utility functions
52 * to modify and verify connectivity states.
53 *
54 * A CM test case should extend this base class.
55 */
56public class ConnectivityManagerTestBase extends InstrumentationTestCase {
57
58    public static final String LOG_TAG = "ConnectivityManagerTestBase";
59    public static final int WAIT_FOR_SCAN_RESULT = 10 * 1000; //10 seconds
60    public static final int WIFI_SCAN_TIMEOUT = 50 * 1000; // 50 seconds
61    public static final int SHORT_TIMEOUT = 5 * 1000; // 5 seconds
62    public static final long LONG_TIMEOUT = 50 * 1000;  // 50 seconds
63    public static final long WIFI_CONNECTION_TIMEOUT = 5 * 60 * 1000; // 5 minutes
64    // 2 minutes timer between wifi stop and start
65    public static final long  WIFI_STOP_START_INTERVAL = 2 * 60 * 1000; // 2 minutes
66    // Set ping test timer to be 3 minutes
67    public static final long PING_TIMER = 3 * 60 *1000; // 3 minutes
68    public static final int SUCCESS = 0;  // for Wifi tethering state change
69    public static final int FAILURE = 1;
70    public static final int INIT = -1;
71    private static final String ACCESS_POINT_FILE = "accesspoints.xml";
72    public ConnectivityReceiver mConnectivityReceiver = null;
73    public WifiReceiver mWifiReceiver = null;
74    private AccessPointParserHelper mParseHelper = null;
75    /*
76     * Track network connectivity information
77     */
78    public State mState;
79    public NetworkInfo mNetworkInfo;
80    public NetworkInfo mOtherNetworkInfo;
81    public boolean mIsFailOver;
82    public String mReason;
83    public boolean mScanResultIsAvailable = false;
84    public ConnectivityManager mCM;
85    public Object wifiObject = new Object();
86    public Object connectivityObject = new Object();
87    public int mWifiState;
88    public NetworkInfo mWifiNetworkInfo;
89    public String mBssid;
90    public String mPowerSsid = "opennet"; //Default power SSID
91    private Context mContext;
92    public boolean scanResultAvailable = false;
93
94    /* Control Wifi States */
95    public WifiManager mWifiManager;
96    /* Verify connectivity state */
97    public static final int NUM_NETWORK_TYPES = ConnectivityManager.MAX_NETWORK_TYPE + 1;
98    NetworkState[] connectivityState = new NetworkState[NUM_NETWORK_TYPES];
99
100    // For wifi tethering tests
101    private String[] mWifiRegexs;
102    public int mWifiTetherResult = INIT;    // -1 is initialization state
103
104    /**
105     * A wrapper of a broadcast receiver which provides network connectivity information
106     * for all kinds of network: wifi, mobile, etc.
107     */
108    private class ConnectivityReceiver extends BroadcastReceiver {
109        @Override
110        public void onReceive(Context context, Intent intent) {
111            log("ConnectivityReceiver: onReceive() is called with " + intent);
112            String action = intent.getAction();
113            if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
114                Log.v("ConnectivityReceiver", "onReceive() called with " + intent);
115                return;
116            }
117
118            boolean noConnectivity =
119                intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
120
121            if (noConnectivity) {
122                mState = State.DISCONNECTED;
123            } else {
124                mState = State.CONNECTED;
125            }
126
127            mNetworkInfo = (NetworkInfo)
128                intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
129
130            mOtherNetworkInfo = (NetworkInfo)
131                intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
132
133            mReason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON);
134            mIsFailOver = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
135
136            log("mNetworkInfo: " + mNetworkInfo.toString());
137            if (mOtherNetworkInfo != null) {
138                log("mOtherNetworkInfo: " + mOtherNetworkInfo.toString());
139            }
140            recordNetworkState(mNetworkInfo.getType(), mNetworkInfo.getState());
141            if (mOtherNetworkInfo != null) {
142                recordNetworkState(mOtherNetworkInfo.getType(), mOtherNetworkInfo.getState());
143            }
144            notifyNetworkConnectivityChange();
145        }
146    }
147
148    private class WifiReceiver extends BroadcastReceiver {
149        @Override
150        public void onReceive(Context context, Intent intent) {
151            String action = intent.getAction();
152            Log.v("WifiReceiver", "onReceive() is calleld with " + intent);
153            if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
154                log("scan results are available");
155                notifyScanResult();
156            } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
157                mWifiNetworkInfo =
158                    (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
159                log("mWifiNetworkInfo: " + mWifiNetworkInfo.toString());
160                if (mWifiNetworkInfo.getState() == State.CONNECTED) {
161                    mBssid = intent.getStringExtra(WifiManager.EXTRA_BSSID);
162                }
163                notifyWifiState();
164            } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
165                mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
166                                                WifiManager.WIFI_STATE_UNKNOWN);
167                notifyWifiState();
168            } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
169                notifyWifiAPState();
170            } else if (action.equals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)) {
171                ArrayList<String> available = intent.getStringArrayListExtra(
172                        ConnectivityManager.EXTRA_AVAILABLE_TETHER);
173                ArrayList<String> active = intent.getStringArrayListExtra(
174                        ConnectivityManager.EXTRA_ACTIVE_TETHER);
175                ArrayList<String> errored = intent.getStringArrayListExtra(
176                        ConnectivityManager.EXTRA_ERRORED_TETHER);
177                updateTetherState(available.toArray(), active.toArray(), errored.toArray());
178            }
179            else {
180                return;
181            }
182        }
183    }
184
185    private class WifiServiceHandler extends Handler {
186        @Override
187        public void handleMessage(Message msg) {
188            switch (msg.what) {
189                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
190                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
191                        //AsyncChannel in msg.obj
192                    } else {
193                        log("Failed to establish AsyncChannel connection");
194                    }
195                    break;
196                default:
197                    //Ignore
198                    break;
199            }
200        }
201    }
202
203    @Override
204    public void setUp() throws Exception {
205        mState = State.UNKNOWN;
206        scanResultAvailable = false;
207        mContext = getInstrumentation().getContext();
208
209        // Get an instance of ConnectivityManager
210        mCM = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
211        // Get an instance of WifiManager
212        mWifiManager =(WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
213
214        if (mWifiManager.isWifiApEnabled()) {
215            // if soft AP is enabled, disable it
216            mWifiManager.setWifiApEnabled(null, false);
217            log("Disable soft ap");
218        }
219
220        initializeNetworkStates();
221
222        // register a connectivity receiver for CONNECTIVITY_ACTION;
223        mConnectivityReceiver = new ConnectivityReceiver();
224        mContext.registerReceiver(mConnectivityReceiver,
225                new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
226
227        mWifiReceiver = new WifiReceiver();
228        IntentFilter mIntentFilter = new IntentFilter();
229        mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
230        mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
231        mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
232        mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
233        mIntentFilter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
234        mIntentFilter.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
235        mContext.registerReceiver(mWifiReceiver, mIntentFilter);
236
237        log("Clear Wifi before we start the test.");
238        removeConfiguredNetworksAndDisableWifi();
239        mWifiRegexs = mCM.getTetherableWifiRegexs();
240     }
241
242    public List<WifiConfiguration> loadNetworkConfigurations() throws Exception {
243        InputStream in = mContext.getAssets().open(ACCESS_POINT_FILE);
244        mParseHelper = new AccessPointParserHelper(in);
245        return mParseHelper.getNetworkConfigurations();
246    }
247
248    // for each network type, initialize network states to UNKNOWN, and no verification flag is set
249    public void initializeNetworkStates() {
250        for (int networkType = NUM_NETWORK_TYPES - 1; networkType >=0; networkType--) {
251            connectivityState[networkType] =  new NetworkState();
252            log("Initialize network state for " + networkType + ": " +
253                    connectivityState[networkType].toString());
254        }
255    }
256
257    // deposit a network state
258    public void recordNetworkState(int networkType, State networkState) {
259        log("record network state for network " +  networkType +
260                ", state is " + networkState);
261        if (connectivityState == null) {
262             log("ConnectivityState is null");
263        }
264        if (connectivityState[networkType] == null) {
265             log("connectivityState[networkType] is null");
266        }
267        connectivityState[networkType].recordState(networkState);
268    }
269
270    // set the state transition criteria
271    public void setStateTransitionCriteria(int networkType, State initState,
272            int transitionDir, State targetState) {
273        connectivityState[networkType].setStateTransitionCriteria(
274                initState, transitionDir, targetState);
275    }
276
277    // Validate the states recorded
278    public boolean validateNetworkStates(int networkType) {
279        log("validate network state for " + networkType + ": ");
280        return connectivityState[networkType].validateStateTransition();
281    }
282
283    // return result from network state validation
284    public String getTransitionFailureReason(int networkType) {
285        log("get network state transition failure reason for " + networkType + ": " +
286                connectivityState[networkType].toString());
287        return connectivityState[networkType].getReason();
288    }
289
290    private void notifyNetworkConnectivityChange() {
291        synchronized(connectivityObject) {
292            log("notify network connectivity changed");
293            connectivityObject.notifyAll();
294        }
295    }
296    private void notifyScanResult() {
297        synchronized (this) {
298            log("notify that scan results are available");
299            scanResultAvailable = true;
300            this.notify();
301        }
302    }
303
304    private void notifyWifiState() {
305        synchronized (wifiObject) {
306            log("notify wifi state changed");
307            wifiObject.notify();
308        }
309    }
310
311    private void notifyWifiAPState() {
312        synchronized (this) {
313            log("notify wifi AP state changed");
314            this.notify();
315        }
316    }
317
318    // Update wifi tethering state
319    private void updateTetherState(Object[] available, Object[] tethered, Object[] errored) {
320        boolean wifiTethered = false;
321        boolean wifiErrored = false;
322
323        synchronized (this) {
324            for (Object obj: tethered) {
325                String str = (String)obj;
326                for (String tethRex: mWifiRegexs) {
327                    log("str: " + str +"tethRex: " + tethRex);
328                    if (str.matches(tethRex)) {
329                        wifiTethered = true;
330                    }
331                }
332            }
333
334            for (Object obj: errored) {
335                String str = (String)obj;
336                for (String tethRex: mWifiRegexs) {
337                    log("error: str: " + str +"tethRex: " + tethRex);
338                    if (str.matches(tethRex)) {
339                        wifiErrored = true;
340                    }
341                }
342            }
343
344            if (wifiTethered) {
345                mWifiTetherResult = SUCCESS;   // wifi tethering is successful
346            } else if (wifiErrored) {
347                mWifiTetherResult = FAILURE;   // wifi tethering failed
348            }
349            log("mWifiTetherResult: " + mWifiTetherResult);
350            this.notify();
351        }
352    }
353
354
355    // Wait for network connectivity state: CONNECTING, CONNECTED, SUSPENDED,
356    //                                      DISCONNECTING, DISCONNECTED, UNKNOWN
357    public boolean waitForNetworkState(int networkType, State expectedState, long timeout) {
358        long startTime = System.currentTimeMillis();
359        while (true) {
360            if ((System.currentTimeMillis() - startTime) > timeout) {
361                log("waitForNetworkState time out, the state of network type " + networkType +
362                        " is: " + mCM.getNetworkInfo(networkType).getState());
363                if (mCM.getNetworkInfo(networkType).getState() != expectedState) {
364                    return false;
365                } else {
366                    // the broadcast has been sent out. the state has been changed.
367                    log("networktype: " + networkType + " state: " +
368                            mCM.getNetworkInfo(networkType));
369                    return true;
370                }
371            }
372            log("Wait for the connectivity state for network: " + networkType +
373                    " to be " + expectedState.toString());
374            synchronized (connectivityObject) {
375                try {
376                    connectivityObject.wait(SHORT_TIMEOUT);
377                } catch (InterruptedException e) {
378                    e.printStackTrace();
379                }
380                if ((mNetworkInfo.getType() != networkType) ||
381                    (mNetworkInfo.getState() != expectedState)) {
382                    log("network state for " + mNetworkInfo.getType() +
383                            "is: " + mNetworkInfo.getState());
384                    continue;
385                }
386                return true;
387            }
388        }
389    }
390
391    // Wait for Wifi state: WIFI_STATE_DISABLED, WIFI_STATE_DISABLING, WIFI_STATE_ENABLED,
392    //                      WIFI_STATE_ENALBING, WIFI_STATE_UNKNOWN
393    public boolean waitForWifiState(int expectedState, long timeout) {
394        long startTime = System.currentTimeMillis();
395        while (true) {
396            if ((System.currentTimeMillis() - startTime) > timeout) {
397                if (mWifiState != expectedState) {
398                    return false;
399                } else {
400                    return true;
401                }
402            }
403            log("Wait for wifi state to be: " + expectedState);
404            synchronized (wifiObject) {
405                try {
406                    wifiObject.wait(SHORT_TIMEOUT);
407                } catch (InterruptedException e) {
408                    e.printStackTrace();
409                }
410                if (mWifiState != expectedState) {
411                    log("Wifi state is: " + mWifiState);
412                    continue;
413                }
414                return true;
415            }
416        }
417    }
418
419    // Wait for Wifi AP state: WIFI_AP_STATE_DISABLED, WIFI_AP_STATE_DISABLING,
420    //                         WIFI_AP_STATE_ENABLED, WIFI_STATE_ENALBING, WIFI_STATE_UNKNOWN
421    public boolean waitForWifiAPState(int expectedState, long timeout) {
422        long startTime = System.currentTimeMillis();
423        while (true) {
424            if ((System.currentTimeMillis() - startTime) > timeout) {
425                if (mWifiManager.getWifiApState() != expectedState) {
426                    return false;
427                } else {
428                    return true;
429                }
430            }
431            log("Wait for wifi AP state to be: " + expectedState);
432            synchronized (wifiObject) {
433                try {
434                    wifiObject.wait(SHORT_TIMEOUT);
435                } catch (InterruptedException e) {
436                    e.printStackTrace();
437                }
438                if (mWifiManager.getWifiApState() != expectedState) {
439                    log("Wifi state is: " + mWifiManager.getWifiApState());
440                    continue;
441                }
442                return true;
443            }
444        }
445    }
446
447    /**
448     * Wait for the wifi tethering result:
449     * @param timeout is the maximum waiting time
450     * @return SUCCESS if tethering result is successful
451     *         FAILURE if tethering result returns error.
452     */
453    public int waitForTetherStateChange(long timeout) {
454        long startTime = System.currentTimeMillis();
455        while (true) {
456            if ((System.currentTimeMillis() - startTime) > timeout) {
457                return mWifiTetherResult;
458            }
459            log("Wait for wifi tethering result.");
460            synchronized (this) {
461                try {
462                    this.wait(SHORT_TIMEOUT);
463                } catch (InterruptedException e) {
464                    e.printStackTrace();
465                }
466                if (mWifiTetherResult == INIT ) {
467                    continue;
468                } else {
469                    return mWifiTetherResult;
470                }
471            }
472        }
473    }
474
475    // Return true if device is currently connected to mobile network
476    public boolean isConnectedToMobile() {
477        return (mNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE);
478    }
479
480    // Return true if device is currently connected to Wifi
481    public boolean isConnectedToWifi() {
482        return (mNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI);
483    }
484
485    public boolean enableWifi() {
486        return mWifiManager.setWifiEnabled(true);
487    }
488
489    // Turn screen off
490    public void turnScreenOff() {
491        log("Turn screen off");
492        PowerManager pm =
493            (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
494        pm.goToSleep(SystemClock.uptimeMillis());
495    }
496
497    // Turn screen on
498    public void turnScreenOn() {
499        log("Turn screen on");
500        PowerManager pm =
501                (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
502        pm.wakeUp(SystemClock.uptimeMillis());
503        // disable lock screen
504        KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
505        if (km.inKeyguardRestrictedInputMode()) {
506            sendKeys(KeyEvent.KEYCODE_MENU);
507        }
508    }
509
510    /**
511     * @param pingServerList a list of servers that can be used for ping test, can be null
512     * @return true if the ping test is successful, false otherwise.
513     */
514    public boolean pingTest(String[] pingServerList) {
515        String[] hostList = {"www.google.com", "www.yahoo.com",
516                "www.bing.com", "www.facebook.com", "www.ask.com"};
517        if (pingServerList != null) {
518            hostList = pingServerList;
519        }
520
521        long startTime = System.currentTimeMillis();
522        while ((System.currentTimeMillis() - startTime) < PING_TIMER) {
523            try {
524                // assume the chance that all servers are down is very small
525                for (int i = 0; i < hostList.length; i++ ) {
526                    String host = hostList[i];
527                    log("Start ping test, ping " + host);
528                    Process p = Runtime.getRuntime().exec("ping -c 10 -w 100 " + host);
529                    int status = p.waitFor();
530                    if (status == 0) {
531                        // if any of the ping test is successful, return true
532                        return true;
533                    }
534                }
535            } catch (UnknownHostException e) {
536                log("Ping test Fail: Unknown Host");
537            } catch (IOException e) {
538                log("Ping test Fail:  IOException");
539            } catch (InterruptedException e) {
540                log("Ping test Fail: InterruptedException");
541            }
542        }
543        // ping test timeout
544        return false;
545    }
546
547    /**
548     * Associate the device to given SSID
549     * If the device is already associated with a WiFi, disconnect and forget it,
550     * We don't verify whether the connection is successful or not, leave this to the test
551     */
552    public boolean connectToWifi(String knownSSID) {
553        WifiConfiguration config = new WifiConfiguration();
554        config.SSID = knownSSID;
555        config.allowedKeyManagement.set(KeyMgmt.NONE);
556        return connectToWifiWithConfiguration(config);
557    }
558
559    /**
560     * Connect to Wi-Fi with the given configuration. Note the SSID in the configuration
561     * is pure string, we need to convert it to quoted string.
562     * @param config
563     * @return
564     */
565    public boolean connectToWifiWithConfiguration(WifiConfiguration config) {
566        String ssid = config.SSID;
567        config.SSID = convertToQuotedString(ssid);
568
569        // If Wifi is not enabled, enable it
570        if (!mWifiManager.isWifiEnabled()) {
571            log("Wifi is not enabled, enable it");
572            mWifiManager.setWifiEnabled(true);
573            // wait for the wifi state change before start scanning.
574            if (!waitForWifiState(WifiManager.WIFI_STATE_ENABLED, 2*SHORT_TIMEOUT)) {
575                log("wait for WIFI_STATE_ENABLED failed");
576                return false;
577            }
578        }
579
580        // Save network configuration and connect to network without scanning
581        mWifiManager.connect(config,
582            new WifiManager.ActionListener() {
583                public void onSuccess() {
584                }
585                public void onFailure(int reason) {
586                    log("connect failure " + reason);
587                }
588            });
589        return true;
590    }
591
592    /*
593     * Disconnect from the current AP and remove configured networks.
594     */
595    public boolean disconnectAP() {
596        // remove saved networks
597        if (!mWifiManager.isWifiEnabled()) {
598            log("Enabled wifi before remove configured networks");
599            mWifiManager.setWifiEnabled(true);
600            sleep(SHORT_TIMEOUT);
601        }
602
603        List<WifiConfiguration> wifiConfigList = mWifiManager.getConfiguredNetworks();
604        if (wifiConfigList == null) {
605            log("no configuration list is null");
606            return true;
607        }
608        log("size of wifiConfigList: " + wifiConfigList.size());
609        for (WifiConfiguration wifiConfig: wifiConfigList) {
610            log("remove wifi configuration: " + wifiConfig.networkId);
611            int netId = wifiConfig.networkId;
612            mWifiManager.forget(netId, new WifiManager.ActionListener() {
613                    public void onSuccess() {
614                    }
615                    public void onFailure(int reason) {
616                        log("Failed to forget " + reason);
617                    }
618                });
619        }
620        return true;
621    }
622    /**
623     * Disable Wifi
624     * @return true if Wifi is disabled successfully
625     */
626    public boolean disableWifi() {
627        return mWifiManager.setWifiEnabled(false);
628    }
629
630    /**
631     * Remove configured networks and disable wifi
632     */
633    public boolean removeConfiguredNetworksAndDisableWifi() {
634        if (!disconnectAP()) {
635           return false;
636        }
637        sleep(SHORT_TIMEOUT);
638        if (!mWifiManager.setWifiEnabled(false)) {
639            return false;
640        }
641        sleep(SHORT_TIMEOUT);
642        return true;
643    }
644
645    private void sleep(long sleeptime) {
646        try {
647            Thread.sleep(sleeptime);
648        } catch (InterruptedException e) {}
649    }
650
651    protected static String convertToQuotedString(String string) {
652        return "\"" + string + "\"";
653    }
654
655    @Override
656    public void tearDown() throws Exception{
657        //Unregister receiver
658        if (mConnectivityReceiver != null) {
659          mContext.unregisterReceiver(mConnectivityReceiver);
660        }
661        if (mWifiReceiver != null) {
662          mContext.unregisterReceiver(mWifiReceiver);
663        }
664        super.tearDown();
665    }
666
667    private void log(String message) {
668        Log.v(LOG_TAG, message);
669    }
670}
671