/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi; import android.annotation.NonNull; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import com.android.server.wifi.hotspot2.NetworkDetail; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; /** * Maps BSSIDs to their individual ScanDetails for a given WifiConfiguration. */ public class ScanDetailCache { private static final String TAG = "ScanDetailCache"; private static final boolean DBG = false; private final WifiConfiguration mConfig; private final int mMaxSize; private final int mTrimSize; private final HashMap mMap; /** * Scan Detail cache associated with each configured network. * * The cache size is trimmed down to |trimSize| once it crosses the provided |maxSize|. * Since this operation is relatively expensive, ensure that |maxSize| and |trimSize| are not * too close to each other. |trimSize| should always be <= |maxSize|. * * @param config WifiConfiguration object corresponding to the network. * @param maxSize Max size desired for the cache. * @param trimSize Size to trim the cache down to once it reaches |maxSize|. */ ScanDetailCache(WifiConfiguration config, int maxSize, int trimSize) { mConfig = config; mMaxSize = maxSize; mTrimSize = trimSize; mMap = new HashMap(16, 0.75f); } void put(ScanDetail scanDetail) { // First check if we have reached |maxSize|. if yes, trim it down to |trimSize|. if (mMap.size() >= mMaxSize) { trim(); } mMap.put(scanDetail.getBSSIDString(), scanDetail); } /** * Get ScanResult object corresponding to the provided BSSID. * * @param bssid provided BSSID * @return {@code null} if no match ScanResult is found. */ public ScanResult getScanResult(String bssid) { ScanDetail scanDetail = getScanDetail(bssid); return scanDetail == null ? null : scanDetail.getScanResult(); } /** * Get ScanDetail object corresponding to the provided BSSID. * * @param bssid provided BSSID * @return {@code null} if no match ScanDetail is found. */ public ScanDetail getScanDetail(@NonNull String bssid) { return mMap.get(bssid); } void remove(@NonNull String bssid) { mMap.remove(bssid); } int size() { return mMap.size(); } boolean isEmpty() { return size() == 0; } Collection keySet() { return mMap.keySet(); } Collection values() { return mMap.values(); } /** * Method to reduce the cache to |mTrimSize| size by removing the oldest entries. * TODO: Investigate if this method can be further optimized. */ private void trim() { int currentSize = mMap.size(); if (currentSize < mTrimSize) { return; // Nothing to trim } ArrayList list = new ArrayList(mMap.values()); if (list.size() != 0) { // Sort by ascending timestamp (oldest scan results first) Collections.sort(list, new Comparator() { public int compare(Object o1, Object o2) { ScanDetail a = (ScanDetail) o1; ScanDetail b = (ScanDetail) o2; if (a.getSeen() > b.getSeen()) { return 1; } if (a.getSeen() < b.getSeen()) { return -1; } return a.getBSSIDString().compareTo(b.getBSSIDString()); } }); } for (int i = 0; i < currentSize - mTrimSize; i++) { // Remove oldest results from scan cache ScanDetail result = list.get(i); mMap.remove(result.getBSSIDString()); } } /** * Return the most recent ScanResult for this network, or null if non exists. */ public ScanResult getMostRecentScanResult() { ArrayList list = sort(); if (list.size() == 0) { return null; } return list.get(0).getScanResult(); } /** * Returns the list of sorted ScanDetails in descending order of timestamp, followed by * descending order of RSSI. * @hide **/ private ArrayList sort() { ArrayList list = new ArrayList(mMap.values()); if (list.size() != 0) { Collections.sort(list, new Comparator() { public int compare(Object o1, Object o2) { ScanResult a = ((ScanDetail) o1).getScanResult(); ScanResult b = ((ScanDetail) o2).getScanResult(); if (a.seen > b.seen) { return -1; } if (a.seen < b.seen) { return 1; } if (a.level > b.level) { return -1; } if (a.level < b.level) { return 1; } return a.BSSID.compareTo(b.BSSID); } }); } return list; } @Override public String toString() { StringBuilder sbuf = new StringBuilder(); sbuf.append("Scan Cache: ").append('\n'); ArrayList list = sort(); long now_ms = System.currentTimeMillis(); if (list.size() > 0) { for (ScanDetail scanDetail : list) { ScanResult result = scanDetail.getScanResult(); long milli = now_ms - scanDetail.getSeen(); long ageSec = 0; long ageMin = 0; long ageHour = 0; long ageMilli = 0; long ageDay = 0; if (now_ms > scanDetail.getSeen() && scanDetail.getSeen() > 0) { ageMilli = milli % 1000; ageSec = (milli / 1000) % 60; ageMin = (milli / (60 * 1000)) % 60; ageHour = (milli / (60 * 60 * 1000)) % 24; ageDay = (milli / (24 * 60 * 60 * 1000)); } sbuf.append("{").append(result.BSSID).append(",").append(result.frequency); sbuf.append(",").append(result.level); if (ageSec > 0 || ageMilli > 0) { sbuf.append("," + ageDay + "." + ageHour + "." + ageMin + "." + ageSec + "." + ageMilli + "ms"); } NetworkDetail networkDetail = scanDetail.getNetworkDetail(); if (networkDetail != null && networkDetail.isInterworking()) { sbuf.append(",Ant=").append(networkDetail.getAnt()).append(",Internet=") .append(networkDetail.isInternet()); } sbuf.append("} "); } sbuf.append('\n'); } return sbuf.toString(); } }