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