1 /* 2 * Copyright (C) 2018 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 17 package com.android.cts.verifier.net; 18 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 20 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 21 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 22 23 import static com.android.cts.verifier.net.MultiNetworkConnectivityTestActivity.ValidatorState.COMPLETED; 24 import static com.android.cts.verifier.net.MultiNetworkConnectivityTestActivity.ValidatorState.NOT_STARTED; 25 import static com.android.cts.verifier.net.MultiNetworkConnectivityTestActivity.ValidatorState.STARTED; 26 import static com.android.cts.verifier.net.MultiNetworkConnectivityTestActivity.ValidatorState.WAITING_FOR_USER_INPUT; 27 28 import android.app.ActivityManager; 29 import android.app.AlertDialog; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.net.ConnectivityManager; 35 import android.net.ConnectivityManager.NetworkCallback; 36 import android.net.DhcpInfo; 37 import android.net.Network; 38 import android.net.NetworkCapabilities; 39 import android.net.NetworkInfo; 40 import android.net.NetworkRequest; 41 import android.net.NetworkSpecifier; 42 import android.net.wifi.SupplicantState; 43 import android.net.wifi.WifiInfo; 44 import android.net.wifi.WifiManager; 45 import android.net.wifi.WifiNetworkSpecifier; 46 import android.os.Build; 47 import android.os.Bundle; 48 import android.os.Handler; 49 import android.os.Looper; 50 import android.provider.Settings; 51 import android.telephony.TelephonyManager; 52 import android.text.Editable; 53 import android.text.TextUtils; 54 import android.text.TextWatcher; 55 import android.util.Log; 56 import android.widget.Button; 57 import android.widget.EditText; 58 import android.widget.TextView; 59 60 import com.android.cts.verifier.PassFailButtons; 61 import com.android.cts.verifier.R; 62 63 import java.util.ArrayList; 64 import java.util.Collections; 65 import java.util.List; 66 67 /** 68 * A CTS verifier to ensure that when device connect to a new Wi-Fi network, 69 * - When the wifi network does not have internet connectivity, the device should 70 * not disable other forms or connectivity, for example cellular. 71 * - When the wifi network that the phone connects to loses connectivity, then 72 * other forms of connectivity are restored, for example cellular when the phone 73 * detects that the Wifi network doesn't have internet. 74 */ 75 public class MultiNetworkConnectivityTestActivity extends PassFailButtons.Activity { 76 public static final String TAG = "MultinetworkTest"; 77 public static final int WIFI_NETWORK_CONNECT_TIMEOUT_MS = 45000; 78 public static final int WIFI_NETWORK_CONNECT_TO_BE_ACTIVE_MS = 25000; 79 public static final int CELLULAR_NETWORK_CONNECT_TIMEOUT_MS = 45000; 80 public static final int CELLULAR_NETWORK_RESTORE_TIMEOUT_MS = 15000; 81 public static final int CELLULAR_NETWORK_RESTORE_AFTER_WIFI_INTERNET_LOST_TIMEOUT_MS = 60000; 82 83 /** 84 * Called by the validator test when it has different states. 85 */ 86 private interface MultinetworkTestCallback { 87 /** Notify test has started */ testStarted()88 void testStarted(); 89 90 /** Show / display progress using the message in progressMessage */ testProgress(int progressMessageResourceId)91 void testProgress(int progressMessageResourceId); 92 93 /** Test completed for the validator */ testCompleted(MultiNetworkValidator validator)94 void testCompleted(MultiNetworkValidator validator); 95 } 96 97 enum ValidatorState { 98 NOT_STARTED, 99 STARTED, 100 WAITING_FOR_USER_INPUT, 101 COMPLETED, 102 } 103 104 private final Handler mMainHandler = new Handler(Looper.getMainLooper()); 105 // Used only for posting bugs / debugging. 106 private final BroadcastReceiver mMultiNetConnectivityReceiver = new BroadcastReceiver() { 107 @Override 108 public void onReceive(Context context, Intent intent) { 109 Log.d(TAG, "Action " + intent.getAction()); 110 if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { 111 NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 112 Log.d(TAG, "New network state " + networkInfo.getState()); 113 } else if (intent.getAction().equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) { 114 SupplicantState state = intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE); 115 Log.d(TAG, "New supplicant state. " + state.name()); 116 Log.d(TAG, "Is connected to expected wifi AP. " + 117 isConnectedToExpectedWifiNetwork()); 118 } 119 } 120 }; 121 private final MultinetworkTestCallback mMultinetworkTestCallback = 122 new MultinetworkTestCallback() { 123 124 @Override 125 public void testStarted() { 126 mTestInfoView.setText(R.string.multinetwork_connectivity_test_running); 127 } 128 129 @Override 130 public void testProgress(int progressMessageResourceId) { 131 mTestInfoView.setText(progressMessageResourceId); 132 } 133 134 @Override 135 public void testCompleted(MultiNetworkValidator validator) { 136 if (validator == mMultiNetworkValidators.get(mMultiNetworkValidators.size() 137 - 1)) { 138 // Done all tests. 139 boolean passed = true; 140 for (MultiNetworkValidator multiNetworkValidator : 141 mMultiNetworkValidators) { 142 passed = passed && multiNetworkValidator.mTestResult; 143 } 144 setTestResultAndFinish(passed); 145 } else if (!validator.mTestResult) { 146 setTestResultAndFinish(false); 147 } else { 148 for (int i = 0; i < mMultiNetworkValidators.size(); i++) { 149 if (mMultiNetworkValidators.get(i) == validator) { 150 mCurrentValidator = mMultiNetworkValidators.get(i + 1); 151 mTestNameView.setText(mCurrentValidator.mTestDescription); 152 mCurrentValidator.startTest(); 153 break; 154 } 155 } 156 } 157 } 158 }; 159 private List<MultiNetworkValidator> mMultiNetworkValidators = Collections.emptyList(); 160 private final Runnable mTimeToCompletionRunnable = new Runnable() { 161 @Override 162 public void run() { 163 mSecondsToCompletion--; 164 if (mSecondsToCompletion > 0) { 165 mStartButton.setText("" + mSecondsToCompletion); 166 mMainHandler.postDelayed(this, 1000); 167 } 168 } 169 }; 170 171 // User interface elements. 172 private Button mStartButton; 173 private TextView mTestNameView; 174 private TextView mTestInfoView; 175 private EditText mAccessPointSsidEditText; 176 private EditText mPskEditText; 177 178 // Current state memebers. 179 private MultiNetworkValidator mCurrentValidator; 180 private int mSecondsToCompletion; 181 private String mAccessPointSsid = ""; 182 private String mPskValue = ""; 183 private ConnectivityManager mConnectivityManager; 184 private WifiManager mWifiManager; 185 186 private int mRecordedWifiConfiguration = -1; 187 188 @Override onCreate(Bundle savedInstanceState)189 protected void onCreate(Bundle savedInstanceState) { 190 super.onCreate(savedInstanceState); 191 mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 192 mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); 193 mMultiNetworkValidators = createMultiNetworkValidators(); 194 195 recordCurrentWifiState(); 196 setupUserInterface(); 197 setupBroadcastReceivers(); 198 } 199 200 @Override onResume()201 protected void onResume() { 202 super.onResume(); 203 setupCurrentTestStateOnResume(); 204 } 205 206 @Override onDestroy()207 protected void onDestroy() { 208 super.onDestroy(); 209 destroyBroadcastReceivers(); 210 } 211 recordCurrentWifiState()212 private void recordCurrentWifiState() { 213 if (!mWifiManager.isWifiEnabled()) { 214 return; 215 } 216 WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); 217 if (wifiInfo != null && SupplicantState.COMPLETED.equals(wifiInfo.getSupplicantState())) { 218 mRecordedWifiConfiguration = wifiInfo.getNetworkId(); 219 } 220 } 221 createMultiNetworkValidators()222 private List<MultiNetworkValidator> createMultiNetworkValidators() { 223 MultiNetworkValidator[] allValidators = { 224 new ConnectToWifiWithNoInternetValidator( 225 R.string.multinetwork_connectivity_test_1_desc), 226 new LegacyConnectToWifiWithNoInternetValidator( 227 R.string.multinetwork_connectivity_test_2_desc), 228 new LegacyConnectToWifiWithIntermittentInternetValidator( 229 R.string.multinetwork_connectivity_test_3_desc) 230 }; 231 232 List<MultiNetworkValidator> result = new ArrayList<>(); 233 boolean isLowRamDevice = isLowRamDevice(); 234 for (MultiNetworkValidator validator : allValidators) { 235 if (!isLowRamDevice || validator.shouldRunOnLowRamDevice()) { 236 result.add(validator); 237 } 238 } 239 return result; 240 } 241 requestSystemAlertWindowPerimissionIfRequired()242 private boolean requestSystemAlertWindowPerimissionIfRequired() { 243 if (isLowRamDevice()) { 244 // For low ram devices, we won't run tests that depend on this permission. 245 return true; 246 } 247 248 boolean hadPermission = false; 249 if (!Settings.canDrawOverlays(this)) { 250 AlertDialog alertDialog = new AlertDialog.Builder(this) 251 .setMessage(R.string.multinetwork_connectivity_overlay_permission_message) 252 .setPositiveButton( 253 R.string.multinetwork_connectivity_overlay_permission_positive, 254 (a, b) -> { 255 Intent myIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); 256 startActivity(myIntent); 257 }) 258 .setNegativeButton( 259 R.string.multinetwork_connectivity_overlay_permission_negative, 260 (a, b) -> {}) 261 .create(); 262 alertDialog.show(); 263 } else { 264 hadPermission = true; 265 } 266 267 return hadPermission; 268 } 269 requestUserEnableWifiAsync(boolean enableWifi, SetWifiCallback callback)270 private void requestUserEnableWifiAsync(boolean enableWifi, SetWifiCallback callback) { 271 if (isWifiEnabled() == enableWifi) { 272 callback.onComplete(/* isSuccess = */ true); 273 return; 274 } 275 276 int wifiEnableMessage = enableWifi ? R.string.multinetwork_connectivity_turn_wifi_on : 277 R.string.multinetwork_connectivity_turn_wifi_off; 278 279 AlertDialog alertDialog = new AlertDialog.Builder(this) 280 .setMessage(wifiEnableMessage) 281 .setPositiveButton(R.string.multinetwork_connectivity_turn_wifi_positive, 282 (a, b) -> requestUserEnableWifiAsync(enableWifi, callback)) 283 .setNegativeButton(R.string.multinetwork_connectivity_turn_wifi_negative, 284 (a, b) -> callback.onComplete(/* isSuccess = */ false)) 285 .create(); 286 alertDialog.show(); 287 } 288 requestUserConnectToApAsync(ConnectApCallback callback)289 private void requestUserConnectToApAsync(ConnectApCallback callback) { 290 if (isConnectedToExpectedWifiNetwork()) { 291 callback.onComplete(/* isSuccess = */ true); 292 return; 293 } 294 295 AlertDialog alertDialog = new AlertDialog.Builder(this) 296 .setMessage(getString(R.string.multinetwork_connectivity_connect_to_target_ap, 297 mAccessPointSsid)) 298 .setPositiveButton(R.string.multinetwork_connectivity_turn_wifi_positive, 299 (a, b) -> requestUserConnectToApAsync(callback)) 300 .setNegativeButton(R.string.multinetwork_connectivity_turn_wifi_negative, 301 (a, b) -> callback.onComplete(/* isSuccess = */ false)) 302 .create(); 303 alertDialog.show(); 304 } 305 toggleWifiAsync(SetWifiCallback callback)306 private void toggleWifiAsync(SetWifiCallback callback) { 307 // Turn off WiFi. 308 requestUserEnableWifiAsync(false, (isSuccess) -> { 309 if (isSuccess) { 310 // Turn on WiFi. 311 requestUserEnableWifiAsync(true, callback); 312 } else { 313 callback.onComplete(/* isSuccess = */ false); 314 } 315 }); 316 } 317 isWifiEnabled()318 private boolean isWifiEnabled() { 319 WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE); 320 int wifiState = wifiManager.getWifiState(); 321 return wifiState == WifiManager.WIFI_STATE_ENABLED 322 || wifiState == WifiManager.WIFI_STATE_ENABLING; 323 } 324 setupUserInterface()325 private void setupUserInterface() { 326 setContentView(R.layout.multinetwork_connectivity); 327 setInfoResources( 328 R.string.multinetwork_connectivity_test, 329 R.string.multinetwork_connectivity_test_instructions, 330 -1); 331 mStartButton = findViewById(R.id.start_multinet_btn); 332 mTestNameView = findViewById(R.id.current_test); 333 mTestInfoView = findViewById(R.id.test_progress_info); 334 mAccessPointSsidEditText = findViewById(R.id.test_ap_ssid); 335 mPskEditText = findViewById(R.id.test_ap_psk); 336 mAccessPointSsidEditText.addTextChangedListener(new TextWatcher() { 337 @Override 338 public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} 339 340 @Override 341 public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} 342 343 @Override 344 public void afterTextChanged(Editable editable) { 345 mAccessPointSsid = editable.toString(); 346 Log.i(TAG, "Connecting to " + mAccessPointSsid); 347 mStartButton.setEnabled(isReadyToStart()); 348 } 349 }); 350 mPskEditText.addTextChangedListener(new TextWatcher() { 351 @Override 352 public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} 353 354 @Override 355 public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} 356 357 @Override 358 public void afterTextChanged(Editable editable) { 359 mPskValue = editable.toString(); 360 mStartButton.setEnabled(isReadyToStart()); 361 } 362 }); 363 mStartButton.setOnClickListener(view -> processStartClicked()); 364 } 365 setupBroadcastReceivers()366 private void setupBroadcastReceivers() { 367 IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 368 intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 369 intentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); 370 intentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); 371 registerReceiver(mMultiNetConnectivityReceiver, intentFilter); 372 } 373 destroyBroadcastReceivers()374 private void destroyBroadcastReceivers() { 375 unregisterReceiver(mMultiNetConnectivityReceiver); 376 } 377 isReadyToStart()378 private boolean isReadyToStart() { 379 return !(TextUtils.isEmpty(mAccessPointSsid) || TextUtils.isEmpty(mPskValue)); 380 } 381 isNetworkCellularAndHasInternet(ConnectivityManager connectivityManager, Network network)382 private static boolean isNetworkCellularAndHasInternet(ConnectivityManager connectivityManager, 383 Network network) { 384 NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); 385 return capabilities.hasTransport(TRANSPORT_CELLULAR) 386 && capabilities.hasCapability(NET_CAPABILITY_INTERNET); 387 } 388 isMobileDataEnabled(TelephonyManager telephonyManager)389 private boolean isMobileDataEnabled(TelephonyManager telephonyManager) { 390 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 391 return telephonyManager.isDataEnabled(); 392 } 393 Network[] allNetworks = mConnectivityManager.getAllNetworks(); 394 for (Network network : allNetworks) { 395 if (isNetworkCellularAndHasInternet(mConnectivityManager, network)) { 396 return true; 397 } 398 } 399 return false; 400 } 401 checkPreRequisites()402 private boolean checkPreRequisites() { 403 TelephonyManager telephonyManager = 404 (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); 405 if (telephonyManager == null) { 406 Log.e(TAG, "Device does not have telephony manager"); 407 mTestInfoView.setText(R.string.multinetwork_connectivity_test_all_prereq_1); 408 return false; 409 } else if (!isMobileDataEnabled(telephonyManager)) { 410 Log.e(TAG, "Device mobile data is not available"); 411 mTestInfoView.setText(R.string.multinetwork_connectivity_test_all_prereq_2); 412 return false; 413 } 414 return true; 415 } 416 417 /** 418 * If tester went back and came in again, make sure that test resumes from the previous state. 419 */ setupCurrentTestStateOnResume()420 private void setupCurrentTestStateOnResume() { 421 mCurrentValidator = null; 422 mStartButton.setEnabled(false); 423 424 if (!checkPreRequisites()) { 425 return; 426 } 427 428 for (MultiNetworkValidator multiNetworkValidator : mMultiNetworkValidators) { 429 if (multiNetworkValidator.mValidatorState != COMPLETED) { 430 mCurrentValidator = multiNetworkValidator; 431 break; 432 } 433 } 434 if (mCurrentValidator != null) { 435 mTestNameView.setText(mCurrentValidator.mTestDescription); 436 437 switch (mCurrentValidator.mValidatorState) { 438 case NOT_STARTED: 439 mStartButton.setText(R.string.multinetwork_connectivity_test_start); 440 mStartButton.setEnabled(isReadyToStart()); 441 break; 442 case STARTED: 443 mTestInfoView.setText(getResources().getString( 444 mCurrentValidator.mTestProgressMessage)); 445 break; 446 case WAITING_FOR_USER_INPUT: 447 mStartButton.setText(R.string.multinetwork_connectivity_test_continue); 448 mStartButton.setEnabled(true); 449 mTestInfoView.setText(getResources().getString( 450 mCurrentValidator.mTestProgressMessage)); 451 case COMPLETED: 452 break; 453 } 454 mTestNameView.setText(mCurrentValidator.mTestDescription); 455 } else { 456 // All tests completed, so need to re run. It's not likely to get here as 457 // the default action when all test completes is to mark success and finish. 458 mStartButton.setText(R.string.multinetwork_connectivity_test_rerun); 459 mStartButton.setEnabled(true); 460 rerunMultinetworkTests(); 461 mCurrentValidator = mMultiNetworkValidators.get(0); 462 } 463 } 464 rerunMultinetworkTests()465 private void rerunMultinetworkTests() { 466 for (MultiNetworkValidator validator : mMultiNetworkValidators) { 467 validator.reset(); 468 } 469 } 470 requestUserConfirmation()471 private void requestUserConfirmation() { 472 mMainHandler.post(() -> { 473 mStartButton.setText(R.string.multinetwork_connectivity_test_continue); 474 mStartButton.setEnabled(true); 475 }); 476 } 477 processStartClicked()478 private void processStartClicked() { 479 if (!requestSystemAlertWindowPerimissionIfRequired()) { 480 Log.e(TAG, "System alert dialog permission not granted to CTSVerifier"); 481 return; 482 } 483 484 if (mCurrentValidator == null) { 485 rerunMultinetworkTests(); 486 setupCurrentTestStateOnResume(); 487 } 488 mStartButton.setEnabled(false); 489 if (mCurrentValidator.mValidatorState == NOT_STARTED) { 490 mCurrentValidator.startTest(); 491 } else if (mCurrentValidator.mValidatorState == WAITING_FOR_USER_INPUT) { 492 mStartButton.setEnabled(false); 493 mCurrentValidator.continueWithTest(); 494 } 495 } 496 isConnectedToExpectedWifiNetwork()497 private boolean isConnectedToExpectedWifiNetwork() { 498 WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); 499 DhcpInfo dhcpInfo = mWifiManager.getDhcpInfo(); 500 Log.i(TAG, "Checking connected to expected " + mAccessPointSsid); 501 if (wifiInfo != null 502 && wifiInfo.getSupplicantState().equals(SupplicantState.COMPLETED) 503 && dhcpInfo != null) { 504 String failsafeSsid = String.format("\"%s\"", mAccessPointSsid); 505 Log.i(TAG, "Connected to " + wifiInfo.getSSID() + " expected " + mAccessPointSsid); 506 return mAccessPointSsid.equals(wifiInfo.getSSID()) 507 || failsafeSsid.equals(wifiInfo.getSSID()); 508 } 509 return false; 510 } 511 startTimerCountdownDisplay(int timeoutInSeconds)512 private void startTimerCountdownDisplay(int timeoutInSeconds) { 513 mMainHandler.post(() -> mSecondsToCompletion = timeoutInSeconds); 514 mMainHandler.post(mTimeToCompletionRunnable); 515 } 516 stopTimerCountdownDisplay()517 private void stopTimerCountdownDisplay() { 518 mMainHandler.removeCallbacks(mTimeToCompletionRunnable); 519 mStartButton.setText("--"); 520 } 521 isLowRamDevice()522 private boolean isLowRamDevice() { 523 ActivityManager activityManager = 524 (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); 525 return activityManager.isLowRamDevice(); 526 } 527 528 /** 529 * Manage the connectivity state for each MultinetworkValidation. 530 */ 531 private class TestConnectivityState { 532 private final MultiNetworkValidator mMultiNetworkValidator; 533 534 final NetworkCallback mWifiNetworkCallback = new NetworkCallback() { 535 @Override 536 public void onAvailable(Network network) { 537 Log.i(TAG, "Wifi network available " + network); 538 stopTimerDisplayIfRequested(); 539 mMultiNetworkValidator.onWifiNetworkConnected(network); 540 } 541 542 @Override 543 public void onUnavailable() { 544 Log.e(TAG, "Failed to connect to wifi"); 545 stopTimerDisplayIfRequested(); 546 mMultiNetworkValidator.onWifiNetworkUnavailable(); 547 } 548 }; 549 final NetworkCallback mCellularNetworkCallback = new NetworkCallback() { 550 @Override 551 public void onAvailable(Network network) { 552 Log.i(TAG, "Cellular network available " + network); 553 stopTimerDisplayIfRequested(); 554 mMultiNetworkValidator.onCellularNetworkConnected(network); 555 } 556 557 @Override 558 public void onUnavailable() { 559 Log.e(TAG, "Cellular network unavailable "); 560 stopTimerDisplayIfRequested(); 561 mMultiNetworkValidator.onCellularNetworkUnavailable(); 562 } 563 }; 564 boolean mCellularNetworkRequested; 565 boolean mWifiNetworkRequested; 566 boolean mTimerStartRequested; 567 TestConnectivityState(MultiNetworkValidator validator)568 TestConnectivityState(MultiNetworkValidator validator) { 569 mMultiNetworkValidator = validator; 570 } 571 reset()572 void reset() { 573 mMainHandler.post(() -> stopTimerDisplayIfRequested()); 574 if (mWifiNetworkRequested) { 575 mConnectivityManager.unregisterNetworkCallback(mWifiNetworkCallback); 576 mWifiNetworkRequested = false; 577 } 578 if (mCellularNetworkRequested) { 579 mConnectivityManager.unregisterNetworkCallback(mCellularNetworkCallback); 580 mCellularNetworkRequested = false; 581 } 582 } 583 requestNetwork(boolean requireInternet)584 private void requestNetwork(boolean requireInternet) { 585 startTimerDisplay(WIFI_NETWORK_CONNECT_TIMEOUT_MS / 1000); 586 NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder() 587 .addTransportType(TRANSPORT_WIFI); 588 if (requireInternet) { 589 networkRequestBuilder.addCapability(NET_CAPABILITY_INTERNET); 590 } 591 NetworkRequest networkRequest = networkRequestBuilder.build(); 592 mWifiNetworkRequested = true; 593 mConnectivityManager.requestNetwork(networkRequest, mWifiNetworkCallback, 594 mMainHandler, WIFI_NETWORK_CONNECT_TIMEOUT_MS); 595 } 596 connectToWifiNetworkWithNoInternet()597 private void connectToWifiNetworkWithNoInternet() { 598 NetworkSpecifier specifier = 599 new WifiNetworkSpecifier.Builder() 600 .setSsid(mAccessPointSsid) 601 .setWpa2Passphrase(mPskValue) 602 .build(); 603 604 NetworkRequest networkRequest = new NetworkRequest.Builder() 605 .addTransportType(TRANSPORT_WIFI) 606 .setNetworkSpecifier(specifier) 607 .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 608 .build(); 609 610 mWifiNetworkRequested = true; 611 mConnectivityManager.requestNetwork(networkRequest, mWifiNetworkCallback, 612 mMainHandler); 613 } 614 connectToCellularNetwork()615 private void connectToCellularNetwork() { 616 NetworkRequest networkRequest = new NetworkRequest.Builder() 617 .addTransportType(TRANSPORT_CELLULAR) 618 .addCapability(NET_CAPABILITY_INTERNET) 619 .build(); 620 startTimerDisplay(CELLULAR_NETWORK_CONNECT_TIMEOUT_MS / 1000); 621 mCellularNetworkRequested = true; 622 mConnectivityManager.requestNetwork(networkRequest, mCellularNetworkCallback, 623 mMainHandler, CELLULAR_NETWORK_CONNECT_TIMEOUT_MS); 624 } 625 startTimerDisplay(int timeInSeconds)626 private void startTimerDisplay(int timeInSeconds) { 627 startTimerCountdownDisplay(timeInSeconds); 628 mTimerStartRequested = true; 629 } 630 631 /** Timer is a shared resource, change the state only if it's started in a request. */ stopTimerDisplayIfRequested()632 private void stopTimerDisplayIfRequested() { 633 if (mTimerStartRequested) { 634 mTimerStartRequested = false; 635 stopTimerCountdownDisplay(); 636 } 637 } 638 } 639 640 /** 641 * Manage the lifecycle of each test to be run in the validator. 642 * 643 * Each test goes through this cycle 644 * - Start 645 * - Connect to cellular network 646 * - Connect to wifi network 647 * - Check expectation 648 * - End test 649 */ 650 private abstract class MultiNetworkValidator { 651 final String mTestName; 652 final MultinetworkTestCallback mTestCallback; 653 final TestConnectivityState mConnectivityState; 654 final boolean mRunTestOnLowMemoryDevices; 655 656 int mTestDescription; 657 boolean mTestResult = false; 658 ValidatorState mValidatorState; 659 int mTestResultMessage = -1; 660 int mTestProgressMessage; 661 MultiNetworkValidator(MultinetworkTestCallback testCallback, String testName, int testDescription, boolean runTestOnLowMemoryDevices)662 MultiNetworkValidator(MultinetworkTestCallback testCallback, 663 String testName, 664 int testDescription, 665 boolean runTestOnLowMemoryDevices) { 666 mTestCallback = testCallback; 667 mTestName = testName; 668 mTestDescription = testDescription; 669 mConnectivityState = new TestConnectivityState(this); 670 mValidatorState = NOT_STARTED; 671 mRunTestOnLowMemoryDevices = runTestOnLowMemoryDevices; 672 } 673 674 /** Start test if not started. */ startTest()675 void startTest() { 676 Handler uiThreadHandler = new Handler(Looper.getMainLooper()); 677 if (mValidatorState == NOT_STARTED) { 678 mTestCallback.testStarted(); 679 toggleWifiAsync(hasToggled -> { 680 if (!hasToggled) { 681 onUnableToSetWifi(); 682 return; 683 } 684 mTestCallback.testProgress( 685 R.string.multinetwork_connectivity_test_connect_cellular); 686 mConnectivityState.connectToCellularNetwork(); 687 }); 688 } 689 } 690 691 /** Make sure that the state is restored for re-running the test. */ reset()692 void reset() { 693 mValidatorState = NOT_STARTED; 694 mTestResultMessage = -1; 695 mTestProgressMessage = -1; 696 } 697 698 /** Called when user has requested to continue with the test */ continueWithTest()699 void continueWithTest() { 700 mValidatorState = STARTED; 701 } 702 onCellularNetworkUnavailable()703 void onCellularNetworkUnavailable() { 704 endTest(false, R.string.multinetwork_status_mobile_connect_timed_out); 705 } 706 onUnableToSetWifi()707 void onUnableToSetWifi() { 708 endTest(false, R.string.multinetwork_status_unable_to_toggle_wifi); 709 } 710 endTest(boolean status, int messageResId)711 void endTest(boolean status, int messageResId) { 712 Log.i(TAG, "Ending test with status " + status + " message " + 713 MultiNetworkConnectivityTestActivity.this.getResources().getString(messageResId)); 714 mMainHandler.post(() -> { 715 mTestResult = status; 716 mTestResultMessage = messageResId; 717 mValidatorState = COMPLETED; 718 mTestCallback.testCompleted(MultiNetworkValidator.this); 719 mConnectivityState.reset(); 720 }); 721 } 722 723 /** Called when cellular network is connected. */ onCellularNetworkConnected(Network network)724 void onCellularNetworkConnected(Network network) { 725 if (mValidatorState != NOT_STARTED) { 726 return; 727 } 728 onContinuePreWifiConnect(); 729 } 730 731 /** 732 * @param transport The active network has this transport type 733 * @return 734 */ isExpectedTransportForActiveNetwork(int transport)735 boolean isExpectedTransportForActiveNetwork(int transport) { 736 Network activeNetwork = mConnectivityManager.getActiveNetwork(); 737 NetworkCapabilities activeNetworkCapabilities = 738 mConnectivityManager.getNetworkCapabilities(activeNetwork); 739 Log.i(TAG, "Network capabilities for " + activeNetwork + " " 740 + activeNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)); 741 return activeNetworkCapabilities.hasTransport(transport) 742 && activeNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET); 743 } 744 745 /** 746 * @param network to check if connected or not. 747 * @return 748 */ isNetworkConnected(Network network)749 boolean isNetworkConnected(Network network) { 750 NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(network); 751 boolean status = networkInfo != null && networkInfo.isConnectedOrConnecting(); 752 Log.i(TAG, "Network connection status " + network + " " + status); 753 return status; 754 } 755 756 /** 757 * Called before connecting to wifi. Specially if the concrete validator wants to 758 * prompt a message 759 */ onContinuePreWifiConnect()760 abstract void onContinuePreWifiConnect(); 761 762 /** Called when a wifi network is connected and available */ onWifiNetworkConnected(Network network)763 void onWifiNetworkConnected(Network network) { 764 Log.i(TAG, "Wifi network connected " + network); 765 } 766 onWifiNetworkUnavailable()767 void onWifiNetworkUnavailable() { 768 endTest(false, R.string.multinetwork_status_wifi_connect_timed_out); 769 } 770 shouldRunOnLowRamDevice()771 boolean shouldRunOnLowRamDevice() { 772 return mRunTestOnLowMemoryDevices; 773 } 774 } 775 776 /** 777 * Test that device does not lose cellular connectivity when it's connected to an access 778 * point with no connectivity using legacy API's. 779 */ 780 private class LegacyConnectToWifiWithNoInternetValidator extends MultiNetworkValidator { 781 LegacyConnectToWifiWithNoInternetValidator(int description)782 LegacyConnectToWifiWithNoInternetValidator(int description) { 783 super(mMultinetworkTestCallback, 784 "legacy_no_internet_test", 785 description, 786 /* runTestOnLowMemoryDevices = */ false); 787 } 788 789 790 @Override continueWithTest()791 void continueWithTest() { 792 super.continueWithTest(); 793 connectToWifi(); 794 } 795 796 @Override onContinuePreWifiConnect()797 void onContinuePreWifiConnect() { 798 mTestProgressMessage = R.string.multinetwork_connectivity_test_1_prereq; 799 mTestCallback.testProgress(mTestProgressMessage); 800 mValidatorState = WAITING_FOR_USER_INPUT; 801 requestUserConfirmation(); 802 } 803 804 @Override onWifiNetworkConnected(Network wifiNetwork)805 void onWifiNetworkConnected(Network wifiNetwork) { 806 super.onWifiNetworkConnected(wifiNetwork); 807 if (isConnectedToExpectedWifiNetwork()) { 808 startTimerCountdownDisplay(CELLULAR_NETWORK_RESTORE_TIMEOUT_MS / 1000); 809 mTestCallback.testProgress(R.string.multinetwork_connectivity_test_progress_2); 810 811 // Wait for CELLULAR_NETWORK_RESTORE_TIMEOUT_MS, before checking if there is still 812 // the active network as the cell network. 813 mMainHandler.postDelayed(() -> { 814 stopTimerCountdownDisplay(); 815 mMainHandler.post(() -> { 816 if (isExpectedTransportForActiveNetwork(TRANSPORT_CELLULAR) 817 && isNetworkConnected(wifiNetwork)) { 818 Log.d(TAG, "PASS test as device has connectivity"); 819 endTest(true, R.string.multinetwork_status_mobile_restore_success); 820 } else { 821 Log.d(TAG, "Fail test as device didn't have connectivity"); 822 endTest(false, R.string.multinetwork_status_mobile_restore_failed); 823 } 824 }); 825 }, CELLULAR_NETWORK_RESTORE_TIMEOUT_MS); 826 } else { 827 endTest(false, R.string.multinetwork_status_wifi_connect_wrong_ap); 828 } 829 } 830 connectToWifi()831 void connectToWifi() { 832 mTestCallback.testProgress(R.string.multinetwork_connectivity_test_connect_wifi); 833 requestUserConnectToApAsync((isSuccess) -> { 834 if (isSuccess) { 835 // Request network 836 mConnectivityState.requestNetwork(false); 837 } else { 838 endTest(false, R.string.multinetwork_status_wifi_connect_wrong_ap); 839 } 840 }); 841 } 842 } 843 844 /** 845 * Test that device restores lost cellular connectivity when it's connected to an access 846 * point which loses internet connectivity using legacy API's. 847 */ 848 private class LegacyConnectToWifiWithIntermittentInternetValidator 849 extends MultiNetworkValidator { 850 boolean mWaitingForWifiConnect = false; 851 boolean mWaitingForCelluarToConnectBack = false; 852 Network mWifiNetwork; 853 LegacyConnectToWifiWithIntermittentInternetValidator(int description)854 LegacyConnectToWifiWithIntermittentInternetValidator(int description) { 855 super(mMultinetworkTestCallback, 856 "legacy_no_internet_test", 857 description, 858 /* runTestOnLowMemoryDevices = */ false); 859 } 860 861 @Override continueWithTest()862 void continueWithTest() { 863 super.continueWithTest(); 864 if (mWaitingForWifiConnect) { 865 connectToWifi(); 866 } else if (mWaitingForCelluarToConnectBack) { 867 mWaitingForCelluarToConnectBack = false; 868 waitForConnectivityRestore(); 869 } 870 } 871 872 @Override onContinuePreWifiConnect()873 void onContinuePreWifiConnect() { 874 mTestProgressMessage = R.string.multinetwork_connectivity_test_2_prereq_1; 875 mTestCallback.testProgress(mTestProgressMessage); 876 mValidatorState = WAITING_FOR_USER_INPUT; 877 mWaitingForWifiConnect = true; 878 requestUserConfirmation(); 879 } 880 connectToWifi()881 void connectToWifi() { 882 mTestCallback.testProgress(R.string.multinetwork_connectivity_test_connect_wifi); 883 requestUserConnectToApAsync((isSuccess) -> { 884 if (isSuccess) { 885 // Request network 886 mConnectivityState.requestNetwork(true); 887 } else { 888 endTest(false, R.string.multinetwork_status_unable_to_toggle_wifi); 889 } 890 }); 891 } 892 893 @Override onWifiNetworkConnected(Network wifiNetwork)894 void onWifiNetworkConnected(Network wifiNetwork) { 895 super.onWifiNetworkConnected(wifiNetwork); 896 if (isConnectedToExpectedWifiNetwork()) { 897 // If the device is connected to the expected network, then update the wifi 898 // network to the latest. 899 mWifiNetwork = wifiNetwork; 900 // Do further processing only when the test is requesting and waiting for a wifi 901 // connection. 902 if (mWaitingForWifiConnect) { 903 mWaitingForWifiConnect = false; 904 startTimerCountdownDisplay(WIFI_NETWORK_CONNECT_TO_BE_ACTIVE_MS / 1000); 905 906 // Wait for WIFI_NETWORK_CONNECT_TO_BE_ACTIVE_MS, before checking 907 // if device has the active network as wifi network.. 908 mTestCallback.testProgress(R.string.multinetwork_connectivity_test_progress_2); 909 mMainHandler.postDelayed(() -> { 910 stopTimerCountdownDisplay(); 911 // In this case both active and peer are same as Wifi has internet access. 912 if (isExpectedTransportForActiveNetwork(TRANSPORT_WIFI) 913 && isNetworkConnected(mWifiNetwork)) { 914 // Ask the user to turn off wifi on the router and check connectivity. 915 mTestProgressMessage = 916 R.string.multinetwork_connectivity_test_2_prereq_2; 917 mValidatorState = WAITING_FOR_USER_INPUT; 918 mTestCallback.testProgress(mTestProgressMessage); 919 mWaitingForCelluarToConnectBack = true; 920 requestUserConfirmation(); 921 } else { 922 Log.d(TAG, "Fail test as device didn't have connectivity"); 923 endTest(false, R.string.multinetwork_status_wifi_connectivity_failed); 924 } 925 }, WIFI_NETWORK_CONNECT_TO_BE_ACTIVE_MS); 926 } 927 } else { 928 endTest(false, R.string.multinetwork_status_wifi_connect_wrong_ap); 929 } 930 } 931 932 @Override reset()933 void reset() { 934 super.reset(); 935 mWaitingForCelluarToConnectBack = false; 936 mWaitingForWifiConnect = false; 937 mWifiNetwork = null; 938 } 939 940 @Override onWifiNetworkUnavailable()941 void onWifiNetworkUnavailable() { 942 if (mWaitingForWifiConnect) { 943 super.onWifiNetworkUnavailable(); 944 } 945 } 946 waitForConnectivityRestore()947 void waitForConnectivityRestore() { 948 mTestCallback.testProgress(R.string.multinetwork_connectivity_test_progress_1); 949 mConnectivityManager.reportNetworkConnectivity(mWifiNetwork, false); 950 startTimerCountdownDisplay( 951 CELLULAR_NETWORK_RESTORE_AFTER_WIFI_INTERNET_LOST_TIMEOUT_MS / 1000); 952 // Wait for CELLULAR_NETWORK_RESTORE_AFTER_WIFI_INTERNET_LOST_TIMEOUT_MS, 953 // before checking if device now has the active network as cell network. 954 mMainHandler.postDelayed(() -> { 955 stopTimerCountdownDisplay(); 956 // Check if device has fallen back to cellular network when it loses internet access 957 // in the wifi network. 958 if (isExpectedTransportForActiveNetwork(TRANSPORT_CELLULAR) 959 && isNetworkConnected(mWifiNetwork)) { 960 Log.d(TAG, "PASS test as device has connectivity"); 961 endTest(true, R.string.multinetwork_status_mobile_restore_success); 962 } else { 963 Log.d(TAG, "Fail test as device didn't have connectivity"); 964 endTest(false, R.string.multinetwork_status_mobile_restore_failed); 965 } 966 }, CELLULAR_NETWORK_RESTORE_AFTER_WIFI_INTERNET_LOST_TIMEOUT_MS); 967 } 968 } 969 970 /** 971 * Test that device does not lose cellular connectivity when it's connected to an access 972 * point with no connectivity using the new API's. 973 */ 974 private class ConnectToWifiWithNoInternetValidator extends MultiNetworkValidator { 975 ConnectToWifiWithNoInternetValidator(int description)976 ConnectToWifiWithNoInternetValidator(int description) { 977 super(mMultinetworkTestCallback, 978 "no_internet_test", 979 description, 980 /* runTestOnLowMemoryDevices = */ true); 981 } 982 983 984 @Override continueWithTest()985 void continueWithTest() { 986 super.continueWithTest(); 987 connectToWifi(); 988 } 989 990 @Override onContinuePreWifiConnect()991 void onContinuePreWifiConnect() { 992 mTestProgressMessage = R.string.multinetwork_connectivity_test_1_prereq; 993 mTestCallback.testProgress(mTestProgressMessage); 994 mValidatorState = WAITING_FOR_USER_INPUT; 995 requestUserConfirmation(); 996 } 997 connectToWifi()998 void connectToWifi() { 999 mTestCallback.testProgress(R.string.multinetwork_connectivity_test_connect_wifi); 1000 mConnectivityState.connectToWifiNetworkWithNoInternet(); 1001 } 1002 1003 @Override onWifiNetworkConnected(Network wifiNetwork)1004 void onWifiNetworkConnected(Network wifiNetwork) { 1005 super.onWifiNetworkConnected(wifiNetwork); 1006 if (isConnectedToExpectedWifiNetwork()) { 1007 startTimerCountdownDisplay(CELLULAR_NETWORK_RESTORE_TIMEOUT_MS / 1000); 1008 mTestCallback.testProgress(R.string.multinetwork_connectivity_test_progress_2); 1009 1010 // Wait for CELLULAR_NETWORK_RESTORE_TIMEOUT_MS, before checking if there is still 1011 // the active network as the cell network. 1012 mMainHandler.postDelayed(() -> { 1013 stopTimerCountdownDisplay(); 1014 mMainHandler.post(() -> { 1015 if (isExpectedTransportForActiveNetwork(TRANSPORT_CELLULAR) 1016 && isNetworkConnected(wifiNetwork)) { 1017 Log.d(TAG, "PASS test as device has connectivity"); 1018 endTest(true, R.string.multinetwork_status_mobile_restore_success); 1019 } else { 1020 Log.d(TAG, "Fail test as device didn't have connectivity"); 1021 endTest(false, R.string.multinetwork_status_mobile_restore_failed); 1022 } 1023 }); 1024 }, CELLULAR_NETWORK_RESTORE_TIMEOUT_MS); 1025 } else { 1026 endTest(false, R.string.multinetwork_status_wifi_connect_wrong_ap); 1027 } 1028 } 1029 } 1030 1031 private interface SetWifiCallback { onComplete(boolean isSuccess)1032 void onComplete(boolean isSuccess); 1033 } 1034 1035 private interface ConnectApCallback { onComplete(boolean isSuccess)1036 void onComplete(boolean isSuccess); 1037 } 1038 } 1039