• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.WifiConfiguration;
21 import android.os.SystemClock;
22 import android.util.Log;
23 
24 import com.android.server.wifi.hotspot2.PasspointMatch;
25 import com.android.server.wifi.hotspot2.PasspointMatchInfo;
26 import com.android.server.wifi.hotspot2.pps.HomeSP;
27 
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.Iterator;
33 import java.util.concurrent.ConcurrentHashMap;
34 
35 /**
36  * Maps BSSIDs to their individual ScanDetails for a given WifiConfiguration.
37  */
38 public class ScanDetailCache {
39 
40     private static final String TAG = "ScanDetailCache";
41     private static final boolean DBG = false;
42 
43     private WifiConfiguration mConfig;
44     private ConcurrentHashMap<String, ScanDetail> mMap;
45     private ConcurrentHashMap<String, PasspointMatchInfo> mPasspointMatches;
46 
ScanDetailCache(WifiConfiguration config)47     ScanDetailCache(WifiConfiguration config) {
48         mConfig = config;
49         mMap = new ConcurrentHashMap(16, 0.75f, 2);
50         mPasspointMatches = new ConcurrentHashMap(16, 0.75f, 2);
51     }
52 
put(ScanDetail scanDetail)53     void put(ScanDetail scanDetail) {
54         put(scanDetail, null, null);
55     }
56 
put(ScanDetail scanDetail, PasspointMatch match, HomeSP homeSp)57     void put(ScanDetail scanDetail, PasspointMatch match, HomeSP homeSp) {
58 
59         mMap.put(scanDetail.getBSSIDString(), scanDetail);
60 
61         if (match != null && homeSp != null) {
62             mPasspointMatches.put(scanDetail.getBSSIDString(),
63                     new PasspointMatchInfo(match, scanDetail, homeSp));
64         }
65     }
66 
get(String bssid)67     ScanResult get(String bssid) {
68         ScanDetail scanDetail = getScanDetail(bssid);
69         return scanDetail == null ? null : scanDetail.getScanResult();
70     }
71 
getScanDetail(String bssid)72     ScanDetail getScanDetail(String bssid) {
73         return mMap.get(bssid);
74     }
75 
remove(String bssid)76     void remove(String bssid) {
77         mMap.remove(bssid);
78     }
79 
size()80     int size() {
81         return mMap.size();
82     }
83 
isEmpty()84     boolean isEmpty() {
85         return size() == 0;
86     }
87 
keySet()88     Collection<String> keySet() {
89         return mMap.keySet();
90     }
91 
values()92     Collection<ScanDetail> values() {
93         return mMap.values();
94     }
95 
96     /**
97      * Method to reduce the cache to the given size by removing the oldest entries.
98      *
99      * @param num int target cache size
100      */
trim(int num)101     public void trim(int num) {
102         int currentSize = mMap.size();
103         if (currentSize <= num) {
104             return; // Nothing to trim
105         }
106         ArrayList<ScanDetail> list = new ArrayList<ScanDetail>(mMap.values());
107         if (list.size() != 0) {
108             // Sort by descending timestamp
109             Collections.sort(list, new Comparator() {
110                 public int compare(Object o1, Object o2) {
111                     ScanDetail a = (ScanDetail) o1;
112                     ScanDetail b = (ScanDetail) o2;
113                     if (a.getSeen() > b.getSeen()) {
114                         return 1;
115                     }
116                     if (a.getSeen() < b.getSeen()) {
117                         return -1;
118                     }
119                     return a.getBSSIDString().compareTo(b.getBSSIDString());
120                 }
121             });
122         }
123         for (int i = 0; i < currentSize - num; i++) {
124             // Remove oldest results from scan cache
125             ScanDetail result = list.get(i);
126             mMap.remove(result.getBSSIDString());
127             mPasspointMatches.remove(result.getBSSIDString());
128         }
129     }
130 
131     /* @hide */
sort()132     private ArrayList<ScanDetail> sort() {
133         ArrayList<ScanDetail> list = new ArrayList<ScanDetail>(mMap.values());
134         if (list.size() != 0) {
135             Collections.sort(list, new Comparator() {
136                 public int compare(Object o1, Object o2) {
137                     ScanResult a = ((ScanDetail) o1).getScanResult();
138                     ScanResult b = ((ScanDetail) o2).getScanResult();
139                     if (a.numIpConfigFailures > b.numIpConfigFailures) {
140                         return 1;
141                     }
142                     if (a.numIpConfigFailures < b.numIpConfigFailures) {
143                         return -1;
144                     }
145                     if (a.seen > b.seen) {
146                         return -1;
147                     }
148                     if (a.seen < b.seen) {
149                         return 1;
150                     }
151                     if (a.level > b.level) {
152                         return -1;
153                     }
154                     if (a.level < b.level) {
155                         return 1;
156                     }
157                     return a.BSSID.compareTo(b.BSSID);
158                 }
159             });
160         }
161         return list;
162     }
163 
164     /**
165      * Method to get cached scan results that are less than 'age' old.
166      *
167      * @param age long Time window of desired results.
168      * @return WifiConfiguration.Visibility matches in the given visibility
169      */
getVisibilityByRssi(long age)170     public WifiConfiguration.Visibility getVisibilityByRssi(long age) {
171         WifiConfiguration.Visibility status = new WifiConfiguration.Visibility();
172 
173         long now_ms = System.currentTimeMillis();
174         long now_elapsed_ms = SystemClock.elapsedRealtime();
175         for (ScanDetail scanDetail : values()) {
176             ScanResult result = scanDetail.getScanResult();
177             if (scanDetail.getSeen() == 0) {
178                 continue;
179             }
180 
181             if (result.is5GHz()) {
182                 //strictly speaking: [4915, 5825]
183                 //number of known BSSID on 5GHz band
184                 status.num5 = status.num5 + 1;
185             } else if (result.is24GHz()) {
186                 //strictly speaking: [2412, 2482]
187                 //number of known BSSID on 2.4Ghz band
188                 status.num24 = status.num24 + 1;
189             }
190 
191             if (result.timestamp != 0) {
192                 if (DBG) {
193                     Log.e("getVisibilityByRssi", " considering " + result.SSID + " " + result.BSSID
194                             + " elapsed=" + now_elapsed_ms + " timestamp=" + result.timestamp
195                             + " age = " + age);
196                 }
197                 if ((now_elapsed_ms - (result.timestamp / 1000)) > age) continue;
198             } else {
199                 // This checks the time at which we have received the scan result from supplicant
200                 if ((now_ms - result.seen) > age) continue;
201             }
202 
203             if (result.is5GHz()) {
204                 if (result.level > status.rssi5) {
205                     status.rssi5 = result.level;
206                     status.age5 = result.seen;
207                     status.BSSID5 = result.BSSID;
208                 }
209             } else if (result.is24GHz()) {
210                 if (result.level > status.rssi24) {
211                     status.rssi24 = result.level;
212                     status.age24 = result.seen;
213                     status.BSSID24 = result.BSSID;
214                 }
215             }
216         }
217 
218         return status;
219     }
220 
221     /**
222      * Method returning the Visibility based on passpoint match time.
223      *
224      * @param age long Desired time window for matches.
225      * @return WifiConfiguration.Visibility matches in the given visibility
226      */
getVisibilityByPasspointMatch(long age)227     public WifiConfiguration.Visibility getVisibilityByPasspointMatch(long age) {
228 
229         long now_ms = System.currentTimeMillis();
230         PasspointMatchInfo pmiBest24 = null, pmiBest5 = null;
231 
232         for (PasspointMatchInfo pmi : mPasspointMatches.values()) {
233             ScanDetail scanDetail = pmi.getScanDetail();
234             if (scanDetail == null) continue;
235             ScanResult result = scanDetail.getScanResult();
236             if (result == null) continue;
237 
238             if (scanDetail.getSeen() == 0) continue;
239 
240             if ((now_ms - result.seen) > age) continue;
241 
242             if (result.is5GHz()) {
243                 if (pmiBest5 == null || pmiBest5.compareTo(pmi) < 0) {
244                     pmiBest5 = pmi;
245                 }
246             } else if (result.is24GHz()) {
247                 if (pmiBest24 == null || pmiBest24.compareTo(pmi) < 0) {
248                     pmiBest24 = pmi;
249                 }
250             }
251         }
252 
253         WifiConfiguration.Visibility status = new WifiConfiguration.Visibility();
254         String logMsg = "Visiblity by passpoint match returned ";
255         if (pmiBest5 != null) {
256             ScanResult result = pmiBest5.getScanDetail().getScanResult();
257             status.rssi5 = result.level;
258             status.age5 = result.seen;
259             status.BSSID5 = result.BSSID;
260             logMsg += "5 GHz BSSID of " + result.BSSID;
261         }
262         if (pmiBest24 != null) {
263             ScanResult result = pmiBest24.getScanDetail().getScanResult();
264             status.rssi24 = result.level;
265             status.age24 = result.seen;
266             status.BSSID24 = result.BSSID;
267             logMsg += "2.4 GHz BSSID of " + result.BSSID;
268         }
269 
270         Log.d(TAG, logMsg);
271 
272         return status;
273     }
274 
275     /**
276      * Method to get scan matches for the desired time window.  Returns matches by passpoint time if
277      * the WifiConfiguration is passpoint.
278      *
279      * @param age long desired time for matches.
280      * @return WifiConfiguration.Visibility matches in the given visibility
281      */
getVisibility(long age)282     public WifiConfiguration.Visibility getVisibility(long age) {
283         if (mConfig.isPasspoint()) {
284             return getVisibilityByPasspointMatch(age);
285         } else {
286             return getVisibilityByRssi(age);
287         }
288     }
289 
290 
291 
292     @Override
toString()293     public String toString() {
294         StringBuilder sbuf = new StringBuilder();
295         sbuf.append("Scan Cache:  ").append('\n');
296 
297         ArrayList<ScanDetail> list = sort();
298         long now_ms = System.currentTimeMillis();
299         if (list.size() > 0) {
300             for (ScanDetail scanDetail : list) {
301                 ScanResult result = scanDetail.getScanResult();
302                 long milli = now_ms - scanDetail.getSeen();
303                 long ageSec = 0;
304                 long ageMin = 0;
305                 long ageHour = 0;
306                 long ageMilli = 0;
307                 long ageDay = 0;
308                 if (now_ms > scanDetail.getSeen() && scanDetail.getSeen() > 0) {
309                     ageMilli = milli % 1000;
310                     ageSec   = (milli / 1000) % 60;
311                     ageMin   = (milli / (60 * 1000)) % 60;
312                     ageHour  = (milli / (60 * 60 * 1000)) % 24;
313                     ageDay   = (milli / (24 * 60 * 60 * 1000));
314                 }
315                 sbuf.append("{").append(result.BSSID).append(",").append(result.frequency);
316                 sbuf.append(",").append(String.format("%3d", result.level));
317                 if (ageSec > 0 || ageMilli > 0) {
318                     sbuf.append(String.format(",%4d.%02d.%02d.%02d.%03dms", ageDay,
319                             ageHour, ageMin, ageSec, ageMilli));
320                 }
321                 if (result.numIpConfigFailures > 0) {
322                     sbuf.append(",ipfail=");
323                     sbuf.append(result.numIpConfigFailures);
324                 }
325                 sbuf.append("} ");
326             }
327             sbuf.append('\n');
328         }
329 
330         return sbuf.toString();
331     }
332 
333 }
334