package com.android.hotspot2.osu; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import com.android.anqp.HSIconFileElement; import com.android.anqp.OSUProvider; import com.android.hotspot2.AppBridge; import com.android.hotspot2.PasspointMatch; import com.android.hotspot2.Utils; import com.android.hotspot2.app.OSUData; import com.android.hotspot2.flow.FlowService; import com.android.hotspot2.flow.OSUInfo; import com.android.hotspot2.osu.service.RemediationHandler; import com.android.hotspot2.flow.IFlowService; import java.io.File; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; public class OSUManager { public static final String TAG = "OSUMGR"; public static final boolean R2_MOCK = true; private static final String REMEDIATION_FILE = "remediation.state"; // Preferred icon parameters public static final Locale LOCALE = java.util.Locale.getDefault(); private final AppBridge mAppBridge; private final Context mContext; private final IconCache mIconCache; private final RemediationHandler mRemediationHandler; private final Set mOSUSSIDs = new HashSet<>(); private final Map mOSUMap = new HashMap<>(); private final AtomicInteger mOSUSequence = new AtomicInteger(); private final OSUCache mOSUCache; public OSUManager(Context context) { mContext = context; mAppBridge = new AppBridge(context); mIconCache = new IconCache(this); File appFolder = context.getFilesDir(); mRemediationHandler = new RemediationHandler(context, new File(appFolder, REMEDIATION_FILE)); mOSUCache = new OSUCache(); } public Context getContext() { return mContext; } public List getAvailableOSUs() { synchronized (mOSUMap) { List completeOSUs = new ArrayList<>(); for (OSUInfo osuInfo : mOSUMap.values()) { if (osuInfo.getIconStatus() == OSUInfo.IconStatus.Available) { completeOSUs.add(new OSUData(osuInfo)); } } return completeOSUs; } } public void setOSUSelection(int osuID) { OSUInfo selection = null; for (OSUInfo osuInfo : mOSUMap.values()) { if (osuInfo.getOsuID() == osuID && osuInfo.getIconStatus() == OSUInfo.IconStatus.Available) { selection = osuInfo; break; } } Log.d(TAG, "Selected OSU ID " + osuID + ": " + selection); if (selection == null) { return; } final OSUInfo osu = selection; mContext.bindService(new Intent(mContext, FlowService.class), new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { try { IFlowService fs = IFlowService.Stub.asInterface(service); fs.provision(osu); } catch (RemoteException re) { Log.e(OSUManager.TAG, "Caught re: " + re); } } @Override public void onServiceDisconnected(ComponentName name) { Log.d(OSUManager.TAG, "Service disconnect: " + name); } }, Context.BIND_AUTO_CREATE); } public void networkDeleted(final WifiConfiguration configuration) { if (configuration.FQDN == null) { return; } mRemediationHandler.networkConfigChange(); mContext.bindService(new Intent(mContext, FlowService.class), new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { try { IFlowService fs = IFlowService.Stub.asInterface(service); fs.spDeleted(configuration.FQDN); } catch (RemoteException re) { Log.e(OSUManager.TAG, "Caught re: " + re); } } @Override public void onServiceDisconnected(ComponentName name) { } }, Context.BIND_AUTO_CREATE); } public void networkConnectChange(WifiInfo newNetwork) { mRemediationHandler.newConnection(newNetwork); } public void networkConfigChanged() { mRemediationHandler.networkConfigChange(); } public void wifiStateChange(boolean on) { if (on) { return; } // Notify the remediation handler that there are no WiFi networks available. // Do NOT turn it off though as remediation, per at least this implementation, can take // place over cellular. The subject of remediation over cellular (when restriction is // "unrestricted") is not addresses by the WFA spec and direct ask to authors gives no // distinct answer one way or the other. mRemediationHandler.newConnection(null); int current = mOSUMap.size(); mOSUMap.clear(); mOSUCache.clearAll(); mIconCache.tick(true); if (current > 0) { notifyOSUCount(); } } public boolean isOSU(String ssid) { synchronized (mOSUMap) { return mOSUSSIDs.contains(ssid); } } public void pushScanResults(Collection scanResults) { Map results = mOSUCache.pushScanResults(scanResults); if (results != null) { updateOSUInfoCache(results); } mIconCache.tick(false); } private void updateOSUInfoCache(Map results) { Map osus = new HashMap<>(); for (Map.Entry entry : results.entrySet()) { OSUInfo existing = mOSUMap.get(entry.getKey()); long bssid = Utils.parseMac(entry.getValue().BSSID); if (existing == null) { osus.put(entry.getKey(), new OSUInfo(entry.getValue(), entry.getKey(), mOSUSequence.getAndIncrement())); } else if (existing.getBSSID() != bssid) { HSIconFileElement icon = mIconCache.getIcon(existing); if (icon != null && icon.equals(existing.getIconFileElement())) { OSUInfo osuInfo = new OSUInfo(entry.getValue(), entry.getKey(), existing.getOsuID()); osuInfo.setIconFileElement(icon, existing.getIconFileName()); osus.put(entry.getKey(), osuInfo); } else { osus.put(entry.getKey(), new OSUInfo(entry.getValue(), entry.getKey(), mOSUSequence.getAndIncrement())); } } else { // Maintain existing entries. osus.put(entry.getKey(), existing); } } mOSUMap.clear(); mOSUMap.putAll(osus); mOSUSSIDs.clear(); for (OSUInfo osuInfo : mOSUMap.values()) { mOSUSSIDs.add(osuInfo.getOsuSsid()); } int mods = mIconCache.resolveIcons(mOSUMap.values()); if (mOSUMap.isEmpty() || mods > 0) { notifyOSUCount(); } } public void notifyIconReceived(long bssid, String fileName, byte[] data) { if (mIconCache.notifyIconReceived(bssid, fileName, data) > 0) { notifyOSUCount(); } } public void doIconQuery(long bssid, String fileName) { WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); wifiManager.queryPasspointIcon(bssid, fileName); } private void notifyOSUCount() { int count = 0; for (OSUInfo existing : mOSUMap.values()) { if (existing.getIconStatus() == OSUInfo.IconStatus.Available) { count++; } } Log.d(TAG, "Latest OSU info: " + count + " with icons, map " + mOSUMap); mAppBridge.showOsuCount(count); } public void deauth(long bssid, boolean ess, int delay, String url) throws MalformedURLException { Log.d(TAG, String.format("De-auth imminent on %s, delay %ss to '%s'", ess ? "ess" : "bss", delay, url)); // TODO: Missing framework functionality: // mWifiNetworkAdapter.setHoldoffTime(delay * Constants.MILLIS_IN_A_SEC, ess); String spName = mRemediationHandler.getCurrentSpName(); mAppBridge.showDeauth(spName, ess, delay, url); } public void wnmRemediate(final long bssid, final String url, PasspointMatch match) { mRemediationHandler.wnmReceived(bssid, url); } public void remediationDone(String fqdn, boolean policy) { mRemediationHandler.remediationDone(fqdn, policy); } }