Home | History | Annotate | Download | only in wifi
      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 
     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 
     53     void put(ScanDetail scanDetail) {
     54         put(scanDetail, null, null);
     55     }
     56 
     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 
     67     ScanResult get(String bssid) {
     68         ScanDetail scanDetail = getScanDetail(bssid);
     69         return scanDetail == null ? null : scanDetail.getScanResult();
     70     }
     71 
     72     ScanDetail getScanDetail(String bssid) {
     73         return mMap.get(bssid);
     74     }
     75 
     76     void remove(String bssid) {
     77         mMap.remove(bssid);
     78     }
     79 
     80     int size() {
     81         return mMap.size();
     82     }
     83 
     84     boolean isEmpty() {
     85         return size() == 0;
     86     }
     87 
     88     Collection<String> keySet() {
     89         return mMap.keySet();
     90     }
     91 
     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      */
    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 */
    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      */
    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      */
    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      */
    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
    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