1 /* 2 * Copyright 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.server.wifi; 18 19 import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE; 20 import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SAE; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertFalse; 24 import static org.junit.Assert.assertNotEquals; 25 import static org.junit.Assert.assertNotNull; 26 import static org.junit.Assert.assertNull; 27 import static org.junit.Assert.assertTrue; 28 import static org.junit.Assert.fail; 29 import static org.mockito.Mockito.any; 30 import static org.mockito.Mockito.doReturn; 31 import static org.mockito.Mockito.eq; 32 import static org.mockito.Mockito.validateMockitoUsage; 33 import static org.mockito.Mockito.verify; 34 import static org.mockito.Mockito.when; 35 import static org.mockito.Mockito.withSettings; 36 37 import android.content.Context; 38 import android.net.MacAddress; 39 import android.net.wifi.ScanResult; 40 import android.net.wifi.WifiConfiguration; 41 import android.net.wifi.WifiSsid; 42 43 import androidx.test.filters.SmallTest; 44 45 import com.android.dx.mockito.inline.extended.ExtendedMockito; 46 import com.android.net.module.util.MacAddressUtils; 47 import com.android.wifi.resources.R; 48 49 import org.junit.After; 50 import org.junit.Before; 51 import org.junit.Test; 52 import org.mockito.Mock; 53 import org.mockito.MockitoAnnotations; 54 import org.mockito.MockitoSession; 55 56 import java.util.ArrayList; 57 import java.util.List; 58 59 /** 60 * Unit tests for {@link com.android.server.wifi.WifiCandidates}. 61 */ 62 @SmallTest 63 public class WifiCandidatesTest extends WifiBaseTest { 64 65 @Mock ScanDetail mScanDetail1; 66 @Mock ScanDetail mScanDetail2; 67 @Mock WifiScoreCard mWifiScoreCard; 68 @Mock WifiScoreCard.PerBssid mPerBssid; 69 @Mock Context mContext; 70 @Mock WifiInjector mWifiInjector; 71 @Mock WifiGlobals mWifiGlobals; 72 @Mock ActiveModeWarden mActiveModeWarden; 73 @Mock ClientModeManager mClientModeManager; 74 private MockitoSession mSession; 75 76 ScanResult mScanResult1; 77 ScanResult mScanResult2; 78 79 WifiConfiguration mConfig1; 80 WifiConfiguration mConfig2; 81 82 WifiCandidates mWifiCandidates; 83 MockResources mResources; 84 85 /** 86 * Sets up for unit test 87 */ 88 @Before setUp()89 public void setUp() throws Exception { 90 MockitoAnnotations.initMocks(this); 91 // static mocking 92 mSession = ExtendedMockito.mockitoSession() 93 .mockStatic(WifiInjector.class, withSettings().lenient()) 94 .startMocking(); 95 when(WifiInjector.getInstance()).thenReturn(mWifiInjector); 96 when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals); 97 when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden); 98 when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager); 99 when(mClientModeManager.getSupportedFeatures()).thenReturn( 100 WIFI_FEATURE_OWE | WIFI_FEATURE_WPA3_SAE); 101 when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true); 102 when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true); 103 104 mWifiCandidates = new WifiCandidates(mWifiScoreCard, mContext); 105 mConfig1 = WifiConfigurationTestUtil.createOpenNetwork(); 106 107 mScanResult1 = new ScanResult(); 108 mScanResult1.setWifiSsid(WifiSsid.fromString(mConfig1.SSID)); 109 mScanResult1.capabilities = "[ESS]"; 110 mScanResult1.BSSID = "00:00:00:00:00:01"; 111 112 mConfig2 = WifiConfigurationTestUtil.createEphemeralNetwork(); 113 mScanResult2 = new ScanResult(); 114 mScanResult2.setWifiSsid(WifiSsid.fromString(mConfig2.SSID)); 115 mScanResult2.capabilities = "[ESS]"; 116 117 doReturn(mScanResult1).when(mScanDetail1).getScanResult(); 118 doReturn(mScanResult2).when(mScanDetail2).getScanResult(); 119 doReturn(mPerBssid).when(mWifiScoreCard).lookupBssid(any(), any()); 120 doReturn(50).when(mPerBssid).estimatePercentInternetAvailability(); 121 MockResources mResources = new MockResources(); 122 mResources.setBoolean(R.bool.config_wifiSaeUpgradeEnabled, true); 123 doReturn(mResources).when(mContext).getResources(); 124 } 125 126 /** 127 * Called after each test 128 */ 129 @After cleanup()130 public void cleanup() { 131 validateMockitoUsage(); 132 if (mSession != null) { 133 mSession.finishMocking(); 134 } 135 } 136 137 /** 138 * Test for absence of null pointer exceptions 139 */ 140 @Test testDontDieFromNulls()141 public void testDontDieFromNulls() throws Exception { 142 mWifiCandidates.add(null, mConfig1, 1, 0.0, false, 100); 143 mWifiCandidates.add(mScanDetail1, null, 2, 0.0, false, 100); 144 doReturn(null).when(mScanDetail2).getScanResult(); 145 mWifiCandidates.add(mScanDetail2, mConfig2, 3, 1.0, true, 100); 146 assertFalse(mWifiCandidates.remove(null)); 147 148 assertEquals(0, mWifiCandidates.size()); 149 } 150 151 /** 152 * Add just one thing 153 */ 154 @Test testAddJustOne()155 public void testAddJustOne() throws Exception { 156 assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100)); 157 158 assertEquals(1, mWifiCandidates.size()); 159 assertEquals(0, mWifiCandidates.getFaultCount()); 160 assertNull(mWifiCandidates.getLastFault()); 161 verify(mPerBssid).setNetworkConfigId(eq(mConfig1.networkId)); 162 } 163 164 /** 165 * Test retrieving the list of candidates. 166 */ 167 @Test testGetCandidates()168 public void testGetCandidates() { 169 assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100)); 170 assertNotNull(mWifiCandidates.getCandidates()); 171 assertEquals(1, mWifiCandidates.getCandidates().size()); 172 } 173 174 /** 175 * Make sure we catch SSID mismatch due to quoting error 176 */ 177 @Test testQuotingBotch()178 public void testQuotingBotch() throws Exception { 179 // Unfortunately ScanResult.SSID is not quoted; make sure we catch that 180 mScanResult1.SSID = mConfig1.SSID; 181 mScanResult1.setWifiSsid(WifiSsid.fromUtf8Text(mConfig1.SSID)); 182 mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, true, 100); 183 184 // Should not have added this one 185 assertEquals(0, mWifiCandidates.size()); 186 // The failure should have been recorded 187 assertEquals(1, mWifiCandidates.getFaultCount()); 188 // The record of the failure should contain the culprit 189 String blah = mWifiCandidates.getLastFault().toString(); 190 assertTrue(blah, blah.contains(mConfig1.SSID)); 191 192 // Now check that we can clear the faults 193 mWifiCandidates.clearFaults(); 194 195 assertEquals(0, mWifiCandidates.getFaultCount()); 196 assertNull(mWifiCandidates.getLastFault()); 197 } 198 199 /** 200 * Test Key equals and hashCode methods 201 */ 202 @Test testKeyEquivalence()203 public void testKeyEquivalence() throws Exception { 204 ScanResultMatchInfo matchInfo1 = ScanResultMatchInfo.fromWifiConfiguration(mConfig1); 205 ScanResultMatchInfo matchInfo1Prime = ScanResultMatchInfo.fromWifiConfiguration(mConfig1); 206 ScanResultMatchInfo matchInfo2 = ScanResultMatchInfo.fromWifiConfiguration(mConfig2); 207 assertFalse(matchInfo1 == matchInfo1Prime); // Checking assumption 208 MacAddress mac1 = MacAddressUtils.createRandomUnicastAddress(); 209 MacAddress mac2 = MacAddressUtils.createRandomUnicastAddress(); 210 assertNotEquals(mac1, mac2); // really tiny probability of failing here 211 212 WifiCandidates.Key key1 = new WifiCandidates.Key(matchInfo1, mac1, 1); 213 214 assertFalse(key1.equals(null)); 215 assertFalse(key1.equals((Integer) 0)); 216 // Same inputs should give equal results 217 assertEquals(key1, new WifiCandidates.Key(matchInfo1, mac1, 1)); 218 // Equal inputs should give equal results 219 assertEquals(key1, new WifiCandidates.Key(matchInfo1Prime, mac1, 1)); 220 // Hash codes of equal things should be equal 221 assertEquals(key1.hashCode(), key1.hashCode()); 222 assertEquals(key1.hashCode(), new WifiCandidates.Key(matchInfo1, mac1, 1).hashCode()); 223 assertEquals(key1.hashCode(), new WifiCandidates.Key(matchInfo1Prime, mac1, 1).hashCode()); 224 225 // Unequal inputs should give unequal results 226 assertFalse(key1.equals(new WifiCandidates.Key(matchInfo2, mac1, 1))); 227 assertFalse(key1.equals(new WifiCandidates.Key(matchInfo1, mac2, 1))); 228 assertFalse(key1.equals(new WifiCandidates.Key(matchInfo1, mac1, 2))); 229 } 230 231 /** 232 * Test toString method 233 */ 234 @Test testCandidateToString()235 public void testCandidateToString() throws Exception { 236 doReturn(57).when(mPerBssid).estimatePercentInternetAvailability(); 237 mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0015001, false, 100); 238 WifiCandidates.Candidate c = mWifiCandidates.getGroupedCandidates() 239 .iterator().next().iterator().next(); 240 String s = c.toString(); 241 assertTrue(s, s.contains(" nominator = 2, ")); 242 assertTrue(s, s.contains(" config = " + mConfig1.networkId + ", ")); 243 assertTrue(s, s.contains(" lastSelectionWeight = 0.002, ")); // should be rounded 244 assertTrue(s, s.contains(" pInternet = 57, ")); 245 for (String x : s.split(",")) { 246 if (x.startsWith("Candidate {")) x = x.substring("Candidate {".length()); 247 if (x.endsWith(" }")) x = x.substring(0, x.length() - 2); 248 String diagnose = s + " !! " + x; 249 assertTrue(diagnose, x.startsWith(" ")); // space between items 250 assertFalse(diagnose, x.contains(" ")); // no double spaces 251 if (x.contains("=")) { 252 // Only one equals sign, if there is one 253 assertTrue(diagnose, x.indexOf("=") == x.lastIndexOf("=")); 254 assertTrue(diagnose, x.matches(" [A-Za-z]+ = [^ ]+")); 255 } else { 256 assertTrue(diagnose, x.matches(" [a-z]+")); 257 } 258 } 259 } 260 261 /** 262 * Test that picky mode works 263 */ 264 @Test testPickyMode()265 public void testPickyMode() throws Exception { 266 // Set picky mode, make sure that it returns the object itself (so that 267 // method chaining may be used). 268 assertTrue(mWifiCandidates == mWifiCandidates.setPicky(true)); 269 try { 270 // As in testQuotingBotch() 271 mScanResult1.SSID = mConfig1.SSID; 272 mScanResult1.setWifiSsid(WifiSsid.fromUtf8Text(mConfig1.SSID)); 273 mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100); 274 fail("Exception not raised in picky mode"); 275 } catch (IllegalArgumentException e) { 276 assertEquals(1, mWifiCandidates.getFaultCount()); 277 assertEquals(e, mWifiCandidates.getLastFault()); 278 } 279 } 280 281 /** 282 * Try cases where we don't overwrite existing candidates 283 */ 284 @Test testNoOverwriteCases()285 public void testNoOverwriteCases() throws Exception { 286 // Setup is to add the first candidate 287 mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100); 288 assertEquals(1, mWifiCandidates.size()); 289 290 // Later nominator. Should not add. 291 assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 5, 0.0, false, 100)); 292 assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 5, 0.0, false, 100)); 293 assertEquals(0, mWifiCandidates.getFaultCount()); // Still no faults 294 // After all that, only one candidate should be there. 295 assertEquals(1, mWifiCandidates.size()); 296 } 297 298 /** 299 * Try cases where we do overwrite existing candidates 300 */ 301 @Test testOverwriteCases()302 public void testOverwriteCases() throws Exception { 303 // Setup is to add the first candidate 304 mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100); 305 assertEquals(1, mWifiCandidates.size()); 306 307 // Same nominator, should replace. 308 assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100)); 309 assertEquals(0, mWifiCandidates.getFaultCount()); // No fault 310 // Nominator out of order. Should replace. 311 assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 1, 0.0, false, 100)); 312 assertEquals(0, mWifiCandidates.getFaultCount()); // But not considered a fault 313 // After all that, only one candidate should be there. 314 assertEquals(1, mWifiCandidates.size()); 315 } 316 317 /** 318 * BSSID validation 319 */ 320 @Test testBssidValidation()321 public void testBssidValidation() throws Exception { 322 // Null BSSID. 323 mScanResult1.BSSID = null; 324 mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100); 325 assertTrue("Expecting NPE, got " + mWifiCandidates.getLastFault(), 326 mWifiCandidates.getLastFault() instanceof NullPointerException); 327 // Malformed BSSID 328 mScanResult1.BSSID = "NotaBssid!"; 329 mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100); 330 assertTrue("Expecting IAE, got " + mWifiCandidates.getLastFault(), 331 mWifiCandidates.getLastFault() instanceof IllegalArgumentException); 332 assertEquals(0, mWifiCandidates.size()); 333 } 334 335 /** 336 * Add candidate BSSIDs in the same network, then remove them 337 */ 338 @Test testTwoBssids()339 public void testTwoBssids() throws Exception { 340 // Make a duplicate of the first config 341 mConfig2 = new WifiConfiguration(mConfig1); 342 // Make a second scan result, same network, different BSSID. 343 mScanResult2.SSID = mScanResult1.SSID; 344 mScanResult2.setWifiSsid(mScanResult1.getWifiSsid()); 345 mScanResult2.BSSID = mScanResult1.BSSID.replace('1', '2'); 346 // Add both 347 mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100); 348 mWifiCandidates.add(mScanDetail2, mConfig2, 2, 0.0, false, 100); 349 // We expect them both to be there 350 assertEquals(2, mWifiCandidates.size()); 351 // But just one group 352 assertEquals(1, mWifiCandidates.getGroupedCandidates().size()); 353 // Now remove them one at a time 354 WifiCandidates.Candidate c1, c2; 355 c1 = mWifiCandidates.getGroupedCandidates().iterator().next().iterator().next(); 356 assertTrue(mWifiCandidates.remove(c1)); 357 assertEquals(1, mWifiCandidates.size()); 358 assertEquals(1, mWifiCandidates.getGroupedCandidates().size()); 359 // Should not be able to remove the one that isn't there 360 assertFalse(mWifiCandidates.remove(c1)); 361 // Remove the other one, too 362 c2 = mWifiCandidates.getGroupedCandidates().iterator().next().iterator().next(); 363 assertTrue(mWifiCandidates.remove(c2)); 364 assertFalse(mWifiCandidates.remove(c2)); 365 assertEquals(0, mWifiCandidates.size()); 366 assertEquals(0, mWifiCandidates.getGroupedCandidates().size()); 367 } 368 369 /** 370 * Test replacing a candidate with a higher scoring one 371 */ 372 @Test testReplace()373 public void testReplace() throws Exception { 374 // Make a duplicate of the first config 375 mConfig2 = new WifiConfiguration(mConfig1); 376 // And the scan result 377 mScanResult2.SSID = mScanResult1.SSID; 378 mScanResult2.setWifiSsid(mScanResult1.getWifiSsid()); 379 mScanResult2.BSSID = mScanResult1.BSSID; 380 // Try adding them both, in a known order 381 assertTrue(mWifiCandidates.add(mScanDetail2, mConfig2, 2, 0.0, false, 100)); 382 assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 90)); 383 // Only one should survive 384 assertEquals(1, mWifiCandidates.size()); 385 assertEquals(0, mWifiCandidates.getFaultCount()); 386 // Make sure we kept the second one 387 WifiCandidates.Candidate c; 388 c = mWifiCandidates.getGroupedCandidates().iterator().next().iterator().next(); 389 assertEquals(90, c.getPredictedThroughputMbps()); 390 } 391 392 /** 393 * Tests passpiont network from same provider(FQDN) can have multiple candidates with different 394 * scanDetails. 395 */ 396 @Test testMultiplePasspointCandidatesWithSameFQDN()397 public void testMultiplePasspointCandidatesWithSameFQDN() { 398 // Create a Passpoint WifiConfig 399 mScanResult2.SSID = mScanResult1.SSID; 400 mScanResult2.setWifiSsid(mScanResult1.getWifiSsid()); 401 mScanResult2.BSSID = mScanResult1.BSSID.replace('1', '2'); 402 // Add candidates with different scanDetail for same passpoint WifiConfig. 403 assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100)); 404 assertTrue(mWifiCandidates.add(mScanDetail2, mConfig1, 2, 0.0, false, 100)); 405 // Both should survive and no faults. 406 assertEquals(2, mWifiCandidates.size()); 407 assertEquals(0, mWifiCandidates.getFaultCount()); 408 } 409 410 /** 411 * Verify CarrierOrPrivileged bit is remembered. 412 */ 413 @Test testAddCarrierOrPrivilegedCandidate()414 public void testAddCarrierOrPrivilegedCandidate() { 415 WifiCandidates.Key key = mWifiCandidates 416 .keyFromScanDetailAndConfig(mScanDetail1, mConfig1); 417 WifiCandidates.Candidate candidate; 418 // Make sure the CarrierOrPrivileged false is remembered 419 assertTrue(mWifiCandidates.add(key, mConfig1, 0, -50, 2412, 420 ScanResult.CHANNEL_WIDTH_20MHZ, 0.0, false, false, 100, null)); 421 candidate = mWifiCandidates.getCandidates().get(0); 422 assertFalse(candidate.isCarrierOrPrivileged()); 423 mWifiCandidates.remove(candidate); 424 // Make sure the CarrierOrPrivileged true is remembered 425 assertTrue(mWifiCandidates.add(key, mConfig1, 0, -50, 2412, 426 ScanResult.CHANNEL_WIDTH_20MHZ, 0.0, false, true, 100, null)); 427 candidate = mWifiCandidates.getCandidates().get(0); 428 assertTrue(candidate.isCarrierOrPrivileged()); 429 mWifiCandidates.remove(candidate); 430 } 431 432 @Test testAddCandidateFrequencyAndChannelWidth()433 public void testAddCandidateFrequencyAndChannelWidth() { 434 int testFrequency = 5975; 435 int testChannelWidth = ScanResult.CHANNEL_WIDTH_80MHZ; 436 WifiCandidates.Key key = mWifiCandidates 437 .keyFromScanDetailAndConfig(mScanDetail1, mConfig1); 438 WifiCandidates.Candidate candidate; 439 440 assertTrue(mWifiCandidates.add(key, mConfig1, 0, -50, testFrequency, 441 testChannelWidth, 0.0, false, false, 100, null)); 442 candidate = mWifiCandidates.getCandidates().get(0); 443 assertEquals(testFrequency, candidate.getFrequency()); 444 assertEquals(testChannelWidth, candidate.getChannelWidth()); 445 } 446 447 /** 448 * Unit test to validate multi-link candidate behavior. 449 */ 450 @Test testMultiLinkCandidates()451 public void testMultiLinkCandidates() { 452 final MacAddress mldAddr1 = MacAddress.fromString("00:AA:BB:CC:DD:00"); 453 // Verify default behavior. 454 assertTrue(mWifiCandidates.getMultiLinkCandidates().isEmpty()); 455 assertNull(mWifiCandidates.getMultiLinkCandidates(mldAddr1)); 456 for (WifiCandidates.Candidate candidate : mWifiCandidates.getCandidates()) { 457 assertFalse(candidate.isMultiLinkCapable()); 458 } 459 // Verify non MLO candidates are handled properly. 460 assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 200)); 461 assertTrue(mWifiCandidates.getMultiLinkCandidates().isEmpty()); 462 // Configure first set of Multi-Link candidates. 463 ScanResult mScanResult1_1 = new ScanResult(mScanResult1); 464 mScanResult1_1.BSSID = "00:00:00:00:00:02"; 465 mScanResult1_1.setApMldMacAddress(mldAddr1); 466 ScanResult mScanResult1_2 = new ScanResult(mScanResult1); 467 mScanResult1_2.BSSID = "00:00:00:00:00:03"; 468 mScanResult1_2.setApMldMacAddress(mldAddr1); 469 doReturn(mScanResult1_1).when(mScanDetail1).getScanResult(); 470 assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 200)); 471 doReturn(mScanResult1_2).when(mScanDetail1).getScanResult(); 472 assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100)); 473 for (WifiCandidates.Candidate candidate : mWifiCandidates.getMultiLinkCandidates( 474 mldAddr1)) { 475 candidate.setPredictedMultiLinkThroughputMbps(300); 476 } 477 // Configure second set of Multi-Link candidates. 478 final MacAddress mldAddr2 = MacAddress.fromString("00:AA:BB:CC:DD:01"); 479 ScanResult mScanResult2_1 = new ScanResult(mScanResult1); 480 mScanResult2_1.BSSID = "00:00:00:00:00:04"; 481 mScanResult2_1.setApMldMacAddress(mldAddr2); 482 ScanResult mScanResult2_2 = new ScanResult(mScanResult1); 483 mScanResult2_2.BSSID = "00:00:00:00:00:05"; 484 mScanResult2_2.setApMldMacAddress(mldAddr2); 485 doReturn(mScanResult2_1).when(mScanDetail1).getScanResult(); 486 assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 400)); 487 doReturn(mScanResult2_2).when(mScanDetail1).getScanResult(); 488 assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100)); 489 for (WifiCandidates.Candidate candidate : mWifiCandidates.getMultiLinkCandidates( 490 mldAddr2)) { 491 candidate.setPredictedMultiLinkThroughputMbps(400); 492 } 493 // Test that we can get two sets of Multi-link candidates. 494 assertEquals(new ArrayList<>(mWifiCandidates.getMultiLinkCandidates()), 495 List.of(mWifiCandidates.getMultiLinkCandidates(mldAddr1), 496 mWifiCandidates.getMultiLinkCandidates(mldAddr2))); 497 // Verify predicted multi-link throughput. 498 for (WifiCandidates.Candidate candidate : mWifiCandidates.getMultiLinkCandidates( 499 mldAddr1)) { 500 assertTrue(candidate.isMultiLinkCapable()); 501 assertEquals(candidate.getPredictedMultiLinkThroughputMbps(), 300); 502 } 503 for (WifiCandidates.Candidate candidate : mWifiCandidates.getMultiLinkCandidates( 504 mldAddr2)) { 505 assertTrue(candidate.isMultiLinkCapable()); 506 assertEquals(candidate.getPredictedMultiLinkThroughputMbps(), 400); 507 } 508 } 509 } 510