| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.cts.verifier.net; |
| |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; |
| import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; |
| import static android.net.NetworkCapabilities.TRANSPORT_WIFI; |
| |
| import static com.android.cts.verifier.net.MultiNetworkConnectivityTestActivity.ValidatorState |
| .COMPLETED; |
| import static com.android.cts.verifier.net.MultiNetworkConnectivityTestActivity.ValidatorState |
| .NOT_STARTED; |
| import static com.android.cts.verifier.net.MultiNetworkConnectivityTestActivity.ValidatorState |
| .STARTED; |
| import static com.android.cts.verifier.net.MultiNetworkConnectivityTestActivity.ValidatorState |
| .WAITING_FOR_USER_INPUT; |
| |
| import android.app.ActivityManager; |
| import android.app.AlertDialog; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.net.ConnectivityManager; |
| import android.net.ConnectivityManager.NetworkCallback; |
| import android.net.DhcpInfo; |
| import android.net.Network; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkInfo; |
| import android.net.NetworkRequest; |
| import android.net.NetworkSpecifier; |
| import android.net.wifi.SupplicantState; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiInfo; |
| import android.net.wifi.WifiManager; |
| import android.net.wifi.WifiNetworkSpecifier; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.provider.Settings; |
| import android.telephony.TelephonyManager; |
| import android.text.Editable; |
| import android.text.TextUtils; |
| import android.text.TextWatcher; |
| import android.util.Log; |
| import android.widget.Button; |
| import android.widget.EditText; |
| import android.widget.TextView; |
| |
| import com.android.cts.verifier.PassFailButtons; |
| import com.android.cts.verifier.R; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * A CTS verifier to ensure that when an app calls WifiManager#enableNetwork, |
| * - When the wifi network does not have internet connectivity, the device should |
| * not disable other forms or connectivity, for example cellular. |
| * - When the wifi network that the phone connects to loses connectivity, then |
| * other forms of connectivity are restored, for example cellular when the phone |
| * detects that the Wifi network doesn't have internet. |
| */ |
| public class MultiNetworkConnectivityTestActivity extends PassFailButtons.Activity { |
| public static final String TAG = "MultinetworkTest"; |
| public static final int ENABLE_DISABLE_WIFI_DELAY_MS = 3000; |
| public static final int WIFI_NETWORK_CONNECT_TIMEOUT_MS = 45000; |
| public static final int WIFI_NETWORK_CONNECT_TO_BE_ACTIVE_MS = 25000; |
| public static final int CELLULAR_NETWORK_CONNECT_TIMEOUT_MS = 45000; |
| public static final int CELLULAR_NETWORK_RESTORE_TIMEOUT_MS = 15000; |
| public static final int CELLULAR_NETWORK_RESTORE_AFTER_WIFI_INTERNET_LOST_TIMEOUT_MS = 60000; |
| |
| /** |
| * Called by the validator test when it has different states. |
| */ |
| private interface MultinetworkTestCallback { |
| /** Notify test has started */ |
| void testStarted(); |
| |
| /** Show / display progress using the message in progressMessage */ |
| void testProgress(int progressMessageResourceId); |
| |
| /** Test completed for the validator */ |
| void testCompleted(MultiNetworkValidator validator); |
| } |
| |
| enum ValidatorState { |
| NOT_STARTED, |
| STARTED, |
| WAITING_FOR_USER_INPUT, |
| COMPLETED, |
| } |
| |
| private final Handler mMainHandler = new Handler(Looper.getMainLooper()); |
| // Used only for posting bugs / debugging. |
| private final BroadcastReceiver mMultiNetConnectivityReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| Log.d(TAG, "Action " + intent.getAction()); |
| if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { |
| NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); |
| Log.d(TAG, "New network state " + networkInfo.getState()); |
| } else if (intent.getAction().equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) { |
| SupplicantState state = intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE); |
| Log.d(TAG, "New supplicant state. " + state.name()); |
| Log.d(TAG, "Is connected to expected wifi AP. " + |
| isConnectedToExpectedWifiNetwork()); |
| } |
| } |
| }; |
| private final MultinetworkTestCallback mMultinetworkTestCallback = |
| new MultinetworkTestCallback() { |
| |
| @Override |
| public void testStarted() { |
| mTestInfoView.setText(R.string.multinetwork_connectivity_test_running); |
| } |
| |
| @Override |
| public void testProgress(int progressMessageResourceId) { |
| mTestInfoView.setText(progressMessageResourceId); |
| } |
| |
| @Override |
| public void testCompleted(MultiNetworkValidator validator) { |
| if (validator == mMultiNetworkValidators.get(mMultiNetworkValidators.size() |
| - 1)) { |
| // Done all tests. |
| boolean passed = true; |
| for (MultiNetworkValidator multiNetworkValidator : |
| mMultiNetworkValidators) { |
| passed = passed && multiNetworkValidator.mTestResult; |
| } |
| setTestResultAndFinish(passed); |
| } else if (!validator.mTestResult) { |
| setTestResultAndFinish(false); |
| } else { |
| for (int i = 0; i < mMultiNetworkValidators.size(); i++) { |
| if (mMultiNetworkValidators.get(i) == validator) { |
| mCurrentValidator = mMultiNetworkValidators.get(i + 1); |
| mTestNameView.setText(mCurrentValidator.mTestDescription); |
| mCurrentValidator.startTest(); |
| break; |
| } |
| } |
| } |
| } |
| }; |
| private List<MultiNetworkValidator> mMultiNetworkValidators = Collections.emptyList(); |
| private final Runnable mTimeToCompletionRunnable = new Runnable() { |
| @Override |
| public void run() { |
| mSecondsToCompletion--; |
| if (mSecondsToCompletion > 0) { |
| mStartButton.setText("" + mSecondsToCompletion); |
| mMainHandler.postDelayed(this, 1000); |
| } |
| } |
| }; |
| |
| // User interface elements. |
| private Button mStartButton; |
| private TextView mTestNameView; |
| private TextView mTestInfoView; |
| private EditText mAccessPointSsidEditText; |
| private EditText mPskEditText; |
| |
| // Current state memebers. |
| private MultiNetworkValidator mCurrentValidator; |
| private int mSecondsToCompletion; |
| private String mAccessPointSsid = ""; |
| private String mPskValue = ""; |
| private ConnectivityManager mConnectivityManager; |
| private WifiManager mWifiManager; |
| |
| private int mRecordedWifiConfiguration = -1; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); |
| mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); |
| mMultiNetworkValidators = createMultiNetworkValidators(); |
| |
| recordCurrentWifiState(); |
| setupUserInterface(); |
| setupBroadcastReceivers(); |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| setupCurrentTestStateOnResume(); |
| } |
| |
| @Override |
| protected void onDestroy() { |
| super.onDestroy(); |
| destroyBroadcastReceivers(); |
| restoreOriginalWifiState(); |
| } |
| |
| private void recordCurrentWifiState() { |
| if (!mWifiManager.isWifiEnabled()) { |
| return; |
| } |
| WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); |
| if (wifiInfo != null && SupplicantState.COMPLETED.equals(wifiInfo.getSupplicantState())) { |
| mRecordedWifiConfiguration = wifiInfo.getNetworkId(); |
| } |
| } |
| |
| private List<MultiNetworkValidator> createMultiNetworkValidators() { |
| MultiNetworkValidator[] allValidators = { |
| new ConnectToWifiWithNoInternetValidator( |
| R.string.multinetwork_connectivity_test_1_desc), |
| new LegacyConnectToWifiWithNoInternetValidator( |
| R.string.multinetwork_connectivity_test_2_desc), |
| new LegacyConnectToWifiWithIntermittentInternetValidator( |
| R.string.multinetwork_connectivity_test_3_desc) |
| }; |
| |
| List<MultiNetworkValidator> result = new ArrayList<>(); |
| boolean isLowRamDevice = isLowRamDevice(); |
| for (MultiNetworkValidator validator : allValidators) { |
| if (!isLowRamDevice || validator.shouldRunOnLowRamDevice()) { |
| result.add(validator); |
| } |
| } |
| return result; |
| } |
| |
| private void restoreOriginalWifiState() { |
| if (mRecordedWifiConfiguration >= 0) { |
| mWifiManager.enableNetwork(mRecordedWifiConfiguration, true); |
| } |
| } |
| |
| private boolean requestSystemAlertWindowPerimissionIfRequired() { |
| if (isLowRamDevice()) { |
| // For low ram devices, we won't run tests that depend on this permission. |
| return true; |
| } |
| |
| boolean hadPermission = false; |
| if (!Settings.canDrawOverlays(this)) { |
| AlertDialog alertDialog = new AlertDialog.Builder(this) |
| .setMessage(R.string.multinetwork_connectivity_overlay_permission_message) |
| .setPositiveButton( |
| R.string.multinetwork_connectivity_overlay_permission_positive, |
| (a, b) -> { |
| Intent myIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); |
| startActivity(myIntent); |
| }) |
| .setNegativeButton( |
| R.string.multinetwork_connectivity_overlay_permission_negative, |
| (a, b) -> {}) |
| .create(); |
| alertDialog.show(); |
| } else { |
| hadPermission = true; |
| } |
| |
| return hadPermission; |
| } |
| |
| private void setupUserInterface() { |
| setContentView(R.layout.multinetwork_connectivity); |
| setInfoResources( |
| R.string.multinetwork_connectivity_test, |
| R.string.multinetwork_connectivity_test_instructions, |
| -1); |
| mStartButton = findViewById(R.id.start_multinet_btn); |
| mTestNameView = findViewById(R.id.current_test); |
| mTestInfoView = findViewById(R.id.test_progress_info); |
| mAccessPointSsidEditText = findViewById(R.id.test_ap_ssid); |
| mPskEditText = findViewById(R.id.test_ap_psk); |
| mAccessPointSsidEditText.addTextChangedListener(new TextWatcher() { |
| @Override |
| public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} |
| |
| @Override |
| public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} |
| |
| @Override |
| public void afterTextChanged(Editable editable) { |
| mAccessPointSsid = editable.toString(); |
| Log.i(TAG, "Connecting to " + mAccessPointSsid); |
| mStartButton.setEnabled(isReadyToStart()); |
| } |
| }); |
| mPskEditText.addTextChangedListener(new TextWatcher() { |
| @Override |
| public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} |
| |
| @Override |
| public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} |
| |
| @Override |
| public void afterTextChanged(Editable editable) { |
| mPskValue = editable.toString(); |
| mStartButton.setEnabled(isReadyToStart()); |
| } |
| }); |
| mStartButton.setOnClickListener(view -> processStartClicked()); |
| } |
| |
| private void setupBroadcastReceivers() { |
| IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); |
| intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); |
| intentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); |
| intentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); |
| registerReceiver(mMultiNetConnectivityReceiver, intentFilter); |
| } |
| |
| private void destroyBroadcastReceivers() { |
| unregisterReceiver(mMultiNetConnectivityReceiver); |
| } |
| |
| private boolean isReadyToStart() { |
| return !(TextUtils.isEmpty(mAccessPointSsid) || TextUtils.isEmpty(mPskValue)); |
| } |
| |
| private static boolean isNetworkCellularAndHasInternet(ConnectivityManager connectivityManager, |
| Network network) { |
| NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); |
| return capabilities.hasTransport(TRANSPORT_CELLULAR) |
| && capabilities.hasCapability(NET_CAPABILITY_INTERNET); |
| } |
| |
| private boolean isMobileDataEnabled(TelephonyManager telephonyManager) { |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| return telephonyManager.isDataEnabled(); |
| } |
| Network[] allNetworks = mConnectivityManager.getAllNetworks(); |
| for (Network network : allNetworks) { |
| if (isNetworkCellularAndHasInternet(mConnectivityManager, network)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean checkPreRequisites() { |
| TelephonyManager telephonyManager = |
| (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); |
| if (telephonyManager == null) { |
| Log.e(TAG, "Device does not have telephony manager"); |
| mTestInfoView.setText(R.string.multinetwork_connectivity_test_all_prereq_1); |
| return false; |
| } else if (!isMobileDataEnabled(telephonyManager)) { |
| Log.e(TAG, "Device mobile data is not available"); |
| mTestInfoView.setText(R.string.multinetwork_connectivity_test_all_prereq_2); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * If tester went back and came in again, make sure that test resumes from the previous state. |
| */ |
| private void setupCurrentTestStateOnResume() { |
| mCurrentValidator = null; |
| mStartButton.setEnabled(false); |
| |
| if (!checkPreRequisites()) { |
| return; |
| } |
| |
| for (MultiNetworkValidator multiNetworkValidator : mMultiNetworkValidators) { |
| if (multiNetworkValidator.mValidatorState != COMPLETED) { |
| mCurrentValidator = multiNetworkValidator; |
| break; |
| } |
| } |
| if (mCurrentValidator != null) { |
| mTestNameView.setText(mCurrentValidator.mTestDescription); |
| |
| switch (mCurrentValidator.mValidatorState) { |
| case NOT_STARTED: |
| mStartButton.setText(R.string.multinetwork_connectivity_test_start); |
| mStartButton.setEnabled(isReadyToStart()); |
| break; |
| case STARTED: |
| mTestInfoView.setText(getResources().getString( |
| mCurrentValidator.mTestProgressMessage)); |
| break; |
| case WAITING_FOR_USER_INPUT: |
| mStartButton.setText(R.string.multinetwork_connectivity_test_continue); |
| mStartButton.setEnabled(true); |
| mTestInfoView.setText(getResources().getString( |
| mCurrentValidator.mTestProgressMessage)); |
| case COMPLETED: |
| break; |
| } |
| mTestNameView.setText(mCurrentValidator.mTestDescription); |
| } else { |
| // All tests completed, so need to re run. It's not likely to get here as |
| // the default action when all test completes is to mark success and finish. |
| mStartButton.setText(R.string.multinetwork_connectivity_test_rerun); |
| mStartButton.setEnabled(true); |
| rerunMultinetworkTests(); |
| mCurrentValidator = mMultiNetworkValidators.get(0); |
| } |
| } |
| |
| private void rerunMultinetworkTests() { |
| for (MultiNetworkValidator validator : mMultiNetworkValidators) { |
| validator.reset(); |
| } |
| } |
| |
| private void requestUserConfirmation() { |
| mMainHandler.post(() -> { |
| mStartButton.setText(R.string.multinetwork_connectivity_test_continue); |
| mStartButton.setEnabled(true); |
| }); |
| } |
| |
| private void processStartClicked() { |
| if (!requestSystemAlertWindowPerimissionIfRequired()) { |
| Log.e(TAG, "System alert dialog permission not granted to CTSVerifier"); |
| return; |
| } |
| |
| if (mCurrentValidator == null) { |
| rerunMultinetworkTests(); |
| setupCurrentTestStateOnResume(); |
| } |
| mStartButton.setEnabled(false); |
| if (mCurrentValidator.mValidatorState == NOT_STARTED) { |
| mCurrentValidator.startTest(); |
| } else if (mCurrentValidator.mValidatorState == WAITING_FOR_USER_INPUT) { |
| mStartButton.setEnabled(false); |
| mCurrentValidator.continueWithTest(); |
| } |
| } |
| |
| private WifiConfiguration buildWifiConfiguration() { |
| WifiConfiguration wifiConfiguration = new WifiConfiguration(); |
| wifiConfiguration.SSID = "\"" + mAccessPointSsid + "\""; |
| wifiConfiguration.preSharedKey = "\"" + mPskValue + "\""; |
| wifiConfiguration.status = WifiConfiguration.Status.ENABLED; |
| wifiConfiguration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); |
| wifiConfiguration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); |
| wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); |
| wifiConfiguration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); |
| wifiConfiguration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); |
| wifiConfiguration.allowedProtocols.set(WifiConfiguration.Protocol.RSN); |
| return wifiConfiguration; |
| } |
| |
| private int getOrAddLegacyNetwork() { |
| List<WifiConfiguration> availableConfigurations = mWifiManager.getConfiguredNetworks(); |
| for (WifiConfiguration configuration : availableConfigurations) { |
| if (mAccessPointSsid.equals(configuration.SSID)) { |
| return configuration.networkId; |
| } |
| } |
| int newNetwork = mWifiManager.addNetwork(buildWifiConfiguration()); |
| return newNetwork; |
| } |
| |
| private boolean isConnectedToExpectedWifiNetwork() { |
| WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); |
| DhcpInfo dhcpInfo = mWifiManager.getDhcpInfo(); |
| Log.i(TAG, "Checking connected to expected " + mAccessPointSsid); |
| if (wifiInfo != null |
| && wifiInfo.getSupplicantState().equals(SupplicantState.COMPLETED) |
| && dhcpInfo != null) { |
| String failsafeSsid = String.format("\"%s\"", mAccessPointSsid); |
| Log.i(TAG, "Connected to " + wifiInfo.getSSID() + " expected " + mAccessPointSsid); |
| return mAccessPointSsid.equals(wifiInfo.getSSID()) |
| || failsafeSsid.equals(wifiInfo.getSSID()); |
| } |
| return false; |
| } |
| |
| private void startTimerCountdownDisplay(int timeoutInSeconds) { |
| mMainHandler.post(() -> mSecondsToCompletion = timeoutInSeconds); |
| mMainHandler.post(mTimeToCompletionRunnable); |
| } |
| |
| private void stopTimerCountdownDisplay() { |
| mMainHandler.removeCallbacks(mTimeToCompletionRunnable); |
| mStartButton.setText("--"); |
| } |
| |
| private boolean isLowRamDevice() { |
| ActivityManager activityManager = |
| (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); |
| return activityManager.isLowRamDevice(); |
| } |
| |
| /** |
| * Manage the connectivity state for each MultinetworkValidation. |
| */ |
| private class TestConnectivityState { |
| private final MultiNetworkValidator mMultiNetworkValidator; |
| |
| final NetworkCallback mWifiNetworkCallback = new NetworkCallback() { |
| @Override |
| public void onAvailable(Network network) { |
| Log.i(TAG, "Wifi network available " + network.netId); |
| stopTimerDisplayIfRequested(); |
| mMultiNetworkValidator.onWifiNetworkConnected(network); |
| } |
| |
| @Override |
| public void onUnavailable() { |
| Log.e(TAG, "Failed to connect to wifi"); |
| stopTimerDisplayIfRequested(); |
| mMultiNetworkValidator.onWifiNetworkUnavailable(); |
| } |
| }; |
| final NetworkCallback mCellularNetworkCallback = new NetworkCallback() { |
| @Override |
| public void onAvailable(Network network) { |
| Log.i(TAG, "Cellular network available " + network.netId); |
| stopTimerDisplayIfRequested(); |
| mMultiNetworkValidator.onCellularNetworkConnected(network); |
| } |
| |
| @Override |
| public void onUnavailable() { |
| Log.e(TAG, "Cellular network unavailable "); |
| stopTimerDisplayIfRequested(); |
| mMultiNetworkValidator.onCellularNetworkUnavailable(); |
| } |
| }; |
| boolean mCellularNetworkRequested; |
| boolean mWifiNetworkRequested; |
| boolean mTimerStartRequested; |
| |
| TestConnectivityState(MultiNetworkValidator validator) { |
| mMultiNetworkValidator = validator; |
| } |
| |
| void reset() { |
| mMainHandler.post(() -> stopTimerDisplayIfRequested()); |
| if (mWifiNetworkRequested) { |
| mConnectivityManager.unregisterNetworkCallback(mWifiNetworkCallback); |
| mWifiNetworkRequested = false; |
| } |
| if (mCellularNetworkRequested) { |
| mConnectivityManager.unregisterNetworkCallback(mCellularNetworkCallback); |
| mCellularNetworkRequested = false; |
| } |
| } |
| |
| private void legacyConnectToWifiNetwork(boolean requireInternet) { |
| // If device is not connected to the expected WifiNetwork, connect to the wifi Network. |
| // Timeout with failure if it can't connect. |
| if (!isConnectedToExpectedWifiNetwork()) { |
| int network = getOrAddLegacyNetwork(); |
| WifiManager wifiManager = (WifiManager) getApplicationContext() |
| .getSystemService(Context.WIFI_SERVICE); |
| wifiManager.enableNetwork(network, true); |
| } |
| startTimerDisplay(WIFI_NETWORK_CONNECT_TIMEOUT_MS / 1000); |
| NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI); |
| if (requireInternet) { |
| networkRequestBuilder.addCapability(NET_CAPABILITY_INTERNET); |
| } |
| NetworkRequest networkRequest = networkRequestBuilder.build(); |
| mWifiNetworkRequested = true; |
| mConnectivityManager.requestNetwork(networkRequest, mWifiNetworkCallback, |
| mMainHandler, WIFI_NETWORK_CONNECT_TIMEOUT_MS); |
| } |
| |
| private void connectToWifiNetworkWithNoInternet() { |
| NetworkSpecifier specifier = |
| new WifiNetworkSpecifier.Builder() |
| .setSsid(mAccessPointSsid) |
| .setWpa2Passphrase(mPskValue) |
| .build(); |
| |
| NetworkRequest networkRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI) |
| .setNetworkSpecifier(specifier) |
| .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) |
| .build(); |
| |
| mWifiNetworkRequested = true; |
| mConnectivityManager.requestNetwork(networkRequest, mWifiNetworkCallback, |
| mMainHandler); |
| } |
| |
| private void connectToCellularNetwork() { |
| NetworkRequest networkRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR) |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .build(); |
| startTimerDisplay(CELLULAR_NETWORK_CONNECT_TIMEOUT_MS / 1000); |
| mCellularNetworkRequested = true; |
| mConnectivityManager.requestNetwork(networkRequest, mCellularNetworkCallback, |
| mMainHandler, CELLULAR_NETWORK_CONNECT_TIMEOUT_MS); |
| } |
| |
| private void startTimerDisplay(int timeInSeconds) { |
| startTimerCountdownDisplay(timeInSeconds); |
| mTimerStartRequested = true; |
| } |
| |
| /** Timer is a shared resource, change the state only if it's started in a request. */ |
| private void stopTimerDisplayIfRequested() { |
| if (mTimerStartRequested) { |
| mTimerStartRequested = false; |
| stopTimerCountdownDisplay(); |
| } |
| } |
| } |
| |
| /** |
| * Manage the lifecycle of each test to be run in the validator. |
| * |
| * Each test goes through this cycle |
| * - Start |
| * - Connect to cellular network |
| * - Connect to wifi network |
| * - Check expectation |
| * - End test |
| */ |
| private abstract class MultiNetworkValidator { |
| final String mTestName; |
| final MultinetworkTestCallback mTestCallback; |
| final TestConnectivityState mConnectivityState; |
| final boolean mRunTestOnLowMemoryDevices; |
| |
| int mTestDescription; |
| boolean mTestResult = false; |
| ValidatorState mValidatorState; |
| int mTestResultMessage = -1; |
| int mTestProgressMessage; |
| |
| MultiNetworkValidator(MultinetworkTestCallback testCallback, |
| String testName, |
| int testDescription, |
| boolean runTestOnLowMemoryDevices) { |
| mTestCallback = testCallback; |
| mTestName = testName; |
| mTestDescription = testDescription; |
| mConnectivityState = new TestConnectivityState(this); |
| mValidatorState = NOT_STARTED; |
| mRunTestOnLowMemoryDevices = runTestOnLowMemoryDevices; |
| } |
| |
| /** Start test if not started. */ |
| void startTest() { |
| if (mValidatorState == NOT_STARTED) { |
| mTestCallback.testStarted(); |
| WifiManager wifiManager = (WifiManager) getApplicationContext() |
| .getSystemService(Context.WIFI_SERVICE); |
| wifiManager.setWifiEnabled(false); |
| mMainHandler.postDelayed(() -> { |
| wifiManager.setWifiEnabled(true); |
| mTestCallback.testProgress( |
| R.string.multinetwork_connectivity_test_connect_cellular); |
| mConnectivityState.connectToCellularNetwork(); |
| }, ENABLE_DISABLE_WIFI_DELAY_MS); |
| } |
| } |
| |
| /** Make sure that the state is restored for re-running the test. */ |
| void reset() { |
| mValidatorState = NOT_STARTED; |
| mTestResultMessage = -1; |
| mTestProgressMessage = -1; |
| } |
| |
| /** Called when user has requested to continue with the test */ |
| void continueWithTest() { |
| mValidatorState = STARTED; |
| } |
| |
| void onCellularNetworkUnavailable() { |
| endTest(false, R.string.multinetwork_status_mobile_connect_timed_out); |
| } |
| |
| void endTest(boolean status, int messageResId) { |
| Log.i(TAG, "Ending test with status " + status + " message " + |
| MultiNetworkConnectivityTestActivity.this.getResources().getString(messageResId)); |
| mMainHandler.post(() -> { |
| mTestResult = status; |
| mTestResultMessage = messageResId; |
| mValidatorState = COMPLETED; |
| mTestCallback.testCompleted(MultiNetworkValidator.this); |
| mConnectivityState.reset(); |
| }); |
| } |
| |
| /** Called when cellular network is connected. */ |
| void onCellularNetworkConnected(Network network) { |
| onContinuePreWifiConnect(); |
| } |
| |
| /** |
| * @param transport The active network has this transport type |
| * @return |
| */ |
| boolean isExpectedTransportForActiveNetwork(int transport) { |
| Network activeNetwork = mConnectivityManager.getActiveNetwork(); |
| NetworkCapabilities activeNetworkCapabilities = |
| mConnectivityManager.getNetworkCapabilities(activeNetwork); |
| Log.i(TAG, "Network capabilities for " + activeNetwork.netId + " " |
| + activeNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)); |
| return activeNetworkCapabilities.hasTransport(transport) |
| && activeNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET); |
| } |
| |
| /** |
| * @param network to check if connected or not. |
| * @return |
| */ |
| boolean isNetworkConnected(Network network) { |
| NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(network); |
| boolean status = networkInfo != null && networkInfo.isConnectedOrConnecting(); |
| Log.i(TAG, "Network connection status " + network.netId + " " + status); |
| return status; |
| } |
| |
| /** |
| * Called before connecting to wifi. Specially if the concrete validator wants to |
| * prompt a message |
| */ |
| abstract void onContinuePreWifiConnect(); |
| |
| /** Called when a wifi network is connected and available */ |
| void onWifiNetworkConnected(Network network) { |
| Log.i(TAG, "Wifi network connected " + network.netId); |
| } |
| |
| void onWifiNetworkUnavailable() { |
| endTest(false, R.string.multinetwork_status_wifi_connect_timed_out); |
| } |
| |
| boolean shouldRunOnLowRamDevice() { |
| return mRunTestOnLowMemoryDevices; |
| } |
| } |
| |
| /** |
| * Test that device does not lose cellular connectivity when it's connected to an access |
| * point with no connectivity using legacy API's. |
| */ |
| private class LegacyConnectToWifiWithNoInternetValidator extends MultiNetworkValidator { |
| |
| LegacyConnectToWifiWithNoInternetValidator(int description) { |
| super(mMultinetworkTestCallback, |
| "legacy_no_internet_test", |
| description, |
| /* runTestOnLowMemoryDevices = */ false); |
| } |
| |
| |
| @Override |
| void continueWithTest() { |
| super.continueWithTest(); |
| connectToWifi(); |
| } |
| |
| @Override |
| void onContinuePreWifiConnect() { |
| mTestProgressMessage = R.string.multinetwork_connectivity_test_1_prereq; |
| mTestCallback.testProgress(mTestProgressMessage); |
| mValidatorState = WAITING_FOR_USER_INPUT; |
| requestUserConfirmation(); |
| } |
| |
| @Override |
| void onWifiNetworkConnected(Network wifiNetwork) { |
| super.onWifiNetworkConnected(wifiNetwork); |
| if (isConnectedToExpectedWifiNetwork()) { |
| startTimerCountdownDisplay(CELLULAR_NETWORK_RESTORE_TIMEOUT_MS / 1000); |
| mTestCallback.testProgress(R.string.multinetwork_connectivity_test_progress_2); |
| |
| // Wait for CELLULAR_NETWORK_RESTORE_TIMEOUT_MS, before checking if there is still |
| // the active network as the cell network. |
| mMainHandler.postDelayed(() -> { |
| stopTimerCountdownDisplay(); |
| mMainHandler.post(() -> { |
| if (isExpectedTransportForActiveNetwork(TRANSPORT_CELLULAR) |
| && isNetworkConnected(wifiNetwork)) { |
| Log.d(TAG, "PASS test as device has connectivity"); |
| endTest(true, R.string.multinetwork_status_mobile_restore_success); |
| } else { |
| Log.d(TAG, "Fail test as device didn't have connectivity"); |
| endTest(false, R.string.multinetwork_status_mobile_restore_failed); |
| } |
| }); |
| }, CELLULAR_NETWORK_RESTORE_TIMEOUT_MS); |
| } else { |
| endTest(false, R.string.multinetwork_status_wifi_connect_wrong_ap); |
| } |
| } |
| |
| void connectToWifi() { |
| mTestCallback.testProgress(R.string.multinetwork_connectivity_test_connect_wifi); |
| mConnectivityState.legacyConnectToWifiNetwork(false); |
| } |
| } |
| |
| /** |
| * Test that device restores lost cellular connectivity when it's connected to an access |
| * point which loses internet connectivity using legacy API's. |
| */ |
| private class LegacyConnectToWifiWithIntermittentInternetValidator |
| extends MultiNetworkValidator { |
| boolean mWaitingForWifiConnect = false; |
| boolean mWaitingForCelluarToConnectBack = false; |
| Network mWifiNetwork; |
| |
| LegacyConnectToWifiWithIntermittentInternetValidator(int description) { |
| super(mMultinetworkTestCallback, |
| "legacy_no_internet_test", |
| description, |
| /* runTestOnLowMemoryDevices = */ false); |
| } |
| |
| @Override |
| void continueWithTest() { |
| super.continueWithTest(); |
| if (mWaitingForWifiConnect) { |
| connectToWifi(); |
| } else if (mWaitingForCelluarToConnectBack) { |
| mWaitingForCelluarToConnectBack = false; |
| waitForConnectivityRestore(); |
| } |
| } |
| |
| @Override |
| void onContinuePreWifiConnect() { |
| mTestProgressMessage = R.string.multinetwork_connectivity_test_2_prereq_1; |
| mTestCallback.testProgress(mTestProgressMessage); |
| mValidatorState = WAITING_FOR_USER_INPUT; |
| mWaitingForWifiConnect = true; |
| requestUserConfirmation(); |
| } |
| |
| void connectToWifi() { |
| mTestCallback.testProgress(R.string.multinetwork_connectivity_test_connect_wifi); |
| mConnectivityState.legacyConnectToWifiNetwork(true); |
| } |
| |
| @Override |
| void onWifiNetworkConnected(Network wifiNetwork) { |
| super.onWifiNetworkConnected(wifiNetwork); |
| if (isConnectedToExpectedWifiNetwork()) { |
| // If the device is connected to the expected network, then update the wifi |
| // network to the latest. |
| mWifiNetwork = wifiNetwork; |
| // Do further processing only when the test is requesting and waiting for a wifi |
| // connection. |
| if (mWaitingForWifiConnect) { |
| mWaitingForWifiConnect = false; |
| startTimerCountdownDisplay(WIFI_NETWORK_CONNECT_TO_BE_ACTIVE_MS / 1000); |
| |
| // Wait for WIFI_NETWORK_CONNECT_TO_BE_ACTIVE_MS, before checking |
| // if device has the active network as wifi network.. |
| mTestCallback.testProgress(R.string.multinetwork_connectivity_test_progress_2); |
| mMainHandler.postDelayed(() -> { |
| stopTimerCountdownDisplay(); |
| // In this case both active and peer are same as Wifi has internet access. |
| if (isExpectedTransportForActiveNetwork(TRANSPORT_WIFI) |
| && isNetworkConnected(mWifiNetwork)) { |
| // Ask the user to turn off wifi on the router and check connectivity. |
| mTestProgressMessage = |
| R.string.multinetwork_connectivity_test_2_prereq_2; |
| mValidatorState = WAITING_FOR_USER_INPUT; |
| mTestCallback.testProgress(mTestProgressMessage); |
| mWaitingForCelluarToConnectBack = true; |
| requestUserConfirmation(); |
| } else { |
| Log.d(TAG, "Fail test as device didn't have connectivity"); |
| endTest(false, R.string.multinetwork_status_wifi_connectivity_failed); |
| } |
| }, WIFI_NETWORK_CONNECT_TO_BE_ACTIVE_MS); |
| } |
| } else { |
| endTest(false, R.string.multinetwork_status_wifi_connect_wrong_ap); |
| } |
| } |
| |
| @Override |
| void reset() { |
| super.reset(); |
| mWaitingForCelluarToConnectBack = false; |
| mWaitingForWifiConnect = false; |
| mWifiNetwork = null; |
| } |
| |
| @Override |
| void onWifiNetworkUnavailable() { |
| if (mWaitingForWifiConnect) { |
| super.onWifiNetworkUnavailable(); |
| } |
| } |
| |
| void waitForConnectivityRestore() { |
| mTestCallback.testProgress(R.string.multinetwork_connectivity_test_progress_1); |
| mConnectivityManager.reportNetworkConnectivity(mWifiNetwork, false); |
| startTimerCountdownDisplay( |
| CELLULAR_NETWORK_RESTORE_AFTER_WIFI_INTERNET_LOST_TIMEOUT_MS / 1000); |
| // Wait for CELLULAR_NETWORK_RESTORE_AFTER_WIFI_INTERNET_LOST_TIMEOUT_MS, |
| // before checking if device now has the active network as cell network. |
| mMainHandler.postDelayed(() -> { |
| stopTimerCountdownDisplay(); |
| // Check if device has fallen back to cellular network when it loses internet access |
| // in the wifi network. |
| if (isExpectedTransportForActiveNetwork(TRANSPORT_CELLULAR) |
| && isNetworkConnected(mWifiNetwork)) { |
| Log.d(TAG, "PASS test as device has connectivity"); |
| endTest(true, R.string.multinetwork_status_mobile_restore_success); |
| } else { |
| Log.d(TAG, "Fail test as device didn't have connectivity"); |
| endTest(false, R.string.multinetwork_status_mobile_restore_failed); |
| } |
| }, CELLULAR_NETWORK_RESTORE_AFTER_WIFI_INTERNET_LOST_TIMEOUT_MS); |
| } |
| } |
| |
| /** |
| * Test that device does not lose cellular connectivity when it's connected to an access |
| * point with no connectivity using the new API's. |
| */ |
| private class ConnectToWifiWithNoInternetValidator extends MultiNetworkValidator { |
| |
| ConnectToWifiWithNoInternetValidator(int description) { |
| super(mMultinetworkTestCallback, |
| "no_internet_test", |
| description, |
| /* runTestOnLowMemoryDevices = */ true); |
| } |
| |
| |
| @Override |
| void continueWithTest() { |
| super.continueWithTest(); |
| connectToWifi(); |
| } |
| |
| @Override |
| void onContinuePreWifiConnect() { |
| mTestProgressMessage = R.string.multinetwork_connectivity_test_1_prereq; |
| mTestCallback.testProgress(mTestProgressMessage); |
| mValidatorState = WAITING_FOR_USER_INPUT; |
| requestUserConfirmation(); |
| } |
| |
| void connectToWifi() { |
| mTestCallback.testProgress(R.string.multinetwork_connectivity_test_connect_wifi); |
| mConnectivityState.connectToWifiNetworkWithNoInternet(); |
| } |
| |
| @Override |
| void onWifiNetworkConnected(Network wifiNetwork) { |
| super.onWifiNetworkConnected(wifiNetwork); |
| if (isConnectedToExpectedWifiNetwork()) { |
| startTimerCountdownDisplay(CELLULAR_NETWORK_RESTORE_TIMEOUT_MS / 1000); |
| mTestCallback.testProgress(R.string.multinetwork_connectivity_test_progress_2); |
| |
| // Wait for CELLULAR_NETWORK_RESTORE_TIMEOUT_MS, before checking if there is still |
| // the active network as the cell network. |
| mMainHandler.postDelayed(() -> { |
| stopTimerCountdownDisplay(); |
| mMainHandler.post(() -> { |
| if (isExpectedTransportForActiveNetwork(TRANSPORT_CELLULAR) |
| && isNetworkConnected(wifiNetwork)) { |
| Log.d(TAG, "PASS test as device has connectivity"); |
| endTest(true, R.string.multinetwork_status_mobile_restore_success); |
| } else { |
| Log.d(TAG, "Fail test as device didn't have connectivity"); |
| endTest(false, R.string.multinetwork_status_mobile_restore_failed); |
| } |
| }); |
| }, CELLULAR_NETWORK_RESTORE_TIMEOUT_MS); |
| } else { |
| endTest(false, R.string.multinetwork_status_wifi_connect_wrong_ap); |
| } |
| } |
| } |
| } |