Home | History | Annotate | Download | only in osu
      1 package com.android.hotspot2.osu;
      2 
      3 import android.net.wifi.AnqpInformationElement;
      4 import android.net.wifi.ScanResult;
      5 import android.util.Log;
      6 
      7 import com.android.anqp.Constants;
      8 import com.android.anqp.HSOsuProvidersElement;
      9 import com.android.anqp.OSUProvider;
     10 
     11 import java.net.ProtocolException;
     12 import java.nio.ByteBuffer;
     13 import java.nio.ByteOrder;
     14 import java.util.Collection;
     15 import java.util.HashMap;
     16 import java.util.Map;
     17 import java.util.Set;
     18 
     19 /**
     20  * This class holds a stable set of OSU information as well as scan results based on a trail of
     21  * scan results.
     22  * The purpose of this class is to provide a stable set of information over a a limited span of
     23  * time (SCAN_BATCH_HISTORY_SIZE scan batches) so that OSU entries in the selection list does not
     24  * come and go with temporarily lost scan results.
     25  * The stable set of scan results are used by the remediation flow to retrieve ANQP information
     26  * for the current network to determine whether the currently associated network is a roaming
     27  * network for the Home SP whose timer has currently fired.
     28  */
     29 public class OSUCache {
     30     private static final int SCAN_BATCH_HISTORY_SIZE = 8;
     31 
     32     private int mInstant;
     33     private final Map<OSUProvider, ScanResult> mBatchedOSUs = new HashMap<>();
     34     private final Map<OSUProvider, ScanInstance> mCache = new HashMap<>();
     35 
     36     private static class ScanInstance {
     37         private final ScanResult mScanResult;
     38         private int mInstant;
     39 
     40         private ScanInstance(ScanResult scanResult, int instant) {
     41             mScanResult = scanResult;
     42             mInstant = instant;
     43         }
     44 
     45         public ScanResult getScanResult() {
     46             return mScanResult;
     47         }
     48 
     49         public int getInstant() {
     50             return mInstant;
     51         }
     52 
     53         private boolean bssidEqual(ScanResult scanResult) {
     54             return mScanResult.BSSID.equals(scanResult.BSSID);
     55         }
     56 
     57         private void updateInstant(int newInstant) {
     58             mInstant = newInstant;
     59         }
     60 
     61         @Override
     62         public String toString() {
     63             return mScanResult.SSID + " @ " + mInstant;
     64         }
     65     }
     66 
     67     public OSUCache() {
     68         mInstant = 0;
     69     }
     70 
     71     private void clear() {
     72         mBatchedOSUs.clear();
     73     }
     74 
     75     public void clearAll() {
     76         clear();
     77         mCache.clear();
     78     }
     79 
     80     public Map<OSUProvider, ScanResult> pushScanResults(Collection<ScanResult> scanResults) {
     81         for (ScanResult scanResult : scanResults) {
     82             AnqpInformationElement[] osuInfo = scanResult.anqpElements;
     83             if (osuInfo != null && osuInfo.length > 0) {
     84                 putResult(scanResult, osuInfo);
     85             }
     86         }
     87         return scanEnd();
     88     }
     89 
     90     private void putResult(ScanResult scanResult, AnqpInformationElement[] elements) {
     91         for (AnqpInformationElement ie : elements) {
     92             if (ie.getElementId() == AnqpInformationElement.HS_OSU_PROVIDERS
     93                     && ie.getVendorId() == AnqpInformationElement.HOTSPOT20_VENDOR_ID) {
     94                 try {
     95                     HSOsuProvidersElement providers = new HSOsuProvidersElement(
     96                             Constants.ANQPElementType.HSOSUProviders,
     97                             ByteBuffer.wrap(ie.getPayload()).order(ByteOrder.LITTLE_ENDIAN));
     98 
     99                     putProviders(scanResult, providers);
    100                 } catch (ProtocolException pe) {
    101                     Log.w(OSUManager.TAG,
    102                             "Failed to parse OSU element: " + pe);
    103                 }
    104             }
    105         }
    106     }
    107 
    108     private void putProviders(ScanResult scanResult, HSOsuProvidersElement osuProviders) {
    109         for (OSUProvider provider : osuProviders.getProviders()) {
    110             // Make a predictive put
    111             ScanResult existing = mBatchedOSUs.put(provider, scanResult);
    112             if (existing != null && existing.level > scanResult.level) {
    113                 // But undo it if the entry already held a better RSSI
    114                 mBatchedOSUs.put(provider, existing);
    115             }
    116         }
    117     }
    118 
    119     private Map<OSUProvider, ScanResult> scanEnd() {
    120         // Update the trail of OSU Providers:
    121         int changes = 0;
    122         Map<OSUProvider, ScanInstance> aged = new HashMap<>(mCache);
    123         for (Map.Entry<OSUProvider, ScanResult> entry : mBatchedOSUs.entrySet()) {
    124             ScanInstance current = aged.remove(entry.getKey());
    125             if (current == null || !current.bssidEqual(entry.getValue())) {
    126                 mCache.put(entry.getKey(), new ScanInstance(entry.getValue(), mInstant));
    127                 changes++;
    128                 if (current == null) {
    129                     Log.d("ZXZ", "Add OSU " + entry.getKey() + " from " + entry.getValue().SSID);
    130                 } else {
    131                     Log.d("ZXZ", "Update OSU " + entry.getKey() + " with " +
    132                             entry.getValue().SSID + " to " + current);
    133                 }
    134             } else {
    135                 Log.d("ZXZ", "Existing OSU " + entry.getKey() + ", "
    136                         + current.getInstant() + " -> " + mInstant);
    137                 current.updateInstant(mInstant);
    138             }
    139         }
    140 
    141         for (Map.Entry<OSUProvider, ScanInstance> entry : aged.entrySet()) {
    142             if (mInstant - entry.getValue().getInstant() > SCAN_BATCH_HISTORY_SIZE) {
    143                 Log.d("ZXZ", "Remove OSU " + entry.getKey() + ", "
    144                         + entry.getValue().getInstant() + " @ " + mInstant);
    145                 mCache.remove(entry.getKey());
    146                 changes++;
    147             }
    148         }
    149 
    150         mInstant++;
    151         clear();
    152 
    153         // Return the latest results if there were any changes from last batch
    154         if (changes > 0) {
    155             Map<OSUProvider, ScanResult> results = new HashMap<>(mCache.size());
    156             for (Map.Entry<OSUProvider, ScanInstance> entry : mCache.entrySet()) {
    157                 results.put(entry.getKey(), entry.getValue().getScanResult());
    158             }
    159             return results;
    160         } else {
    161             return null;
    162         }
    163     }
    164 
    165     private static String toBSSIDStrings(Set<Long> bssids) {
    166         StringBuilder sb = new StringBuilder();
    167         for (Long bssid : bssids) {
    168             sb.append(String.format(" %012x", bssid));
    169         }
    170         return sb.toString();
    171     }
    172 }
    173