• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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