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