1 package com.android.hotspot2.osu; 2 3 import android.content.ComponentName; 4 import android.content.Context; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.net.wifi.ScanResult; 8 import android.net.wifi.WifiConfiguration; 9 import android.net.wifi.WifiInfo; 10 import android.net.wifi.WifiManager; 11 import android.os.IBinder; 12 import android.os.RemoteException; 13 import android.util.Log; 14 15 import com.android.anqp.HSIconFileElement; 16 import com.android.anqp.OSUProvider; 17 import com.android.hotspot2.AppBridge; 18 import com.android.hotspot2.PasspointMatch; 19 import com.android.hotspot2.Utils; 20 import com.android.hotspot2.app.OSUData; 21 import com.android.hotspot2.flow.FlowService; 22 import com.android.hotspot2.flow.OSUInfo; 23 import com.android.hotspot2.osu.service.RemediationHandler; 24 import com.android.hotspot2.flow.IFlowService; 25 26 import java.io.File; 27 import java.net.MalformedURLException; 28 import java.util.ArrayList; 29 import java.util.Collection; 30 import java.util.HashMap; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Locale; 34 import java.util.Map; 35 import java.util.Set; 36 import java.util.concurrent.atomic.AtomicInteger; 37 38 public class OSUManager { 39 public static final String TAG = "OSUMGR"; 40 public static final boolean R2_MOCK = true; 41 private static final String REMEDIATION_FILE = "remediation.state"; 42 43 // Preferred icon parameters 44 public static final Locale LOCALE = java.util.Locale.getDefault(); 45 46 private final AppBridge mAppBridge; 47 private final Context mContext; 48 private final IconCache mIconCache; 49 private final RemediationHandler mRemediationHandler; 50 private final Set<String> mOSUSSIDs = new HashSet<>(); 51 private final Map<OSUProvider, OSUInfo> mOSUMap = new HashMap<>(); 52 private final AtomicInteger mOSUSequence = new AtomicInteger(); 53 54 private final OSUCache mOSUCache; 55 OSUManager(Context context)56 public OSUManager(Context context) { 57 mContext = context; 58 mAppBridge = new AppBridge(context); 59 mIconCache = new IconCache(this); 60 File appFolder = context.getFilesDir(); 61 mRemediationHandler = 62 new RemediationHandler(context, new File(appFolder, REMEDIATION_FILE)); 63 mOSUCache = new OSUCache(); 64 } 65 getContext()66 public Context getContext() { 67 return mContext; 68 } 69 getAvailableOSUs()70 public List<OSUData> getAvailableOSUs() { 71 synchronized (mOSUMap) { 72 List<OSUData> completeOSUs = new ArrayList<>(); 73 for (OSUInfo osuInfo : mOSUMap.values()) { 74 if (osuInfo.getIconStatus() == OSUInfo.IconStatus.Available) { 75 completeOSUs.add(new OSUData(osuInfo)); 76 } 77 } 78 return completeOSUs; 79 } 80 } 81 setOSUSelection(int osuID)82 public void setOSUSelection(int osuID) { 83 OSUInfo selection = null; 84 for (OSUInfo osuInfo : mOSUMap.values()) { 85 if (osuInfo.getOsuID() == osuID && 86 osuInfo.getIconStatus() == OSUInfo.IconStatus.Available) { 87 selection = osuInfo; 88 break; 89 } 90 } 91 92 Log.d(TAG, "Selected OSU ID " + osuID + ": " + selection); 93 94 if (selection == null) { 95 return; 96 } 97 98 final OSUInfo osu = selection; 99 100 mContext.bindService(new Intent(mContext, FlowService.class), new ServiceConnection() { 101 @Override 102 public void onServiceConnected(ComponentName name, IBinder service) { 103 try { 104 IFlowService fs = IFlowService.Stub.asInterface(service); 105 fs.provision(osu); 106 } catch (RemoteException re) { 107 Log.e(OSUManager.TAG, "Caught re: " + re); 108 } 109 } 110 111 @Override 112 public void onServiceDisconnected(ComponentName name) { 113 Log.d(OSUManager.TAG, "Service disconnect: " + name); 114 } 115 }, Context.BIND_AUTO_CREATE); 116 } 117 networkDeleted(final WifiConfiguration configuration)118 public void networkDeleted(final WifiConfiguration configuration) { 119 if (configuration.FQDN == null) { 120 return; 121 } 122 123 mRemediationHandler.networkConfigChange(); 124 mContext.bindService(new Intent(mContext, FlowService.class), new ServiceConnection() { 125 @Override 126 public void onServiceConnected(ComponentName name, IBinder service) { 127 try { 128 IFlowService fs = IFlowService.Stub.asInterface(service); 129 fs.spDeleted(configuration.FQDN); 130 } catch (RemoteException re) { 131 Log.e(OSUManager.TAG, "Caught re: " + re); 132 } 133 } 134 135 @Override 136 public void onServiceDisconnected(ComponentName name) { 137 138 } 139 }, Context.BIND_AUTO_CREATE); 140 } 141 networkConnectChange(WifiInfo newNetwork)142 public void networkConnectChange(WifiInfo newNetwork) { 143 mRemediationHandler.newConnection(newNetwork); 144 } 145 networkConfigChanged()146 public void networkConfigChanged() { 147 mRemediationHandler.networkConfigChange(); 148 } 149 wifiStateChange(boolean on)150 public void wifiStateChange(boolean on) { 151 if (on) { 152 return; 153 } 154 155 // Notify the remediation handler that there are no WiFi networks available. 156 // Do NOT turn it off though as remediation, per at least this implementation, can take 157 // place over cellular. The subject of remediation over cellular (when restriction is 158 // "unrestricted") is not addresses by the WFA spec and direct ask to authors gives no 159 // distinct answer one way or the other. 160 mRemediationHandler.newConnection(null); 161 int current = mOSUMap.size(); 162 mOSUMap.clear(); 163 mOSUCache.clearAll(); 164 mIconCache.tick(true); 165 if (current > 0) { 166 notifyOSUCount(); 167 } 168 } 169 isOSU(String ssid)170 public boolean isOSU(String ssid) { 171 synchronized (mOSUMap) { 172 return mOSUSSIDs.contains(ssid); 173 } 174 } 175 pushScanResults(Collection<ScanResult> scanResults)176 public void pushScanResults(Collection<ScanResult> scanResults) { 177 Map<OSUProvider, ScanResult> results = mOSUCache.pushScanResults(scanResults); 178 if (results != null) { 179 updateOSUInfoCache(results); 180 } 181 mIconCache.tick(false); 182 } 183 updateOSUInfoCache(Map<OSUProvider, ScanResult> results)184 private void updateOSUInfoCache(Map<OSUProvider, ScanResult> results) { 185 Map<OSUProvider, OSUInfo> osus = new HashMap<>(); 186 for (Map.Entry<OSUProvider, ScanResult> entry : results.entrySet()) { 187 OSUInfo existing = mOSUMap.get(entry.getKey()); 188 long bssid = Utils.parseMac(entry.getValue().BSSID); 189 190 if (existing == null) { 191 osus.put(entry.getKey(), new OSUInfo(entry.getValue(), entry.getKey(), 192 mOSUSequence.getAndIncrement())); 193 } else if (existing.getBSSID() != bssid) { 194 HSIconFileElement icon = mIconCache.getIcon(existing); 195 if (icon != null && icon.equals(existing.getIconFileElement())) { 196 OSUInfo osuInfo = new OSUInfo(entry.getValue(), entry.getKey(), 197 existing.getOsuID()); 198 osuInfo.setIconFileElement(icon, existing.getIconFileName()); 199 osus.put(entry.getKey(), osuInfo); 200 } else { 201 osus.put(entry.getKey(), new OSUInfo(entry.getValue(), 202 entry.getKey(), mOSUSequence.getAndIncrement())); 203 } 204 } else { 205 // Maintain existing entries. 206 osus.put(entry.getKey(), existing); 207 } 208 } 209 210 mOSUMap.clear(); 211 mOSUMap.putAll(osus); 212 213 mOSUSSIDs.clear(); 214 for (OSUInfo osuInfo : mOSUMap.values()) { 215 mOSUSSIDs.add(osuInfo.getOsuSsid()); 216 } 217 218 int mods = mIconCache.resolveIcons(mOSUMap.values()); 219 220 if (mOSUMap.isEmpty() || mods > 0) { 221 notifyOSUCount(); 222 } 223 } 224 notifyIconReceived(long bssid, String fileName, byte[] data)225 public void notifyIconReceived(long bssid, String fileName, byte[] data) { 226 if (mIconCache.notifyIconReceived(bssid, fileName, data) > 0) { 227 notifyOSUCount(); 228 } 229 } 230 doIconQuery(long bssid, String fileName)231 public void doIconQuery(long bssid, String fileName) { 232 WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); 233 wifiManager.queryPasspointIcon(bssid, fileName); 234 } 235 notifyOSUCount()236 private void notifyOSUCount() { 237 int count = 0; 238 for (OSUInfo existing : mOSUMap.values()) { 239 if (existing.getIconStatus() == OSUInfo.IconStatus.Available) { 240 count++; 241 } 242 } 243 Log.d(TAG, "Latest OSU info: " + count + " with icons, map " + mOSUMap); 244 mAppBridge.showOsuCount(count); 245 } 246 deauth(long bssid, boolean ess, int delay, String url)247 public void deauth(long bssid, boolean ess, int delay, String url) 248 throws MalformedURLException { 249 Log.d(TAG, String.format("De-auth imminent on %s, delay %ss to '%s'", 250 ess ? "ess" : "bss", delay, url)); 251 // TODO: Missing framework functionality: 252 // mWifiNetworkAdapter.setHoldoffTime(delay * Constants.MILLIS_IN_A_SEC, ess); 253 String spName = mRemediationHandler.getCurrentSpName(); 254 mAppBridge.showDeauth(spName, ess, delay, url); 255 } 256 wnmRemediate(final long bssid, final String url, PasspointMatch match)257 public void wnmRemediate(final long bssid, final String url, PasspointMatch match) { 258 mRemediationHandler.wnmReceived(bssid, url); 259 } 260 remediationDone(String fqdn, boolean policy)261 public void remediationDone(String fqdn, boolean policy) { 262 mRemediationHandler.remediationDone(fqdn, policy); 263 } 264 } 265