1/*
2 * Copyright (C) 2015 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 android.net.wifi.ScanResult;
20import android.net.wifi.WifiScanner.ScanData;
21import android.net.wifi.WifiSsid;
22
23import com.android.server.wifi.hotspot2.NetworkDetail;
24
25import java.math.BigInteger;
26import java.nio.charset.Charset;
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.Collections;
30import java.util.Comparator;
31import java.util.List;
32import java.util.Random;
33
34/**
35 * Utility for creating scan results from a scan
36 */
37public class ScanResults {
38    private final ArrayList<ScanDetail> mScanDetails = new ArrayList<>();
39    private final ScanData mScanData;
40    private final ScanData mRawScanData;
41    private final ScanResult[] mScanResults;
42
43    private ScanResults(ArrayList<ScanDetail> scanDetails, ScanData scanData,
44            ScanResult[] scanResults) {
45        mScanDetails.addAll(scanDetails);
46        mScanData = scanData;
47        mRawScanData = scanData;
48        mScanResults = scanResults;
49    }
50
51    /**
52     * Merge the results contained in a number of ScanResults into a single ScanResults
53     */
54    public static ScanResults merge(ScanResults... others) {
55        ArrayList<ScanDetail> scanDetails = new ArrayList<>();
56        ArrayList<ScanResult> scanDataResults = new ArrayList<>();
57        ArrayList<ScanResult> rawScanResults = new ArrayList<>();
58        for (ScanResults other : others) {
59            scanDetails.addAll(other.getScanDetailArrayList());
60            scanDataResults.addAll(Arrays.asList(other.getScanData().getResults()));
61            rawScanResults.addAll(Arrays.asList(other.getRawScanResults()));
62        }
63        Collections.sort(scanDataResults, SCAN_RESULT_RSSI_COMPARATOR);
64        int id = others[0].getScanData().getId();
65        return new ScanResults(scanDetails, new ScanData(id, 0, scanDataResults
66                        .toArray(new ScanResult[scanDataResults.size()])),
67                rawScanResults.toArray(new ScanResult[rawScanResults.size()]));
68    }
69
70    private static String generateBssid(Random r) {
71        return String.format("%02X:%02X:%02X:%02X:%02X:%02X",
72                r.nextInt(256), r.nextInt(256), r.nextInt(256),
73                r.nextInt(256), r.nextInt(256), r.nextInt(256));
74    }
75
76    public static final Comparator<ScanResult> SCAN_RESULT_RSSI_COMPARATOR =
77            new Comparator<ScanResult>() {
78        public int compare(ScanResult r1, ScanResult r2) {
79            return r2.level - r1.level;
80        }
81    };
82
83    public static ScanResult.InformationElement generateSsidIe(String ssid) {
84        ScanResult.InformationElement ie = new ScanResult.InformationElement();
85        ie.id = ScanResult.InformationElement.EID_SSID;
86        ie.bytes = ssid.getBytes(Charset.forName("UTF-8"));
87        return ie;
88    }
89
90    /**
91     * Generates an array of random ScanDetails with the given frequencies, seeded by the provided
92     * seed value and test method name and class (annotated with @Test). This method will be
93     * consistent between calls in the same test across runs.
94     *
95     * @param seed combined with a hash of the test method this seeds the random number generator
96     * @param freqs list of frequencies for the generated scan results, these will map 1 to 1 to
97     *              to the returned scan details. Duplicates can be specified to create multiple
98     *              ScanDetails with the same frequency.
99     */
100    private static ScanDetail[] generateNativeResults(boolean needIE, int seed, int... freqs) {
101        ScanDetail[] results = new ScanDetail[freqs.length];
102        // Seed the results based on the provided seed as well as the test method name
103        // This provides more varied scan results between individual tests that are very similar.
104        Random r = new Random(seed + WifiTestUtil.getTestMethod().hashCode());
105        for (int i = 0; i < freqs.length; ++i) {
106            int freq = freqs[i];
107            String ssid = new BigInteger(128, r).toString(36);
108            String bssid = generateBssid(r);
109            int rssi = r.nextInt(40) - 99; // -99 to -60
110            ScanResult.InformationElement[] ie;
111            if (needIE) {
112                ie = new ScanResult.InformationElement[1];
113                ie[0] = generateSsidIe(ssid);
114            } else {
115                ie = new ScanResult.InformationElement[0];
116            }
117            List<String> anqpLines = new ArrayList<>();
118            NetworkDetail nd = new NetworkDetail(bssid, ie, anqpLines, freq);
119            ScanDetail detail = new ScanDetail(nd, WifiSsid.createFromAsciiEncoded(ssid),
120                    bssid, "", rssi, freq,
121                    Long.MAX_VALUE, /* needed so that scan results aren't rejected because
122                                        they are older than scan start */
123                    ie, anqpLines);
124            results[i] = detail;
125        }
126        return results;
127    }
128
129    /**
130     * Create scan results with no IE information.
131     */
132    public static ScanDetail[] generateNativeResults(int seed, int... freqs) {
133        return generateNativeResults(true, seed, freqs);
134    }
135
136    /**
137     * Create a ScanResults with randomly generated results seeded by the id.
138     * @see #generateNativeResults for more details on how results are generated
139     */
140    public static ScanResults create(int id, int... freqs) {
141        return new ScanResults(id, -1, generateNativeResults(id, freqs));
142    }
143
144    /**
145     * Create a ScanResults with no IE information.
146     */
147    public static ScanResults createWithNoIE(int id, int... freqs) {
148        return new ScanResults(id, -1, generateNativeResults(false, id, freqs));
149    }
150
151    /**
152     * Create a ScanResults with the given ScanDetails
153     */
154    public static ScanResults create(int id, ScanDetail... nativeResults) {
155        return new ScanResults(id, -1, nativeResults);
156    }
157
158    /**
159     * Create scan results that contain all results for the native results and
160     * full scan results, but limits the number of onResults results after sorting
161     * by RSSI
162     */
163    public static ScanResults createOverflowing(int id, int maxResults,
164            ScanDetail... nativeResults) {
165        return new ScanResults(id, maxResults, nativeResults);
166    }
167
168    private ScanResults(int id, int maxResults, ScanDetail... nativeResults) {
169        mScanResults = new ScanResult[nativeResults.length];
170        for (int i = 0; i < nativeResults.length; ++i) {
171            mScanDetails.add(nativeResults[i]);
172            mScanResults[i] = nativeResults[i].getScanResult();
173        }
174        ScanResult[] sortedScanResults = Arrays.copyOf(mScanResults, mScanResults.length);
175        Arrays.sort(sortedScanResults, SCAN_RESULT_RSSI_COMPARATOR);
176        mRawScanData = new ScanData(id, 0, sortedScanResults);
177        if (maxResults == -1) {
178            mScanData = mRawScanData;
179        } else {
180            ScanResult[] reducedScanResults = Arrays.copyOf(sortedScanResults,
181                    Math.min(sortedScanResults.length, maxResults));
182            mScanData = new ScanData(id, 0, reducedScanResults);
183        }
184    }
185
186    public ArrayList<ScanDetail> getScanDetailArrayList() {
187        return mScanDetails;
188    }
189
190    public ScanData getScanData() {
191        return mScanData;
192    }
193
194    public ScanResult[] getRawScanResults() {
195        return mScanResults;
196    }
197
198    public ScanData getRawScanData() {
199        return mRawScanData;
200    }
201}
202