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 17 package com.android.server.wifi; 18 19 import android.net.wifi.ScanResult; 20 import android.net.wifi.WifiScanner.ScanData; 21 import android.net.wifi.WifiSsid; 22 23 import com.android.server.wifi.hotspot2.NetworkDetail; 24 25 import java.math.BigInteger; 26 import java.nio.charset.Charset; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.Collections; 30 import java.util.Comparator; 31 import java.util.List; 32 import java.util.Random; 33 34 /** 35 * Utility for creating scan results from a scan 36 */ 37 public 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 create(id, generateNativeResults(id, freqs)); 142 } 143 public static ScanResults create(int id, boolean allChannelsScanned, int... freqs) { 144 return create(id, allChannelsScanned, generateNativeResults(id, freqs)); 145 } 146 147 /** 148 * Create a ScanResults with no IE information. 149 */ 150 public static ScanResults createWithNoIE(int id, int... freqs) { 151 return create(id, generateNativeResults(false, id, freqs)); 152 } 153 154 /** 155 * Create a ScanResults with the given ScanDetails 156 */ 157 public static ScanResults create(int id, ScanDetail... nativeResults) { 158 return new ScanResults(id, false, -1, nativeResults); 159 } 160 161 public static ScanResults create(int id, boolean allChannelsScanned, 162 ScanDetail... nativeResults) { 163 return new ScanResults(id, allChannelsScanned, -1, nativeResults); 164 } 165 166 /** 167 * Create scan results that contain all results for the native results and 168 * full scan results, but limits the number of onResults results after sorting 169 * by RSSI 170 */ 171 public static ScanResults createOverflowing(int id, int maxResults, 172 ScanDetail... nativeResults) { 173 return new ScanResults(id, false, maxResults, nativeResults); 174 } 175 176 private ScanResults(int id, boolean allChannelsScanned, int maxResults, 177 ScanDetail... nativeResults) { 178 mScanResults = new ScanResult[nativeResults.length]; 179 for (int i = 0; i < nativeResults.length; ++i) { 180 mScanDetails.add(nativeResults[i]); 181 mScanResults[i] = nativeResults[i].getScanResult(); 182 } 183 ScanResult[] sortedScanResults = Arrays.copyOf(mScanResults, mScanResults.length); 184 Arrays.sort(sortedScanResults, SCAN_RESULT_RSSI_COMPARATOR); 185 mRawScanData = new ScanData(id, 0, 0, allChannelsScanned, sortedScanResults); 186 if (maxResults == -1) { 187 mScanData = mRawScanData; 188 } else { 189 ScanResult[] reducedScanResults = Arrays.copyOf(sortedScanResults, 190 Math.min(sortedScanResults.length, maxResults)); 191 mScanData = new ScanData(id, 0, 0, allChannelsScanned, reducedScanResults); 192 } 193 } 194 195 public ArrayList<ScanDetail> getScanDetailArrayList() { 196 return mScanDetails; 197 } 198 199 public ScanData getScanData() { 200 return mScanData; 201 } 202 203 public ScanResult[] getRawScanResults() { 204 return mScanResults; 205 } 206 207 public ScanData getRawScanData() { 208 return mRawScanData; 209 } 210 } 211