blob: 24cd10f1e583c97c0b0ac641aafd891e4def5f32 [file] [log] [blame]
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<String> mOSUSSIDs = new HashSet<>();
private final Map<OSUProvider, OSUInfo> 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<OSUData> getAvailableOSUs() {
synchronized (mOSUMap) {
List<OSUData> 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<ScanResult> scanResults) {
Map<OSUProvider, ScanResult> results = mOSUCache.pushScanResults(scanResults);
if (results != null) {
updateOSUInfoCache(results);
}
mIconCache.tick(false);
}
private void updateOSUInfoCache(Map<OSUProvider, ScanResult> results) {
Map<OSUProvider, OSUInfo> osus = new HashMap<>();
for (Map.Entry<OSUProvider, ScanResult> 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);
}
}