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.annotation.NonNull; 20 import android.net.wifi.ScanResult; 21 import android.net.wifi.WifiConfiguration; 22 23 import com.android.server.wifi.hotspot2.NetworkDetail; 24 25 import java.util.ArrayList; 26 import java.util.Collection; 27 import java.util.Collections; 28 import java.util.Comparator; 29 import java.util.HashMap; 30 31 /** 32 * Maps BSSIDs to their individual ScanDetails for a given WifiConfiguration. 33 */ 34 public class ScanDetailCache { 35 36 private static final String TAG = "ScanDetailCache"; 37 private static final boolean DBG = false; 38 39 private final WifiConfiguration mConfig; 40 private final int mMaxSize; 41 private final int mTrimSize; 42 private final HashMap<String, ScanDetail> mMap; 43 44 /** 45 * Scan Detail cache associated with each configured network. 46 * 47 * The cache size is trimmed down to |trimSize| once it crosses the provided |maxSize|. 48 * Since this operation is relatively expensive, ensure that |maxSize| and |trimSize| are not 49 * too close to each other. |trimSize| should always be <= |maxSize|. 50 * 51 * @param config WifiConfiguration object corresponding to the network. 52 * @param maxSize Max size desired for the cache. 53 * @param trimSize Size to trim the cache down to once it reaches |maxSize|. 54 */ ScanDetailCache(WifiConfiguration config, int maxSize, int trimSize)55 ScanDetailCache(WifiConfiguration config, int maxSize, int trimSize) { 56 mConfig = config; 57 mMaxSize = maxSize; 58 mTrimSize = trimSize; 59 mMap = new HashMap(16, 0.75f); 60 } 61 put(ScanDetail scanDetail)62 void put(ScanDetail scanDetail) { 63 // First check if we have reached |maxSize|. if yes, trim it down to |trimSize|. 64 if (mMap.size() >= mMaxSize) { 65 trim(); 66 } 67 68 mMap.put(scanDetail.getBSSIDString(), scanDetail); 69 } 70 71 /** 72 * Get ScanResult object corresponding to the provided BSSID. 73 * 74 * @param bssid provided BSSID 75 * @return {@code null} if no match ScanResult is found. 76 */ getScanResult(String bssid)77 public ScanResult getScanResult(String bssid) { 78 ScanDetail scanDetail = getScanDetail(bssid); 79 return scanDetail == null ? null : scanDetail.getScanResult(); 80 } 81 82 /** 83 * Get ScanDetail object corresponding to the provided BSSID. 84 * 85 * @param bssid provided BSSID 86 * @return {@code null} if no match ScanDetail is found. 87 */ getScanDetail(@onNull String bssid)88 public ScanDetail getScanDetail(@NonNull String bssid) { 89 return mMap.get(bssid); 90 } 91 remove(@onNull String bssid)92 void remove(@NonNull String bssid) { 93 mMap.remove(bssid); 94 } 95 size()96 int size() { 97 return mMap.size(); 98 } 99 isEmpty()100 boolean isEmpty() { 101 return size() == 0; 102 } 103 keySet()104 Collection<String> keySet() { 105 return mMap.keySet(); 106 } 107 values()108 Collection<ScanDetail> values() { 109 return mMap.values(); 110 } 111 112 /** 113 * Method to reduce the cache to |mTrimSize| size by removing the oldest entries. 114 * TODO: Investigate if this method can be further optimized. 115 */ trim()116 private void trim() { 117 int currentSize = mMap.size(); 118 if (currentSize < mTrimSize) { 119 return; // Nothing to trim 120 } 121 ArrayList<ScanDetail> list = new ArrayList<ScanDetail>(mMap.values()); 122 if (list.size() != 0) { 123 // Sort by ascending timestamp (oldest scan results first) 124 Collections.sort(list, new Comparator() { 125 public int compare(Object o1, Object o2) { 126 ScanDetail a = (ScanDetail) o1; 127 ScanDetail b = (ScanDetail) o2; 128 if (a.getSeen() > b.getSeen()) { 129 return 1; 130 } 131 if (a.getSeen() < b.getSeen()) { 132 return -1; 133 } 134 return a.getBSSIDString().compareTo(b.getBSSIDString()); 135 } 136 }); 137 } 138 for (int i = 0; i < currentSize - mTrimSize; i++) { 139 // Remove oldest results from scan cache 140 ScanDetail result = list.get(i); 141 mMap.remove(result.getBSSIDString()); 142 } 143 } 144 145 /** 146 * Return the most recent ScanResult for this network, or null if non exists. 147 */ getMostRecentScanResult()148 public ScanResult getMostRecentScanResult() { 149 ArrayList<ScanDetail> list = sort(); 150 if (list.size() == 0) { 151 return null; 152 } 153 return list.get(0).getScanResult(); 154 } 155 156 /** 157 * Returns the list of sorted ScanDetails in descending order of timestamp, followed by 158 * descending order of RSSI. 159 * @hide 160 **/ sort()161 private ArrayList<ScanDetail> sort() { 162 ArrayList<ScanDetail> list = new ArrayList<ScanDetail>(mMap.values()); 163 if (list.size() != 0) { 164 Collections.sort(list, new Comparator() { 165 public int compare(Object o1, Object o2) { 166 ScanResult a = ((ScanDetail) o1).getScanResult(); 167 ScanResult b = ((ScanDetail) o2).getScanResult(); 168 if (a.seen > b.seen) { 169 return -1; 170 } 171 if (a.seen < b.seen) { 172 return 1; 173 } 174 if (a.level > b.level) { 175 return -1; 176 } 177 if (a.level < b.level) { 178 return 1; 179 } 180 return a.BSSID.compareTo(b.BSSID); 181 } 182 }); 183 } 184 return list; 185 } 186 187 @Override toString()188 public String toString() { 189 StringBuilder sbuf = new StringBuilder(); 190 sbuf.append("Scan Cache: ").append('\n'); 191 192 ArrayList<ScanDetail> list = sort(); 193 long now_ms = System.currentTimeMillis(); 194 if (list.size() > 0) { 195 for (ScanDetail scanDetail : list) { 196 ScanResult result = scanDetail.getScanResult(); 197 long milli = now_ms - scanDetail.getSeen(); 198 long ageSec = 0; 199 long ageMin = 0; 200 long ageHour = 0; 201 long ageMilli = 0; 202 long ageDay = 0; 203 if (now_ms > scanDetail.getSeen() && scanDetail.getSeen() > 0) { 204 ageMilli = milli % 1000; 205 ageSec = (milli / 1000) % 60; 206 ageMin = (milli / (60 * 1000)) % 60; 207 ageHour = (milli / (60 * 60 * 1000)) % 24; 208 ageDay = (milli / (24 * 60 * 60 * 1000)); 209 } 210 sbuf.append("{").append(result.BSSID).append(",").append(result.frequency); 211 sbuf.append(",").append(result.level); 212 if (ageSec > 0 || ageMilli > 0) { 213 sbuf.append("," + ageDay + "." + ageHour + "." + ageMin + "." + ageSec + "." 214 + ageMilli + "ms"); 215 } 216 NetworkDetail networkDetail = scanDetail.getNetworkDetail(); 217 if (networkDetail != null && networkDetail.isInterworking()) { 218 sbuf.append(",Ant=").append(networkDetail.getAnt()).append(",Internet=") 219 .append(networkDetail.isInternet()); 220 } 221 sbuf.append("} "); 222 } 223 sbuf.append('\n'); 224 } 225 226 return sbuf.toString(); 227 } 228 229 } 230