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