• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ScanInstance(ScanResult scanResult, int instant)40         private ScanInstance(ScanResult scanResult, int instant) {
41             mScanResult = scanResult;
42             mInstant = instant;
43         }
44 
getScanResult()45         public ScanResult getScanResult() {
46             return mScanResult;
47         }
48 
getInstant()49         public int getInstant() {
50             return mInstant;
51         }
52 
bssidEqual(ScanResult scanResult)53         private boolean bssidEqual(ScanResult scanResult) {
54             return mScanResult.BSSID.equals(scanResult.BSSID);
55         }
56 
updateInstant(int newInstant)57         private void updateInstant(int newInstant) {
58             mInstant = newInstant;
59         }
60 
61         @Override
toString()62         public String toString() {
63             return mScanResult.SSID + " @ " + mInstant;
64         }
65     }
66 
OSUCache()67     public OSUCache() {
68         mInstant = 0;
69     }
70 
clear()71     private void clear() {
72         mBatchedOSUs.clear();
73     }
74 
clearAll()75     public void clearAll() {
76         clear();
77         mCache.clear();
78     }
79 
pushScanResults(Collection<ScanResult> scanResults)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                 Log.d(OSUManager.TAG, scanResult.SSID +
85                         " has " + osuInfo.length + " ANQP elements");
86                 putResult(scanResult, osuInfo);
87             }
88         }
89         return scanEnd();
90     }
91 
putResult(ScanResult scanResult, AnqpInformationElement[] elements)92     private void putResult(ScanResult scanResult, AnqpInformationElement[] elements) {
93         for (AnqpInformationElement ie : elements) {
94             Log.d(OSUManager.TAG, String.format("ANQP IE %d vid %x size %d", ie.getElementId(),
95                     ie.getVendorId(), ie.getPayload().length));
96             if (ie.getElementId() == AnqpInformationElement.HS_OSU_PROVIDERS
97                     && ie.getVendorId() == AnqpInformationElement.HOTSPOT20_VENDOR_ID) {
98                 try {
99                     HSOsuProvidersElement providers = new HSOsuProvidersElement(
100                             Constants.ANQPElementType.HSOSUProviders,
101                             ByteBuffer.wrap(ie.getPayload()).order(ByteOrder.LITTLE_ENDIAN));
102 
103                     putProviders(scanResult, providers);
104                 } catch (ProtocolException pe) {
105                     Log.w(OSUManager.TAG,
106                             "Failed to parse OSU element: " + pe);
107                 }
108             }
109         }
110     }
111 
putProviders(ScanResult scanResult, HSOsuProvidersElement osuProviders)112     private void putProviders(ScanResult scanResult, HSOsuProvidersElement osuProviders) {
113         Log.d(OSUManager.TAG, osuProviders.getProviders().size() + " OSU providers in element");
114         for (OSUProvider provider : osuProviders.getProviders()) {
115             // Make a predictive put
116             ScanResult existing = mBatchedOSUs.put(provider, scanResult);
117             if (existing != null && existing.level > scanResult.level) {
118                 // But undo it if the entry already held a better RSSI
119                 mBatchedOSUs.put(provider, existing);
120             }
121         }
122     }
123 
scanEnd()124     private Map<OSUProvider, ScanResult> scanEnd() {
125         // Update the trail of OSU Providers:
126         int changes = 0;
127         Map<OSUProvider, ScanInstance> aged = new HashMap<>(mCache);
128         for (Map.Entry<OSUProvider, ScanResult> entry : mBatchedOSUs.entrySet()) {
129             ScanInstance current = aged.remove(entry.getKey());
130             if (current == null || !current.bssidEqual(entry.getValue())) {
131                 mCache.put(entry.getKey(), new ScanInstance(entry.getValue(), mInstant));
132                 changes++;
133                 if (current == null) {
134                     Log.d(OSUManager.TAG,
135                             "Add OSU " + entry.getKey() + " from " + entry.getValue().SSID);
136                 } else {
137                     Log.d(OSUManager.TAG, "Update OSU " + entry.getKey() + " with " +
138                             entry.getValue().SSID + " to " + current);
139                 }
140             } else {
141                 Log.d(OSUManager.TAG, "Existing OSU " + entry.getKey() + ", "
142                         + current.getInstant() + " -> " + mInstant);
143                 current.updateInstant(mInstant);
144             }
145         }
146 
147         for (Map.Entry<OSUProvider, ScanInstance> entry : aged.entrySet()) {
148             if (mInstant - entry.getValue().getInstant() > SCAN_BATCH_HISTORY_SIZE) {
149                 Log.d(OSUManager.TAG, "Remove OSU " + entry.getKey() + ", "
150                         + entry.getValue().getInstant() + " @ " + mInstant);
151                 mCache.remove(entry.getKey());
152                 changes++;
153             }
154         }
155 
156         mInstant++;
157         clear();
158 
159         // Return the latest results if there were any changes from last batch
160         if (changes > 0) {
161             Map<OSUProvider, ScanResult> results = new HashMap<>(mCache.size());
162             for (Map.Entry<OSUProvider, ScanInstance> entry : mCache.entrySet()) {
163                 results.put(entry.getKey(), entry.getValue().getScanResult());
164             }
165             return results;
166         } else {
167             return null;
168         }
169     }
170 
toBSSIDStrings(Set<Long> bssids)171     private static String toBSSIDStrings(Set<Long> bssids) {
172         StringBuilder sb = new StringBuilder();
173         for (Long bssid : bssids) {
174             sb.append(String.format(" %012x", bssid));
175         }
176         return sb.toString();
177     }
178 }
179