1/*
2 * Copyright (C) 2016 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
17package com.android.server.wifi;
18
19import static com.android.server.wifi.WifiConfigurationTestUtil.generateWifiConfig;
20
21import static org.junit.Assert.*;
22import static org.mockito.Mockito.*;
23
24import android.app.test.MockAnswerUtil.AnswerWithArguments;
25import android.net.NetworkKey;
26import android.net.RssiCurve;
27import android.net.ScoredNetwork;
28import android.net.WifiKey;
29import android.net.wifi.ScanResult;
30import android.net.wifi.WifiConfiguration;
31import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
32import android.net.wifi.WifiNetworkScoreCache;
33import android.net.wifi.WifiSsid;
34import android.test.suitebuilder.annotation.SmallTest;
35import android.text.TextUtils;
36
37import com.android.server.wifi.util.ScanResultUtil;
38
39import java.util.ArrayList;
40import java.util.HashMap;
41import java.util.List;
42import java.util.Map;
43
44/**
45 * Helper for WifiNetworkSelector unit tests.
46 */
47@SmallTest
48public class WifiNetworkSelectorTestUtil {
49
50    /**
51     * A class that holds a list of scanDetail and their associated WifiConfiguration.
52     */
53    public static class ScanDetailsAndWifiConfigs {
54        List<ScanDetail> mScanDetails;
55        WifiConfiguration[] mWifiConfigs;
56
57        ScanDetailsAndWifiConfigs(List<ScanDetail> scanDetails, WifiConfiguration[] configs) {
58            mScanDetails = scanDetails;
59            mWifiConfigs = configs;
60        }
61
62        List<ScanDetail> getScanDetails() {
63            return mScanDetails;
64        }
65
66        WifiConfiguration[] getWifiConfigs() {
67            return mWifiConfigs;
68        }
69    }
70
71    /**
72     * Build a list of ScanDetail based on the caller supplied network SSID, BSSID,
73     * frequency, capability and RSSI level information. Create the corresponding
74     * WifiConfiguration for these networks and set up the mocked WifiConfigManager.
75     *
76     * @param ssids an array of SSIDs
77     * @param bssids an array of BSSIDs
78     * @param freqs an array of the network's frequency
79     * @param caps an array of the network's capability
80     * @param levels an array of the network's RSSI levels
81     * @param securities an array of the network's security setting
82     * @param wifiConfigManager the mocked WifiConfigManager
83     * @return the constructed ScanDetail list and WifiConfiguration array
84     */
85    public static ScanDetailsAndWifiConfigs setupScanDetailsAndConfigStore(String[] ssids,
86                String[] bssids, int[] freqs, String[] caps, int[] levels, int[] securities,
87                WifiConfigManager wifiConfigManager, Clock clock) {
88        List<ScanDetail> scanDetails = buildScanDetails(ssids, bssids, freqs, caps, levels, clock);
89        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, securities);
90        prepareConfigStore(wifiConfigManager, savedConfigs);
91        scanResultLinkConfiguration(wifiConfigManager, savedConfigs, scanDetails);
92
93        return new ScanDetailsAndWifiConfigs(scanDetails, savedConfigs);
94    }
95
96    /**
97     * Verify whether the WifiConfiguration chosen by WifiNetworkSelector matches
98     * with the chosen scan result.
99     *
100     * @param chosenScanResult the chosen scan result
101     * @param chosenCandidate  the chosen configuration
102     */
103    public static void verifySelectedScanResult(WifiConfigManager wifiConfigManager,
104            ScanResult chosenScanResult, WifiConfiguration chosenCandidate) {
105        verify(wifiConfigManager, atLeastOnce()).setNetworkCandidateScanResult(
106                eq(chosenCandidate.networkId), eq(chosenScanResult), anyInt());
107    }
108
109
110    /**
111     * Build a list of scanDetails based on the caller supplied network SSID, BSSID,
112     * frequency, capability and RSSI level information.
113     *
114     * @param ssids an array of SSIDs
115     * @param bssids an array of BSSIDs
116     * @param freqs an array of the network's frequency
117     * @param caps an array of the network's capability
118     * @param levels an array of the network's RSSI levels
119     * @return the constructed list of ScanDetail
120     */
121    public static List<ScanDetail> buildScanDetails(String[] ssids, String[] bssids, int[] freqs,
122                                            String[] caps, int[] levels, Clock clock) {
123        List<ScanDetail> scanDetailList = new ArrayList<ScanDetail>();
124
125        long timeStamp = clock.getElapsedSinceBootMillis();
126        for (int index = 0; index < ssids.length; index++) {
127            ScanDetail scanDetail = new ScanDetail(WifiSsid.createFromAsciiEncoded(ssids[index]),
128                    bssids[index], caps[index], levels[index], freqs[index], timeStamp, 0);
129            scanDetailList.add(scanDetail);
130        }
131        return scanDetailList;
132    }
133
134
135    /**
136     * Generate an array of {@link android.net.wifi.WifiConfiguration} based on the caller
137     * supplied network SSID and security information.
138     *
139     * @param ssids an array of SSIDs
140     * @param securities an array of the network's security setting
141     * @return the constructed array of {@link android.net.wifi.WifiConfiguration}
142     */
143    public static WifiConfiguration[] generateWifiConfigurations(String[] ssids,
144                int[] securities) {
145        if (ssids == null || securities == null || ssids.length != securities.length
146                || ssids.length == 0) {
147            return null;
148        }
149
150        Map<String, Integer> netIdMap = new HashMap<>();
151        int netId = 0;
152
153        WifiConfiguration[] configs = new WifiConfiguration[ssids.length];
154        for (int index = 0; index < ssids.length; index++) {
155            String configKey = ssids[index] + Integer.toString(securities[index]);
156            Integer id;
157
158            id = netIdMap.get(configKey);
159            if (id == null) {
160                id = new Integer(netId);
161                netIdMap.put(configKey, id);
162                netId++;
163            }
164
165            configs[index] = generateWifiConfig(id.intValue(), 0, ssids[index], false, true, null,
166                    null, securities[index]);
167        }
168
169        return configs;
170    }
171
172    /**
173     * Add the Configurations to WifiConfigManager (WifiConfigureStore can take them out according
174     * to the networkd ID) and setup the WifiConfigManager mocks for these networks.
175     * This simulates the WifiConfigManager class behaviour.
176     *
177     * @param wifiConfigManager the mocked WifiConfigManager
178     * @param configs input configuration need to be added to WifiConfigureStore
179     */
180    private static void prepareConfigStore(final WifiConfigManager wifiConfigManager,
181                final WifiConfiguration[] configs) {
182        when(wifiConfigManager.getConfiguredNetwork(anyInt()))
183                .then(new AnswerWithArguments() {
184                    public WifiConfiguration answer(int netId) {
185                        for (WifiConfiguration config : configs) {
186                            if (netId == config.networkId) {
187                                return new WifiConfiguration(config);
188                            }
189                        }
190                        return null;
191                    }
192                });
193        when(wifiConfigManager.getConfiguredNetwork(anyString()))
194                .then(new AnswerWithArguments() {
195                    public WifiConfiguration answer(String configKey) {
196                        for (WifiConfiguration config : configs) {
197                            if (TextUtils.equals(config.configKey(), configKey)) {
198                                return new WifiConfiguration(config);
199                            }
200                        }
201                        return null;
202                    }
203                });
204        when(wifiConfigManager.getSavedNetworks())
205                .then(new AnswerWithArguments() {
206                    public List<WifiConfiguration> answer() {
207                        List<WifiConfiguration> savedNetworks = new ArrayList<>();
208                        for (int netId = 0; netId < configs.length; netId++) {
209                            savedNetworks.add(new WifiConfiguration(configs[netId]));
210                        }
211                        return savedNetworks;
212                    }
213                });
214        when(wifiConfigManager.clearNetworkCandidateScanResult(anyInt()))
215                .then(new AnswerWithArguments() {
216                    public boolean answer(int netId) {
217                        if (netId >= 0 && netId < configs.length) {
218                            configs[netId].getNetworkSelectionStatus().setCandidate(null);
219                            configs[netId].getNetworkSelectionStatus()
220                                    .setCandidateScore(Integer.MIN_VALUE);
221                            configs[netId].getNetworkSelectionStatus()
222                                    .setSeenInLastQualifiedNetworkSelection(false);
223                            return true;
224                        } else {
225                            return false;
226                        }
227                    }
228                });
229        when(wifiConfigManager.setNetworkCandidateScanResult(
230                anyInt(), any(ScanResult.class), anyInt()))
231                .then(new AnswerWithArguments() {
232                    public boolean answer(int netId, ScanResult scanResult, int score) {
233                        if (netId >= 0 && netId < configs.length) {
234                            configs[netId].getNetworkSelectionStatus().setCandidate(scanResult);
235                            configs[netId].getNetworkSelectionStatus().setCandidateScore(score);
236                            configs[netId].getNetworkSelectionStatus()
237                                    .setSeenInLastQualifiedNetworkSelection(true);
238                            return true;
239                        } else {
240                            return false;
241                        }
242                    }
243                });
244        when(wifiConfigManager.clearNetworkConnectChoice(anyInt()))
245                .then(new AnswerWithArguments() {
246                    public boolean answer(int netId) {
247                        if (netId >= 0 && netId < configs.length) {
248                            configs[netId].getNetworkSelectionStatus().setConnectChoice(null);
249                            configs[netId].getNetworkSelectionStatus()
250                                    .setConnectChoiceTimestamp(
251                                            NetworkSelectionStatus
252                                                    .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
253                            return true;
254                        } else {
255                            return false;
256                        }
257                    }
258                });
259        when(wifiConfigManager.setNetworkConnectChoice(anyInt(), anyString(), anyLong()))
260                .then(new AnswerWithArguments() {
261                    public boolean answer(int netId, String configKey, long timestamp) {
262                        if (netId >= 0 && netId < configs.length) {
263                            configs[netId].getNetworkSelectionStatus().setConnectChoice(configKey);
264                            configs[netId].getNetworkSelectionStatus().setConnectChoiceTimestamp(
265                                    timestamp);
266                            return true;
267                        } else {
268                            return false;
269                        }
270                    }
271                });
272    }
273
274
275    /**
276     * Link scan results to the saved configurations.
277     *
278     * The shorter of the 2 input params will be used to loop over so the inputs don't
279     * need to be of equal length. If there are more scan details then configs the remaining scan
280     * details will be associated with a NULL config.
281     *
282     * @param wifiConfigManager the mocked WifiConfigManager
283     * @param configs     saved configurations
284     * @param scanDetails come in scan results
285     */
286    private static void scanResultLinkConfiguration(WifiConfigManager wifiConfigManager,
287                WifiConfiguration[] configs, List<ScanDetail> scanDetails) {
288        if (configs == null || scanDetails == null) {
289            return;
290        }
291
292        if (scanDetails.size() <= configs.length) {
293            for (int i = 0; i < scanDetails.size(); i++) {
294                ScanDetail scanDetail = scanDetails.get(i);
295                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
296                        .thenReturn(configs[i]);
297            }
298        } else {
299            for (int i = 0; i < configs.length; i++) {
300                ScanDetail scanDetail = scanDetails.get(i);
301                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
302                        .thenReturn(configs[i]);
303            }
304
305            // associated the remaining scan details with a NULL config.
306            for (int i = configs.length; i < scanDetails.size(); i++) {
307                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(
308                        eq(scanDetails.get(i)))).thenReturn(null);
309            }
310        }
311    }
312
313
314    /**
315     * Configure the score cache for externally scored networks
316     *
317     * @param scoreCache   Wifi network score cache to be configured
318     * @param scanDetails  a list of ScanDetail
319     * @param scores       scores of the networks
320     * @param meteredHints hints of if the networks are metered
321     */
322    public static void configureScoreCache(WifiNetworkScoreCache scoreCache,
323            List<ScanDetail> scanDetails, Integer[] scores, boolean[] meteredHints) {
324        List<ScoredNetwork> networks = new ArrayList<>();
325
326        for (int i = 0; i < scanDetails.size(); i++) {
327            ScanDetail scanDetail = scanDetails.get(i);
328            ScanResult scanResult = scanDetail.getScanResult();
329            WifiKey wifiKey = new WifiKey("\"" + scanResult.SSID + "\"", scanResult.BSSID);
330            NetworkKey ntwkKey = new NetworkKey(wifiKey);
331            RssiCurve rssiCurve;
332
333            if (scores != null) { // fixed score
334                byte rssiScore;
335                Integer score = scores[i];
336
337                if (scores[i] == null) {
338                    rssiScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
339                } else {
340                    rssiScore = scores[i].byteValue();
341                }
342                rssiCurve = new RssiCurve(-100, 100, new byte[] {rssiScore});
343            } else {
344                rssiCurve = new RssiCurve(-80, 20, new byte[] {-10, 0, 10, 20, 30, 40});
345            }
346            ScoredNetwork scoredNetwork = new ScoredNetwork(ntwkKey, rssiCurve, meteredHints[i]);
347
348            networks.add(scoredNetwork);
349        }
350
351        scoreCache.updateScores(networks);
352    }
353
354    /**
355     * Setup WifiConfigManager mock for ephemeral networks.
356     *
357     * @param wifiConfigManager WifiConfigManager mock
358     * @param networkId         ID of the ephemeral network
359     * @param scanDetail        scanDetail of the ephemeral network
360     * @param meteredHint       flag to indidate if the network has meteredHint
361     */
362    public static WifiConfiguration setupEphemeralNetwork(WifiConfigManager wifiConfigManager,
363            int networkId, ScanDetail scanDetail, boolean meteredHint) {
364        // Return the correct networkID for ephemeral network addition.
365        when(wifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
366                .thenReturn(new NetworkUpdateResult(networkId));
367        final WifiConfiguration config =
368                ScanResultUtil.createNetworkFromScanResult(scanDetail.getScanResult());
369        config.ephemeral = true;
370        config.networkId = networkId;
371        config.meteredHint = meteredHint;
372
373        when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
374                .thenReturn(new WifiConfiguration(config));
375        when(wifiConfigManager.getConfiguredNetwork(eq(networkId)))
376                .then(new AnswerWithArguments() {
377                    public WifiConfiguration answer(int netId) {
378                        return new WifiConfiguration(config);
379                    }
380                });
381        when(wifiConfigManager.setNetworkCandidateScanResult(
382                eq(networkId), any(ScanResult.class), anyInt()))
383                .then(new AnswerWithArguments() {
384                    public boolean answer(int netId, ScanResult scanResult, int score) {
385                        config.getNetworkSelectionStatus().setCandidate(scanResult);
386                        config.getNetworkSelectionStatus().setCandidateScore(score);
387                        config.getNetworkSelectionStatus()
388                                .setSeenInLastQualifiedNetworkSelection(true);
389                        return true;
390                    }
391                });
392        when(wifiConfigManager.updateNetworkSelectionStatus(eq(networkId),
393                eq(WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE)))
394                .then(new AnswerWithArguments() {
395                    public boolean answer(int netId, int status) {
396                        config.getNetworkSelectionStatus().setNetworkSelectionStatus(
397                                WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
398                        return true;
399                    }
400                });
401        return config;
402    }
403}
404