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